Setting Up a Socket in µC/TCP-IP (Part 1)

Part 1: UDP Server

In an application based on µC/TCP-IP, a socket for a UDP server can be established via a straightforward sequence of function calls.

If you plan to use Micrium's µC/TCP-IP to implement network communication in your next project, then you'll likely end up writing sockets code. Sockets, in networking parlance, are abstractions for sending and receiving data. Many example projects from Micrium are devoid of any sockets calls—that's because stacks like µC/TCP-IP are capable of carrying out a variety of basic operations, including the formulation of responses to pings (or echo requests) automatically. However, in your own project, once you move beyond pinging your board to prove that the stack is running, sockets will be your vehicle for sending and receiving data.

When you are writing application code around µC/TCP-IP, there is a general sequence that you should follow to open and initialize a socket. The steps in the sequence vary according to the transport-layer protocol on which your socket will be based. The TCP/IP protocol suite that forms the foundation of the Internet provides two options for transport-layer communication—UDP and TCP—each of which offers its own unique set of services. In this post, we'll focus on the initialization of a UDP socket, and, in particular, a UDP socket used to implement the server side of a client-server application. In a subsequent post, we'll take a look at the analogous code for a TCP server.

A snippet of example initialization code for a UDP server socket is provided below. The basic steps taken by this code are hardly unique to Micrium's stack—a similar initialization process would be leveraged to set up a UDP server with nearly any other TCP/IP implementation. However, the function names and prototypes used here are Micrium-specific. They are part of a proprietary API, which, as we will see in more detail later, is actually one of two APIs offered by µC/TCP-IP.

NET_SOCK_ID          sock;
NET_SOCK_ADDR_IPv4   server_sock_addr_ip;
CPU_INT32U           addr_any;
NET_ERR              err;

addr_any = NET_IPv4_ADDR_ANY;
                                    /* Open UDP (datagram) socket */
sock = NetSock_Open( NET_SOCK_PROTOCOL_FAMILY_IP_V4,
                     NET_SOCK_TYPE_DATAGRAM,
                     NET_SOCK_PROTOCOL_UDP,
                    &err);
if (err != NET_SOCK_ERR_NONE) {
    /* Handle error */
}
                                   /* Initialize address struct   */
NetApp_SetSockAddr((NET_SOCK_ADDR *)&server_sock_addr_ip,
                                     NET_SOCK_ADDR_FAMILY_IP_V4,
                                     UDP_SERVER_PORT,
                   (CPU_INT08U    *)&addr_any,
                                     NET_IPv4_ADDR_SIZE,
                                    &err);
if (err != NET_APP_ERR_NONE) {
    /* Handle error */
}

                                    /* Bind address to socket     */
NetSock_Bind(                  sock,
             (NET_SOCK_ADDR *)&server_sock_addr_ip,
                               NET_SOCK_ADDR_SIZE,
                              &err);
if (err != NET_SOCK_ERR_NONE) {
    /* Handle error */
}

Step 1: Opening the Socket

The first call in the above example, to NetSock_Open(), can be understood to create the socket that the server will ultimately use for communication. As part of the creation of a socket, application code must specify an IP version.  µC/TCP-IP supports both IPv4 and IPv6 at the network or internet layer of the TCP/IP protocol suite, and you can use either of these for a new UDP socket. The #defines for the two protocols are NET_SOCK_ADDR_FAMILY_IP_V4 and NET_SOCK_ADDR_FAMILY_IP_V6, one of which must be passed by your application code as the first argument to NetSock_Open().

The second and third parameters of NetSock_Open() allow application code to specify a socket type and protocol type, respectively. For a UDP socket, you will typically use the socket type NET_SOCK_TYPE_DATAGRAM, as indicated in the example. You should specify NET_SOCK_PROTOCOL_UDP as the protocol.

The final argument passed to NetSock_Open() is interpreted by µC/TCP-IP as a pointer that references a memory location to be used for storing error codes. In other words, this parameter allows you to diagnose any errors that might occur during the execution of NetSock_Open(). Your own application code, much like the example, should always check for any errors following calls to stack API functions. It is not uncommon for problems that arise from negligence in error checking to result in hours of time spent debugging and rewriting code.

Step 2: Initializing the Address Struct

Following the call to NetSock_Open(), the example application code invokes NetApp_SetSockAddr() to prepare for assigning an address to the new socket. The preparation involves initializing the fields of a struct of type NET_SOCK_ADDR_IPv4 or NET_SOCK_ADDR_IPv6 with addressing information. You should choose between the two possible struct types based on your socket's IP version, which should likewise determine the values you pass for the second and fifth arguments of NetApp_SetSockAddr(). For a socket utilizing IPv4 as the underlying network or internet layer, the first argument to NetApp_SetSockAddr() should be of type NET_SOCK_ADDR_IPv4, while the #defines NET_SOCK_ADDR_FAMILY_IP_V4 and NET_IPv4_ADDR_SIZE should be used for the second and fifth arguments, respectively. In the case of an IPv6 socket, the first argument should be of type NET_SOCK_ADDR_IPv6, while NET_SOCK_ADDR_FAMILY_IP_V6 and NET_IPv6_ADDR_SIZE should be passed as the second and fifth arguments.

The actual addressing information—in the form of a port number and IP address—that µC/TCP-IP will place in the struct provided through the first argument of NetApp_SetSockAddr() is specified via the function's third and fourth parameters. For the IP address, which corresponds to the fourth parameter, it is common for application code to use the value NET_IPv4_ADDR_ANY, as in the example. This value allows a socket to receive any packet destined for the port number given by the third argument to NetApp_SetSockAddr(), regardless of the network interface over which the packet was received. To force a socket to receive only over a particular interface, application code would simply pass that interface's address as the fourth argument.

You might notice that the address-setting function's name differs somewhat from that of the other functions in the example code: Instead of a NetSock prefix, the address function begins with NetApp. This naming difference exists because NetApp_SetSockAddr() is not a standard sockets API function in µC/TCP-IP, but is more accurately described as a wrapper. It automates an initialization that would otherwise be done manually. µC/TCP-IP provides a number of wrapper functions of this sort in the file net_app.c.

Step 3: Binding an Address

Once initialization of an address struct has been realized, either manually or automatically via NetApp_SetSockAddr(), code seeking to create a UDP server socket should invoke NetSock_Bind() to establish an association between the socket and the address specified in the struct. NetSock_Bind() has four parameters. The first is used to supply a socket number, which, as the example indicates, is a value returned by NetSock_Open() to be used as an identifier for a new socket. Application code specifies an address for such a socket by passing the stack a reference to an instance of one of the types of addressing structs initialized by NetApp_SetSockAddr(). The struct is passed as the second argument of NetSock_Bind(), and the size of the struct is furnished as the third argument. As in the case of the other functions shown in the example, the final parameter of NetSock_Bind() is an error pointer that allows application code to properly respond to any problems that arise during the function call.

The error reporting capabilities of the functions shown in the example constitute a major reason for selecting these particular API calls over the BSD socket interface that is also available in µC/TCP-IP. However, in choosing between µC/TCP-IP's two APIs—as in most engineering decisions—there is a tradeoff to consider. The APIs comprising the BSD interface, which is the basis for a plethora of open-source stacks and network applications, result in much more portable code than would be possible via Micrium's unique functions. Thus, BSD is a better option for systems in which portability is a top concern, while the Micrium functions shown in the example are ideal for developers who are already familiar with Micrium's naming conventions and place a high value on the error reporting these functions offer.

In an application based on Micrium's API functions, like the example, the call to NetSock_Bind() would represent the end of the socket initialization process and would normally be followed by API calls to actually receive and transmit data over the socket. The additional API calls are beyond the scope of this particular post, as are the BSD functions that could be used as an alternative to the Micrium API under consideration here, but both are described in detail in Micrium's online documentation, where you'll find a comprehensive UDP server example from which the above code was derived. If UDP is not an option for your network application—perhaps due to reliability requirements—then you should stay tuned for our next post, in which we'll shift our focus to TCP and examine the initialization process required for sockets based on this protocol.

Tags: , ,

Questions or Comments?

Have a question or a suggestion for a future article?
Don't hesitate to contact us and let us know!
All comments and ideas are welcome.