C++
  Home arrow C++ arrow Page 4 - A Reusable Windows Socket Server Class Wit...
Dev Articles Forums 
ADO.NET  
Apache  
ASP  
ASP.NET  
C#  
C++  
ColdFusion  
COM/COM+  
Delphi-Kylix  
Design Usability  
Development Cycles  
DHTML  
Embedded Tools  
Flash  
Graphic Design  
HTML  
IIS  
Interviews  
Java  
JavaScript  
MySQL  
Oracle  
Photoshop  
PHP  
Reviews  
Ruby-on-Rails  
SQL  
SQL Server  
Style Sheets  
VB.Net  
Visual Basic  
Web Authoring  
Web Services  
Web Standards  
XML  
Mobile Linux 
App Generation ROI 
IBM® developerWorks 
Weekly Newsletter
 
Developer Updates  
Free Website Content 
 RSS  Articles
 RSS  Forums
 RSS  All Feeds
Write For Us Get Paid 
Request Media Kit
Contact Us 
Site Map 
Privacy Policy 
Support 
 USERNAME
 
 PASSWORD
 
 
  >>> SIGN UP!  
  Lost Password? 
C++

A Reusable Windows Socket Server Class With C++
By: Len Holgate
  • Search For More Articles!
  • Disclaimer
  • Author Terms
  • Rating: 5 stars5 stars5 stars5 stars5 stars / 42
    2002-06-28

    Table of Contents:
  • A Reusable Windows Socket Server Class With C++
  • What does a socket server need to do?
  • Asynchronous IO
  • Some example servers
  • Chunking the byte stream (Contd.)
  • Conclusion

  • Rate this Article: Poor Best 
      ADD THIS ARTICLE TO:
      Del.ici.ous Digg
      Blink Simpy
      Google Spurl
      Y! MyWeb Furl
    Email Me Similar Content When Posted
    Add Developer Shed Article Feed To Your Site
    Email Article To Friend
    Print Version Of Article
    PDF Version Of Article
     
     
    ADVERTISEMENT


    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.

    More C++ Articles
    More By Len Holgate


     

    C++ ARTICLES

    - 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++
    - Advanced File Handling with Streams in C++
    - File Handling and Streams in C++
    - The STL String Class







    © 2003-2009 by Developer Shed. All rights reserved. DS Cluster 5 Hosted by Hostway
    For more Enterprise Application Development news, visit eWeek