Project 5 - TCP part 2

Introduction

There are several new issues introduced with respect to the TCP protocol in this project. First the simple echo of received characters will be replaced by a socket interface and a simple application which is implemented as a new thread. Then the TCP layer will be extended with

This project is finished when

Note: The "simple" application will be an implementation of the echo port as in lab 4 but with some commands controlling the streaming of data and the connection.

There is some administration in order to obtain the source code. Refer to the suggested design of the class hierarchy as well as advice on how to approach the completion of the skeleton code. An executable which may be loaded into the ETRAX unit is compiled, linked, and loaded in the same manner as in the previous overview of the system. Finally, you will have to test your solution.

Recommended reading

Read chapters 19, TCP interactive data flow, 20, TCP bulk data flow, and 21 TCP timeout and retransmission, in [Stevens96]. Section 20.3 covering the subject of sliding windows is very important.

Consult RFC793 on TCP.

Suggested solution design

The socket interface and the simple application

The socket interface is intended to provide a tool in order to design simple TCP applications. An additional association in the class TCPConnection refers to an instance of the class TCPSocket. The socket class has two semaphores which are used in order to synchronise read and write operations.

When a new TCP connection is established in the method TCP::connectionEstablished, an instance of the TCPSocket is created and registered within the TCPConnection. After the registration, the simple application is scheduled as a job with the socket instance as an association.

 Details in the implementation

The socket interface and the simple application

The functionality of the socket interface may be divided into two separate parts, the connection establishment and the transfer of data.

Two methods must be added to the TCP class in order to support the connection establishment

class TCP
{
  ...
  bool acceptConnection(uword portNo);
  // Is true when a connection is accepted on port portNo.
  void connectionEstablished(TCPConnection* theConnection);
  // Create a new TCPSocket. Register it in TCPConnection.
  // Create and start a SimpleApplication.
  ...
};

and two new methods are also required in the TCPConnection class

class TCPConnection
{
  ...
  uword serverPortNumber();
  // Return myPort.
  void  registerSocket(TCPSocket* theSocket);
  // Set mySocket to theSocket.
  ...
}

When an instance of a TCPConnection receives a SYN flag in the state LISTEN, the method TCP::instance().acceptConnection(theConnection->myPort) is invoked in order to determine if the connection may be established. At present, only the ECHO port, 7, should be supported.

Assume that the request to connect is granted. The connection will then enter the state SYN_RCVD in the TCP state machine and expect an acknowledge. When the ACK flag is received, the method TCP::instance().connectionEstablished(theConnection) is invoked and the associations created

void
TCP::connectionEstablished(TCPConnection *theConnection)
{
  if (theConnection->serverPortNumber() == 7)
  {
    TCPSocket* aSocket = new TCPSocket(theConnection);
    // Create a new TCPSocket.
    theConnection->registerSocket(aSocket);
    // Register the socket in the TCPConnection.
    Job::schedule(new SimpleApplication(aSocket));
    // Create and start an application for the connection.
  }
}

Refer to the file tcpsocket.hh for the declarations of the classes TCPSocket and SimpleApplication.

The TCP state machine has now entered the state ESTABLISHED and the transfer of data handled by the instance of the SimpleApplication may begin. The implementation of the echo functionality is simple as the application is executed in a dedicated thread

SimpleApplication::doit()
{
  udword aLength;
  byte* aData;
  bool done = false;
  while (!done && !mySocket->isEof())
  {
    aData = mySocket->Read(aLength);
    if (aLength > 0)
    {
      mySocket->Write(aData, aLength);
      if ((char)*aData == 'q')
      {
        done = true;
      }
      delete aData;
    }
  }
  mySocket->Close();
}

The application above recognises the command q which means quit. The implementation of the active close in response to a quit command is postponed.

There is a semaphore associated with each of the Read and Write methods in the TCPSocket class. Thus, both methods are blocked until data is available or transmitted and acknowledged.

The application is blocked on the call mySocket->Read due to the semaphore associated with the method. The state ESTABLISHED in the TCP state machine implies that when a segment with the PSH flag set is received, the data in the segment should be passed to the application as soon as possible. Thus, the method TCPSocket::socketDataReceived will be invoked in the method EstablishedState::Receive and let the application leave the wait statement and handle the data.

byte*
TCPSocket::Read(udword& theLength)
{
  myReadSemaphore->wait(); // Wait for available data
  theLength = myReadLength;
  byte* aData = myReadData;
  myReadLength = 0;
  myReadData = 0;
  return aData;
}

void
TCPSocket::socketDataReceived(byte* theData, udword theLength)
{
  myReadData = new byte[theLength];
  memcpy(myReadData, theData, theLength);
  myReadLength = theLength;
  myReadSemaphore->signal(); // Data is available
}

Now, the application will invoke the method mySocket->Write in order to echo the data and it will block until TCPSocket::socketDataSent is invoked.

void
TCPSocket::Write(byte* theData, udword theLength)
{
  myConnection->Send(theData, theLength);
  myWriteSemaphore->wait(); // Wait until the data is acknowledged
}

void
TCPSocket::socketDataSent()
{
  myWriteSemaphore->signal(); // The data has been acknowledged
}

The method TCPSocket::socketDataSent should only be invoked from the state machine when all data are sent and acknowledged by the recipient. This is very important!

It is also possible to receive a segment with the FIN flag set in the state ESTABLISHED. The corresponding method EstablishedState::NetClose should invoke the method TCPSocket::socketEof() instead of the direct state transition into CLOSE_WAIT which was used in the previous project.

void
TCPSocket::socketEof()
{
  eofFound = true;
  myReadSemaphore->signal();
}

Finally, the method TCPSocket::Close invokes the method EstablishedState::AppClose in the state machine as a response to the command q or a FIN flag in a received segment.

The active close

A TCP connection may be terminated both from a client with a passive close and from a server with an active close. In the previous project the passive close was implemented which lead to the states CLOSE_WAIT and LAST_ACK in the state machine. The active close as a response to the command q in the simple application will be implemented in this project, i.e., when q is the only character on a line the connection will be closed.

In practice, the receival of the command q leads to a call to TCPSocket::Close() which invokes the method EstablishedState::AppClose(). In the method, a segment with the FIN flag set is sent and a state transition into the FIN_WAIT_1 state should be performed. When a segment which contains a set ACK flag is received another state transition is performed into FIN_WAIT_2.

Introduce the states FinWait1State and FinWait2State in the state machine and implement the methods in order to obtain an orderly active close.

The transmission queue

An instance of the class TCPConnection must have a transmission queue in order to send large amounts of data. The queue must be maintained until all data are sent and acknowledged by the recipient. A simple queue may be implemented with three state variables in the class TCPConnection

Simple arithmetic operations where the state variable sendNext is also used yield

A transmission is initiated in the application with the method TCPSocket::Write. The method invokes TCPConnection::Send and blocks on the associated semaphore until all data are sent and acknowledged by the recipient.

In order to send large amounts of data, add a new method in the class TCPSender called sendFromQueue. The responsibility of the method is to maintain the queue and send smaller sets of data in segments by the method TCPSender::sendData until all data are acknowledged. The new method will initially be invoked from the method TCPConnection::Send.

The initially sent segments will generate acknowledgements from the recipient which invoke the method EstablishedState::Acknowledge. The method must continue the transmission by a new call to TCPSender::sendFromQueue until the entire queue has been transmitted and acknowledged.

The state variables of the queue must be reset when the last byte of the queue is acknowledged. Then, the signal to release the blocked method TCPSocket::WriteTCPSocket::socketDataSent, is issued from EstablishedState::Acknowledge.

The dynamically allocated array which transmitQueue is a reference to must be returned to operating system by the application. It is not the responsibility of the TCP state to handle the deallocation.

Implement the recognition of a send command in the application, e.g. s, and allocate a data block of about 10 kB size in response to the command. Continue by sending the entire data block with the method TCPSocket::Write and remember to return the memory to the operating system in the application. Initialise the data block with a fixed character sequence, e.g. 40 characters long if the telnet window is 80 characters wide. Errors in the implementation will easily be detectable since the segment length 1460 is not a multiple of 40. Note: do NOT insert any line feed or carriage return characters into the stream. The telnet client will automatically break lines.

The sliding window protocol

The sliding window protocol in TCP is an important functionality in bulk data throughput.  A buffer is utilised on each endpoint of an established connection. The window advertised from one endpoint is known as the offered window and the size is directly correlated to the buffer size. When a sender intends to send data, it must calculate the usable window of the other endpoint. The usable window size is the offered window size subtracted by the number of byte sent previously without receipt of an acknowledge.

The sliding window allows transmission of at most the size of the offered window when all previous data are acknowledged. The advantage of this concept is that the throughput is increased when it is possible to send new segments without the need to await an acknowledge for each transmitted segment. Each segment sent in either direction must have a window advertisment in the TCP header.

An instance of the class TCPConnection must know the offered window from the other endpoint. A state variable is introduced, e.g.

and is updated each time a segment is received in the method TCPInPacket::decode

aConnection->myWindowSize = HILO(aTCPHeader->windowSize);

Change the implementation of the method TCPSender::sendFromQueue to include the usable window in the transmission. All data which may be sent within the usable window should be sent immediately. The usable window may be calculated from previously introduced state variables

udword theWindowSize = theConnection->myWindowSize -
    (theConnection->sendNext - theConnection->sentUnAcked);

A macro which decides the minimum of two variables, MIN(a, b), is available and may be used in the calculation of the amount of data to send.

Hint: Introduce some different commands, e.g., s or r, that sends data of different size for example 10 kbyte and 1 megabyte.

Retransmission of lost segments

Retransmission is the last important functionality to be implemented in order to obtain a rudimentary TCP implementation. A retransmission timer is started each time a segment is sent from the queue. When all the data sent from the queue is acknowledged, the timer is stopped. If all segments sent are received and acknowledged, the timer will never time out and cause a retransmission. However, if a segment is lost the recipients window will be filled sooner or later and the sender will have to stop further transmission of data. The responsibility of the retransmission timer is to restart the transmission from the lost segment.

The state variable sendNext must be assigned the value of the variable sentUnAcked if the retransmission timer times out. Then the transmission of data is restarted by sending another segment starting at the position sendNext. Since the recipient stores all data received after the lost segment it is unnecessary to retransmit all the data from the present position in the stream.

In order to limit the amouunt of data retransmitted a new state variable, sentMaxSeq, is introduced which is assigned the highest sequence number transmitted so far. The new state variable may also be used in order to determine if a retransmission is in progress.

Since the variable sendNext is moved back in the stream into the position of the variable sentUnAcked at retransmission, the method sendFromQueue may detect the retransmission from the fact that the variable sentMaxSeq is larger than sendNext. In response, sendFromQueue only sends one segment even if the usable window of the recipient is larger.

The variable sendNext may be moved into the new position of sentUnAcked if a segment with the acknowledge flag is received where the acknowledgement number is larger than the value of sendNext.

Implement a retransmission timer class, instantiate a timer in every instance of the class TCPConnection and add the functionality of retransmission. Use a static counter variable in the method TCPSender::sendData in order to throw away one segment out of thirty.

One second is a good estimate of the retransmission delay in a local area network.

class retransmitTimer : public Timed
{
 public:
   retransmitTimer(TCPConnection* theConnection,
                   Duration retransmitTime);
   void start();
   // this->timeOutAfter(myRetransmitTime);
   void cancel();
   // this->resetTimeOut();
 private:
   void timeOut();
   // ...->sendNext = ...->sentUnAcked; ..->sendFromQueue();
   TCPConnection* myConnection;
   Duration myRetransmitTime;
   // one second
};

Source code, compilation, linking and loading

Make sure your present working directory is ~/kurs/src. Remove the subfolder lab5 if it exists in ~/kurs/src. Then, copy all files from your solution in project 4 with the command

cp -r lab4 lab5

and change your present working directory into ~/kurs/src/lab5. This project will be an extension of your solution in project 4. Add the skeleton of project 5 to your previous files with the command

cp -r ~inin/kurs/src/lab5/* .

There should be a new file in the directory lab5  in addition to the ones from project 4,

The target of the make process is defined in the file /kurs/make/lab5/modules. Make sure your present working directory is ~/kurs/make and type the commands:

Testing the solution

The tools in this project are

The telnet program is used in order to verify the solution

telnet <ETRAX IP-address> 7

where 7 corresponds to the ECHO port number. In addition to the echo functionality at least two commands must be supported

The active close

Test the active close by typing the command q in the telnet window after the connection has been established and record the segments exchanged in the network monitor. Compare the result with the description of the state machine.

The transmission queue

Test the transmission queue by typing the command s in the telnet window after the connection has been established and capture the segments exchanged in the network monitor.
Confirm that the number of byte sent from the stream are actually received by the recipient.
 

The sliding window protocol

Send large amounts of data, 100 kB - 1 MB, and capture the segments exchanged. It takes a long time to initiate 1 MB of data with a fixed character sequence but the test should be performed anyway. Confirm that the number of byte sent from the stream are actually received by the recipient.

Measure the throughput of data by sending 1 MB of unitialised data. What is the capacity of the connection? Does the advertised window become zero at any point in the transmission?
The capacity may be measured in the network monitor if the option Buffer Size is changed into 2 MB in the sub menu  Buffer Settings... below the main menu Capture. Capture all segments and calculate the duration by subtracting the time stamp of the last segment from the time stamp of the first one.

You should reach a bulk transfer speed of roughtly 500 kbytes to 1.2 megabytes per second.