The Message Queue
The server never deals directly
with incoming messages; instead, the server pulls
messages from the queue. If a message needs to be processed, it must be inserted
into the queue. Using a queue ensures that the server never gets bogged down
with
processing incoming network data.
The queue is just an array of
sMsg structures that is allocated when the application
class is initialized. I set a limit of 1,024 messages to be allocated for the
server, but you
can change that amount just by altering the MAX_MESSAGE macro in the source
code.
To track messages being added
and removed from the queue, use two variables—
m_msg_head and m_msg_tail. Check out Figure 19.12 to see how the queue uses
those
two variables to track which messages are to be inserted or removed.
Whenever a message needs to be
added to the message queue, a special function is
called. That function is cApp::queue_msg, and it takes a single argument: the
sMsg structure to add to the queue.
Remember the incoming message
functions of cApp (covered in the section “DirectPlay
Messages to Game Messages”)? Those functions built a message structure and added
the message to the queue via queue_msg. Look at the queue_msg code to see what’s
going on:
bool cApp::queue_msg(const void* msg)
{
const sMsgHeader* header = (const sMsgHeader*) msg;
// return if no room left in queue
if((m_msg_head + 1) % MAX_MESSAGES == m_msg_tail)
return false;
// stuff message into queue
if(header->size <= sizeof(sMsg))
{
EnterCriticalSection(&m_msg_cs);
memcpy(&m_msgs[m_msg_head], msg, header->size);
// goto next empty message (flip around if at end)
m_msg_head++;
if(m_msg_head >= MAX_MESSAGES)
m_msg_head = 0;
LeaveCriticalSection(&m_msg_cs);
}
return true;
}
As you can see, queue_msg merely
copies the supplied sMsg structure into the
next available element in the message array (pointed to by m_msg_head). Two
things
you haven’t seen are the EnterCriticalSection and LeaveCriticalSection
functions.
Windows uses these two functions to restrict the application’s access to memory
(using the EnterCriticalSection function), only allowing a single process to
modify
that memory. Once you finish modifying the memory, you need to inform
Windows by calling LeaveCriticalSection.
Although this may not make sense
at first, think about it like this—the network
component (a process) is running in the background at the same time as the
application
(another process). If the network component is adding messages to the
array while the application is trying to remove messages or modify the messages,
the program data can become corrupt. Critical sections ensure that only one
process gets sole access to data for a short time.