Sending Data over UDP using UdpClient
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.
[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]
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.
[sourcecode language="csharp"]
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
{
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
}
[/sourcecode]
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.
[sourcecode language="csharp"]
public class SimpleReceiver
{
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
}
[/sourcecode]
Next snippet shows how to use SimpleSender class.
[sourcecode language="csharp"]
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();
}
[/sourcecode]
Next snippet shows how to use SimpleReceiver<T>.
[sourcecode language="csharp"]
static void Main(string[] args)
{
SimpleReceiver
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);
}
[/sourcecode]
March 17th, 2009 at 4:02 pm
I tried this example, but I get SerializationException on deserializing. I have built two projects inside one solution, and compiled as sender.exe and receiver.exe. Could it be the Namespace issue?
March 17th, 2009 at 5:23 pm
Hi tynar,
The example uses BinaryFormatter class which requires the exact match of the serialized type, its namespace and the assembly where it is compiled.
If you have two classes in one assembly but under different namespaces you will get InvalidCastException after deserialization. In your case I guess you have your class (let’s say Contact) declared in both sender.exe and receiver.exe. In this case you will get SerializationException during deserialization.
I can offer you to extract the Contact class into a separated assembly and to reference it in both sender.exe and receiver.exe. Then you can try the code again.
Rgards,
Anton