Click here to Skip to main content
Click here to Skip to main content

Scalable Server Events with .NET UDP Sockets

, 21 Nov 2006
Rate this:
Please Sign up or sign in to vote.
An article on notifying WinForms clients on a network that an event which concerns them has transpired.

Screenshot

Introduction

I recently needed to notify multiple WinForms clients (+/- 350) on a LAN that a certain event has happened which affects one of them.

There was also eventually a requirement to dynamically find these client applications and communicate with them. I went through various solutions to this problem, but the one which ended up working and scaling the best was using UDP broadcasts.

This article explains the process that led me to this, and the final solution.

Background

My starting requirement was to integrate our call centre data capturing application with the call centre call recording software. This is a deceptively simple requirement, because the only functionality required was to determine when a call started for a specific instance of our application, and then link that call to the currently active customer in the application.

Due to the number of clients active at any one time, licensing constraints required that only one or at most a few copies of the communications control supplied by the vendor be used, and that we write software to keep track of which clients are currently active, and notify them when a phone call starts or ends for any extension.

You may have noticed that this would require distributed events, and that is at the core of my sample application. Every copy of my client application listens for UDP traffic on a specific port, and will respond to commands over that port. Additionally, I decided to abstract the messiness of decoding and encoding commands, and so I used binary serialization of AppCommand objects to pass events and commands back and forth. To keep things readable and have a common understanding of possible events, I used a SystemCommandEnum enumeration to identify which command is being sent.

To enable the evolution of this software through the development process (performance has been an especial nightmare, requiring the call linking to be enabled and disabled without interrupting the call center's work or requiring them to restart their applications), I implemented a strategy pattern in the program, and by using a SwapStrategy command, I can swap between different versions of the call linking client code.

Because UDP packets sometimes go looking for greener pastures as opposed to being received by the intended recipient (they get lost Smile | :) ), I also use a request/acknowledgement paradigm for all communication that is critical.

The body of my application logic was implemented in remoting calls, and the UDP broadcasts were kept as small as possible to notify the appropriate instance of the app that the time was ripe to perform a remoting call to do the actual work. This serves to minimize network traffic.

Process

My first thought was to use remoting events. I found a few articles on them on the net, but they proved not to be scalable the first time I tried them. It seems that the server has to keep a connection open for every event registered, and even the beast of a server that we run at the client could not handle more than 20 concurrent connections. This was well short of the 400 that we had to be able to handle.

My next thought was a remoting poll strategy. The client would poll the server every 5 seconds and see whether there was an event waiting for it. I used SOAP serialization for this, and it also proved to be inadequate. 37 clients could be supported, but 40 was too much. Somewhere along the line with all of these different ideas, I got tired of walking through multiple rooms on different floors in the building to ask users to restart their copies of the front end, and the idea to use some sort of system communication via UDP was born.

I did some research, and found out that the experts (unfortunately, I could not find the website again) recommend binary serialization instead. Thinking about it once I knew this made perfect sense - the default SOAP serializer uses reflection, and that is a notoriously poor performer in high volume systems. You could implement the serialization yourself, or just use the binary formatter on your channel.

With this enhancement, the system worked, but was still not stable enough for me. This volume of remoting calls invariably caused strange errors when running concurrently from hundreds of computers to a server, and I was not willing to take the risk that some events could get lost because of weird exceptions thrown in the client once every 100 times it does the remoting call.

Finally, I decided that in addition to using UDP for system communication (i.e., enable poll strategy, enable limited link strategy etc.), I would use it to notify the clients that there is an event waiting to be picked up by a specific phone extension's copy of the front end.

Using the sample app

Follow these instructions to see the basic functionality of the demo app:

  • Launch the UDPEventManager.exe application.
  • Click the "Server" button once.
  • Click the "Launch" button in the "Clients" group three times.
  • Click the "Ping Client" button. Note how every copy of the client app reports itself to the manager.
  • Click on one of the items in the list, and click "Monitor Selected". Note how the server app gets an item in its list as the extension is registered with it, and how the client app updates to indicate that the extension just got monitored.
  • Select the client extension in the server window, change its Phone status selection, and click Update. Note how the message goes into the broadcast queue, and then disappears when the client acknowledges it. Also note that the client has updated its phone status display.

You can play around some more with the functionality, clients can also register and de-register themselves, and the manager app can close the server and all client apps with the click of a button. Although I do have this last feature in production, I have not enabled it at my client for obvious reasons.

Architecture

Different Programs

The UDPEventServer program is the server, and keeps track of the various items registered with it, as well as providing the remoting service to be called to get the state of those items.

The UDPEventClient program is the client app that is interested in the phone extension's status. It can manually be registered with the server, or it can wait for the management console to tell it to register.

The UDPEventManager program is the management console that can request that all copies of the program report to it, and also instruct them to do things.

Important Classes

The UDPBroadcastListener class listens for UDP traffic on the port assigned. It uses the sockets directly because of apparent bugs with sequential packet receives in the .NET UDPClient class.

The AppCommand class represents a command sent between different applications. All the possible commands are defined in the SystemCommandEnum enumeration.

UDP Broadcast Policy

Commands from the management console to the client apps are always broadcast to the whole network. This allows the management app to communicate with all copies of the client running. Responses from the clients to the management app are also broadcast. This allows the management app to run on any machine on the network. Access to this app would be restricted in a production environment.

UDP traffic from the server to the client is broadcast, so that the server does not need to worry about tracking individual clients. This could be a potential performance upgrade, but I have seen no problems with it in production.

UDP traffic from the client to the server is directed at the server's IP address, which the client needs to know in any case for the remoting calls. I have implemented this as a hostname to IP lookup to allow for DHCP.

Demonstration Considerations

Because the demo app was written to be a demonstration, I have implemented a feature which you would not put in a production app. This is automatic assigning of client ports. In practice, you would use only one port for the clients, but I have done multiple ones so that you can run up to three copies of the client app on a single machine and see it communicate between different apps.

Furthermore, in order to keep the apps simple, I have assumed that you are running all apps on the same computer. If you want to change this to test on your LAN, change the ServerName() method in the frmClient class to return your server's name as opposed to "localhost".

Also, to improve the clarity of the code, I have left out things I put in my production app like multiple threads in the server app to deal with communication to the third party control blocking. In my production app, the main server thread only fills queue lists which are serviced by worker threads which perform the connection, disconnection, and UDP broadcasts. I have kept the broadcast thread, though.

Using the Code

All the common UDP code as well as the remoting interface is encapsulated in the UDPEvents.dll assembly.

Data

Firstly, we need something to send around on the network. This is the AppCommand class, which has the following interface:

[Serializable]
public class AppCommand
{
    private SystemCommandEnum mCommandType;
    private object[] mArgs;

    public SystemCommandEnum CommandType
    {
        get { return mCommandType; }
    }

    public object[] Args
    {
        get { return mArgs; }
    }
}

Note that we only ever use one possible parameter (extension) in our app. In a more complex implementation, you may well want to send all sorts of things around. This is the reason for the Args array. If the notion of using an untyped array for arguments offends you, you could always subclass AppCommand into different classes - one for every event, and drop the SystemCommandEnum.

The SystemCommandEnum enumeration specifies which UDP event is being fired, and is declared as follows:

/// <SUMMARY>
/// List of possible command types sent via UDP
/// </SUMMARY>
public enum SystemCommandEnum
{
    // Request that all clients report. No Parameters
    PingClients,
    // Clients response. Contains the extension being responded for
    PongClients,
    // Manager acknowledging the client's
    // response so it can stop responding.
    // Contains the extension being responded for
    PongClientAcknowledge,
    // Tell all clients that they must respond
    // again to the next ping request     
    PingReset,
    // Server telling all clients a phone
    // extension has changes. Contains the 
    // extension that the notification is for
    HaveServerEvent,
    // Client telling server it knows about
    // the phone event and will now 
    // get it via remoting
    AcknowledgeServerEvent,
    // Message to all client apps to close down
    CloseAllClients,
    // Manager asking a client app with a specific extension to 
    // register it with the server. Contains the extension number
    RequestMonitorExtension,
    // Manager asking a client app with a specific extension to 
    // unregister it with the server.
    // Contains the extension number
    RequestReleaseExtension
}

Encoding / Decoding

The AppCommandTranslator class translates between an AppCommand for using and a byte[] for sending. It is implemented as follows:

/// <SUMMARY>
/// Class that translates an app command
/// from a binary array to a command
/// object and vice versa
/// </SUMMARY>
public class AppCommandTranslator
{
    public static byte[] ToBinary(AppCommand item)
    {
        BinaryFormatter format = new BinaryFormatter();
        MemoryStream str = new MemoryStream();
        format.Serialize(str, item);
        return str.ToArray();
    }
    
    public static AppCommand FromBinary(byte[] data)
    {
        BinaryFormatter format = new BinaryFormatter();
        MemoryStream str = new MemoryStream(data);
        str.Position = 0;
        return (AppCommand)format.Deserialize(str);
    }
}

UDP Broadcasting

The routine that does the work in the UDPBroadcaster class looks as follows:

private static void SendCommand(AppCommand cmd, int port, string hostName)
{
    byte[] data = AppCommandTranslator.ToBinary(cmd);
    UdpClient sender = new UdpClient();
    IPAddress endAddress = IPAddress.Broadcast;
    if (hostName != null)
    {
        IPHostEntry host = Dns.GetHostByName(hostName);
        if (host != null && host.AddressList != null && 
                         host.AddressList.Length > 0)
            endAddress = host.AddressList[0];
    }
    IPEndPoint end = new IPEndPoint(endAddress, port);
    sender.Send(data, data.Length, end);
}

UDP Receiving

The UDPBroadcastListener class owns a socket that is used for receiving any UDP traffic to the local machine on one of the ports that we are interested in. It uses a void CommandReceived(AppCommand cmd) delegate to notify the owner that a command has been received.

Its constructor sets up the socket as follows:

public UDPBroadcastListener(CommandReceived callBack, int udpPort)
{
    mCallBack = callBack;
    
    mListener = new Socket(AddressFamily.InterNetwork, SocketType.Dgram,
        ProtocolType.Udp);
    IPEndPoint end = new IPEndPoint(IPAddress.Any, udpPort);
    mListener.Bind(end);
    StartReceive();
}

The StartReceive method is where the actual async listening is done, as follows:

private void StartReceive()
{
    byte[] buff = new byte[1024];
    // Here we use the Async Receive to allow
    // concurrent packets to be received.
    // for some reason the synchronous version
    // of this command throws concurrent 
    // packets away
    mListener.BeginReceive(buff, 0, buff.Length, SocketFlags.None,
        new AsyncCallback(DataReceived), buff);
}

Note that we pass our buffer as the state object to the async receive, and it sends it back in the DataReceived method, where it can be used:

private void DataReceived(IAsyncResult ar)
{
    byte[] buff = (byte[])ar.AsyncState;
    if (buff != null)
    {
        AppCommand cmd = AppCommandTranslator.FromBinary(buff);
        if (mCallBack != null)
            mCallBack(cmd);
        StartReceive();
    }
}

There is lots of other code in the sample, but these points represent the key points of the UDP event sample - serializing the command, and sending and receiving UDP events. Please read through the sample code, and email me your thoughts / comments / suggested improvements.

Points of Interest

During the writing of this application, I learnt that the .NET 1.1 socket does not accept concurrent UDP packets when being used in synchronous mode. Only the first packet is accepted, and while it is dealing with that packet, subsequent ones are ignored. This manifested itself as a bug where the first response of a PingClient's call was accepted, and any other responses simply ignored.

The asynchronous socket receive which I ended up implementing also does not queue indefinitely, it seems to support round about 60 simultaneous packets before it starts dropping them. This is the reason for the request / response implementation of the ping.

The one very important tip when writing networking / socket programs is this: get yourself a copy of Wireshark (formerly Ethereal), and use it to monitor what your app is doing. This is the single easiest way to identify broadcast / receive bugs.

History

  • 21 November 2006 - Uploaded.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Joon du Randt
Web Developer
South Africa South Africa
Joon du Randt is a technical team leader for DVT, a software company based in Cape Town, South Africa.
 
He has been programming since the age of 10, and has been programming as a career since age 19.
 
Originally a Delphi developer, he made the swtich to C# in 2005 and has not regretted it since

Comments and Discussions

 
GeneralNice article PinmemberMichelM6-Jan-10 6:57 
GeneralRe: Nice article PinmemberJoon du Randt28-May-10 1:46 
GeneralThanks a lot! Joon du Randt. PinmemberZhou'Liang6-Dec-09 20:21 
General[Message Deleted] Pinmemberit.ragester2-Apr-09 21:49 
Questionshowing a form, passing udpclient object and other parameters to it Pinmembercrazenator29-Jul-08 0:13 
GeneralWhy is DataReceived Called Twice Pinmembersammy-e9-May-08 9:51 
GeneralRe: Why is DataReceived Called Twice Pinmembersammy-e9-May-08 10:42 
AnswerRe: Why is DataReceived Called Twice PinmemberSteve Riggall16-Sep-10 4:33 
Generalvery confusing Pinmemberschuetz28-Dec-07 7:52 
GeneralRe: very confusing PinmemberJoon du Randt2-Jan-08 19:42 
Generalwell done :-) Pinmemberschuetz9-Apr-07 1:49 
GeneralRe: well done :-) PinmemberJoon du Randt10-Apr-07 1:32 
GeneralNice article PinmemberTodd Smith21-Nov-06 23:08 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140821.2 | Last Updated 21 Nov 2006
Article Copyright 2006 by Joon du Randt
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid