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

Example Servers - Timer Example Server 1

This example shows you how to build another simple server. The basic structure is very similar to the Basic Echo Server example and you should go and read about that first and have a good understanding of how everything fits together. This document will only cover the differences between the Basic Echo Server example and this example.

This example is shipped with all licensed versions of The Server Framework and it requires the core server framework libraries (see here for licensing options). You can always download the latest version of this example from here; and although you will need the correct libraries to be able to build it you can look at the example code and see how it works and perhaps get ideas from it. A compiled, unicode release, build of this example is available on request if you require it for performance analysis of the framework.

The main difference between this server and the Basic Echo Server is that this server uses per connection timers to generate periodic data on its connections.

Often when you're writing a server or client application with the framework you will find yourself needing to perform operations periodically or after a timeout. The framework provides a light weight timer queue that can be used to schedule timers and that uses minimal system resources so that there's no problem with having multiple timers per connection; even on systems with 10,000 connections.

The class used to manage per connection timers is the CThreadedCallbackTimerQueue class. This has been written about here where the design and testing of the class is discussed in depth.

You start by creating an instance of the CThreadedCallbackTimerQueue object, when you do this you get to select the implementation of the timer queue and the timer dispatch policy. The best options for this are to use BestForPlatformNoLock as this will automatically use the most appropriate timer queue implementation and it will dispatch timers in such a way that there is no risk of deadlock in your code when actioning timers and setting timers.

There are several timer queue implementations in the framework and future releases may bring more so it's always best to operate on the timer queue via its interface, IQueueTimers, doing this will mean that it's easy for you to swap out this implementation for a new one in a later release if it seems appropriate to do so.

Each timer that you wish to set is identified by a handle and these are created by calling CreateTimer(). This returns you a handle which can then be used in calls to SetTimer() and CancelTimer(). When you're finished with a timer for good, i.e. when you don't need to reset it again in the future, you should call DestroyTimer() to release the resources used.

To create a timer per connection you might do something like this. Note that we use per connection user data to store each connection's timer handle.

 void CSocketServer::OnConnectionEstablished(
    IStreamSocket &socket,
    const IAddress & /*address*/)
 {
    Output(_T("OnConnectionEstablished"));

    IQueueTimers::Handle timerHandle = m_timerQueue.CreateTimer();

    socket.SetUserData(m_userDataIndex, timerHandle);

    if (socket.TryWrite(m_welcomeMessage.c_str(), GetStringLengthAsDWORD(m_welcomeMessage)))
    {
       socket.TryRead();
    }

    SetTimer(socket);
 }

 void CSocketServer::OnSocketReleased(
    IIndexedOpaqueUserData &userData)
 {
    IQueueTimers::Handle timerHandle = userData.GetUserData(m_userDataIndex);

    m_timerQueue.DestroyTimer(timerHandle);
 }
To be able to set a timer you need two things, a timer handle (which we've just learnt how to obtain) and a class that implements the IQueueTimers::Timer callback interface. This interface is very simple:

 class IQueueTimers::Timer
 {
    public :

       /// User data that can be passed to Timer via the OnTimer() call when
       /// the timeout expires.

       typedef IQueueTimers::UserData UserData;

       /// Called after the timer expires.

       virtual void OnTimer(
          UserData userData) = 0;

    protected :

       /// We never delete instances of this interface; you must manage the
       /// lifetime of the class that implements it.

       ~Timer() {}
 };
Derived classes simply implement OnTimer() and this is called when a timer expires. The timer callback is passed 'user data' which can be used to communicate between the place where you set the timer and the place where the timer executes.

In a socket server scenario you might use the connection's socket as the user data. So to set a timer for a connection you might make a call like this:

 void CSocketServer::SetTimer(
    IStreamSocket &socket)
 {
    IQueueTimers::Handle timerHandle = socket.GetUserData(m_userDataIndex);

    socket.AddRef();  // Take a reference to hold until the timer is cancelled or expires...

    static const Milliseconds timeout = 2000;  // 2 seconds

    if (m_timerQueue.SetTimer(timerHandle, *this, timeout, reinterpret_cast<UserData>(&socket)))
    {
       socket.Release();    // Timer was already pending so a reference is already held
    }
 }
Note that it is VERY important that you increment the socket's reference count before you pass the socket into the timer queue as user data. The reason for this is that the connection may terminate before the timer expires or is cancelled and if that happens and you haven't taken a reference to the socket then the socket may well have been destroyed (or reused!) before it is used in the timer handler. Note also that we take the socket reference BEFORE we set the timer and that we release the reference if the timer was already set. We can't do this the other way around (i.e. only take a reference if the timer was not set) because then we create a race condition between the timer being set and the timer expiring. Since when the timer expires the socket's reference is released if we waited until after we'd set the timer to call AddRef() we might get into a situation whereby the timer went off before we called AddRef(). This could lead to the socket being over-released...

The timer handler might then look something like this:

 void CSocketServer::OnTimer(
    UserData userData)
 {
    Output(_T("OnTimer"));

    IStreamSocket &socket = *reinterpret_cast<IStreamSocket *>(userData);

    static const string message("Periodic per connection message\r\n");

    socket.Write(message.c_str(), GetStringLengthAsDWORD(message));

    SetTimer(socket);

    socket.Release(); // Release the reference that we took when we set this
                      // timer.
 }
Note that we release the reference that we took when we set the timer. This is VERY important or you would be leaking socket references which would cause a memory leak within the server and might prevent sockets from closing completely or at the correct time. This particular example sets the timer again once it expires, you may or may not need to do this. If you needed to cancel the timer for a reason, perhaps because the client had disconnected, then you'd do something like this:

 void CSocketServer::OnConnectionClientClose(
     IStreamSocket &socket)
 {

    if (m_timerQueue.CancelTimer(timerHandle))
    {
       socket.Release();  // The timer was active when it was cancelled, release the reference
                          // that we took when we called SetTimer().
    }
 }


More information

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