In Part I, we looked at setting up a UDP server. Now we will cover setting up a socket for a UDP server.
The process for setting up a TCP server, although considerably more complex than a typical application using UDP as the underlying protocol, can be broken down into five steps.
In part one of this series, we examined the function calls necessary to set up a socket for a UDP server. In part I, we looked at the UDP half of the transport-layer protocols. Now we will consider the other protocol, TCP. The typical sequence followed in establishing TCP communication in a server application begins similarly to UDP. Where it deviates will be the focus of part II as we cover the new API’s.
Perhaps the most notable difference between a server based on UDP and one that relies on TCP is the latter typically requires a minimum of two sockets, whereas the former, requires the creation of one. At least two sockets for TCP is a result of this protocol's robust feature set; while UDP is often described as a "connection-less" protocol, TCP is best described as "connection-oriented". The creation of a data connection (essentially, state information for a sender and receiver) is maintained by two communicating devices. Thus, before a TCP client can communicate with a server, it must establish a connection. In application code, this process typically unfolds as illustrated below.

Following the approach taken in Part I, let’s examine the steps involved for a TCP server to initialize, as pertains to uC/TCP-IP API calls. However, as the UDP post noted, µC/TCP-IP offers a BSD interface along with the proprietary API, and everything described herein could easily be accomplished with either set of functions. Example server code based on the Micrium API below will flesh out some of the details omitted in the above diagram:
NET_SOCK_ID sock_listen;
NET_SOCK_ID sock_req;
NET_SOCK_ADDR_IPv4 server_sock_addr_ip;
NET_SOCK_ADDR_IPv4 client_sock_addr_ip;
NET_SOCK_ADDR_LEN client_sock_addr_ip_size;
CPU_INT32U addr_any;
NET_ERR err;
addr_any = NET_IPv4_ADDR_ANY;
sock_listen = NetSock_Open(NET_SOCK_PROTOCOL_FAMILY_IP_V4,
NET_SOCK_TYPE_STREAM,
NET_SOCK_PROTOCOL_TCP,
&err);
if (err != NET_SOCK_ERR_NONE) {
/* Handle error */
}
NetApp_SetSockAddr((NET_SOCK_ADDR *)&server_sock_addr_ip,
NET_SOCK_ADDR_FAMILY_IP_V4,
APP_SERVER_PORT_NBR,
(CPU_INT08U *)&addr_any,
NET_IPv4_ADDR_SIZE,
&err);
NetSock_Bind(sock_listen,
(NET_SOCK_ADDR *)&server_sock_addr_ip,
(NET_SOCK_ADDR_LEN)NET_SOCK_ADDR_SIZE,
&err);
if (err != NET_SOCK_ERR_NONE) {
/* Handle error */
}
NetSock_Listen(sock_listen,
APP_TCP_Q_SIZE,
&err);
if (err != NET_SOCK_ERR_NONE) {
/* Handle error */
}
do {
client_sock_addr_ip_size = sizeof(client_sock_addr_ip);
sock_req = NetSock_Accept(sock_listen,
(NET_SOCK_ADDR *)&client_sock_addr_ip,
&client_sock_addr_ip_size,
&err);
if ((err != NET_SOCK_ERR_NONE) && (err != NET_SOCK_ERR_CONN_SIGNAL_TIMEOUT)) {
/* Handle error */
}
} while (sock_req < 0);
Step 1: Opening the Socket
The first function invoked in our TCP example was also found at the beginning of the last post's UDP code: NetSock_Open(). In both examples, the open function returns a socket ID for use in subsequent socket API calls. However, the arguments passed to NetSock_Open() for a TCP socket are slightly different than those used in a UDP application. Developers of a TCP server can still create sockets based on IPv4 or IPv6. Simply pass either NET_SOCK_PROTOCOL_FAMILY_IP_V4 or NET_SOCK_PROTOCOL_FAMILY_IP_V6 as the first argument to NetSock_Open(). Diverging from the UDP example, the second and third arguments of this function must be a socket type of NET_SOCK_TYPE_STREAM and the protocol identifier NET_SOCK_PROTOCOL_TCP, not NET_SOCK_TYPE_DATAGRAM and NET_SOCK_PROTOCOL_UDP.
Step 2: Initializing the Address Struct
For servers utilizing both UDP and TCP, a call to NetSock_Open() is typically followed by code initializing a struct for the purpose of associating an address with the newly created socket. Like the UDP example, the TCP routine provided here relies on NetApp_SetSockAddr() to automate the address initialization process. For either server, the struct provided to this function must be of type NET_SOCK_ADDR_IPv4 or NET_SOCK_ADDR_IPv6. Depending on the underlying network layer (UDP or TCP) the application must specify a port number and IP address through the function's third and fourth arguments, respectively. Again, following the UDP example, TCP uses the IP address NET_IPv4_ADDR_ANY, to permit receiving packets from any interface by the socket, provided it has the correct destination port number.
Step 3: Binding an Address
Following the call to NetApp_SetSockAddr(), the TCP server invokes NetSock_Bind() to assign an address to the new socket. This bind call is practically identical to the UDP example. In each case, application code provides the TCP/IP stack with a socket id, an address structure, and a size value for the struct. An error pointer is also passed to the bind function for help identifying issues and diagnosing problems when debugging and running your network tasks.
Step 4: Listening for New Connections
The TCP server's call to NetSock_Listen() is where the examples diverge API wise. A TCP server requires the use of at least two connections in TCP (or, pseudo connections, since the routers between two communicating devices are not involved). Whereas a UDP server simply creates a socket and waits for data to begin arriving on that socket, a TCP server will instead wait for connection requests. In terms of the underlying communication protocol, it waits on a SYN segment to initiate the sequence normally referred to as a "three-way handshake" in TCP.
NetSock_Listen(), which enables a server to begin receiving and processing connection requests, has a fairly simple parameter list. It begins with a socket ID that, like other API functions, identifies the socket to operate with. The third and final parameter is an error pointer that is, likewise, a staple of Micrium’s API functions. Now, the second parameter warrants a bit more discussion. After a server invokes NetSock_Listen(), it may receive multiple connection requests before attempting to establish communication with any of the requesting clients. For a server to successfully respond to all such requests, µC/TCP-IP creates space for queueing up requests. The size of these queues is determined by the second parameter of NetSock_Listen().
Step 5: Accepting a Connection
How does a server go about responding to connection requests? To answer this question, we must consider additional API functions and an additional socket. The first socket created by a TCP server, via NetSock_Open(), is typically designated a "listen socket". After this designation via NetSock_Listen(), it remains open indefinitely so the server can respond to various connection requests. Rather than using this socket to exchange data with requesting clients, the server will create a new socket for each request.
When the server receives a connection request it invokes NetSock_Accept() to receive the ID of this socket. By default, it’s configured as a blocking function, although it can be set for non-blocking behavior. A task that calls NetSock_Accept() specifies a socket id (listening socket) as the function's first argument. The second and third arguments provide pointers to an address struct and address length, respectively. If the socket is configured as blocking, the calling task is placed in a waiting(pending) state until either a connection request is received, or a timeout occurs. The default timeout is 5 seconds. NetSock_Accept() will return to the calling task with a socket ID of -1 and an error code of NET_SOCK_ERR_CONN_SIGNAL_TIMEOUT if a request is not received within this period. If the accept function receives a request in time it will return a valid socket ID. Using the pointer passed as the second argument it will supply address data for the connected client. The length of the address data is furnished via the third argument, which, interestingly enough, is an input/output parameter: It points to the length of the address data upon the accept function's return, but is expected to reference a storage capacity (for the address struct that will hold the data) when the function is called.
In the TCP server example, NetSock_Accept() is called within a do while loop, the condition for which will fail once the function returns a valid socket ID. This way the server is able to accept a single connection from a client. In a server capable of simultaneously communicating with multiple clients, further calls to NetSock_Accept() would be needed for each successive connection to be established. A developer implementing such a server might, make calls to NetSock_Accept() as part of an infinite loop within a dedicated listening task. After a successful return from the function, the code in the loop could use a kernel call to create a separate task for communicating over the newly created socket.
A TCP server can easily grow to incorporate thousands of lines of code, and the details of implementing a server able to simultaneously communicate with multiple clients is beyond the scope of this discussion. However, the function calls described above can be viewed as part of an essential toolkit needed to develop any class of TCP server.