用完成端口(IOCP)实现一个简单的服务器框架
IOCP 对于高并发的应用程序提供了良好的支持,使得开发高并发的应用程序的难度降低了很多。
IOCP 作为系统底层的 API ,保持了尽可能高的灵活性,对于很多复杂的情况,IOCP 也一样适用。
这种灵活性是一种双刃剑,对于复杂的情况,没有这种灵活性就没有办法完成工作。
但是对于简单的情况,这种灵活性就带来了额外的负担。
这里将要提到的这个服务器框架(spserver/iocp版),主要是针对以下的这几种典型的应用程序
1.echo/http 类型,server 不需要在多个 client 之间转发消息
2.chatroom 类型,server 在多个 client 之间转发消息
在使用 IOCP 来实现这两类应用程序的时候,会遇到一些共同的问题
1.当连接断开的时候,在 server 端为这个连接分配的资源如何妥善地进行释放
2.如何为每一个 IO 操作设定一个超时时间,以使得 server 能够更好地了解每个连接的当前情况
spserver 通过使用 hash 表的方式来解决资源释放的问题,具体的讨论可以参考
spserver 通过使用类似 libevent 的 min-heap 来解决 IO 操作超时的问题,具体可以参考
在上一个版本(0.9)中,spserver 在 windows 平台还是基于 libevent + pthread-win32 来实现的。
最新版本(0.9.1),在 windows 平台改成使用 IOCP + Windows Thread 来重新实现。
最新版本在 Windows 平台已经不再需要依赖第三方的库了。
主页和下载地址
下面来看一个使用 spserver 实现的简单的 line echo server 。
得益于 IOCP 本身的 IO 能力,下面这个简单的 echo server ,如果在同一台机器上运行 server(testiocpecho.exe) 和 client(testiocpstress.exe) ,能够达到 recv 10万/秒 ,send 10万/秒 的速度;如果在两台用 100M 带宽相连的机器上分别执行 server 和 client ,在测试期间能够保持对带宽 90% 的利用率。
class SP_EchoHandler : public SP_Handler {
public:
SP_EchoHandler(){}
virtual ~SP_EchoHandler(){}
// 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;
}
}
virtual void error( SP_Response * response ) {}
virtual void timeout( SP_Response * response ) {}
virtual void close() {}
};
class SP_EchoHandlerFactory : public SP_HandlerFactory {
public:
SP_EchoHandlerFactory() {}
virtual ~SP_EchoHandlerFactory() {}
virtual SP_Handler * create() const {
return new SP_EchoHandler();
}
};
int main( int argc, char * argv[] )
{
int port = 3333;
SP_Server server( "", port, new SP_EchoHandlerFactory() );
server.runForever();
return 0;
}
在最简单的情况下,使用 spserver 实现一个 TCP server 需要实现两个类:SP_Handler 的子类 和 SP_HandlerFactory 的子类。
SP_Handler 的子类负责处理具体业务。
SP_HandlerFactory 的子类协助 spserver 为每一个连接创建一个 SP_Handler 子类实例。
1.SP_Handler 生命周期
SP_Handler 和 TCP 连接一对一,SP_Handler 的生存周期和 TCP 连接一样。
当 TCP 连接被接受之后,SP_Handler 被创建,当 TCP 连接断开之后,SP_Handler将被 destroy。
2.SP_Handler 函数说明
SP_Handler 有 5 个纯虚方法需要由子类来重载。这 5 个方法分别是:
start:当一个连接成功接受之后,将首先被调用。返回 0 表示继续,-1 表示结束连接。
handle:当一个请求包接收完之后,将被调用。返回 0 表示继续,-1 表示结束连接。
error:当在一个连接上读取或者发送数据出错时,将被调用。error 之后,连接将被关闭。
timeout:当一个连接在约定的时间内没有发生可读或者可写事件,将被调用。timeout 之后,连接将被关闭。
close:当一个 TCP 连接被关闭时,无论是正常关闭,还是因为 error/timeout 而关闭。
3.SP_Handler 函数调用时机
当需要调用 SP_Handler 的 start/handle/error/timeout 方法时,相关的参数将被放入队列,然后由线程池来负责执行 SP_Handler 对应的方法。因此在 start/handle/error/timeout/close 中可以使用同步
操作来编程,可以直接使用阻塞型 I/O 。