Network Sockets Concepts
Byte-Ordering
TCP/IP has an universal standard to allow communication between any platforms. Thus, it's necessary to have method arranging information so that big-endian and little-endian machines can understand each other. On platforms where data is already correctly ordered, the functions do nothing and are frequently macro'd into empty statements. Byte-ordering functions should always be used as they do not impact performance on systems that are already correctly ordered and they promote code portability.
BSD | µC/TCPIP Functions | Description |
---|---|---|
htonl
|
NET_UTIL_HOST_TO_NET_32
| Convert 32-bit integer values from network-order to CPU host-order. |
htons()
|
NET_UTIL_HOST_TO_NET_16()
| Convert 16-bit integer values from network-order to CPU host-order. |
ntohl()
|
NET_UTIL_NET_TO_HOST_32()
| Convert 32-bit integer values from network-order to CPU host-order. |
ntohs()
|
NET_UTIL_NET_TO_HOST_16()
| Convert 16-bit integer values from network-order to CPU host-order. |
The four byte-ordering functions stand for host to network short, host to network long, network to host short, and network to host long. htons() translates a short integer from host byte-order to network byte-order. htonl is similar, translating a long integer. The other two functions do the reverse, translating from network byte-order to host byte-order.
IP Addresses and Data Structures
Communication using sockets requires configuring or reading network addresses from network socket address structures.
IP Address Versions
There are two version of IP. The most known version is the IPv4 address which is 4 bytes long and commonly written in dots and numbers format, such as
192.168.1.1
The next generation of IP is IPv6 which is also supported by µC/TCP-IP. The IPv6 address is 128 bits long and it is represented using numbers, letters and semi-colons such as:
fe80:14f:c9d2:12::51
IPv4 Addresses
The BSD socket API defines a generic socket address structure as a blank template with no address-specific configuration.
struct sockaddr { /* Generic BSD socket address structure */ CPU_INT16U sa_family; /* Socket address family */ CPU_CHAR sa_data[14]; /* Protocol-specific address information */ }; typedef struct net_sock_addr { /* Generic µC/TCP-IP socket address structure */ NET_SOCK_ADDR_FAMILY AddrFamily; CPU_INT08U Addr[14]; } NET_SOCK_ADDR;
…as well as specific socket address structures to configure each specific protocol address family’s network address configuration (e.g., IPv4 socket addresses):
struct in_addr { NET_IP_ADDR s_addr; /* IPv4 address (32 bits) */ }; struct sockaddr_in { /* BSD IPv4 socket address structure */ CPU_INT16U sin_family; /* Internet address family (e.g. AF_INET) */ CPU_INT16U sin_port; /* Socket address port number (16 bits) */ struct in_addr sin_addr; /* IPv4 address (32 bits) */ CPU_CHAR sin_zero[8]; /* Not used (all zeroes) */ }; typedef struct net_sock_addr_ipv4 { /* µC/TCP-IP socket address structure */ NET_SOCK_ADDR_FAMILY AddrFamily; NET_PORT_NBR Port; NET_IPv4_ADDR Addr; CPU_INT08U Unused[8]; } NET_SOCK_ADDR_IPv4;
A socket address structure’s AddrFamily/sa_family/sin_family
value must be read/written in host CPU byte order, while all Addr/sa_data
values must be read/written in network byte order (big endian).
Even though socket functions – both µC/TCP-IP and BSD – pass pointers to the generic socket address structure, applications must declare and pass an instance of the specific protocol’s socket address structure (e.g., an IPv4 address structure). For microprocessors, that requires data access to be aligned to appropriate word boundaries. This forces compilers to declare an appropriately-aligned socket address structure so that all socket address members are correctly aligned to their appropriate word boundaries.
Caution: Applications should avoid, or be cautious when, declaring and configuring a generic byte array as a socket address structure, since the compiler may not correctly align the array or the socket address structure’s members to appropriate word boundaries.
The figure below shows a sample IPv4 instance of the µC/TCP-IP NET_SOCK_ADDR_IPv4
(sockaddr_in)
structure overlaid on top of a NET_SOCK_ADDR (sockaddr)
structure.
A socket could configure the example socket address structure in the figure below to bind on IPv4 address 10.10.1.65 and port number 49876 with the following code:
NET_SOCK_ADDR_IPv4 addr_local; NET_IPv4_ADDR addr_ip; NET_PORT_NBR addr_port; NET_SOCK_RTN_CODE rtn_code; NET_ERR err; addr_ip = NetASCII_Str_to_IPv4("10.10.1.65", &err); addr_port = 49876; Mem_Clr((void *)&addr_local, (CPU_SIZE_T) sizeof(addr_local)); addr_local.AddrFamily = NET_SOCK_ADDR_FAMILY_IP_V4; /* = AF_INET†† Figure 9-1 */ addr_local.Addr = NET_UTIL_HOST_TO_NET_32(addr_ip); addr_local.Port = NET_UTIL_HOST_TO_NET_16(addr_port); rtn_code = NetSock_Bind((NET_SOCK_ID ) sock_id, (NET_SOCK_ADDR *)&addr_local, /* Cast to generic addr† */ (NET_SOCK_ADDR_LEN) sizeof(addr_local), (NET_ERR *)&err);
† The address of the specific IPv4 socket address structure is cast to a pointer to the generic socket address structure.
IPv6 Addresses
Socket addresses structures for IPv6 are similar to IPv4.
struct in6_addr { in6_addr_t s_addr[16]; }; struct sock_addr_in6 { sa_family_t sin6_family; /* Internet address family (AF_INET6) */ in_port_t sin6_port; /* Socket address port number (16 bits) */ CPU_INT32U sin6_flowinfo; /* Flow info. */ struct in6_addr sin6_addr; /* IPv6 address. (128 bits) */ CPU_INT32U sin6_scope_id; /* Scope zone ix. */ }; typedef struct net_sock_addr_ipv6 { NET_SOCK_ADDR_FAMILY AddrFamily; NET_PORT_NBR Port; CPU_INT32U FlowInfo; NET_IPv6_ADDR Addr; CPU_INT32U ScopeID; } NET_SOCK_ADDR_IPv6;
A socket could configure the example socket address structure in the figure below to bind on IPv6 address FE80::1111:AAAA and port number 49876 with the following code:
NET_SOCK_ADDR_IPv6 addr_local; NET_IPv6_ADDR addr_ip; NET_PORT_NBR addr_port; NET_SOCK_RTN_CODE rtn_code; NET_ERR err; addr_ip = NetASCII_Str_to_IPv6("FE80::1111:AAAA", &err); addr_port = 49876; Mem_Clr((void *)&addr_local, (CPU_SIZE_T) sizeof(addr_local)); addr_local.AddrFamily = NET_SOCK_ADDR_FAMILY_IP_V6; /* = AF_INET6 */ Mem_Copy((void *)&addr_local->Addr, (void *)&addr_ip, (CPU_SIZE_T) sizeof(addr_local)); addr_local.Port = NET_UTIL_HOST_TO_NET_16(addr_port); rtn_code = NetSock_Bind((NET_SOCK_ID ) sock_id, (NET_SOCK_ADDR *)&addr_local, /* Cast to generic addr† */ (NET_SOCK_ADDR_LEN) sizeof(addr_local), (NET_ERR *)&err);
† The address of the specific IPv6 socket address structure is cast to a pointer to the generic socket address structure.
Complete send() Operation
send()
returns the number of bytes actually sent out. This might be less than the number that are available to send. The function will send as much of the data as it can. The developer must make sure that the rest of the packet is sent later.
{ int total = 0; /* how many bytes we've sent */ int bytesleft = *len; /* how many we have left to send */ int n; while (total < *len) { n = send(s, buf + total, bytesleft, 0); (1) if (n == -1) { break; } total += n; (2) bytesleft -= n; (3) } }
(1) Send as many bytes as there are transmit network buffers available.
(2) Increase the number of bytes sent.
(3) Calculate how many bytes are left to send.
This is another example that, for a TCP/IP stack to operate smoothly, sufficient memory to define enough buffers for transmission and reception is a design decision that requires attention if optimum performance for the given hardware is desired.
IPv4 to IPv6
For a client migrating from IPv4 to IPv6 is basically just using the correct structure following the IP address version to use. For a server, if both IP address must be supported at same time, the application might require some modification, depending it's design since two listening socket will be required one for IPv4 and another for IPv6.
Loopback for Inter-Process Communication
It is possible for tasks to communicate with sockets via the localhost interface which must be enabled. See Network Interfaces Configuration.
µC/TCP-IP Socket Error Codes
When socket functions return error codes, the error codes should be inspected to determine if the error is a temporary, non-fault condition (such as no data to receive) or fatal (such as the socket has been closed).
Fatal Socket Error Codes
Whenever any of the following fatal error codes are returned by any µC/TCP-IP socket function, that socket must be immediately closed()
’d without further access by any other socket functions:
NET_SOCK_ERR_INVALID_FAMILY
NET_SOCK_ERR_INVALID_PROTOCOL
NET_SOCK_ERR_INVALID_TYPE
NET_SOCK_ERR_INVALID_STATE
NET_SOCK_ERR_FAULT
Whenever any of the following fatal error codes are returned by any µC/TCP-IP socket function, that socket must not be accessed by any other socket functions but must also not be closed()
’d:
NET_SOCK_ERR_NOT_USED
Socket Error Code List
See net_err.h for a brief explanation of all µC/TCP-IP socket error codes.