Introduction
In the previous article, I discussed a chat application using TCP sockets. In this part, I will show how to do it using UDP sockets.
Differences between UDP and TCP
TCP is connection oriented, and provides error and flow control. UDP provides no such services, and relies on the application layer for them. UDP allows sending a packet with or without checksum; no connection is maintained, so each packet is sent independently. If a packet gets lost or the packets arrive out of order, then the application should detect and remedy the situation on its own. Also, UDP doesn’t give the security features of TCP like the three-way handshake.
So what is in UDP that TCP doesn’t do? Firstly, UDP supports multicast – sending a single packet to multiple machines. This is useful as it saves bandwidth, each packet is transmitted only once and the entire network receives it. UDP is also used in places where the overhead (delay) involved with TCP is expensive.
Some applications of UDP are in VoIP, streaming of audio and video, DNS, TFTP, SNMP, online gaming, and etcetera.
Asynchronous UDP sockets
Asynchronous UDP sockets have a Begin and End appended to the standard socket functions, like BeginSendTo
, BeginReceiveFrom
, EndSendTo
, and EndReceiveFrom
. Let's take a look at one of them:
IAsyncResult BeginReceiveFrom(byte[] buffer, int offset, int size,
SocketFlags sockflag, ref EndPoint ep, AsyncCallback callback, object state)
The BeginReceiveFrom()
method accepts data from any remote host on a connectionless socket. Notice that the BeginReceiveFrom()
method is similar to the BeginReceive()
method, except that it specifies a reference to an EndPoint
object. The EndPoint
object defines the remote host IP address and the port number that sent the data.
The AsyncCallback
function is called when the function completes. Just as events can trigger delegates, .NET also provides a way for methods to trigger delegates. The .NET AsyncCallback
class allows methods to start an asynchronous function and supply a delegate method to call when the asynchronous function completes.
The state object is used to pass information between the BeginAccept
and the corresponding AsyncCallback
function.
A sample BeginReceiveFrom()
method would look like this:
sock.BeginReceive(data, 0, data.Length, SocketFlags.None,
ref iep, new AsyncCallback(ReceiveData), sock);
The corresponding EndReceiveFrom()
method is placed in the appropriate AsyncCallback
method:
void ReceiveData(IasyncResult iar)
{
Socket remote = (Socket)iar.AsyncState;
int recv = remote.EndReceiveFrom(iar);
string stringData = Encoding.ASCII.GetString(data, 0, recv);
Console.WriteLine(stringData);
}
The EndReceiveFrom()
method returns the number of bytes read from the socket, and places the received data in the data buffer defined in the BeginReceiveFrom()
method. To access this data, the data buffer should be accessible from both the methods.
Getting Started
The architecture of the application using TCP sockets and the one using UDP sockets is very similar. Both applications use the same data structures to communicate between the server and the client.
For exchange of messages between the client and the server, both of them use the following trivial commands:
enum Command
{
Login,
Logout,
Message,
List
}
The data structure used to exchange between the client and the server is shown below. Sockets transmit and receive data as an array of bytes, the overloaded constructor and the ToByte
member function performs this conversion.
class Data
{
public Data()
{
this.cmdCommand = Command.Null;
this.strMessage = null;
this.strName = null;
}
public Data(byte[] data)
{
this.cmdCommand = (Command)BitConverter.ToInt32(data, 0);
int nameLen = BitConverter.ToInt32(data, 4);
int msgLen = BitConverter.ToInt32(data, 8);
if (nameLen > 0)
this.strName =
Encoding.UTF8.GetString(data, 12, nameLen);
else
this.strName = null;
if (msgLen > 0)
this.strMessage =
Encoding.UTF8.GetString(data, 12 + nameLen, msgLen);
else
this.strMessage = null;
}
public byte[] ToByte()
{
List<byte> result = new List<byte>();
result.AddRange(BitConverter.GetBytes((int)cmdCommand));
if (strName != null)
result.AddRange(BitConverter.GetBytes(strName.Length));
else
result.AddRange(BitConverter.GetBytes(0));
if (strMessage != null)
result.AddRange(
BitConverter.GetBytes(strMessage.Length));
else
result.AddRange(BitConverter.GetBytes(0));
if (strName != null)
result.AddRange(Encoding.UTF8.GetBytes(strName));
if (strMessage != null)
result.AddRange(Encoding.UTF8.GetBytes(strMessage));
return result.ToArray();
}
public string strName;
public string strMessage;
public Command cmdCommand;
}
UDP Server
The following are some of the data members used by the server application:
struct ClientInfo
{
public EndPoint endpoint;
public string strName;
}
ArrayList clientList;
Socket serverSocket;
byte[] byteData = new byte[1024];
One important point to note here is that, in UDP, there is no such distinction between the client and server applications. Unlike TCP, UDP servers don’t listen for incoming clients; they just look for data coming from other clients. Once we receive data, we see if it’s a message for login, logout, etc.
private void Form1_Load(object sender, EventArgs e)
{
try
{
serverSocket = new Socket(AddressFamily.InterNetwork,
SocketType.Dgram, ProtocolType.Udp);
IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Any, 1000);
serverSocket.Bind(ipeServer);
IPEndPoint ipeSender = new IPEndPoint(IPAddress.Any, 0);
EndPoint epSender = (EndPoint) ipeSender;
serverSocket.BeginReceiveFrom (byteData, 0, byteData.Length,
SocketFlags.None, ref epSender,
new AsyncCallback(OnReceive), epSender);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "SGSServerUDP",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
With IPAddress.Any
, we specify that the server should accept client requests coming on any interface. To use any particular interface, we can use IPAddress.Parse (“192.168.1.1”)
instead of IPAddress.Any
. The Bind
function then bounds the serverSocket
to this IP address. The epSender
identifies the clients from where the data is coming.
With BeginReceiveFrom
, we start receiving the data that will be sent by the client. Note that we pass epSender
as the last parameter of BeginReceiveFrom
, the AsyncCallback OnReceive
gets this object via the AsyncState
property of IAsyncResult
, and it then processes the client requests (login, logout, and send message to the users). Please see the code attached to understand the implementation of OnReceive
.
TCP Client
Some of the data members used by the client are:
public Socket clientSocket;
public string strName;
public EndPoint epServer;
byte []byteData = new byte[1024];
The client firstly connects to the server:
try
{
clientSocket = new Socket(AddressFamily.InterNetwork,
SocketType.Dgram, ProtocolType.Udp);
IPAddress ipAddress = IPAddress.Parse(txtServerIP.Text);
IPEndPoint ipEndPoint = new IPEndPoint(ipAddress, 1000);
epServer = (EndPoint)ipEndPoint;
Data msgToSend = new Data ();
msgToSend.cmdCommand = Command.Login;
msgToSend.strMessage = null;
msgToSend.strName = strName;
byte[] byteData = msgToSend.ToByte();
clientSocket.BeginSendTo(byteData, 0, byteData.Length,
SocketFlags.None, epServer,
new AsyncCallback(OnSend), null);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "SGSclient",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
The client after connecting to the server sends a login message. And, after that, we send a List message to get the names of clients in the chat room.
private void btnSend_Click(object sender, EventArgs e)
{
try
{
Data msgToSend = new Data();
msgToSend.strName = strName;
msgToSend.strMessage = txtMessage.Text;
msgToSend.cmdCommand = Command.Message;
byte[] byteData = msgToSend.ToByte();
clientSocket.BeginSendTo (byteData, 0, byteData.Length,
SocketFlags.None, epServer,
new AsyncCallback(OnSend), null);
txtMessage.Text = null;
}
catch (Exception)
{
MessageBox.Show("Unable to send message to the server.",
"SGSclientUDP: " + strName, MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
}
The message typed by the user is sent as a Command message to the server which then sends it to all other users in the chat room.
Upon receiving a message, the client processes it accordingly (depending on whether it’s a login, logout, command, or a list message). The code for this is fairly straightforward, kindly see the attached project.
Conclusion
As you would have noticed, the UDP chat application is not very different from the TCP one; in fact, converting the TCP one into UDP was less than a day's work for me. I hope you find these articles useful. Thank you.