INFORMATION & COMPUTER SCIENCE DEPARTMENT, KFUPM

SWE344: Internet Protocols & Client-Server Programming

LAB  #05: TCP Client-Server Application

 


Objectives:

To gain experience with:

·        Creating TCP Client-Server applications using the TcpListener and TcpClient classes.

·        Creating TCP Client-Server applications using the Socket class

 

1.       IPAddress and IPEndPoint classes

 

IPAddress class:

This class is used to represent an IP address as an object.

 

A common way of creating IPAddress object is using its factory Parse() method.

 

Following are important methods & properties of the IPAddress class.

static IPAddress Parse(String address)

Takes an IP address as a string and returns an IPAddress object

static bool IsLoopback(IPAddress address)

returns true if address is a loop back address

IPAddress  Any

Represents any IP address available on the local system.

IPAddress Broadcast

Represents broadcast address

IPAddress Loopback

Represents loopback address

 

IPEndPoint:

This class is used to represent the combination of IP address and Port number as an object.   Its most frequently used constructor is:

public IPEndPoint(IPAddress address, int port)

 

It has properties, IPAddress and Port, that can be used to get the corresponding components of the end-point,

 

 

2.       Creating a TCP server using the TcpListener class

The easiest way to create a TCP server is by using the TcpListener class.

 

The following list the common constructors and methods of the TcpListener class.

TcpListener(IPAddress,  int port)

Binds the server to a specific IPAddress object and port number. 

TcpListener(IPEndPoint ie)

Binds the server to an IPEndPoint object. 

void Start()

Starts the server

TcpClient AcceptClient()

Accepts connection from a TcpClient

void Stop()

Stops the server

bool Pending()

Determines if there are pending connections

 

Example 1:

The following example creates a simple Tcp server.

using System;

using System.Net;

using System.Net.Sockets;

using System.IO;

 

class SimpleTcpServer {

  public static void Main() {

     TcpListener server = new TcpListener(IPAddress.Any, 9050);

     server.Start();

    

     Console.WriteLine("Waiting for Client...");

     TcpClient client = server.AcceptTcpClient();

     Console.WriteLine("Connected with a client");

    

     NetworkStream stream = client.GetStream();

    StreamReader reader = new StreamReader(stream);

     StreamWriter writer = new StreamWriter(stream);

    

     writer.WriteLine("Welcome to my test server");

     writer.Flush();

     String line = null;

     while((line = reader.ReadLine()).Length != 0) {

       Console.WriteLine(line);

       writer.WriteLine(line);

       writer.Flush();

     }

     client.Close();

     server.Stop();

  }

}

 

 

3.                 Using Telnet to test a server application

 

We can easily test our server above using the Microsoft Telnet program that comes standard with all Windows platforms.

 

To start the Telnet, simply go command window and type:

 

C:\>telnet  ipaddress  port

 

where ipaddress and port are the IP address and the port number on which the server is running.

 


4.                 Creating a TCP client using the TcpClient class

 

While a TcpListener object listen to a local EndPoint for connection, a TcpClient instance connects to a remote EndPoint - the EndPoint to which a server is listening.

 

The following are the constructors and methods of the TcpClient class:

TcpClient(string host, int port)

TcpClient(IPEndPoint localEP)

TcpClient()

void Connect(string host,  int port)

NetworkStream GetStream()

Close()

 

Example 2: The following example is a Tcp client application designed to work with the Tcp server discussed above:

using System;

using System.Net;

using System.Net.Sockets;

using System.IO;

 

class SimpleTcpClient {

  public static void Main() {

    TcpClient client = new TcpClient("localhost", 9050);

 

    NetworkStream stream = client.GetStream();

    StreamReader reader = new StreamReader(stream);

    StreamWriter writer = new StreamWriter(stream);

   

    String input = reader.ReadLine();

    Console.WriteLine(input);

 

    String line = null;

    do {

       Console.Write("Enter Message for Server <Enter to Stop>: ");

       line = Console.ReadLine();

       writer.WriteLine(line);

       writer.Flush();

       if (line.Length != 0) {

         line = "Echo: "+ reader.ReadLine();

         Console.WriteLine(line);

       }

    } while(line.Length != 0);

    client.Close();

  }

}

 

 

5.                 The Socket class

Although many client-server applications can be developed using the helper classes, TcpListener, TcpClient (and UdpClient) classes, to really have full control on the behavior of such applications, one need to use the Socket class. 

 

In particular, the Socket class must be used for applications involving protocols other than TCP/UDP and applications that require asynchronous communications.

 

The constructor of the Socket class has the following format:

public Socket(AddressFamily af, SocketType st, ProtocolType pt)

 

AddressFamily represent the addressing scheme being used for the communication. 

Some of the values of this enumeration are: InterNetwork, InterNetworkV6, etc.   

 

SocketType represnts the type of socket being created, while ProtocolType indicates the type of protocol being used to transfer data on the socket.

 

In the InterNetwork address family, we have only the following combinations of SocketTypes and ProtocolTypes:

 

Socket Type

Protocol Type

Description

Dgram

Udp

Connection-less

Stream

Tcp

Connection-oriented

Raw

Icmp

Internet Control Message Protocol

Raw

Raw

Plain IP packets

 

The following statement shows how to create a Socket instance:

 

Socket server = new Socket(AddressFamily.InterNetwork,  SocketType.Stream,

ProtocolType.Tcp);

 

Methods:

The following are some of the methods of the Socket class. 

Method

Description

Applicable

void Bind(IPEndPoint ep)

binds a server socket to a local end-point

Tcp Server

void Listen(int max)

listen for clients; max is the maximum number of clients to enqueue, while waiting for connection

Tcp Server

Socket Accept()

connects to a client and returns a reference to the client’s socket

Tcp Server

void Connect(IPEndPoint)

connects to a remote end-point

Tcp Client

int Receive(byte[] data)

int Receive(byte[] data, int size, SocketFlags sf)

int Receive(byte[] data, int offset, int size, SocketFlags sf)

overloaded,: reads bytes from a socket

Tcp server/client

int Send(byte[] )

int Send(byte[] data, int size, SocketFlags sf)

int Send(byte[] data, int offset, int size, SocketFlags sf)

overloaded,  sends bytes to a socket

Tcp server/client

void ReceiveFrom(byte[], ref EndPoint)

overloaded, receives from a client at EndPoint

Udp client

void SentTo(byte[] ref EndPoint)

overloaded, sends to a client at EndPoint

Udp client

void Shutdown( SocketShutdown how)

Disables sends and receives on a socket

All

void Close()

close a socket.

All

 

The following figure shows how some of these methods may be called by a Tcp client-server application.

 

 


Example 3: Creating a TCP Server Using the Socket class

 

using System;

using System.Net;

using System.Net.Sockets;

using System.Text;

 

class SimpleTcpSocketServer {

  public static void Main() {

     Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

     IPEndPoint localEP = new IPEndPoint(IPAddress.Any, 9050);

     server.Bind(localEP);

     server.Listen(10);

    

     Console.WriteLine("Waiting for Client...");

     Socket client = server.Accept();

     IPAddress clientAddress = ((IPEndPoint)client.RemoteEndPoint).Address;

     Console.WriteLine("Got connection from "+clientAddress);

    

     byte[] data = Encoding.ASCII.GetBytes("Welcome to my test server");

     client.Send(data);

 

     int dataSize = 0;

     while(true) {

       data = new byte[1024];

       dataSize = client.Receive(data);

       if (dataSize == 0)

          break;

 

       Console.WriteLine(Encoding.ASCII.GetString(data,0, dataSize));

       client.Send(data, dataSize, SocketFlags.None);

     }

     client.Close();

     server.Close();

  }

}

 

 


Example 4: Creating a TCP Client Using the Socket class

 

using System;

using System.Net;

using System.Net.Sockets;

using System.IO;

using System.Text;

 

class SimpleTcpSocketClient {

  public static void Main() {

     Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

     IPEndPoint remoteEP = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9050);

     try {

       socket.Connect(remoteEP);

     }catch (SocketException e) {

       Console.WriteLine("Unable to connect to server. ");

       Console.WriteLine(e);

       return;

     }

    

     byte[] data = new byte[1024];

     int dataSize = socket.Receive(data);

     Console.WriteLine(Encoding.ASCII.GetString(data, 0, dataSize));

 

     String input = null;

     while (true) {

       Console.Write("Enter Message for Server <Enter to Stop>: ");

       input = Console.ReadLine();

       if (input.Length == 0)

          break;

       socket.Send(Encoding.ASCII.GetBytes(input));

       data = new byte[1024];

       dataSize = socket.Receive(data);

      

       Console.WriteLine("Echo: "+ Encoding.ASCII.GetString(data, 0, dataSize));

     }

     Console.WriteLine("Disconnecting from Server..");

     socket.Shutdown(SocketShutdown.Both);

     socket.Close();

  }

}

 


6.                 Handling Text in Socket applications

 

We saw that the TcpClient class has a method, GetStream(), that returns a NetworkSream instance, which can then be used to create StreamReader and StreamWriter instances.  These instances simplify handling text files through their ReadLine() and WriteLine() methods.

 

In the case of the Socket class,  the NetworkStream class has a constructor that accepts a Socket instance as argument.

 

Thus, after accepting a connection from a client (in the case of server) or after connecting to a server (in the case of a client), the resulting socket is be used to create a NetworkStream instance, which can then be used to create StreamReader and StreamWriter instances as before. 

 

The following example shows how to do this.

 

In the Server:

Socket client = server.Accept();

IPAddress clientAddress = ((IPEndPoint)client.RemoteEndPoint).Address;

Console.WriteLine("Got connection from "+clientAddress);

 

NetworkStream stream = new NetworkStream(client);

StreamReader reader = new StreamReader(stream);

StreamWriter writer = new StreamWriter(stream);

 

In the Client:

IPEndPoint remoteEP = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9050);

try {

  socket.Connect(remoteEP);

}catch (SocketException e) {

  Console.WriteLine("Unable to connect to server. ");

  Console.WriteLine(e);

  return;

}

 

NetworkStream stream = new NetworkStream(socket);

StreamReader reader = new StreamReader(stream);

StreamWriter writer = new StreamWriter(stream);

 

5.  Problems to be aware of in TCP Communication

 

Too Small Buffer Size:

In our simple example, a byte array of 1024 was used as the buffer size for the Send and Receive method calls.  What happens if more data arrives than the buffer size?

 

Message Boundary Problem:

Another problem with TCP communication is that due to its connection-oriented nature, messages are considered to form a continuous stream of bytes.  This is implemented using TCP internal buffers, which are used to store messages until they are Received/Sent by applications.

 

 

 

Thus, TCP does not respect message boundaries.  That is, there is no one-to-one correspondence between number/size of individual messages sent and the number/size of individual messages received. 

 

The problem is, how does an application knows how many times it needs to read before it collects the whole message?

 

Solutions:

1.          For Text messages only, use ReadLine and WriteLine methods of the StreamReader and StreamWriter classes respectively.

Thus, the WriteLine will insert end-of-line markers in the message and the ReadLine will read one line at a time until there is no more lines to read.

 

2.          Send the size of the message before sending the message.  This is applicable for any type of data where Send and Receive methods are used for sending and Receiving.

Thus, since the receiver knows the total size of the data, it will read in a loop until the entire size is read.  A loop similar to the following is used.

 

int total = int.Parse(reader.ReadLine());

byte [] buffer = new byte[1024];

int recv = 0;

int sofar = 0;

 

while (sofar < total) {

  recv = s.Receive(buffer);

  process(buffer, recv);

  sofar += recv;

}

7.                 Tasks:

1.  (a) Use the TcpListener class to write a DOS-based TCP server, TempConvertServer, that converts Temperature from Fahrenheit to Centigrade and vise-versa.  The server first sends a welcome message to its client.  It then enters into a loop, receiving and sending the following commands and responses until the Quit command is received. 

Command

Response

C value

Temperature in Fahrenheit

F value

Temperature in Centigrade

QUIT or “”

close the connection

any other command

ERROR

 

Use the formulae:            centigrade = (fahrenheit - 32) * 5/9

                                           fahrenheit = centigrade * 9/5 + 32

 

(b)  Test your server using Telnet.

 

(c)   Use the TcpClient class to write a GUI client with the following interface, for your server in (a). 

 

The buttons should toggle appropriately between active and inactive, depending on whether the client is connected to the server or not.

 

 

1(a)

1(b)

 

 

2        (a)  Use the Socket class to write a DOS-based file sharing server,  FileServer, that first sends a welcome message to the client,  then it receives the required filename from the client.  The server then sends the file size to the client, followed by the content of the file. 

 

Note that since the file is not necessarily a text file, you need to send/receive the file content using the Send/Receive methods of the Socket class or the Read/Write methods of the associated NetworkStream class.  However, the exchange of welcome message and the file size must be done using the StreamReader and StreamWriter classes. 

 

(b) Use the Socket class to write a GUI based file sharing client to work with your server in (a) above. If the downloaded file is a text file, the file content should be displayed as shown in figure 2(a) below, otherwise, a message should be shown in the display area indicating the file name that has been downloaded  as shown in figure 2(b).  

 

2(a)

2(b)