Sunday, March 14, 2010

Listening for UDP packets in a Windows service using an UdpClient

In this post, I'll show you how can you listen for UDP packets in a Windows service.

OnStart

When the service starts, I set the started flag to true, initialize the ManualResetEvent, initialize an UdpClient and a WorkingThread. The ManualResetEvent will help us on a later stage to make our service stop elegantly.

   1:  protected override void OnStart(string[] args)
   2:  {
   3:       Start();
   4:  }
   5:       
   6:  public void Start()
   7:  {
   8:       m_started = true;
   9:   
  10:       m_stop = new ManualResetEvent(false);
  11:   
  12:       InitializeUdpListener();
  13:       InitializeWorkingThread();
  14:  }

Initializing

First we need to initialize an IPEndpoint. When the IPEndpoint is initialized we can initialize the UdpClient using that IPEndpoint.

   1:  private void InitializeUdpClient()
   2:  {
   3:       m_endPoint = new IPEndPoint(IPAddress.Any, PORT_NUMBER);            
   4:       m_client = new UdpClient(m_endPoint);
   5:  }

After initializing our UdpClient, we can initialize and start the WorkingThread.

   1:  private void InitializeWorkingThread()
   2:  {
   3:       m_workingThread = new Thread(WorkerFunction);
   4:       m_workingThread.Name = "WorkingThread";
   5:       m_workingThread.Start();
   6:  }


WorkerFunction

The WorkerFunction does all the work.

While the service is started, we start receiving packets. We pass in an AsyncCallback Delegate which is called when the asynchronous operation completes. In this delegate we make sure that the result we receive is complete. If the result is complete we end receiving and get the content of the UDP packet.

Finally we use the WaitHandle to wait for either the asynchronous operation to complete or the workerthread to grant a termination request through the stop ManualResetEvent.

   1:  private void WorkerFunction()
   2:  {
   3:       while (m_started)
   4:       {
   5:           //BeginReceive starts an asynchronous operation, in reality to allow us to achieve
   6:           //semi-synchronous invocation, where we wait for either the asynchronous operation
   7:           //to complete or the worker thread to grant a termination request through stop
   8:           var res = m_client.BeginReceive(iar =>
   9:           {
  10:                if (iar.IsCompleted)
  11:                {
  12:                     byte[] receivedBytes = m_client.EndReceive(iar, ref m_endPoint);
  13:                     string receivedPacket = Encoding.ASCII.GetString(receivedBytes);
  14:                }
  15:            }, null);
  16:   
  17:            if (WaitHandle.WaitAny(new[] { m_stop, res.AsyncWaitHandle }) == 0)
  18:            {
  19:                break;
  20:            }
  21:       }
  22:  }        

OnStop

In the OnStop event we need to set the ManualResetEvent, so our WorkerFunction can exit gracefully.

   1:  protected override void OnStop()
   2:  {
   3:      m_stop.Set();
   4:      m_started = false;
   5:  }

I'm pretty sure this is a robust solution. A service based on this example deployed to production has been handling 1000 packets an hour on average for the last three weeks without problems.

Thanks to Bart De Smet for helping me out with the threading stuff!

8 comments:

  1. The source cannot be downloaded anymore because of a Rapidshare error, could you rehost it so you can share your genius with the world? :)

    ReplyDelete
  2. I reuploaded the source! I changed the link in the post.

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
  4. Source cant be downloaded again, because Rapidshare download limit reached.

    ReplyDelete
  5. I'm sorry. I reuploaded the source (http://rapidshare.com/files/413293156/UDPListener.zip). I had to redo it because I lost the original source myself.

    ReplyDelete
  6. Is the source code referenced in this article still available?

    ReplyDelete
    Replies
    1. Sorry, this was a looong time ago. I removed the broken link.

      Delete
  7. Thanks for the reply. I was able to piece together a working sample from the code shown in the article. I am now working on sending a reply to the sender of the incoming message and was looking for assistance.

    ReplyDelete