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 & )
{
Output(_T("OnConnectionEstablished"));
IQueueTimers::Handle timerHandle = m_timerQueue.CreateTimer();
socket.SetUserData(m_userDataIndex, timerHandle);
if (socket.TryWrite(m_welcomeMessage.c_str(), GetStringLength<DWORD>(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 :
typedef IQueueTimers::UserData UserData;
virtual void OnTimer(
UserData userData) = 0;
protected :
~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();
static const Milliseconds timeout = 2000;
if (m_timerQueue.SetTimer(timerHandle, *this, timeout, reinterpret_cast<UserData>(&socket)))
{
socket.Release();
}
}
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. If the timer is already set then
SetTimer() will return
true and it's important to release the reference you just took before you called
SetTimer() as we only need to hold a single reference whilst we have a timer set (the timer only goes off once, if you call
SetTimer() whilst the timer is already set then all you're doing is changing the time when the timer will go off and so there's no need to take a new reference on the socket. However, you MUST take a reference before calling
SetTimer() as you can't tell if the timer is already set and there's a race condition if you wait until
SetTimer() returns
false before taking your reference. 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(), GetStringLength<DWORD>(message));
SetTimer(socket);
socket.Release();
}
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();
}
}
More information