The following details how to use buffered TCP to transmit TCP frames.
Buffered TCP support can be activated by setting the define USE_BUFFERED_TCP in config.h.
This then allows the use of the following routine to transmit frames:
QUEUE_TRANSFER fnSendBufTCP(USOCKET TCP_socket, unsigned char *ptrBuf, unsigned short usDataLen, unsigned char ucCommand)
This technique is used for example by the TELNET module, which serves as a good example of its use.
The noticable difference to the the fnSendTCP() call is the ucCommand parameter instead of the Flag parameter. The following show how it can be used.
CHAR test_message[] = "Hello World ";
QUEUE_TRANSFER nr_of_bytes = (sizeof(test_message) - 1);
fnSendBufTCP(Test_socket, (unsigned char *)test_message, nr_of_bytes, (TCP_BUF_SEND | TCP_BUF_SEND_REPORT_COPY));
fnSendBufTCP(Test_socket, (unsigned char *)test_message, nr_of_bytes, (TCP_BUF_SEND | TCP_BUF_SEND_REPORT_COPY));
fnSendBufTCP(Test_socket, (unsigned char *)test_message, nr_of_bytes, (TCP_BUF_SEND | TCP_BUF_SEND_REPORT_COPY));
The next noticable difference is the fact that 3 test messages are sent immediately after another, whereas previous fnSendTCP() examples have always sent only one message and waited until it had been acked. The command flag TCP_BUF_SEND informs the buffered TCP socket that the data should be transmitted as soon as possible. The flag TCP_BUF_SEND_REPORT_COPY causes the function to return the number of bytes of the test message which were actually accepted for transmission.
The first time that a buffered socket is used, a structure TCP_TX_BUFFER is automatically created which contains various control variables and also a data buffer of size TCP_BUFFER.
The size of this buffer can be defined in app_hw_xxxx.h as in the following:
#define TCP_BUFFER 2800
This allocates buffer space of 2800 bytes. When fnSendBufTCP() is called, the data to transmit is first copied into this buffer. As many bytes are copied as possible and if the buffer becomes full, the trailing ones will be ignored.
If the user needs to ensure than no data can be lost due to such a buffer overflow, the following will first verify whether there is enough buffer space to accept the message:
UTASK_TASK OurTask = OWN_TASK;
if (fnSendBufTCP(Test_socket, (unsigned char *)&OurTask, nr_of_bytes, TCP_BUF_CHECK) > 0) {
fnSendBufTCP(Test_socket, (unsigned char *)test_message, nr_of_bytes, TCP_BUF_SEND);
}
The value returned will be equal to the remaining buffer space BEFORE adding the data. If the available space is less that the message length it will return 0. In addition to returning 0, the owner task will be entered to be woken by an TX_FREE interrupt event when the buffer becomes free again. This allows tasks to be suspended on waiting for buffer space to become freed.
Note that the define WAKE_BLOCKED_TCP_BUF should be set in config.h to enable this option!
As soon as the first TCP frame has been copied to the transmitter buffer a TCP frame is also sent. Generally there will be no ACK received from this frame for at least 150ms (assuming active delayed ack operation at the destination). The second call to fnSendBufTCP(), which follows immediately in the example, will cause the second part of the data to be added to the buffer. As long as the congestion windows (this will be explained later) allows it, a second TCP frame will immediately be sent, although there has not yet been an acknowledge to the first one. The same is also true for the third call of fnSendBufTCP() although the congestion window value will generally not allow it to actually send the associated data just yet.
At this point in time two TCP frames are under way to the destination and three blocks of data are in the tcpbuffer. The fact that the data is available as a backup in the tcp buffer means that the application itself doesn't need to also have a copy of the data, or be able to regenerate it if a frame needs to be repeated. In the case of a repetition - signalled by TCP_EVENT_REGENERATE, the socket's callback routine can simply call
fnSendBufTCP(Test_socket, 0, 0, TCP_BUF_REP);
which will start the repetition of lost data.
NOTE: See the further example at the end of this note for the recommended method of repetating to ensure that windowing operation remains efficient.
One important fact to be noted about buffered TCP is that the data copied to the output buffer is treated as a stream of data and not as individual frames of the same lengths as entered. This means that it is very suited to debug interfaces and the likes since the user can send the data in small packets (such as "text + value + text", as often used when constructing an output) and the data will tend to be sent as frames of maximum size rather than small TCP frames.
This behaviour would be observed if the example frames were not to be acked:
1. Frame 1 "Hello World "
2. Frame 2 "Hello World "
3. No ack to either of the 2 frames so TCP_EVENT_REGENERATE received by the callback routine.
4. On use of fnSendBufTCP(Test_socket, 0, 0, TCP_BUF_REP); the next TCP frame will have the content "Hello World Hello World Hello World " since the frame is constructed from as many waiting bytes as possible (the first 2 non-acked frame contents plus the third waiting one).
The maximum size of individual framnes can also be defined in app_hw_xxxx.h
#define TCP_BUFFER_FRAME 1400
When using buffered TCP, the fact that more than one tx frame is sent before receiving an ACK means that TCP windowing is in operation. One great advantage of TCP windowing is the fact that bulk data throughput is much greater than when using the simple TCP transmission method described earlier. Not only do round trip delays impact less the transmission efficiency but the fact that the destination immedately receives two TCP frames effectively stops any outstanding Delayed ACK timer it has in operation and thus causes an ACK to be immediately returned to the second TCP frame. By using just 2 outspanding frames in a LAN can already increase data throughput greatly.
The defined SUPPORT_PEER_WINDOW should also be used when operating with buffered TCP because this support the processing of partial ACKs by TCP. Partical ACKs are are acknowledgements to TCP data which do not include all outstanding frames and these must be handled in the callback in the case for TCP_EVENT_PARTIAL_ACK.
This handling is in fact very simple since it only has to do the following:
fnSendBufTCP(Socket, 0, usPortLen, TCP_BUF_NEXT);
In the case of TCP_EVENT_ACK (which means that all outstanding data have been acked), the corresponding call is fnSendBufTCP(Socket, 0, 0, TCP_BUF_NEXT);
In both cases further waiting data (again as much as possible) will be sent in following TCP frames. The variable usPortLen, rather than 0, informs the buffered TCP routine exactly how much of the outstanding TCP data has successfully been acked. The TCP buffer is effectively freed of acked data so that the buffer has more space to accept further data from the user.
Buffered TCP operation allows improved data throughput at the expense of RAM buffer space. The routines suppport a congestion window which allows a number of TCP frames to be sent before their ACKs have been received. Initially a connection allows 2 frames to be outstanding but this value is allowed to increase by one between each ack up to the maximum defined by WINDOWING_BUFFERS in config.h. This ensures that the number of outstanding TCP acks builds up to mathh the return trip time of a connection and so achieves optimum bulk data throughput.
A difficults is generally in deciding the amount of SRAM to allocate to the TCP buffer since in small footprint projects such RAM represently quite a luxury. In any case there is also the question as to what happens if the user data rate is still faster than the TCP bulk data throughput rate, in which case even the largest buffer will probably not stop it ever filling up completely. The use of the call fnSendBufTCP() with flag TCP_BUF_CHECK has already been introduced and this can be used to stop temporarily the source of data until the TCP buffer size has increased again. The uTasker demo project uses quite a small TELNET buffer (which is also using buffered TCP) to illustrate how, for example, large menus are displayed with limited buffer space. If you study this example code, where the copying of the source text to the TCPbuffer is suspended until space becomes available again, you will probably get enough ideas to solve your own such data bottle-necks.
One popular use of such a TCP connection is for the transfer of data from a UART to a remote PC via TCP. flow control is required throughout the link and a typical technique would be to issue an XOFF or remove CTS (depending on UART flow control in operation) when the TCP buffer is getting critically full) and removing it when the TX_FREE event is received.
Here is a full list of the flags available when using fnSendBufTCP(). Some of the flags can be used together - their combined use should be logical.
#define TCP_BUF_SEND put the attached data into the buffer and send asap
#define TCP_BUF_NEXT an ack has been received so send as much more waiting data as possible from the TCP buffer.
#define TCP_BUF_REP data has been lost. Repeat as much more waiting data as possible from the TCP buffer.
#define TCP_BUF_CHECK check whether the specified amount of data will fit into the TCP buffer. If it won't fit, send a TX_FREE event when the buffer is free.
#define TCP_BUF_SEND_REPORT_COPY report the amound of data copied to teh TCP buffer (used together with TCP_BUF_SEND).
#define TCP_CONTENT_NEGOTIATION used by TELNET together with TCP_BUF_SEND to flag that data is a negotiation frame and so must not be combined with other data
#define TCP_REPEAT_WINDOW used only internally to TCP to handle specific repetitions
#define TCP_BUF_KICK_NEXT This can be used kick in windowing operation after data loss. The next example illustrates the recommended use of it in response to the TCP_EVENT_REGENERATE event.
case TCP_EVENT_REGENERATE:
if (fnSendBufTCP(Socket, 0, 0, TCP_BUF_REP) != 0) { // repeat send buffered
fnSendBufTCP(Socket, 0, 0, (TCP_BUF_NEXT | TCP_BUF_KICK_NEXT)); // kick off any following data as long as windowing allows it
return APP_SENT_DATA;
}
break;
The buffered TCP support allows quite simple use of the technique to achieve high speed transmission in most applications. However the operation of TCP is still rather more complicated than has been described here and the description of TCP itself is far from complete. Flow control from one end of a connection (possibly including more than the TCP stage in practical systems) is not as simple as one initially believes. But the uTasker should support all that is necessary to achieve respectable results even in small footprint solution (a uTasker powered Serial<->TCP converter based on the ATSAM7X128 - 48MHz ARM7 with 32k SRAM - has in fact outperformed an ARM9 solution with 16Meg SDRAM - based on my own measurements...). But this description should serve as a good starting point and the forum can be used to elaborate on the things which I have unintentionally or intentionally left out for the moment.
Comments are very welcome.
The final part of this series will discuss TELNET, which is based on the buffered TCP technique. It will be shown how a TELNET-like socket can be used rather than a direct buffered TCP socket to acheive the same improvements but with a more simple interface - using the existing TELNET handling routines as a interface engine.
Before closing it is worth mentioning one more point. It is also possible, in some situations, to still achieve high data throughput based on TCP windowing without using buffered TCP. This is feasible when the data is not of random nature (like data which is being transferred from a UART) but can be regenerated on demand. A web server is a good example since the files which it is serving (also when partly dynamically generated) can usually be regenerated without having to keep a large data store of previous transmissions.
A new version of HTTP server for the uTasker project is being developed (tests have already proved very successful with page serving rates of larger http files or graphics in a LAN of up to 25x the rate when using simple TCP transmission) which should be available with the next service pack!!
Regards
Mark