Sending data over TCP using TcpClient and TcpListener
Sometimes we need to send data via TCP. For this purpose .Net contains two classes - TcpClient and TcpListener.
Let’s assume that we need transfer a list of contacts from a server to client. [Update 07/04/2008: The Contact class is presented in the snippet below. Note that it is decorated with Serializable attribute. It is needed because we want to serialize it using BinaryFormatter.
[sourcecode language="csharp"]
[Serializable]
public class Contact
{
public string Name { get; set; }
public int Age { get; set; }
public string Address { get; set; }
public string Phone { get; set; }
}
[/sourcecode]
We also will declare our custom delegate and event arguments. They are used by SimpleReceiver<T>. When it receives a data it raises the event with proper data into event arguments.
[sourcecode language="csharp"]
public delegate void DataReceivedDelegate(object sender, DataReceivedEventArgs args);
public class DataReceivedEventArgs : EventArgs
{
public object Data { get; set; }
public DataReceivedEventArgs() { }
public DataReceivedEventArgs(object data)
{
Data = data;
}
}
[/sourcecode]
]
The first what we will do is a class which will be our server named SimpleServer<T>. It will accept a port and an IP address in order to configure a TcpListener. It also contains a Data property which accepts data of type IEnumerable<T>. This data will be send item by item to the client. It is clear that we can serialize all data and send it at once but I’d like to show an example which transfers several chunks of data. SendData method does the job of the server. It waits to accept a TCP client at line 39. After a connection with a client is established it retrieves the network stream (line 41) and each item from Data is serialized and send (line 45-47). Before sending an array of bytes its number is send (line 55). It is done because the client needs to know how many bytes is the message.
[sourcecode language="csharp"]
public sealed class SimpleServer
{
private int _port;
private IPAddress _ip;
private TcpListener _listener;
public IEnumerable
public int Port { get { return _port; } }
public IPAddress IP { get { return _ip; } }
public SimpleServer(int port, IPAddress ip)
{
if (port < 1024 || port > 65535)
{
throw new ArgumentOutOfRangeException(”port”, “Parameter’s value must be between 1024 and 65535.”);
}
if (ip == null)
{
throw new ArgumentNullException(”ip”);
}
_port = port;
_ip = ip;
_listener = new TcpListener(_ip, _port);
}
private void SendData()
{
if (Data == null)
{
throw new ApplicationException(”Data cannot be null.”);
}
try
{
while (true)
{
using (TcpClient client = _listener.AcceptTcpClient())
{
using (NetworkStream netStream = client.GetStream())
{
while (true)
{
using (MemoryStream memStream = new MemoryStream())
{
foreach (T item in Data)
{
BinaryFormatter formatter = new BinaryFormatter();
// serialize current object
formatter.Serialize(memStream, item);
byte[] data = memStream.GetBuffer();
byte[] length = BitConverter.GetBytes(data.Length);
// send the length of data’s bytes. It is needed by the server in order to know how many data to wait.
netStream.Write(length, 0, length.Length);
netStream.Write(data, 0, data.Length);
}
break;
}
}
}
}
}
}
finally
{
_listener.Stop();
}
}
public void Listen()
{
_listener.Start();
SendData();
}
}
[/sourcecode]
Before to go through the client class we need to develop a custom event arguments class and a custom event.
They are used by the client class. When a data is received and deserialized an event is raised to all listeners.
This way received data is acceptable for other classes.
[sourcecode language="csharp"]
public delegate void DataReceivedDelegate(object sender, DataReceivedEventArgs args);
public class DataReceivedEventArgs : EventArgs
{
public object Data { get; set; }
public DataReceivedEventArgs()
{
}
public DataReceivedEventArgs(object data)
{
Data = data;
}
}
[/sourcecode]
The next what we need to do is to develop a client class. SimpleClient<T> is our client class.
It accepts port and IP address to open a connection and a port and an IP address to connect the server. ReceiveFixedData method retrieves a number of bytes with data from provided NetworkStream (line 55). It waits until all data is received (line 69 - 77). The main job is done by ConnectServer method. It opens a TcpClient and connects it to the server (line 82-84). Note that when an object is deserialized an event is raised (line 110).
[sourcecode language="csharp"]
public sealed class SimpleClient
{
private int _port;
private IPAddress _ip;
private int _serverPort;
private IPAddress _serverIp;
private DataReceivedDelegate _dataReceivedEvent;
public event DataReceivedDelegate DataReceivedEvent
{
add
{
_dataReceivedEvent += value;
}
remove
{
_dataReceivedEvent -= value;
}
}
public int Port { get { return _port; } }
public IPAddress IP { get { return _ip; } }
public int ServerPort { get { return _serverPort; } }
public IPAddress ServerIP { get { return _serverIp; } }
public SimpleClient(int port, IPAddress ip, int serverPort, IPAddress serverIp)
{
if (port < 1024 || port > 65535)
{
throw new ArgumentOutOfRangeException(”port”, “Parameter’s value must be between 1024 and 65535.”);
}
if (ip == null)
{
throw new ArgumentNullException(”ip”);
}
if (serverPort < 1024 || serverPort > 65535)
{
throw new ArgumentOutOfRangeException(”serverPort”, “Parameter’s value must be between 1024 and 65535.”);
}
if (serverIp == null)
{
throw new ArgumentNullException(”serverIp”);
}
_port = port;
_ip = ip;
_serverPort = serverPort;
_serverIp = serverIp;
}
private void OnDataReceivedEvent(DataReceivedEventArgs args)
{
if (_dataReceivedEvent != null)
{
_dataReceivedEvent(this, args);
}
}
private byte[] ReceiveFixedData(NetworkStream netStream, int count)
{
if (netStream == null)
{
throw new ArgumentNullException(”stream”);
}
if (count <= 0)
{
throw new ArgumentOutOfRangeException("count", "Parameter's value cannot be 0 or less than 0.");
}
byte[] buffer = new byte[count];
int receivedCount;
int position = 0;
// read until all data is received.
do
{
receivedCount = netStream.Read(buffer, position, count - position);
if (receivedCount == 0)
{
return null;
}
position += receivedCount;
} while (position < count);
return buffer;
}
public void ConnectServer()
{
using (TcpClient client = new TcpClient(new IPEndPoint(_ip, _port)))
{
client.Connect(new IPEndPoint(_serverIp, _serverPort));
using(NetworkStream netStream = client.GetStream())
{
while (true)
{
// get the number of bytes to be read in order to retrieve all message
byte[] data = ReceiveFixedData(netStream, 4);
if (data == null)
{
return;
}
int messageLength = BitConverter.ToInt32(data, 0);
if (messageLength > 0)
{
// get the message
data = ReceiveFixedData(netStream, messageLength);
if (data == null)
{
return;
}
BinaryFormatter formatter = new BinaryFormatter();
using (MemoryStream memStream = new MemoryStream(data))
{
// Deserialize the message
T message = (T)formatter.Deserialize(memStream);
// raise an event that an object is received
OnDataReceivedEvent(new DataReceivedEventArgs(message));
}
}
}
}
}
}
}
[/sourcecode]
Next snippet is an example of how to use SimpleServer<T>. Note that if the client drops the connection an IOException will be thrown when data is written into NetworkStream.
[sourcecode language="csharp"]
static void Main(string[] args)
{
List
{
new Contact() { Name = “Some name”, Age = 30, Address = “Some Address”, Phone = “123456789″},
new Contact() { Name = “Some name”, Age = 30, Address = “Some Address”, Phone = “123456789″},
new Contact() { Name = “Some name”, Age = 30, Address = “Some Address”, Phone = “123456789″}
};
SimpleServer
server.Data = data;
try
{
server.Listen();
}
catch (IOException ioex)
{
Console.WriteLine(ioex.ToString());
}
}
[/sourcecode]
Next snippet is an example of how to use SimpleClient<T>. A SocketException is thrown if there is no
server to connect at target port and IP address.
[sourcecode language="csharp"] static void client_DataReceivedEvent(object sender, DataReceivedEventArgs args)
static void Main(string[] args)
{
SimpleClient
client.DataReceivedEvent += new DataReceivedDelegate(client_DataReceivedEvent);
try
{
client.ConnectServer();
}
catch (SocketException se)
{
Console.WriteLine(se.ToString());
}
Console.Read();
}
{
Console.WriteLine((args.Data as Contact).Name);
}
[/sourcecode]
July 10th, 2008 at 5:41 pm
[...] days ago I wrote a post how to send data over TCP. Today, I’d like to share a sample how to do it over UDP using [...]
March 6th, 2009 at 5:20 pm
T message = (T)formatter.Deserialize(memStream);
what is “T” ???
March 6th, 2009 at 5:39 pm
Hi ken,
As you may notice the SimpleClient class is a generic class (more about generics you can read at MSDN - http://msdn.microsoft.com/en-us/library/ms379564(VS.80).aspx).
T is a generic type parameter that is used further by server side and client side compilers. You can think about T like a real type that you can specify at the time of the class use. For instance,
SimpleClient client = new SimpleClient ();
In this sample the T will represent type string and the code below
T message = (T)formatter.Deserialize(memStream);
will have the same effect as if you was wrote
String message = (String)formatter.Deserialize(memStream);.
But using generic to declare the SimpleClient class allows you to reuse the class implementation with different types.
Regards,
Anton