The C++ framework for developing highly scalable, high performance servers on Windows platforms.

Writing your first UDP server

Probably the easiest way to start writing your own server is to take a look at one of the many example servers that come with The Server Framework code. There are TCP and UDP examples and they start from the simplest echo server and build up slowly towards more advanced servers, UDP servers that support 'virtual' connections, compression or semi-reliable data transfer and include servers that run as Windows Services and servers that expose internal monitoring via perfmon counters.

This guide will assume that you have a copy of the most basic EchoServerUDP example and will explain how the server is put together, how you get it to do the work that you want and how you configure it.

A simple server will usually consist of at least 3 files; a ServerMain.cpp file that puts together the objects required to run the server and configures them, and SocketServer.h and SocketServer.cpp files that provide a link between the framework and the socket server callbacks that you will implement to act on various network events that happen during the lifetime of the connections to your server. We'll start by looking at the SocketServer.h file. For the simplest UDP EchoServer this file could look something like this:

 #include "JetByteTools\SocketTools\DatagramSocketServer.h"

 #include "ServerCommon\DatagramSocketServerCallback.h"

 class CSocketServer :
    public JetByteTools::Socket::CDatagramSocketServer,
    private CDatagramSocketServerCallback
    public :

          const JetByteTools::Socket::IFullAddress &address,
          const JetByteTools::Socket::ListenBacklog listenBacklog,
          JetByteTools::IO::IIOPool &pool,
          JetByteTools::Socket::IAllocateDatagramServerSockets &socketAllocator,
          JetByteTools::IO::IAllocateBuffers &bufferAllocator,
          const JetByteTools::Socket::SocketBufferSize recvBufferSize,
          const JetByteTools::Socket::SocketBufferSize sendBufferSize,
          JetByteTools::Socket::ILimitConnections &connectionLimiter = JetByteTools::Socket::CConnectionLimiter::NoLimitLimiter);


    private :

       // Implement just the bits of IDatagramSocketServerCallback that we need

       virtual void OnReadCompleted(
          JetByteTools::Socket::IDatagramServerSocket &socket,
          JetByteTools::IO::IBuffer &buffer);

       // Our business logic

       void EchoMessage(
          JetByteTools::Socket::IDatagramServerSocket &socket,
          JetByteTools::IO::IBuffer &buffer) const;

       /// No copies do not implement
       CSocketServer(const CSocketServer &rhs);
       /// No copies do not implement
       CSocketServer &operator=(const CSocketServer &rhs);
The CSocketServer class derives from the framework's basic UDP server class but it doesn't actually need to except for the convenience of being able to manipulate a single object as your server. The important base class is the CDatagramSocketServerCallback as this allows you to override just the callback functions that you're interested in dealing with rather than having to provide a default implementation for the whole of IDatagramSocketServerCallback .

The constructor connects together the various support objects in the same was as a TCP server does. There's no 'connection' with UDP servers so there's no need to handle connection establishment. Once the server is listening, nothing happens until the datagram arrives and a read completes.

 void CSocketServer::OnReadCompleted(
    IDatagramServerSocket &socket,
    IBuffer &buffer)
       Output(_T("ReadCompleted - socket: ") + ToString(&socket) + _T(" - ") + CAddressRenderer::AsString(socket.GetRemoteAddress(), true));

       EchoMessage(socket, buffer);
    catch(const CException &e)
       Output(_T("ReadCompleted - Exception - ") + e.GetDetails());
       Output(_T("ReadCompleted - Unexpected exception"));
When the read completes our OnReadCompleted() handler is called and we are given a buffer which contains the bytes that made up the datagram.

It's good practice to protect the framework from exceptions that you throw whilst working in a callback method, you don't have to, the framework will catch anything that comes blasting out of your handlers but it's generally better to do your own cleanup work. We simply issue a debug message.

Since we're just a simple server our business logic doesn't care about what data has been sent to us, we just pass the bytes that we've been given to the EchoMessage() function and, well, this is what it does with them:
 void CSocketServer::EchoMessage(
    IDatagramServerSocket &socket,
    IBuffer &buffer) const
    DEBUG_ONLY(Output(_T(" Local Address: ") + CAddressRenderer::AsString(socket.GetLocalAddress(), true)));
    DEBUG_ONLY(Output(_T("Remote Address: ") + CAddressRenderer::AsString(socket.GetRemoteAddress(), true)));

    DEBUG_ONLY(Output(_T("Data Echoed -\r\n") + DumpData(buffer.GetMemory(), buffer.GetUsed(), 60, true)));

All we do is display what we're echoing (in debug builds only), and where it has come from and then write it back to the client.

Note that we don't issue a new read request in our read handler. UDP servers treat all datagrams as separate, even those from the same client address and port. There is no "linkage" between one datagram and another and so the server itself maintains pending reads so that it can pass the data to your callbacks when datagrams arrive. If you need to deal with datagrams from the same client address and port as a 'stream' of some sort then you will need to write that into your business logic. Some of the later server examples demonstrate how you might go about doing this.

Configuring and setting up a UDP server is very similar to setting up a TCP server. The ServerMain.cpp is pretty much identical except for the type of socket allocator used.

Generated on Sun Sep 12 19:06:45 2021 for The Server Framework - v7.4 by doxygen 1.5.3