Blogs | Technical Articles | Videos

In part one of this series we covered a majority of µC/TCP-IP’s basic configurations. In this final blog of the series, we’ll examine window sizes and queues, both of which can affect the rate of data flowing in and out of a device.

Configuring TCP Window Size

What happens when data packets start arriving at one of your sockets, but the application has not yet attempted to receive those packets? Similarly, what is the outcome when application code attempts to transmit a packet, but the underlying network controller is busy? In either case, the behavior is shaped, by the queues. Seen in the figure below, every µC/TCP-IP-based socket created has both a receive and a transmit queue. The capacities of these queues dictate the amount of data that can be stored either for subsequent application processing (in the case of the receive queue) or for transmission by the network controller (in the case of the transmit queue).

For those developers with applications using only UDP, queue sizes represent the only configurable parameters related to the pre-transmit and post-receive storage of data. However, with TCP, there are additional parameters to consider. By design TCP (the protocol itself) provides flow control via window sizing. Each TCP segment includes a window variable to track the amount of buffer space the transmitting device has available for additional data. By design, µC/TCP-IP advertises window value, which functions as a receive window by representing space set aside for incoming data. This is accompanied by a transmit window, the value of which should reflect the buffer space available for outgoing data.

By default, µC/TCP-IP configures the receive and transmit windows equivalent to the receive and transmit queue sizes, respectively. The window sizes can be configured via the definitions below. These definitions are found in a comment block towards the end of the net_cfg.h configuration file. This is provided with µC/TCP-IP’s source code and can be uncommented if the user wishes to re-define them.

#define   NET_TCP_DFLT_RX_WIN_SIZE_OCTET   NET_SOCK_CFG_RX_Q_SIZE_OCTET
#define   NET_TCP_DFLT_TX_WIN_SIZE_OCTET   NET_SOCK_CFG_TX_Q_SIZE_OCTET

When designing your application and setting the TCP stack window sizes you should consider several factors. Regarding the receive window, properly configuring these values advertises to other devices the amount of incoming data that a socket can accommodate. It is critical that this value not exceed the total receive buffer space your stack has available for use. You can calculate this space using the simple formula below and your values from net_dev_cfg.h:

Total Rx Buffer Space = Size of Rx Buffer * Nbr Rx Buffers

Keep in mind, for TCP-based applications, your configured buffer space won’t be exclusively utilized for just TCP data. TCP, like the lower layer protocols, requires data to be appended with header information, which is ultimately stored in buffers. With µC/TCP-IP, creating an Ethernet network typically requires configuring the system's receive buffers to a size of 1518 Bytes. This value is the largest possible Ethernet frame allowed. However, at least 58 Bytes of this buffer will be needed for headers, leaving only 1460 Bytes for actual TCP data. Calculations below explain this further.

TCP Max Payload = Interface Max (Ethernet 1518 Bytes)
                - Interface Header and Footer (Ethernet 18 Bytes)
                - Min IP Header (20 Bytes)
                - Min TCP Header (20 Bytes)
                = 1518 - 18 - 20 - 20 = 1460 Bytes

Window size calculations should account for the space required to carry the header information. When making these calculations the number of sockets that will be managed by the system in question must also be considered. A system's total buffer space is shared amongst its open sockets. Setting the window size equal to that space (header’s included) is not recommended. Instead, the window size should be configured so that each socket will get a share of the available buffer space. The below formula, for receive window size, would meet this objective by dividing a system's buffer space evenly according to the number of connections utilizing those resources.

                  Nbr Rx Buffers * TCP Max Payload
Rx Window Size = -----------------------------------
                 Nbr Simultaneous Active Connections

The denominator in the above formula involves connections, rather than sockets. This is to reflect the stack's ability to buffer data that hasn’t been assigned to a socket yet. This means data received in the (usually brief) interval of time between the establishment of a connection and the allocation of a socket for that connection. As the formula below indicates, the maximum number of connections existing in this state is determined by the configuration constant NET_SOCK_CFG_CONN_ACCEPT_Q_SIZE_MAX.

                                       Nbr Rx Buffers * TCP Max Payload
Rx Window Size = -------------------------------------------------------------------------------------
                 Nbr TCP Server Sockets + NET_SOCK_CFG_CONN_ACCEPT_Q_SIZE_MAX + Nbr TCP Client Sockets

Although the last two formulas both involved receive window size, the transmit windows should likewise be predicated on the amount of available buffer space and the number of sockets in the system. (For transmit, the accept queue represented by NET_SOCK_CFG_CON_ACCEPT_Q_SIZE_MAX would not need to be considered.) To better adjust your window size to your applications total buffer space and socket count, use the previously mentioned NET_TCP_DFLT_RX_WIN_SIZE_OCTET and NET_TCP_DFLT_RX_WIN_SIZE_OCTET #defines. You can also change window sizes on a per-connection basis via the API functions NetTCP_ConnCfgRxWinSize() and NetTCP_ConnCfgTxWinSize().

With those function calls, it’s possible to establish a relatively large window for one high-throughput connection and a much smaller window for another lesser-used connection. These functions exist so the developer can efficiently allocation their resources. However, when tweaking window sizes make sure to never employ a value higher than the total usable buffer space. Again, this is a result of multiplying the previously provided maximum payload size (1460) by the number of buffers in your system. Remember, for each of a TCP socket's two data paths (receive and transmit), there is an associated queue capacity and window size. If the two are not equal, it is the smaller of the values that determines how much data can be stored.

Conclusion

Part II covered the intricacies of accounting for your networks throughput either to or from your device. By taking this into account and adjusting the provided configurations within µC/TCPIP the application can be tailored to provide better performance numbers. Setting these values without understanding their ripple effect on the network can be a bit tricky and if assistance is needed our support team is available as well.