Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

A Simple and Easy to Use IOCP Server Framework

3.43/5 (8 votes)
30 Jun 2008LGPL33 min read 1   1.7K  
An introduction to SPServer Framework

1. Introduction

SPServer is a simple and easy to use server framework library based on Windows IOCP. It includes a base TCP server framework, and a simple HTTP server framework. It has built-in support for a chatroom like application. SPServer can simplify TCP server construction.

2. Background

IOCP is good, but we still need to do some complex things when we use IOCP to implement a stable server. Generally, we need to deal with the following things:

  1. Deal with partial read, we need to do several WSARecv to read a complete request message.
  2. Deal with partial write, we need to do several WSASend to write a complete response message.
  3. If we want to implement a chatroom like application, we need to process several sockets at the same time. If we use a thread pool to run IOCP, we need to deal with this situation carefully.

SPServer is a framework which tries to encapsulate the above complex things, and provide a simple and easy to use interface for the application programmer.

3. Using the Code

A simple line echo server is described below. This echo server is different from the general echo server: it does not respond until it reads a line terminator -- CRLF.

This sample shows the basic concepts of the framework. To use this framework, you need to implement an application specified handler and handler factory.

If you also use CRLF as the delimiter of the request message, you can reuse the SP_LineMsgDecoder; otherwise you need to implement an application specified message decoder.

C++
class SP_LineMsgDecoder : public SP_MsgDecoder {
public:
    virtual int decode( SP_Buffer * inBuffer );

    const char * getMsg();
};

class SP_EchoHandler : public SP_Handler {
public:
    // return -1 : terminate session, 0 : continue
    virtual int start( SP_Request * request, SP_Response * response ) {
        request->setMsgDecoder( new SP_LineMsgDecoder() );
        response->getReply()->getMsg()->append(
            "Welcome to line echo server, enter 'quit' to quit.\r\n" );

        return 0;
    }

    // return -1 : terminate session, 0 : continue
    virtual int handle( SP_Request * request, SP_Response * response ) {
        SP_LineMsgDecoder * decoder = (SP_LineMsgDecoder*)request->getMsgDecoder();

        if( 0 != strcasecmp( (char*)decoder->getMsg(), "quit" ) ) {
            response->getReply()->getMsg()->append( (char*)decoder->getMsg() );
            response->getReply()->getMsg()->append( "\r\n" );
            return 0;
        } else {
            response->getReply()->getMsg()->append( "Byebye\r\n" );
            return -1;
        }
    }
};

class SP_EchoHandlerFactory : public SP_HandlerFactory {
public:
    virtual SP_Handler * create() const {
        return new SP_EchoHandler();
    }
};

int main( int argc, char * argv[] )
{
    const char * host = "127.0.0.1";
    int port = 3333;

    SP_IocpServer server( host, port, new SP_EchoHandlerFactory() );
    server.runForever();

    return 0;
}

4. Class Interface

The line echo server refers some classes:

  • SP_MsgDecoder
  • SP_Request
  • SP_Message
  • SP_Response
  • SP_Buffer
  • SP_Handler

4.1 SP_MsgDecoder and SP_Request

C++
class SP_MsgDecoder {
public:
    enum { eOK, eMoreData };
    virtual int decode( SP_Buffer * inBuffer ) = 0;
};

/* act as the SP_MsgDecoder's container */
class SP_Request {
public:
    SP_MsgDecoder * getMsgDecoder();

    void setMsgDecoder( SP_MsgDecoder * decoder );
};

4.2 SP_Message and SP_Response

C++
typedef struct tagSP_Sid SP_Sid_t;

class SP_SidList {
public:
    int getCount() const;
    void add( SP_Sid_t sid );
    SP_Sid_t get( int index ) const;
    SP_Sid_t take( int index );

    int find( SP_Sid_t sid ) const;
};

class SP_Message {
public:
    SP_SidList * getToList();
    SP_Buffer * getMsg();
};

/* act as the SP_Message's container */
class SP_Response {
public:
    SP_Message * getReply();

    void addMessage( SP_Message * msg );
    SP_Message * peekMessage();
    SP_Message * takeMessage();
};

4.3 SP_Handler and SP_Buffer

C++
class SP_Handler {
public:
    // return -1 : terminate session, 0 : continue
    virtual int start( SP_Request * request, SP_Response * response ) = 0;

    // return -1 : terminate session, 0 : continue
    virtual int handle( SP_Request * request, SP_Response * response ) = 0;

    virtual void error( SP_Response * response ) = 0;

    virtual void timeout( SP_Response * response ) = 0;

    virtual void close() = 0;
};

class SP_Buffer {
public:
    int append( const void * buffer, int len = 0 );
    int append( const SP_Buffer * buffer );
    void erase( int len );
    void reset();
    const void * getBuffer() const;
    size_t getSize() const;
    int take( char * buffer, int len );

    char * getLine();
    const void * find( const void * key, size_t len );

    SP_Buffer * take();
};

5. The Key Process Flow of the Framework

5.1 Deal with Partial Read

The line echo server uses the SP_LineMsgDecoder to deal with partial read. SP_LineMsgDecoder is a subclass of the SP_MsgDecoder.

The framework's process steps are as follows:

  1. The framework calls the SP_Handler::start when it accepts a client, we can create the application specified MsgDecoder -- SP_LineMsgDecoder at this moment.
  2. The framework issues a WSARecv for the socket.
  3. The framework calls the SP_MsgDecoder::decode method, when it reads some data from socket.
  4. If the data which is held in inBuffer represents a complete request message, then it returns SP_MsgDecoder::eOK and goes to step 5, else it returns SP_MsgDecoder::eMoreData and goes to step 2.
  5. Copy the data to your own buffer, and erase the data from inBuffer.
  6. The framework calls the SP_Handler::handle method. We can retrieve the SP_LineMsgDecoder, and get the request messages from the SP_LineMsgDecoder.

5.2 Deal with Partial Write

The framework uses the SP_Response and SP_Message to deal with partial write. SP_Response is the container of the SP_Message. SP_Message is used to hold a response message.

The framework's process steps are as follows:

  1. The framework calls the SP_Handler::handle method. The request messages are processed, and then it generates the response messages. The response messages are appended to SP_Response.
  2. The framework issues a WSASend for the socket.
  3. The framework checks the TransferredBytes when WSASend is completed, if it still leaves some data to send, then go to step 2.

5.3 Built-in Support for Chatroom-like Application

The framework assigns a uuid for every client when the client is connected. We can send a response message to a client by adding the client's uuid into SP_Message::mToList when we generate a response. The framework encapsulates the complex for sending a response message for several sockets at the same time.

There is a chatroom demo -- testiocpdispatcher.cpp in the source package. For more detailed information, please refer to the source code available for download at the top of this article.

6. Miscellaneous

The core part of SPServer is standalone in Windows platform, it does not depend on any third-party libraries. So you can build the library and the demo programs just with Visual Studio (VC++6 or later).

SPServer implements a plugin mechanism to support SSL communication. If you need to use SSL, you must install OpenSSL or XySSL.

7. References

8. History

  • 2008-06-28: First version

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)