Hi All
The uTasker demo project uses TCP for several services. These include HTTP, SMTP, FTP, TIME SERVER and TELNET, to name the ones which initially spring to mind.
The demo project enables these services to be easily studied and own services to be written (usually termed clients and servers) based on these example. There is, at the time of writing, no 'official' document which contains a definitive guide.
This is logically the reason why the use of TCP is a frequent question and frequent discussion topic in private mails. It is also a reason why this document, which is long overdue, should soon be made available.
Therefore this thread is designed to be an introduction to the subject to answer the first burning questions and to give first practical examples, as well as more detailed insight into how it works, how it can be used and why it all works like that. If all goes well, the thread comntent can be moved to a more structured document at a later point in time.
TCP is in fact a very simple protocol - in its simpest form. But in order to allow it to perform with the speed and reliability which has enabled it to become originally the standard in the Internet and eventually the standard in almost all networks it does need a few additional frills which can then make it quite complicated indeed.
If you would like to discuss standard TCP details please feel free to create your own topic to get a discussion going. I have always found it very difficult to find any really meaningful discussions about the practical aspects in the Internet so it would be nice to be able to find some good ones here!
A simple TCP server example. (The HTTP service is a good source for reference).
First we get a socket from the TCP socket pool. The size of the socket pool is defined in config.h, where the define USER_TCP_SOCKETS can be incremented for every additional socket which user code (as opposed to standard services like HTTP) will need.
The following configures a socket with the defined characteristic (minimum delay as opposed to maximum throughput or maximum reliability - these are router parameters), a timeout of 10 seconds (after this length of time with no activity it will close the connection. A value 0xffff means no timeout!), and a callback routine called fnTestListener() which will be doing the work later.
static USOCKET Test_socket = -1; // declare a static socket variable
...
Test_socket = fnGetTCP_Socket(TOS_MINIMISE_DELAY, (unsigned short)10, fnTestListener); // from task or sub-routine code
The socket is not in the listening state (which is needed for it to act as a server) so we start it in this state by calling
static unsigned short usTestPort = 0x9999; // declare a static socket variable (the value could also be fixed)
....
fnTCP_Listen(test_socket, usTestPort, 0); // after getting the socket, bind it to a listening port
Now the socket is listening on port 0x9999, which means if you have a test client (eg. Telnet on this port) send a connection request to it it will start a connection process.
Note that the parameter 0 when setting the listening state is the maximum windows size which the connection can receive - a zero defaults to the standard LAN TCP pay load size of 1460 bytes. For some applications with special buffer restrictions this can be used to improve flow control in the rx sense (CONTROL_WINDOW_SIZE needs to be enabled in config.h) but this won't be described here just yet.
The simple server listener looks something like this.
#define TEST_BUFFER_LENGTH 10
typedef struct stTCP_MESSAGE
{
TCP_HEADER tTCP_Header; // reserve header space
unsigned char ucTCP_Message[TEST_BUFFER_LENGTH];
} TCP_MESSAGE;
static int fnTestListener(USOCKET Socket, unsigned char ucEvent, unsigned char *ucIp_Data, unsigned short usPortLen) {
TCP_MESSAGE test_message;
switch (ucEvent) {
case TCP_EVENT_CONREQ:
case TCP_EVENT_CONNECTED:
case TCP_EVENT_CLOSE:
case TCP_EVENT_ACK:
case TCP_EVENT_ARP_RESOLUTION_FAILED:
case TCP_EVENT_PARTIAL_ACK:
break;
case TCP_EVENT_REGENERATE:
case TCP_EVENT_DATA:
if ((ucEvent == TCP_EVENT_REGENERATE) || (!uMemcmp((CHAR*)ucIp_Data, "TEST" , 4))) {
uStrcpy((CHAR*)test_message.ucTCP_Message, "Hi!!");
if (fnSendTCP(Socket, (unsigned char *)&test_message.tTCP_Header, 4, TCP_FLAG_PUSH) > 0) {
return APP_SENT_DATA;
}
}
break;
case TCP_EVENT_ABORT:
case TCP_EVENT_CLOSED:
fnTCP_Listen(Socket, ++usTestPort, 0); // go back to listening state on next port number
break;
}
return APP_ACCEPT;
}
As you can see it receives events from the TCP layer, some with other details such as a pointer to data.
This is a list of the events which can arrive:
- TCP_EVENT_ABORT TCP connection was closed (due to error or a reset from peer)
- TCP_EVENT_CONNECTED TCP connection has been successfully established
- TCP_EVENT_ACK Data which we sent have been successfully acknowledged (for all sent data, with outstanding acks)
- TCP_EVENT_DATA Data frame received with data content for us to treat
- TCP_EVENT_CLOSE Peer is starting a close of the present TCP connection
- TCP_EVENT_CLOSED The TCP connection has been fully closed
- TCP_EVENT_REGENERATE The last data frame was lost and must be resent!!!
- TCP_EVENT_CONREQ A peer wants to establish a TCP connection (SYN received)
- TCP_EVENT_ARP_RESOLUTION_FAILED Generally only when working as client and sending a connection request or data after a longer period of no activity. It means that the the SYN or the data could not be sent by the IP layer since the address of the destination could not be resolved (destination powered down or network problem, or just doesn’t exist). In this cause the application is informed that the connection is either not possible of the connection has broken down due to this.
- TCP_EVENT_PARTIAL_ACK This is only used when Windowing is in operation. Presently only TELNET uses it. It means that an ACK has been received for a part of the data which has been sent (rather that all data that has been sent). This is used to acknowledge that only a part of the data has been successfully delivered rather than all outstanding data. This has to be treated differently to a normal ACK event since the ack number has to be used to calculate which outstanding data acks are still open and expect their own ack later.
Now if you are new to TCP, enter in the above test program - best in the simulator - and you can see your first simple TCP server in operation. It won't do a lot but it will at least do the following.
1. When the client requests a connection to port 0x9999 the event TCP_EVENT_CONREQ will be received. Here it would be possible to deny the connection to a black-listed IP for example but our server always allows connections.
2. The TCP connection will be established and the event TCP_EVENT_CONNECTED is received.
3. If the client sends any data it will arrive with the event TCP_EVENT_DATA. The server will only respond to the message "TEST" in which case it will send the string "Hi!!" as answer.
4. After 10s with no activity, the server will close the connection (it is actually TCP that does this after the idle timer fires but the server receives the event TCP_EVENT_CLOSED when the connection has successfully closed).
5. The lister socket is not active after the connection has been closed so the code sets it up as listener again, this time with port number 0x999a.
6. If the connection is repeated, the port number is always incremented each time and the connection always times out after 10s.
Note that when the server sends data it returns APP_SENT_DATA. This informs TCP that the application has just successfully sent something and TCP doesn't have to acknowledge the received data - the ack has already been piggy-backed with the transmitted data.
If the transmission were to fail and need repeating, the server has to handle the event TCP_EVENT_REGENERATE. Since we only ever send one message it is obviouse that this has failed and so it is simply resent. The event TCP_EVENT_ACK will be received when the transmitted message actually has arrived safely.
It is in fact a great shame that messages can and do get lost (but it is not a perfect world and so we have to be ready for it) because regeneration of previously sent messages is probably the most complicated and/or RAM consuming parts of TCP implementations. This example has it real easy, but this is also a real simple example!
Here is a list of all possible return codes from the listener.
- APP_ACCEPT This is normal return when no data has been sent. The TCP level will then acknowledge the received frame and the connection will continue
- APP_SENT_DATA The listener is informing TCP that it has sent data (be sure that data was really sent when returning this, it should not be returned if a transmission attempt failed)
The TCP level will therefore not send an ACK of its own since the data will have had a piggy-back ack of its own. If there is a mistake and data is sent and APP_ACCEPT is returned it is not serious – there will simply be an unnecessary ACK sent which doesn’t actually hurt anything (just not so efficient) - APP_REJECT This is used to reject a connection (for example if the IP address is on a black list…) It causes TCP level to return a RESET rather than perform the TCP connection sequence
- APP_REQUEST_CLOSE The application informs TCP that the application has just actively started the close of the present TCP connection.
It is returned after the application calls fnTCP_close(socket); - APP_REJECT_DATA This is a special case where the application doesn’t want to accept data at the moment. It doesn’t cause the connection to be broken down but TCP will not ACK the data. The peer will then be forced to repeat the data – resulting in a delay (using the peer as a queue, possibly until data can be received).
- APP_WAIT Same as previous but delay on TCP connection establishment.
Our example has made use of very few features up to now and there is much more to be explained. However this should allow first steps to be taken and also the servers in the project (HTTP, FTP etc.) to already make a lot more sense.
I will continue the topic later with a first simple client example. If you already have confusions or questions, or corrections etc. please feel free to comment.
Regards
Mark