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:
- Deal with partial read, we need to do several
WSARecv
to read a complete request message. - Deal with partial write, we need to do several
WSASend
to write a complete response message. - 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.
class SP_LineMsgDecoder : public SP_MsgDecoder {
public:
virtual int decode( SP_Buffer * inBuffer );
const char * getMsg();
};
class SP_EchoHandler : public SP_Handler {
public:
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;
}
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
class SP_MsgDecoder {
public:
enum { eOK, eMoreData };
virtual int decode( SP_Buffer * inBuffer ) = 0;
};
class SP_Request {
public:
SP_MsgDecoder * getMsgDecoder();
void setMsgDecoder( SP_MsgDecoder * decoder );
};
4.2 SP_Message and SP_Response
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();
};
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
class SP_Handler {
public:
virtual int start( SP_Request * request, SP_Response * response ) = 0;
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:
- The framework calls the
SP_Handler::start
when it accepts a client, we can create the application specified MsgDecoder
-- SP_LineMsgDecoder
at this moment. - The framework issues a
WSARecv
for the socket. - The framework calls the
SP_MsgDecoder::decode
method, when it reads some data from socket. - 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. - Copy the data to your own buffer, and erase the data from
inBuffer
. - 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:
- 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
. - The framework issues a
WSASend
for the socket. - 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