Most of us at some stage in our careers have had to, or was interested in developing some application that involved TCP or UDP networking. I have had to do a few in my 10 years in software development. When I was required to do another such utility, I realised soon after starting the design, that I could say deja vu to a lot of the features required. I decided to build something that will hopefully be useful (if not sufficient) in most of my future network utility applications.
From the outset, I had four design objectives:
Ease of Use
I wanted to make this toolkit useable for all levels of developers. I am exposed to a wide range of developers when it comes to experience and skill level. Making the toolkit easy to use gave me a greater degree of reuse as a rather junior developer should be able to use this library just as easily as a very senior developer.
I achieved the ease of use by keeping the number of publicly exposed methods to a minimum. I also added numerous overloads to the basic operations, e.g. sending a message and instantiating the client.
Having reuse in mind, the toolkit needed to be robust. For instance, using it in a peer-to-peer environment as well as in a client-server environment should require no special knowledge of either methodology. Any data can be sent and there are no explicit limitations on size. Sending finite messages or streaming can both be achieved.
The user can implement his own network protocol that piggybacks on the underlying protocol.
A very important feature for me was for the toolkit to split up large messages into chunks of a specified size, sending them and then be concatenated again before being presented to the consumer. This feature is hidden to the consumer and will automatically happen, depending on the packet size that the user chooses.
UDP is inherently an unreliable protocol in that whether or not a network packet is received, cannot be guaranteed. Being easy to use meant that message reliability must be optional as well as hidden from the consumer. I implemented a delivery receipt protocol for messages that are specified as needing reliability. This takes away the necessity for the developer to implement his/her own delivery confirmation mechanism.
Points of Interest
Included is a
BitConverter class. This class uses the standard .NET
BitConverter, but checks the endianness of the environment. It may happen that network packets are sent between hosts that do not both use Little Endian bit formats for example. The
BitConverter class always converts the operands to big endian.
For sending operations, the built-in .NET
ThreadPool is used. Thread pooling provides an easy to use, optimised method for queuing work. It was left up to the
ThreadPool object to decide on the correct number of threads to use.
The receiving of packets is achieved with a separate background thread dedicated to listening for incoming packets.
To further achieve the ease of use, I used two events to notify the consumer of a message that was sent or received. More about this later.
Using the Code
The classes included in the project are very simple and very self-explanatory. The only class that needs to be instantiated is the
Client class. To send your first message, simply instantiate the
Client class and call the appropriate
Here is some code to show how to use the toolkit.
private void MainForm_Load(object sender, EventArgs e)
client = new Client(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 5), 1024, 10);
client.MessageSent += new OnMessageSent(client_MessageSent);
client.MessageReceived += new OnMessageReceived(client_MessageReceived);
In the snippet above, the Windows Form's
Load event is used to instantiate the
Client class. For this particular overload, the localhost I.P. address is passed as the local end point. The second parameter is the packet size. This number is the number of bytes into which messages will be broken up into if the sent message is larger than the number. For instance, if the message you send is 3092 bytes long, the message will be sent as three packets of 1024 bytes and a fourth packet of 20 bytes. The third parameter is the time in seconds that the client will wait after sending a packet, before assuming the packet has failed and resending it. The default number of times a packet will be attempted to send is three. This value can be overridden using a different overload for the constructor.
The two events of the
Client class are bound to methods here.
OnMessageSent is bound to
OnMessageReceived is bound to
OnMessageReceived();. These events are raised after a message has been sent and received respectively.
void client_MessageReceived(object sender, MessageReceivedEventArgs args)
ReceivedMessageTextBox.Text = "\r\n\r\n" +
In the implementation of the event method above, I simply add the received message's text to a text box on the Windows Form.
Of course, this wouldn't make sense if for instance a binary file was sent as the message. The
MessageReceivedEventArgs passed to the method contains the data that was received as a byte array.
void client_MessageSent(object sender, MessageSentEventArgs args)
ActivityListBox.Items.Add("Failed message: " +
ActivityListBox.Items.Add("Sent message: " + args.MessageID.ToString());
ActivityListBox.Items.Add("Delivered message: " +
When a message was sent not specifying the reliable flag, this event will be raised immediately after sending the message. The
MessageSentEventArgs contains a field called
SendStatus. This field will be set to
Sent indicating the message was sent, but no further assumption can be made as to reaching its destination.
If the message was sent reliably, that is, sending with the
Reliable flag set to
MessageSentEventArgs will have either the value
Failed in the
SendStatus field. When the value is
Delivered, the consumer can then safely assume the entire message was received by the recipient. Conversely, when the value is
Failed, the message was sent, but no delivery confirmation was received. The consumer should in this case assume the message failed.
private void SendButton_Click(object sender, EventArgs e)
ActivityListBox.Items.Add("Attempt Send message: " +
64, new System.Net.IPEndPoint(IPAddress.Parse(textBox2.Text), 5), 5,
The event above is the implementation of a button click event. In here, I call the
client.QueueMessage(); method. This method is so called as messages are sent asynchronously and thus queued until they are ready to be sent.
- 2010-10-04 -- Created article