TCP Client

TCP principles

With TCP, a connection is established between two machines. Usually, the client asks for the connection creation and the server accepts it. Once the connection is established, the client and the server are able to read and write data.

There are two channels in a single connection:

  • one channel to write data to the machine we are connected to
  • one channel to read data from the machine we are connected to

One of these channel can be closed without closing the connection.

Connection with two channels

Guarantees of TCP

  • Reliability / Fiabilité: TCP ensures that no data is lost.

  • Integrity / Intégrité: TCP ensures that data arrive in the same order they have been sent, and that they are not modified.

  • No conservation of limits: TCP does not ensure that sent data is received in one go.

    If 100 bytes are written on a TCP connection, it is possible to receive 40 bytes first, and then 60. Or possibly 100 in one go. If 50 bytes are written first, and then 50 other bytes, it is also possible to receive 100 bytes in one go.

Client vs Server

Usually, several clients connect to a single server.

As in UDP, both client-side and server-side attachment point is done through sockets, linked to an IP address and a port number.

For now, in this lecture, we only consider clients.

TCP client-side

A TCP socket is represented by an object of the class SocketChannel.

A SocketChannel is created using the method factory SocketChannel.open(); it is not connected.

Connection to a server is initiated by a call to socketChannel.connect(SocketAddress).

// creation of the socket (not connected)
SocketChannel sc = SocketChannel.open();

SocketAddress serverAddress
  = new InetSocketAddress("www.google.com",80);
// connection to server
sc.connect(serverAddress);

Reading and writing on a SocketChannel

Once the socketChannel is connected to the server, it is possible to send bytes to the serveur and to receive bytes from the server.

  • The method int socketChannel.read(ByteBuffer bb) reads bytes from the server and stores them in the ByteBuffer.
  • The method socketChannel.write(ByteBuffer bb) writes bytes of the ByteBuffer to the server.
These methods are the same as for the class FileChannel.

By default, the methods socketChannel.read and socketChannel.write are blocking.
The method socketChannel.read returns when at least 1 byte has been read or if the end of the connection has been detected. Method socketChannel.write returns when all data in the work-zone of the buffer has been written.

Reading on a SocketChannel (1/2)

Reading is done through method int read(ByteBuffer buff). Read data is stored in the work-zone of the ByteBuffer (at most buff.remaining()).

Warning there is no guarantee that the work-zone of the ByteBuffer will be entirely filled.

If there is more data to read that the buffer can store, data is not lost; there will be read by a forthcoming call.

Method read returns

  • the number of bytes read, or
  • -1 if the connection has been closed for write by the server. ⇒ in this case, it is still possible to write to the server, but nothing more can be received.

Reading on a SocketChannel (2/2)

  ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
  int read=sc.read(buffer);
  if (read==-1){
    System.out.println("Connection closed for reading");
  } else {
    System.out.println("Read "+ read +" bytes");
  }

Writing in a SocketChannel

Writing is done by method write(ByteBuffer buff).

Data in the work-zone of the ByteBuffer is entirely written. By default, this method blocks until the write operation is completed.

  ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
  buffer.putLong(1l);
  buffer.putLong(2l);
  buffer.flip();
  sc.write(buffer);

Closure

  • It is possible to close only the writing channel with shutdownOutput(). The server is notified (its read returns -1 once it has read all the data written before the call to shutdownOutput()).
  • It is possible to close only the reading channel with shutdownInput(). Purely local: the server is not notified.
  • It is possible to close both channels with close().
    Essential to free resources of the operating system.

No conservation of limits

When receiving data, it is not possible a priori:

  • to know when/if all data has been received,
  • to know how to split/analyze received data.

This problem is solved by the protocol

There are three common solutions:

  • close the connection to signal the end of data,
  • use a mark (i.e. a sequence of bytes) to signal the end of data,
  • send first the size of forthcoming data (on a fixed number of bytes).

Close the connection (1/3)

If we only have a single block of data to send, it is possible to send it, and then shutdown the writing part of the channel. The server will understand that there is no more data when the method read returns -1.
(This is what HTTP 1.0 does)

Problems:

  • Several requests = several connections
  • How to set the size of the reception buffer ?

Use an end mark (2/3)

If we have several blocks of data to send, for instance several ASCII encoded strings, it is possible to choose a sequence of bytes as a "end mark" for each string.
(This is done in headers of HTTP, with \r\n)

Problems:

  • Complex to implement from the point of view of receiver.
  • How to set the size of the reception buffer ?

Send first the size (3/3)

It is possible to send the size (for instance with a long) of data before to send them.

Problem:

  • The sender must know the total size of data before starting to send the first byte.

(this is the principle of chunks in HTTP/1.1)