Sending Data over UDP using UdpClient
By ganton ~ July 4th, 2008. Filed under: C#.
Few 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 UdpClient class. What we cannot do is to keep the state of our connection between sender and receiver. That is because the UDP protocol is stateless. The sender just sends a datagram without to open a connection to the receiver. We have a Contact class and we will use an instance of it to send. Note that it is marked as Serializable. It is needed because we plan to serialize the object using BinaryFormatter.
[Serializable]
public class Contact
{
public string Name { get; set; }
public int Age { get; set; }
public string Address { get; set; }
public string Phone { get; set; }
}
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.
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;
}
}
Next we will create a class which will allow us to send data. Its name is SimpleSender. This class implements IDisposable interface because we need to close the UdpClient instance SimpleSender uses. Using Start method we can specify the IP address and port of the receiver. After we start our sender we can use its Send method to send any type of data marked as Serializable. When we finish we should call Close method in order to close used instance of UdpClient.
public class SimpleSender : IDisposable
{
private int _port;
private UdpClient _udpClient = null;
public SimpleSender(int port)
{
if (port < 1024 || port > 65535)
{
throw new ArgumentOutOfRangeException("port", "Parameter's value must be between 1024 and 65535.");
}
_port = port;
}
public void Start(int receiverPort, IPAddress receiverIP)
{
if (receiverPort < 1024 || receiverPort > 65535)
{
throw new ArgumentOutOfRangeException("receiverPort", "Parameter's value must be between 1024 and 65535.");
}
if (receiverIP == null)
{
throw new ArgumentNullException("receiverIP");
}
if (_udpClient != null)
{
return;
}
_udpClient = new UdpClient(_port);
_udpClient.Connect(receiverIP, receiverPort);
}
public void Close()
{
if (_udpClient != null)
_udpClient.Close();
}
public void Send<t>(T data)
{
if (data != null)
{
using (MemoryStream stream = new MemoryStream())
{
BinaryFormatter formatter = new BinaryFormatter();
// serialize current object
formatter.Serialize(stream, data);
byte[] serializedData = stream.GetBuffer();
_udpClient.Send(serializedData, serializedData.Length);
}
}
}
#region IDisposable Members
public void Dispose()
{
try
{
Close();
}
catch { }
}
#endregion
}
Here is the class named SimpleReceiver<T> which will receive data sent by SimpleSender. This class also implements IDisposable interface for the same reason as SimpleSender does it. Using its Start method we specify sender IP address. If we provide a null our UdpClient instance will be configured to receive data from any sender. Start method also places our work method named DoWork into a separate thread in order to execute it asynchronously. When a data is received DataReceivedEvent is raised. The Stop method will abort the threat and will wait it to finish for 10 seconds.
public class SimpleReceiver<t> : IDisposable
{
private int _port;
private UdpClient _udpClient;
private Thread _workerThread;
private DataReceivedDelegate _dataReceivedEvent;
public event DataReceivedDelegate DataReceivedEvent
{
add
{
_dataReceivedEvent += value;
}
remove
{
_dataReceivedEvent -= value;
}
}
public SimpleReceiver(int port)
{
if (port < 1024 || port > 65535)
{
throw new ArgumentOutOfRangeException("port", "Parameter's value must be between 1024 and 65535.");
}
_port = port;
}
private void OnDataReceivedEvent(DataReceivedEventArgs args)
{
if (_dataReceivedEvent != null)
{
_dataReceivedEvent(this, args);
}
}
public void Start(IPAddress senderIP)
{
IPEndPoint endPoint;
if (senderIP == null)
{
//receive from any sender
endPoint = new IPEndPoint(IPAddress.Any, 0);
}
else
{
endPoint = new IPEndPoint(senderIP, 0);
}
_udpClient = new UdpClient(_port);
_workerThread = new Thread(new ParameterizedThreadStart(DoWork));
_workerThread.Start(endPoint);
}
public void Stop()
{
_workerThread.Abort();
_workerThread.Join(10000);
}
public void Close()
{
if (_udpClient != null)
{
_udpClient.Close();
}
}
private void DoWork(Object infoState)
{
IPEndPoint endPoint = infoState as IPEndPoint;
if (endPoint == null)
{
throw new ArgumentNullException("endPoint");
}
while (true)
{
// retrive data
byte[] data = _udpClient.Receive(ref endPoint);
using (MemoryStream stream = new MemoryStream(data))
{
BinaryFormatter formatter = new BinaryFormatter();
// Deserialize the message
T message = (T)formatter.Deserialize(stream);
// raise an event that an object is received
OnDataReceivedEvent(new DataReceivedEventArgs(message));
}
}
}
#region IDisposable Members
public void Dispose()
{
try
{
Close();
}
catch { }
}
#endregion
}
Next snippet shows how to use SimpleSender class.
static void Main(string[] args)
{
Contact contact = new Contact() { Name = "Some name", Age = 30, Address = "Some Address", Phone = "123456789" };
SimpleSender sender = new SimpleSender(7777);
sender.Start(7778, IPAddress.Parse("127.0.0.1"));
sender.Send(contact);
sender.Close();
}
Next snippet shows how to use SimpleReceiver<T>.
static void Main(string[] args)
{
SimpleReceiver<contact> receiver = new SimpleReceiver<contact>(7778);
receiver.DataReceivedEvent += new DataReceivedDelegate(receiver_DataReceivedEvent);
receiver.Start(null);
// wait for five seconds and stop the receiver
Thread.Sleep(5000);
receiver.Stop();
receiver.Close();
Console.Read();
}
static void receiver_DataReceivedEvent(object sender, DataReceivedEventArgs args)
{
Console.WriteLine((args.Data as Contact).Name);
}