Home arrow C++ arrow Page 4 - A Reusable Windows Socket Server Class With C++
C++

A Reusable Windows Socket Server Class With C++


Ever thought of writing your own Windows socket server class? In this article Len shows you exactly how to do just that, including details of what a socket server should do and example C++ code.

Author Info:
By: Len Holgate
Rating: 5 stars5 stars5 stars5 stars5 stars / 52
June 28, 2002
TABLE OF CONTENTS:
  1. · A Reusable Windows Socket Server Class With C++
  2. · What does a socket server need to do?
  3. · Asynchronous IO
  4. · Some example servers
  5. · Chunking the byte stream (Contd.)
  6. · Conclusion

print this article
SEARCH DEVARTICLES

A Reusable Windows Socket Server Class With C++ - Some example servers
(Page 4 of 6 )

We now have a framework for creating servers. The user needs to provide a worker thread class that is derived from CSocketServer::WorkerThread and a socket server that's derived from CSocketServer. These classes could look something like this:

class CSocketServerWorkerThread : public CSocketServer::WorkerThread
{
public :

CSocketServerWorkerThread(
CIOCompletionPort &iocp);

private :

virtual void ReadCompleted(
CSocketServer::Socket *pSocket,
CIOBuffer *pBuffer);
};

class CMySocketServer : CSocketServer
{
public :

CMySocketServer (
unsigned long addressToListenOn,
unsigned short portToListenOn);

private :

virtual WorkerThread *CreateWorkerThread(
CIOCompletionPort &iocp);

virtual SOCKET CreateListeningSocket(
unsigned long address,
unsigned short port);

virtual void OnConnectionEstablished(
Socket *pSocket,
CIOBuffer *pAddress);
};


Implementations for CreateListeningSocket() and OnConnectionEstablished() have already been presented. CreateWorkerThread() is as simple as this:

CSocketServer::WorkerThread *CMySocketServer::CreateWorkerThread(
CIOCompletionPort &iocp)
{
return new CSocketServerWorkerThread(iocp);
}


Which leaves us with the implementation of our worker thread's ReadCompleted() method. This is where the server handles incoming data and, in the case of a simple Echo server ;) it could be as simple as this:

void CSocketServerWorkerThread::ReadCompleted(
CSocketServer::Socket *pSocket,
CIOBuffer *pBuffer)
{
pSocket->Write(pBuffer);
}


A complete echo server is available for download in SocketServer1.zip. The server simply echos the incoming byte stream back to the client. In addition to implementing the methods discussed above the socket server and worker thread derived classes also implement several 'notifciation' methods that the server and worker thread classes call to inform the derived class of various internal goings on. The echo server simply outputs a message to the screen (and log file) when these notifications occur but the idea behind them is that the derived class can use them to report on internal server state via performance counters or suchlike. You can test the echo server by using telnet. Simply telnet to localhost on port 5001 (the port that the sample uses by default) and type stuff and watch it get typed back at you. The server runs until a named event is set and then shuts down. The very simple Server Shutdown program, available in ServerShutdown.zip, provides an off switch for the server.

More complex servers
Servers that do nothing but echo a byte stream are rare, except as poor examples. Normally a server will be expecting a message of some kind, the exact format of the message is protocol specific but two common formats are a binary message with some form of message length indicator in a header and an ASCII text message with a predefined set of 'commands' and a fixed command terminator, often "rn". As soon as you start to work with real data you are exposed to a real-world problem that is simply not an issue for echo servers. Real servers need to be able to break the input byte stream provided by the TCP/IP socket interface into distinct commands. The results of issuing a single read on a socket could be any number of bytes up to the size of the buffer that you supplied. You may get a single, distinct, message or you may only get half of a message, or 3 messages, you just can't tell. Too often inexperienced socket developers assume that they'll always get a complete, distinct, message and often their testing methods ensure that this is the case during development.

Chunking the byte stream
One of the simplest protocols that a server could implement is a packet based protocol where the first X bytes are a header and the header contains details of the length of the complete packet. The server can read the header, work out how much more data is required and keep reading until it has a complete packet. At this point it can pass the packet to the business logic that knows how to process it. The code to handle this kind of situation might look something like this:

void CSocketServerWorkerThread::ReadCompleted(
CSocketServer::Socket *pSocket,
CIOBuffer *pBuffer)
{
pBuffer = ProcessDataStream(pSocket, pBuffer);

pSocket->Read(pBuffer);
}

CIOBuffer *CSocketServerWorkerThread::ProcessDataStream(
CSocketServer::Socket *pSocket,
CIOBuffer *pBuffer)
{
bool done;

do
{
done = true;

const size_t used = pBuffer->GetUsed();

if (used >= GetMinimumMessageSize())
{
const size_t messageSize = GetMessageSize(pBuffer);

if (used == messageSize)
{
// we have a whole, distinct, message

EchoMessage(pSocket, pBuffer);

pBuffer = 0;

done = true;
}
else if (used > messageSize)
{
// we have a message, plus some more data
// allocate a new buffer, copy the extra data into it and try again...

CIOBuffer *pMessage = pBuffer->SplitBuffer(messageSize);

EchoMessage(pSocket, pMessage);

pMessage->Release();

// loop again, we may have another complete message in there...

done = false;
}
else if (messageSize > pBuffer->GetSize())
{
Output(_T("Error: Buffer too smallnExpecting: ") + ToString(messageSize) +
_T("Got: ") + ToString(pBuffer->GetUsed()) + _T("nBuffer size = ") +
ToString(pBuffer->GetSize()) + _T("nData = n") +
DumpData(pBuffer->GetBuffer(), pBuffer->GetUsed(), 40));

pSocket->Shutdown();

// throw the rubbish away
pBuffer->Empty();

done = true;
}
}
}
while (!done);

// not enough data in the buffer, reissue a read into the same buffer to collect more data
return pBuffer;
}


The key points of the code above are that we need to know if we have at least enough data to start looking at the header, if we do then we can work out the size of the message somehow. Once we know that we have the minimum amount of data required we can work out if we have all the data that makes up this message. If we do, great, we process it. If the buffer only contains our message then we simply process the message and since processing simply involves us posting a write request for the data buffer we return 0 so that the next read uses a new buffer. If we have a complete message and some extra data then we split the buffer into two, a new one with our complete message in it and the old one which has the extra data copied to the front of the buffer. We then pass our complete message to the business logic to handle and loop to handle the data that we had left over. If we dont have enough data we return the buffer and the Read() that we issue in ReadCompleted() reads more data into the same buffer, starting at the point that we're at now.
blog comments powered by Disqus
C++ ARTICLES

- Intel Threading Building Blocks
- Threading Building Blocks with C++
- Video Memory Programming in Text Mode
- More Tricks to Gain Speed in Programming Con...
- Easy and Efficient Programming for Contests
- Preparing For Programming Contests
- Programming Contests: Why Bother?
- Polymorphism in C++
- Overview of Virtual Functions
- Inheritance in C++
- Extending the Basic Streams in C++
- Using Stringstreams in C++
- Custom Stream Manipulation in C++
- General Stream Manipulation in C++
- Serialize Your Class into Streams in C++

Watch our Tech Videos 
Dev Articles Forums 
 RSS  Articles
 RSS  Forums
 RSS  All Feeds
Write For Us 
Weekly Newsletter
 
Developer Updates  
Free Website Content 
Contact Us 
Site Map 
Privacy Policy 
Support 

Developer Shed Affiliates

 




© 2003-2017 by Developer Shed. All rights reserved. DS Cluster - Follow our Sitemap
Popular Web Development Topics
All Web Development Tutorials