Author Topic: Using TCP in the uTasker project  (Read 83996 times)

Offline mark

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 3234
    • View Profile
    • uTasker
Re: Using TCP in the uTasker project
« Reply #15 on: July 24, 2007, 09:47:36 PM »
Here is a discussion about sending TCP data using the simplest method. Later I will discuss using buffered TCP to achieve greater bulk data transfer rates as well as TELNET sockets to do the same without need for the application to handle the details.


The simplest method of sending data is by using the following code (based on fnSendTCP())

#define MAX_DATA_LEN 100
static unsigned char usTestData[MIN_TCP_HLEN + MAX_DATA_LEN];
usData[MIN_TCP_HLEN + 0] = 0x01;
usData[MIN_TCP_HLEN + 1] = 0x02;
usData[MIN_TCP_HLEN + 2] = 0x03;
usData[MIN_TCP_HLEN + 3] = 0x04;
fnSendTCP(Test_TCP_socket, ucTestData, 4, TCP_FLAG_PUSH);

Note here that the data buffer passed to TCP must contain a header space which TCP will then fill for TCP header information. In this example 4 user data bytes are added at the correct location in the buffer and then sent with the TCP_FLAG_PUSH set. This flag indicates to the destination that all TCP data has been sent and it should process it now. If the data is sent as several TCP frames this fag is usually only set in the last one - however practically there is no great diffence in most applications.

Another method of achieving the same thing is with the following code which is equivalent but uses a structure - this is often preferrable since it may be a bit easier to read.

#define MAX_DATA_LEN 100
typedef struct stTCP_MESSAGE
{
    TCP_HEADER     tTCP_Header;                    // reserve header space
    unsigned char  ucTCP_Message[MAX_DATA_LEN];    // data payload space
} TCP_MESSAGE;

static TCP_MESSAGE TestData;
TestData.ucTCP_Message[0] = 0x01;
TestData.ucTCP_Message[1] = 0x02;
TestData.ucTCP_Message[2] = 0x03;
TestData.ucTCP_Message[3] = 0x04;
fnSendTCP(Test_TCP_socket, (unsigned char *)&TestData, 4, TCP_FLAG_PUSH);

To check whether the data could be sent, the following check can be added.

if (fnSendTCP(Test_TCP_socket, (unsigned char *)&TestData, 4, TCP_FLAG_PUSH) > 0) {
    // successful
}

The call will usually only fail when the destination IP address (which is not given here since it is a parameter of the connected socket) is not resolved and so ARP has first to be started. This can happen when the TCP connection has been idle for some time and the IP entry flushed from the ARP table. It is therefore not an error but a natural occurance.

A successfully sent message can also fail to arrive at the destination due to a transmission error for example - it may arrive too but the ack could get lost instead. In this case the transmission should be retried after TCP has detected the timeout - or after ARP has resolved the destination IP address. Both result in the callback event TCP_EVENT_REGENERATE.

This means that the previous message much be resent. In the example case it can be rebuilt using the same subroutine and once TCP has determined that it has successfully reached its destination, the callback event TCP_EVENT_ACK will be received.

If however the data can not be simply regenerated as in our example - for example it arrived from a serial interface - a copy must be kept until the TCP_EVENT_ACK has finally arrived. The code must be prepared to repeat the message (by copying it from a backup to the TCPbuffer) as many times as necessary before it can destroy the backup (probably by overwriting it with the next data). The need for keeping a backup and repeating data is usually the most complicated part of using TCP...

One last but important point about using fnSendTCP() is its use from within the callback function. When data has been successfully sent, it should be signalled by returning APP_SENT_DATA. This informs TCP that data has been sent and it then does not have to generate an ACK to any received frame. The ACK will already have been piggy-backed with the data. It is not a serious error if this is not respected, instead it simply means that TCP will generate unnecessary ACK frames which cause a little more data on the LAN than really necessary.

This technique is simple, reliable and adequate for many TCP connections. However if you require large amounts of bulk data to be transferred you will probably have a surprise since the throughput is not as high as initially expected. This is due to the fact that the next block of data can only be sent when there has been an acknowledgement to the previous. In a LAN the round trip time is very short but in the Internet this may rise to hundreds of ms or even several seconds. This will clearly reduce throughput since there will be dead times.
However in a LAN you will often find that the receiver will be using a technique called "delayed acks" where rather than immediately sending an ACK to received TCP frames there is a delay of 150ms..200ms inserted. This is in the hope that a receiving application will be sending a frame back and the TCP layer can piggy back its ACK on it - and also save unnecessary short ACK frames. The result is that usually only about 5 TCP frames can be transferred in a second. This gives a data throughput (using 1460 byte payloads) of only 7300 bytes a second (or 58400 bits a second). This is no faster than most serial interfaces.

However the simple technique results also in absolute minimum complication and code size so there is no reason why it is not used if the bulk speed is not the issue. Many TCP applications also allow delayed acks to be disabled, which then compensates most speed loss in a LAN.

In the next part in the series about using TCP in the uTasker project I will introduce buffered TCP to show how TCP windowing can be activated to greatly improve bulk data throughtput, although often at the cost of more RAM requirement...


Regards

Mark


Offline neil

  • Sr. Member
  • ****
  • Posts: 438
    • View Profile
Re: Using TCP in the uTasker project
« Reply #16 on: July 25, 2007, 02:28:37 PM »
Hi Mark,
  Nice article above.

Regarding 'TCP_EVENT_REGENERATE'. You mention that the previous message must be resent. Is there a chance that there can be an endless event, where the call back keeps getting 'TCP_EVENT_REGENERATE' everytime the message is resent (for some weird reason)? If so I assume that its best to retry 'x' amount of times before closing the connection?

I have written a lot in sockets on Windows applications, and never had to worry about the lo-level 'ACKs' etc as these are all hidden. So please excuse my basic  questions  :)

I understand that if I simply receive a 'TCP_EVENT_REGENERATE', I do a resend. And if I get a 'APP_SENT_DATA', the data was transmitted/received okay.
I thought the only messages I would really have to manage are 'TCP_EVENT_REGENERATE' , 'APP_SENT_DATA', 'TCP_EVENT_CONNECTED'. But after I resend if a  'TCP_EVENT_REGENERATE'  is returned, I will get a ''TCP_EVENT_ACK' instead of a  'APP_SENT_DATA' . Is this correct?

But if the data cannot be resent, if I receive a  'TCP_EVENT_REGENERATE', I simply wait for a ''TCP_EVENT_ACK' before doing anything else with the socket (apart from closing it)?

Is that the basics, or am I missing something else  ???
Thanks
Neil
 


Offline mark

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 3234
    • View Profile
    • uTasker
Re: Using TCP in the uTasker project
« Reply #17 on: July 25, 2007, 08:56:16 PM »
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

Offline mark

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 3234
    • View Profile
    • uTasker
Re: Using TCP in the uTasker project
« Reply #18 on: July 25, 2007, 09:39:09 PM »
Hi Neil

TCP_EVENT_REGENERATE is sent when ARP has resolved an IP address. This will only ever happen once.
It is also called when a TCP message transmission times out without an ACK. The socket counts the timeouts and automatically limits the repeats to TCP_DEF_RETRIES (this is fixed at 7 in tcpip.h). If maximum repeats still fails it is obvious that the connection has failed and so it will be closed by using a reset.

Yes, when using Windows sockets there are a lot of details which are hidden. However there is also a lot of software involved and this obviously has to be handled somewhere. I have now also added a description of using buffered TCP where ACKs do still have to be handled. The last in the series will detail TELNET and the use of a TELNET-like socket. In this case it inserts another layer between the application and the buffered TCP socket which does in fact offload the 'dirty-work' from the user layer. Then the user never has anything to do with the regeneration event. It makes it easier to use and, if you do already have Telnet active in your device, the software is all there so it doesn't add to code size. Most events are handled by the TELNET layer but some are also passed on via a second 'user' callback so that some details can still be controlled there as well if needed.

Regards

Mark

Offline mark

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 3234
    • View Profile
    • uTasker
Re: Using TCP in the uTasker project
« Reply #19 on: July 26, 2007, 08:38:48 PM »
The following details how Telnet works and how to use a TELNET-like socket for general TCP socket implementation.

When TELNET is activated - by setting the define USE_TELNET in config.h - buffered TCP is also enabled since the TELNET implemmentation is based on this method. The uTasker demo project controls the TELNET socket in the file debug.c where is is used to allow a menu-driven command interface to be controlled via a TELNET connection. When no Telnet connection is active the same menu-driven interface is available via a serial interface (activated by the define SERIAL_INTERFACE in config.h). This shows that the actual use of the TELNET connection is very similar to a typical serial terminal connection and it is thus very useful for configuration and general debugging purposes.

Before beginning with the details of Telnet it should be mentioned that the uTasker project includes several optimised routines which help display data, some of which work with a serial interface. The most common is the following function:

extern QUEUE_TRANSFER fnDebugMsg (CHAR *ucToSend); // send string to debug interface

Using this call, the standard "Hello World!" welcome can be send:

    fnDebugMsg("Hello World!");

The question is to which interface this message is actually sent? The answer is that it depends on the setting of a global variable:
    QUEUE_HANDLE DebugHandle = NETWORK_HANDLE;

This can be set by the user to any serial interface handle - for example the uTasker demo project sets it to a serial port if a serial port is enabled in the project:
    DebugHandle = SerialPortID;

If nothing is changed, it is destined to NETWORK_HANDLE which means that the message will in fact be passed on to a routine called fnNetworkTx():

extern QUEUE_TRANSFER fnNetworkTx(unsigned char *output_buffer, QUEUE_TRANSFER nr_of_bytes);

This routine must be defined somewhere in the project and it can also be a dummy function if it is not actually used. In the uTasker demo project it can be found in debug.c where it transfers data to the Telnet socket, if this is enabled:

return (fnSendBufTCP(Telnet_socket, output_buffer, nr_of_bytes, (TCP_BUF_SEND | TCP_BUF_SEND_REPORT_COPY)));

Here we see that it is using the buffered TCP interface and the socket is defined as the Telnet_socket, which we will discuss below.
Note however that DebugHandle can be changed by any code to switch between serial interfaces or a network interface, it doesn't have to be left at the default NETWORK_HANDLE value. The uTasker demo project does just this and so enables messages to be sent over the serial interface or over an active Telnet interface, depending on which is desired.

To open a Telnet socket use the function:
   Telnet_socket = fnStartTelnet(23, (5*60), 0, 0, fnTELNETListener); 

This is the prototype of the call:
extern USOCKET fnStartTelnet(unsigned short usTelnetPortNumber,
      unsigned short usIdleTimeout,
      unsigned short usMaxWindow,
      UTASK_TASK wakeOnAck,
      int (*listener)(USOCKET, unsigned char, unsigned char *, unsigned short) )

As can be seen, a Telnet socket is started (it will be set to listener state) on the standard Telnet port 23. It will have an inactivity timeout period of 5 minutes and uses the callback function fnTELNETListener(). For the moment we will leave the other parameters at 0, which means that they will take default values.

Note that the Telnet socket can later be stopped by simply calling fnStopTelnet(Telnet_socket);

The Telnet callback function (in its simplest form) is in fact quite similar to a standard TCP callback function:

static int fnTELNETListener(USOCKET Socket, unsigned char ucEvent, unsigned char *ucIp_Data, unsigned short usPortLen)
{
    switch (ucEvent) {
    case TCP_EVENT_CONREQ:                                     // session request
    case TCP_EVENT_ABORT:
    case TCP_EVENT_CLOSE:
    case TCP_EVENT_CLOSED:
    case TCP_EVENT_ACK:
        break;

    case TCP_EVENT_CONNECTED:                                  // TCP connection
        return fnTelnet(Telnet_socket, GOTO_ECHO_MODE);        // Set echo mode

    case TCP_EVENT_DATA:
        if (fnCommandInput(ucIp_Data, usPortLen, SOURCE_NETWORK)) { // collect data input and try to interpret if a LF is found
            return APP_SENT_DATA;                // we will always have sent an answer
        }
        break;
    }
    return APP_ACCEPT;                           // default is to accept a connection
}

Here it has been simplified in comparison to the use in debug.c since it uses the events to switch debug output between the serial port and Telnet and also supports a user name / password login sequence.

When data is received, it is passed on to the menu handling routine, which will not be described here but quite simply matches a list of available command to the received data and performs the command if found. When a TCP connection has been established - event TCP_EVENT_CONNECTED is received - a further Telnet command is used to switch the Telnet layer to echo mode. In this mode each received frame or, in the case of a remote user typing data in, each received character will be echoed back.

It can thus be seen that by opening a Telnet socket we have in fact opened a buffered TCP socket which has a few more capabilities that a standard buffered TCP socket. There is no handling of TCP_EVENT_REGENERATE since the TELNET layer is doing most of the dirty work for us, which also makes its use very simple. It is however a Telnet socket, which means that it has some Telnet specific function. It is best to look a little closer at the function fnTelnet() which can be used together with a Telnet socket.

extern int fnTelnet(USOCKET Telnet_socket, int iCommand);

The following command are possible:
GOTO_ECHO_MODE                 switches echo on
LEAVE_ECHO_MODE                switches echo off
PASSWORD_ENTRY                 flags that text entry is a password and so * is echoed back
CLEAR_TEXT_ENTRY               leave password entry mode
TELNET_ASCII_MODE              sets ASCII mode (this is the default mode of the Telnet socket)
TELNET_RAW_MODE                sets RAW mode - this means that the Telnet socket will not perform any Telnet-specific things
TELNET_RAW_RX_IAC_ON           enables IAC searching although in RAW mode
TELNET_RAW_RX_IAC_OFF          disables IAC searching in RAW mode
TELNET_RAW_TX_IAC_ON           enables IAC byte stuffing although in RAW mode
#define TELNET_RAW_TX_IAC_OFF  disables IAC byte stuffing in RAW mode
#define TELNET_RESET_MODE      reset mode to default (ASCII mode)

The first thing to note is that by calling fnTelnet(Telnet_socket, TELNET_RAW_MODE); you have now a Telnet-like socket which is not doing any Telnet specific stuff. This is then a buffered TCP socket which is doing the dirty work without trying to interpret any Telnet negotiations. This is thus a handy method of working with one or more buffered TCP sockets with minimum effort at the application layer!

If a Telnet-like socket is required to connect as a client to a remote server, it could use the following sequence, based on the standard fnTCP_Connect() command:

Telnet_socket = fnStartTelnet(usPortNumber, (5*60), 0, 0, fnTELNETListener);
if (Telnet_socket >= 0) {
    fnTelnet(Telnet_socket, TELNET_RAW_MODE);               // set RAW mode
    fnTCP_Connect(Telnet_socket, ucIP, usPortNumber, 0, 0); // connect to remote server
}



It has to be said that Telnet is rather complicated due to the various things that it can do. The uTasker Telnet implementation just does some basic but useful things to enable a reliable Telnet connection in ASCII or Binary mode (or Telnet-like connection in RAW mode). Since it supports TCP Windowing it is fast, but don't forget that the price penalty is in the RAM required for the TCP buffer(s).

Since a typical application for Telnet is to allow simple remote debugging, it supports negotiation of an ECHO mode. In this mode all received characters are echoes back to the remote client.

The default Telnet mode is ASCII mode. In this mode all received data is being scanned for negotiation sequences. These negotiation commands begin always with the IAC character (0xff) which doesn't occur in the ASCII data content.
However when transmitting binary data over a Telnet connection (with possibility of 0xff appearing in the data stream) byte stuffing is used. When 0xff is detecting in the tx data stream it is sent as a sequence of 2 x 0xff. The receiver performs the inverse so that it is still possible to detect real command sequences within binary data transfers.
This allows switching between ASCII and Binary modes, as well as other Telnet commands, during data transfers.


Depending on which programs are being used to transfer data, different types of operation are often observed. By setting up the various combinations of modes using fnTelnet() it is always possible to define the specific combination required to successfully transfer any type of data.

Some special notes on the operation of buffered TCP relevant to Telnet:

1. When negotiation sequences are sent, they are marked as such so that they will always be sent (including regeneration) as individual commands. This ensures that they will never be converted to binary data due to byte stuffing, nor be grouped together with other commands or data when being repeated. The buffered TCP transmission call uses the flag TCP_CONTENT_NEGOTIATION to acheive this.

2. When sending data which needs to be byte-stuffed there is a risk that the data content can, in the most extreme case, double. i.e. when 100 x 0xff are to be sent, they will be sent as 200 x 0xff because each byte will be stuffed to produce 2!
This means that the TCP buffer would have to be dimensioned to twice the normal required size to ensure that all data could be backed up in the most extreme case. The solution in the uTasker project is however not to back up the stuffed data (which would infact be the simplest method, but with the disadvantage of doubled buffer sizes) but instead to back up only the raw data and mark it appropriately.
In the case of TCP regeneration (hidded in the Telnet part) retransmitted frames are always regenerated from a backup of the raw data. This means a little more work (and it is rather more complicated...) when regenerating, but optimum buffer space efficiency as a result. Since the efficiency of regenerating lost messages is unimportant (they have already suffered a long timeout delay) the space saving has much higher importance.


I hope that the practical use of Telnet and Telnet-like sockets will now be very simple. They are easy to use and enable high speed data transfer to be acheived. The configuration possibilites enable fast data transfer between any combination of programs.

Not all details of TCP operation have been mentioned here but the descripton should be adequate for first implementations. There are some additional complications concerning TCP flow control across connections which may or may not be noticable in specific projects. The uTasker implementation does however allow all known difficulties to be solved and I look forward to comments and feedback so that additional points can be elaborated in real-world contexts.

Best regards

Mark

Offline eduardovra

  • Newbie
  • *
  • Posts: 11
    • View Profile
Re: Using TCP in the uTasker project
« Reply #20 on: January 24, 2008, 07:31:31 PM »
Hi Mark!

I working on a project that uTasker will have to work as a protocol converter. A PC will connect (TCP connection) to the board running uTasker and sends some datagrams, then the microcontroler converts them into ethernet raw packets and send to an ASIC chipset that we have. The problem is that some times the TCP datagram sent by the PC application is bigger than the MSS of TCP protocol. In this case, the datagram is divided and sent into two separate packages.
How can I know if the datadram came fragmented and how can I remount them ?

Thanks in advance !
Regards

Eduardo Vieira

Offline mark

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 3234
    • View Profile
    • uTasker
Re: Using TCP in the uTasker project
« Reply #21 on: January 24, 2008, 08:53:03 PM »
Hi Eduardo

TCP data can arrive at the TCP receiver call-back function as a sequence of several frames. The order in which the frames arrive is always the same as they were sent, but there is no information as to whether the content is a single data packet or a fragment of a larger data packet.

The application which controls the data content can add its own extra information (such as a small header which contains the total length of data which will arrive belonging to a single unity). In this case the receiver call-back handler can know that the data packet has not yet completed (if the present frame length < reported total length). It also knows how much more data is expected before it is complete. I this case it will need to save the data to a local buffer until it is complete - warning: once the call back routine is quit the input data, which is still in the Ethernet reception buffer, will be destroyed (overwritten by subsequent Ethernet receptions). Once the complete data has been received (the buffer used to save it must be adequately large for the largest expected datagram) it can be passed for handling.

Another possible solution (without having to add a header) is if the TCP stack at the PC is using the TCP PUSH flag correctly. The push flag is used to indicate that the present frame is the last in a sequence - it is used to push data to the application, whereas frames received without the PUSH flag set indicate that they first have to be buffered until complete. On the PC the PUSH flag can possibly be controlled by the program sending the data (but you will need to consult the details of the stack used there). It may work automatically but it may not...

The details of the present TCP header is presently not available in the TCP call back routine but this could be easily changed to make the PUSH flag visible (or to pass a different event depending on PUSH flag active or not) if necessary.

Personally I would add a small header to the data content since this will be fully controlable - the PUSH flag operation may change between PC stack implementations and versions so I wouldn't like to have to rely on it.

Regards

Mark






Offline seulater

  • Newbie
  • *
  • Posts: 38
    • View Profile
Re: Using TCP in the uTasker project
« Reply #22 on: March 29, 2008, 02:47:29 PM »
can you expound on the static int fnTestListener.

it it set up as a task of its own ? i would imagine that in your example for this it is set up as a task since it is not in a while loop.





Offline mark

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 3234
    • View Profile
    • uTasker
Re: Using TCP in the uTasker project
« Reply #23 on: March 29, 2008, 03:09:23 PM »
Hi

fnTestListener() is a call-back function and so doen't have any association with tasks in itself.

A call-back function is a routine defined when the socket was opened, which will be called by TCP whenever it has received data or has to report on events (such as connection open or close). It is called directly from the TCP code with a pointer to any data and length of the data.

This means that the call-back is not being scheduled to start as a result of TCP, but instead being called as a sub-routine from TCP. The Ethernet input buffer is thus guarantied to be stable during its processing by the call-back routine. The data is (depending on processor and Ethernet controller details) directly in the Ethernet input buffer ("zero copy" technique as sometimes known as).

Regards

Mark

Offline seulater

  • Newbie
  • *
  • Posts: 38
    • View Profile
Re: Using TCP in the uTasker project
« Reply #24 on: March 29, 2008, 03:18:21 PM »
so if things are slowly sinking in, i could create to listening sockets by doing...


USOCKET Test_socket_1 = fnGetTCP_Socket(TOS_MINIMISE_DELAY, (unsigned short)10, fnTestListener_1);

USOCKET Test_socket_2 = fnGetTCP_Socket(TOS_MINIMISE_DELAY, (unsigned short)10, fnTestListener_2);


static unsigned short usTestPort_1 = 10000;
fnTCP_Listen(test_socket_1, usTestPort_1, 0);

static unsigned short usTestPort_2 = 20000;
fnTCP_Listen(test_socket_2, usTestPort_2, 0);






Offline mark

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 3234
    • View Profile
    • uTasker
Re: Using TCP in the uTasker project
« Reply #25 on: March 29, 2008, 04:55:31 PM »
Yes, this is correct.

Three notes:

1. Make sure that you have enough sockets in the TCP socket pool to allow as many as you would like to use.
USER_TCP_SOCKETS in config.h specifies how many (non-standard) user TCP sockets should be reserved. The "standard" ones, like HTTP etc., are reserved automatically.

2. Look at the HTTP listerer to see how to handle multiple users on a single port.

3. Try using the uTasker simulator when experimenting with TCP stuff because it is much faster and easier to practice and learn. Using the Ethereal/Wireshark playback support helps playing with TCP frames because connections will not break down when debugging. [All is described, with example, in the tutorial].

Regards

Mark

Offline seulater

  • Newbie
  • *
  • Posts: 38
    • View Profile
Re: Using TCP in the uTasker project
« Reply #26 on: March 29, 2008, 11:41:36 PM »
Thanks, i will try it on the real thing, debugging is out of the question for me. i dont trust them

Offline mark

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 3234
    • View Profile
    • uTasker
Re: Using TCP in the uTasker project
« Reply #27 on: March 30, 2008, 12:09:20 AM »
I would still give it one try for TCP/Network work - I assure you, you can save well over 50% development time. It is very accurate and I know commercial projects which have used only the simulator for all web interface development and testing. On the target there was no difference in behaviour (sometimes after several days of new development additions) but the project schedule could easily be halved over traditional target level debugging.

Personally I can't live without it - I would be otherwise totally overwhelmed by the support questions. As it is I can run the project with any processor anywhere I am (as long as I have my laptop handy) and even play back complete network transitions from a remote installation (form a Wireshark recording) and usually immediately explain what the problem is or verify a solution.

The tutorial takes about 20 minutes to work through and you may find that you will in fact like what you see!!

Regards

Mark


Offline seulater

  • Newbie
  • *
  • Posts: 38
    • View Profile
Re: Using TCP in the uTasker project
« Reply #28 on: March 30, 2008, 12:13:03 AM »
ok, i will give it a whirl. i dont think the Ethernet will be cause of many problems as using new external peripheral. I will take your advise and give it a try.
So the PC debugger even is able to match the boards cpu speed in simulating as well ?

Offline mark

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 3234
    • View Profile
    • uTasker
Re: Using TCP in the uTasker project
« Reply #29 on: March 30, 2008, 12:38:18 AM »
Hi

The simulator will not try to run at the CPU speed but it will check the PLL settings and display the speed that it is programmed to (usefully for checking). You will find that if you play around with the PLL settings (MUL and DIV etc.) and do anything illegal it will give compiler errors (not necessarily on the Luminary because this is more or less automated).

[I wrote the next after misreading your question as to be related to the COM port speeds. I won't delete it though because it may be of interest...]
The UART simulator will automatically adjust the COM settings to match the UART settings (map the UARTs to COMs in app_hw_xxxx.h - eg: #define SERIAL_PORT_0   '3' maps UART to COM3, etc.)

The simulator will also change the COM setting 'on-the'fly' if you change the UART configuration via the serial menu (either via the UART/COM or via Telnet).

In the Coldfire project it will also accurately control the RTS/CTS lines for HW flow control work and can work with break generation and detection. Also UART DMA mode is simulated to work with the COM port. Presently the LPC/Luminary projects (and their simulators) will do neither hardware flow control nor break detection (useful for terminating frames when receiving in DMA mode) - this is not due to the lack of support in the simulator but rather in the present UART driver for these chips.

Regards

Mark

PS. The idea of the simulator is not to simulate the instruction set of the CPU but rather to allow applications to run in 'real-time' using all project code, including interrupt routines. The goal was to allow embedded projects to be developed on a PC even before chips were available and allow network debugging to be performed in 'real-time' and also in off-line mode.