When designing a µC/TCP-IP based application, setting up a socket for a UDP server can be accomplished with a straightforward sequence of function calls.
When utilizing Micrium's network stack µC/TCP-IP to establish connections, you'll likely end up writing sockets code. Sockets, in networking jargon, represent a medium for sending and receiving data. Many basic Micrium examples are devoid of socket call implementation — this is due to stacks like µC/TCP-IP that carrying out a variety of basic operations, including the creation of responses to pings (or echo requests) automatically. As a developer, your own designs will typically require more than the basics. After establishing that your project has the essentials needed for network development, pinging your board for instance, sockets will be your vehicle for transmitting and receiving data.
When writing your application code around µC/TCP-IP, there’s a basic sequence to follow when setting up and initializing a socket. The variation between socket setup is dependent on which transport-layer protocol your socket is utilizing. There are two options provided by the TCP/IP protocol suite that constitute the foundation for transport-layer communication—UDP and TCP—each offering their own unique set of services. This article will focus on the initialization of UDP sockets, specifically, utilizing UDP sockets to implement the server side of a client-server application. In Part II, we'll discuss the analogous code for a TCP server.
Below is an example UDP server socket implementation. The steps taken in this example are hardly unique to Micrium's stack—a similar initialization sequence can be leveraged when setting up most UDP server implementations. Obviously, API calls and prototypes used here are Micrium-specific. Those shown below are part of the functions offered by the µC/TCP-IP network stack.
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, to NetSock_Open(), creates the socket object to be used by your server for communication. During this socket creation sequence, application code must specify the IP version. µC/TCP-IP supports both IPv4 and IPv6 at the network or internet layer of the TCP/IP protocol suite. Either can be used for creating a new UDP socket. The #define for selecting the protocol is NET_SOCK_ADDR_FAMILY_IP_V4 and NET_SOCK_ADDR_FAMILY_IP_V6. One of these must be passed by the application as the first argument to NetSock_Open().
The second and third parameters for NetSock_Open() specify socket and protocol type, respectively. For a UDP socket, the typical socket type NET_SOCK_TYPE_DATAGRAM is used as indicated in the example. For UDP sockets, obviously, NET_SOCK_PROTOCOL_UDP should be the chosen the protocol value.
The final argument for NetSock_Open() is a pointer given to µC/TCP-IP referencing a memory location for storing error codes. Essentially, this parameter allows you to diagnose any errors that might occur during the execution of NetSock_Open(). The application, much like the example, should always check for errors following an API function call. Problems frequently arise from negligence in error checking that results in hours of time spent debugging and rewriting code.
Step 2: Initializing the Address Struct
After creating the socket with NetSock_Open(), the example calls NetApp_SetSockAddr() to setup an address for the new socket. Preparation involves initializing the fields of a struct of type NET_SOCK_ADDR_IPv4 or NET_SOCK_ADDR_IPv6 with addressing information. Select between the two possible struct types based on your socket's IP version. This is determined by the values you specify for the second and fifth arguments of NetApp_SetSockAddr(). Sockets utilizing IPv4 as the underlying network or internet layer, the first argument to NetApp_SetSockAddr() must be of type NET_SOCK_ADDR_IPv4, while the #defines NET_SOCK_ADDR_FAMILY_IP_V4 and NET_IPv4_ADDR_SIZE must be used for the second and fifth arguments, respectively. An IPv6 socket will set the first argument to type NET_SOCK_ADDR_IPv6, while NET_SOCK_ADDR_FAMILY_IP_V6 and NET_IPv6_ADDR_SIZE must be passed as the second and fifth arguments. Ultimately, don’t pass IPv4 type parameters for an IPv6 socket and vice-versa.
The third and fourth parameters of NetApp_SetSockAddr() is used to setup the actual addressing information as a port number and IP address. For the IP address, parameter four, typically the value NET_IPv4_ADDR_ANY is used. This configures the 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. It’s possible to force a socket to receive only over a particular interface as well. Pass that interface's address as the fourth argument instead of the addr_any value.
You’ll notice that the prefix for this function differs from the others in the example code: Instead of the NetSock prefix, the NetApp prefix is used. This is due to NetApp_SetSockAddr() being part of a separate wrapper API set which automates an initialization that would otherwise be done manually. µC/TCP-IP provides various wrapper functions of this sort in the file net_app.c.
Step 3: Binding an Address
Once initialization of an address struct has completed, done manually or automatically via NetApp_SetSockAddr(), code seeking to create a UDP server socket should invoke NetSock_Bind(). This will establish an association between the socket and the address specified in the struct. NetSock_Bind() has four parameters. The first provides a socket number, which is a value returned by NetSock_Open(). This can be used to identify the created socket. Application code specifies an address for the socket by passing the stack a reference to the instance struct initialized by NetApp_SetSockAddr() previously. The struct is passed as the second argument of NetSock_Bind(), and the size of the struct is provided as the third argument. As with many of these API’s, 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 in the example constitute a major reason for selecting these API calls over the BSD socket interface that is 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 could benefit systems in which portability is a top concern. The Micrium functions are ideal for developers who benefit from the robustness Micrium's functions bring with their added situational error reporting.
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.