- 浏览: 409565 次
最新评论
-
iunknown:
909601686 写道我理解下来,之所以使用奇数应该不仅仅是 ...
以两军问题为背景来演绎Basic Paxos -
909601686:
我理解下来,之所以使用奇数应该不仅仅是为了判断谁赢。而是这样就 ...
以两军问题为背景来演绎Basic Paxos -
feclipse:
you are my hero! 看了你的图示之后,我的理解 ...
以两军问题为背景来演绎Basic Paxos -
lcc0739:
相当好!通俗易懂,看了好几篇paxos只有你这个最深入浅出!
以两军问题为背景来演绎Basic Paxos -
iunknown:
tangfu 写道hi,问一下,博主提供的库与pb兼容么,比如 ...
一个轻量的 wire format 解释器(google protobuf 的二进制格式)
spserver 是一个实现了半同步/半异步(Half-Sync/Half-Async)和领导者/追随者(Leader/Follower) 模式的服务器框架,能够简化 TCP server 的开发工作。
spserver 使用 c++ 实现,目前实现了以下功能:
1.封装了 TCP server 中接受连接的功能;
2.使用非阻塞型I/O和事件驱动模型,由主线程负责处理所有 TCP 连接上的数据读取和发送,因此连接数不受线程数的限制;
3.主线程读取到的数据放入队列,由一个线程池处理实际的业务。
4.一个 http 服务器框架,即嵌入式 web 服务器(请参考: SPWebServer:一个基于 SPServer 的 web 服务器框架)
0.6 版本之前只包含 Half-Sync/Half-Async 模式的实现,0.6 版本开始包含 Leader/Follower 模式的实现
0.7 版本开始支持 ssl 。把 socket 相关的操作抽象为一个 IOChannel 层,关于 openssl 的部分单独实现为一个 plugin 的形式,对于不使用 ssl 功能的用户,不需要引入 ssl 相关的头文件和库。
0.7.5 增加了一个 sptunnel 程序,是一个通用的 ssl proxy 。类似 stunnel 。
0.9.0 移植 spserver 到 windows 平台,需要在 windows 下编译 libevent 和 pthread 。
0.9.1 在 windows 平台,去掉了对 libevent 和 pthread 依赖,完全使用 iocp 和 windows 的线程机制实现了半同步半异步的框架。
0.9.2 移植了所有的功能到 windows 平台,同时新增加了 xyssl 的插件。
主页:
http://code.google.com/p/spserver/
源代码下载:
http://spserver.googlecode.com/files/spserver-0.6.src.tar.gz
http://code.google.com/p/spserver/downloads/list
在实现并发处理多事件的应用程序方面,有如下两种常见的编程模型:
ThreadPerConnection的多线程模型和事件驱动的单线程模型。
ThreadPerConnection的多线程模型
优点:简单易用,效率也不错。在这种模型中,开发者使用同步操作来编写程序,比如使用阻塞型I/O。使用同步操作的程序能够隐式地在线程的运行堆栈中维护应用程序的状态信息和执行历史,方便程序的开发。
缺点:没有足够的扩展性。如果应用程序只需处理少量的并发连接,那么对应地创建相应数量的线程,一般的机器都还能胜任;但如果应用程序需要处理成千上万个连接,那么为每个连接创建一个线程也许是不可行的。
事件驱动的单线程模型
优点:扩展性高,通常性能也比较好。在这种模型中,把会导致阻塞的操作转化为一个异步操作,主线程负责发起这个异步操作,并处理这个异步操作的结果。由于所有阻塞的操作都转化为异步操作,理论上主线程的大部分时间都是在处理实际的计算任务,少了多线程的调度时间,所以这种模型的性能通常会比较好。
缺点:要把所有会导致阻塞的操作转化为异步操作。一个是带来编程上的复杂度,异步操作需要由开发者来显示地管理应用程序的状态信息和执行历史。第二个是目前很多广泛使用的函数库都很难转为用异步操作来实现,即是可以用异步操作来实现,也将进一步增加编程的复杂度。
并发系统通常既包含异步处理服务,又包含同步处理服务。系统程序员有充分的理由使用异步特性改善性能。相反,应用程序员也有充分的理由使用同步处理简化他们的编程强度。
针对这种情况,ACE 的作者提出了 半同步/半异步 (Half-Sync/Half-Async) 模式。
《POSA2》上对这个模式的描述如下:
半同步/半异步 体系结构模式将并发系统中的异步和同步服务处理分离,简化了编程,同时又没有降低性能。该模式介绍了两个通信层,一个用于异步服务处理,另一个用于同步服务处理。
目标:
需要同步处理的简易性的应用程序开发者无需考虑异步的复杂性。同时,必须将性能最大化的系统开发者不需要考虑同步处理的低效性。让同步和异步处理服务能够相互通信,而不会使它们的编程模型复杂化或者过度地降低它们的性能。
解决方案:
将系统中的服务分解成两层,同步和异步,并且在它们之间增加一个排队层协调异步和同步层中的服务之间的通信。在独立线程或进程中同步地处理高层服务(如耗时长的数据库查询或文件传输),从而简化并发编程。相反,异步地处理底层服务(如从网络连接上读取数据),以增强性能。如果驻留在相互独立的同步和异步层中的服务必须相互通信或同步它们的处理,则应允许它们通过一个排队层向对方传递消息。
模式原文:Half-Sync/Half-Async: An Architectural Pattern for Efficient and Well-structured Concurrent I/O
http://www.cs.wustl.edu/~schmidt/PDF/HS-HA.pdf
中文翻译:http://blog.chinaunix.net/u/31756/showart_245841.html
如果上面关于 半同步/半异步 的说明过于抽象,那么可以看一个《POSA2》中提到的例子:
许多餐厅使用 半同步/半异步 模式的变体。例如,餐厅常常雇佣一个领班负责迎接顾客,并在餐厅繁忙时留意给顾客安排桌位,为等待就餐的顾客按序排队是必要的。领班由所有顾客“共享”,不能被任何特定顾客占用太多时间。当顾客在一张桌子入坐后,有一个侍应生专门为这张桌子服务。
下面来看一个使用 spserver 实现的简单的 line echo server 。
在最简单的情况下,使用 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 中可以使用同步操作来编程,可以直接使用阻塞型 I/O 。
在发生 error 和 timeout 事件之后,close 紧跟着这两个方法之后被调用。
如果是程序正常指示结束连接,那么在主线程中直接调用 close 方法。
4.高级功能--MsgDecoder
这个 line echo server 比起常见的 echo server 有一点不同:只有在读到一行时才进行 echo。
这个功能是通过一个称为 MsgDecoder 的接口来实现的。不同的 TCP server 在应用层的传输格式上各不相同。
比如在 SMTP/POP 这一类的协议中,大部分命令是使用 CRLF 作为分隔符的。而在 HTTP 中是使用 Header + Body 的形式。
为了适应不同的 TCP server,在 spserver 中有一个 MsgDecoder 接口,用来处理这些应用层的协议。
比如在这个 line echo server 中,把传输协议定义为:只有读到一行时将进行 echo 。
那么相应地就要实现一个 SP_LineMsgDecoder ,这个 LineMsgDecoder 负责判断目前的输入缓冲区中是否已经有完整的一行。
MsgDecoder 的接口如下:
decode 方法对 inBuffer 里面的数据进行检查,看是否符合特定的要求。如果已经符合要求,那么返回 eOK ;如果还不满足要求,那么返回 eMoreData。比如 LineMsgDecoder 的 decode 方法的实现为:
spserver 默认提供了几个 MsgDecoder 的实现:
SP_DefaultMsgDecoder :它的 decode 总是返回 eOK ,即只要有输入就当作是符合要求了。
如果应用不设置 SP_Request->setMsgDecoder 的话,默认使用这个。
SP_LineMsgDecoder : 检查到有一行的时候,返回 eOK ,按行读取输入。
SP_DotTermMsgDecoder :检查到输入中包含了特定的 <CRLF>.<CRLF> 时,返回 eOK。
具体的使用例子可以参考示例:testsmtp 。
5.高级功能--实现聊天室
spserver 还提供了一个广播消息的功能。使用消息广播功能可以方便地实现类似聊天室的功能。具体的实现可以参考示例:testchat 。
6.libevent
spserver 使用 c++ 实现,使用了一个第三方库--libevent,以便在不同的平台上都能够使用最有效的事件驱动机制(Currently, libevent supports /dev/poll, kqueue(2), select(2), poll(2) and epoll(4). )。
SPServer内部保存了所有注册的 struct event,可以取消所有注册的 event 的。不过 libevent 1.1 和 1.2 中有 bug ,在 event_init 的时候,libevent 自身注册了一些 event ,这些 event 在 event_base_free 的时候,没有取消,导致 event_base_free 会被挂住。另外 event_base_free 是从 1.2 开始才有的,1.1 都没有 event_base_free ,所以目前在 SPServer 中都把 event_base_free 调用注释掉了。
想到了一个方法,就是为 SP_Buffer 增加一个 take 的方法,如下
通过这个方法,可以直接把 SP_Buffer 底层控制的内存块拿出来用,同时 SP_Buffer 自身重新分配控制块。这个控制块很小,大概只有 50 bytes 。这样可以减少内存的复制操作。
使用的例子可以参考 spserver/openssl/sptunnelimpl.cpp 文件中的
int SP_TunnelDecoder :: decode( SP_Buffer * inBuffer ) 函数。
在这里重新分配了内存,有没有更好的办法?
还是就是如果有两个数据包同时被接收,后面一个包怎么处理?
不知道我说的对不对,还有几个问题想要请救,方便留个MSN?
我的是yinaesop@msn.com
对于一个 fd,如果有两个数据包同时被读入 inBuffer 里面,后面的数据包将在前一个数据包被处理完之后,继续被处理。这两个数据包不会被并行处理,因为 SP_Handler 和 fd 是一一对应的,如果并行处理数据包,将导致 SP_Handler 可能同时被多个线程调用,这样将使得 SP_Handler 非常难于实现。
关于重新分配内存的问题:inBuffer 是使用 read 系统调用从 fd 里面读入的。使用 read 的时候,是尽可能地读入数据,因此 inBuffer 里面的数据就不一定是逻辑上的一个数据包。由于 inBuffer 里面的数据不一定正好是一个逻辑上的数据包,也有可能多于一个逻辑上的数据包,而为了能够提供给应用层(SP_Handler::handle)方便地使用,因此不得不做了一次数据 copy 。如果 inBuffer 里面的数据不需要做逻辑上的 decode 操作,那么这次 copy 的确有浪费之嫌。如果 inBuffer 里面的数据需要经过 decode 操作(比如解压缩,解密,或者协议解释),那么这次 copy 操作就是无法避免的了。比如 http msg decoder 的实现
另外有一点,上面的 decode( SP_Buffer * inBuffer ) 的实现是用一种最简单,但内存使用最多的方式来实现的。其实可以在 else 中把 inBuffer 复制到 mBuffer 中,不过前面的判断是否已经完整读取到一个数据包的逻辑就变得比较复杂了。这样可以避免把内容一直放在 inBuffer 。
说到这里,可能就是一个关于 SPServer 适用范围的问题了,如果多出来的这次 copy 操作是无法接受的,那么可能就需要另外的专门的解决方案了。
关于 SP_MsgDecoder 的设计思路,当初主要是模仿 java 的 mina 框架。mina 框架号称
如果想实现复杂的如LDAP这样的协议怎么办呢?它似乎是一个恶梦,因为IO层没有帮助你分离‘message解析’和‘实际的业务逻辑(比如访问一个目录数据库)’。MINA提供了一个协议层来解决这个问题。协议层将ByteBuffer事件转换成高层的POJO事件:
就像前面提到的,你只需撰写面向POJO的message而不是ByteBuffer的。ProtocolEncoder 将message对象解释成ByteBuffers以便IO层能够将他们输出到socket。
是指解决什么问题?是说 SP_MsgDecoder 实现的时候,缓冲有问题?
int SP_DotTermMsgDecoder :: decode( SP_Buffer * inBuffer )
{
if( NULL != mBuffer ) free( mBuffer );
const char * pos = (char*)inBuffer->find( "\r\n.\r\n", 5 );
if( NULL == pos ) {
pos = (char*)inBuffer->find( "\n.\n", 3 );
}
if( NULL != pos ) {
int len = pos - (char*)inBuffer->getBuffer();
mBuffer = (char*)malloc( len + 1 );
memcpy( mBuffer, inBuffer->getBuffer(), len );
mBuffer[ len ] = '\0';
inBuffer->erase( len );
在这里重新分配了内存,有没有更好的办法?
还是就是如果有两个数据包同时被接收,后面一个包怎么处理?
不知道我说的对不对,还有几个问题想要请救,方便留个MSN?
我的是yinaesop@msn.com
是指解决什么问题?是说 SP_MsgDecoder 实现的时候,缓冲有问题?
经过测试,我这个说法是错误的。epoll有个EPOLLONESHOT可以完成一次性事件,可以让epoll_wait每次只取一个事件,从而达到没有线程锁的情况下实现LF模式。一个线程调用epoll_wait时还允许其它线程调用epoll_ctl来注册事件。多个线程在同一个epoll描述符上执行epoll_wait内核会进行排队,不会出现一个事件在有EPOLLONESHOT时还被多个线程得到的情况,所以可以在无锁的情况下完成整个过程,目前简单测试通过了。
测试的主要关键点是在测试案例的选取上。
就目前这个 testhttp.cpp 的案例来说,多 CPU 对性能的提升应该不大了。对于这种案例,如果要在多 CPU 上获得更好的性能,应该使用 memcached 的方法,每个线程使用一个 event_base 。
在 POSA2 书中,多次提到 win32 WaitForMultiObjects 函数允许线程池在同一个句柄集上同时等待来支持并发句柄集。
对于 event-driven 编程方式的封装,libevent 其实做得很好了。libevent 主要的特点是使用 callback ,但这个也是由 event-driven 决定的。这种实现方式就相当于设计模式中的“被动迭代器”。
通过上面提到的方法可以把“被动迭代器”转换为“主动迭代器”。
如果要改进,应该就是要考虑如何一步到位地把 event-driven 编程方式封装为“主动迭代器”的方式。
今天试着用这个思路来实现 Leader/Follower ,为 SPServer 增加了一个 SP_LFServer 的类,接口和 SP_Server 一样。
最新代码
http://spserver.googlecode.com/files/spserver-0.6.src.tar.gz
然后修改了 testhttp.cpp 的代码,使得可以在命令行指定使用的线程池模型,然后用 ab 做了一些简单的测试。
testhttp.cpp 的功能很简单,只是把收到的 http 请求解释出来,然后把解释到的内容作为 response 返回给客户端。由于 testhttp.cpp 的功能足够简单,所以用来测试线程的切换的消耗还是比较理想的。
从初步的测试结果来看,在 testhttp.cpp 这种应用中, LF 比 HAHS 快。
测试不是很严格,针对 HAHS 和 LF 分别测试使用 1 个线程和 2 个线程的情况,每个 case 运行 5 次,取中间的结果。性能最好的是 LF 使用 1 个线程的时候,因为这里完全没有线程的切换, 。
对比上次 测试memcached 得到的数据,LF 使用 1 线程的时候,处理请求的速度和 memcached 差不多。memcached 是单线程的。
对于 HAHS 模型的测试结果
对于 LF 模型的测试结果
目前是实现了一个 Executor ,这个 Executor 对象是一个主动对象,内部维持一个任务队列和一个线程池( worker )。创建 Executor 对象的时候,自动会创建一个线程(称为 scheduler 线程 ),这个线程复杂分派队列中的任务给线程池中的线程。
Executor 的使用场景类似这样:
这样带来的好处是 Executor 可以用 lazy 的方式来创建线程池里面的线程,也可以在必要的时候回收线程。
另外就是使用者容易使用,接口容易理解。这种线程池的使用和普通的 connection pool 之类的池很类似。
过量的任务只会阻塞 scheduler ,不会阻塞 main_thread 。
坏处就是多了线程的切换。executor.execute 的时候,从 main_thread 切换到 scheduler 线程,然后 scheduler 线程分派任务,又从 scheduler 切换到 worker 线程。
LF 的确有这种问题。所以可能要做到由用户来选择,把 HAHS 和 LF 封装起来,可以方便地切换。
使用 libevent 和线程池,一般很自然地就把线程池和 reactor 混在一起了。
主要的原因是 libevent 的 event_loop 的实现,当底层的 select/poll/epoll 探测到有事件的时候,event_loop 是逐个处理所有的事件,在 event_loop 内部调用所有的 callback 。而一般就是在 callback 里面直接处理事件了。
为了把 reactor/proactor 和线程池分开,可以用一个间接的方法。在 callback 中不直接处理事件,把事件保存到一个列表(eventList)里面,这样就可以使得 event_loop 要做的事情很简单。等到 event_loop 运行完之后,就可以使用相应的线程池策略来处理 eventList 了。
在这里有类似的描述
Modification of the leader-follower pattern for Proactor
代码大概是这样的
我也正准备按这个思路来尝试修改 spserver ,然后对比一下 HAHS 和 LF 线程池模型的性能差异。
从目前想象到的运行过程来看,LF 的线程切换应该比 HAHS 少很多,性能应该会更好。
关键的地方是
对于不是使用真正的异步 IO (*nix aio 或者 win iocp) 实现的 proactor ,其实都可以认为是用 reactor 模拟出来的。
用 reactor 模拟 proactor ,在这篇文章中描述得很清楚
Comparing Two High-Performance I/O Design Patterns
在不使用真正的异步 IO 的情况下,reactor 还是 proactor ,差别只在于曝露出来的 handler 接口。
reactor 的接口体现的是 IO 句柄上发生了事件。(在 POSA2 的 page114)
proactor 的接口体现的是 IO 读写事件已经完成了。(在 POSA2 的 page139)
用 reactor 模拟 proactor ,就是设计一个 Event_Handler 的子类,设计一套 Async_Result ,把 Event_Handler 接口适配为 Completion_Handler 。
在 SPServer 的设计中,SP_EventCallback 可以看成是这个适配子类,SP_MsgDecoder 可以看成是 read 的 Async_Result,SP_Message 可以看成是 write 的 Async_Result 。
对于HTTP这种没有长度前缀的,最好还是用reactor方式,当然用上面的proactor方式也是可以的,因为有上readall参数可以设置为 false。好像和原来的reactor又兼容得不好了,我想是不是可以在收到数据后判断是否有读请求,如果没有则直接调用dataReceived。不过感觉和requestRead这种请求/回调的思想不一致。也不想再维护一个单线程的reactor、一个多线程reactor和一个多线程的 proactor。
对于 HTTP 这种协议,如果直接使用 blocking TCP socket 的话,很容易实现。
但无论是使用 reactor 还是 proactor ,都是为了避免 blocking TCP socket ,因此就不可避免需要实现一个面向流的 http message parser 了。有了这个面向流的 http message parser ,那么无论使用何种接口,都可以容易地实现功能了。
不是很明白这里 qiezi 提到的 “和原来的reactor又兼容得不好了”的意思。
PS:qiezi 是使用 D 语言在实现吧?不知道有没有计划 open 这个实现?
spserver 使用 c++ 实现,目前实现了以下功能:
1.封装了 TCP server 中接受连接的功能;
2.使用非阻塞型I/O和事件驱动模型,由主线程负责处理所有 TCP 连接上的数据读取和发送,因此连接数不受线程数的限制;
3.主线程读取到的数据放入队列,由一个线程池处理实际的业务。
4.一个 http 服务器框架,即嵌入式 web 服务器(请参考: SPWebServer:一个基于 SPServer 的 web 服务器框架)
0.6 版本之前只包含 Half-Sync/Half-Async 模式的实现,0.6 版本开始包含 Leader/Follower 模式的实现
0.7 版本开始支持 ssl 。把 socket 相关的操作抽象为一个 IOChannel 层,关于 openssl 的部分单独实现为一个 plugin 的形式,对于不使用 ssl 功能的用户,不需要引入 ssl 相关的头文件和库。
0.7.5 增加了一个 sptunnel 程序,是一个通用的 ssl proxy 。类似 stunnel 。
0.9.0 移植 spserver 到 windows 平台,需要在 windows 下编译 libevent 和 pthread 。
0.9.1 在 windows 平台,去掉了对 libevent 和 pthread 依赖,完全使用 iocp 和 windows 的线程机制实现了半同步半异步的框架。
0.9.2 移植了所有的功能到 windows 平台,同时新增加了 xyssl 的插件。
主页:
http://code.google.com/p/spserver/
源代码下载:
http://spserver.googlecode.com/files/spserver-0.6.src.tar.gz
http://code.google.com/p/spserver/downloads/list
在实现并发处理多事件的应用程序方面,有如下两种常见的编程模型:
ThreadPerConnection的多线程模型和事件驱动的单线程模型。
ThreadPerConnection的多线程模型
优点:简单易用,效率也不错。在这种模型中,开发者使用同步操作来编写程序,比如使用阻塞型I/O。使用同步操作的程序能够隐式地在线程的运行堆栈中维护应用程序的状态信息和执行历史,方便程序的开发。
缺点:没有足够的扩展性。如果应用程序只需处理少量的并发连接,那么对应地创建相应数量的线程,一般的机器都还能胜任;但如果应用程序需要处理成千上万个连接,那么为每个连接创建一个线程也许是不可行的。
事件驱动的单线程模型
优点:扩展性高,通常性能也比较好。在这种模型中,把会导致阻塞的操作转化为一个异步操作,主线程负责发起这个异步操作,并处理这个异步操作的结果。由于所有阻塞的操作都转化为异步操作,理论上主线程的大部分时间都是在处理实际的计算任务,少了多线程的调度时间,所以这种模型的性能通常会比较好。
缺点:要把所有会导致阻塞的操作转化为异步操作。一个是带来编程上的复杂度,异步操作需要由开发者来显示地管理应用程序的状态信息和执行历史。第二个是目前很多广泛使用的函数库都很难转为用异步操作来实现,即是可以用异步操作来实现,也将进一步增加编程的复杂度。
并发系统通常既包含异步处理服务,又包含同步处理服务。系统程序员有充分的理由使用异步特性改善性能。相反,应用程序员也有充分的理由使用同步处理简化他们的编程强度。
针对这种情况,ACE 的作者提出了 半同步/半异步 (Half-Sync/Half-Async) 模式。
引用
《POSA2》上对这个模式的描述如下:
半同步/半异步 体系结构模式将并发系统中的异步和同步服务处理分离,简化了编程,同时又没有降低性能。该模式介绍了两个通信层,一个用于异步服务处理,另一个用于同步服务处理。
目标:
需要同步处理的简易性的应用程序开发者无需考虑异步的复杂性。同时,必须将性能最大化的系统开发者不需要考虑同步处理的低效性。让同步和异步处理服务能够相互通信,而不会使它们的编程模型复杂化或者过度地降低它们的性能。
解决方案:
将系统中的服务分解成两层,同步和异步,并且在它们之间增加一个排队层协调异步和同步层中的服务之间的通信。在独立线程或进程中同步地处理高层服务(如耗时长的数据库查询或文件传输),从而简化并发编程。相反,异步地处理底层服务(如从网络连接上读取数据),以增强性能。如果驻留在相互独立的同步和异步层中的服务必须相互通信或同步它们的处理,则应允许它们通过一个排队层向对方传递消息。
模式原文:Half-Sync/Half-Async: An Architectural Pattern for Efficient and Well-structured Concurrent I/O
http://www.cs.wustl.edu/~schmidt/PDF/HS-HA.pdf
中文翻译:http://blog.chinaunix.net/u/31756/showart_245841.html
如果上面关于 半同步/半异步 的说明过于抽象,那么可以看一个《POSA2》中提到的例子:
许多餐厅使用 半同步/半异步 模式的变体。例如,餐厅常常雇佣一个领班负责迎接顾客,并在餐厅繁忙时留意给顾客安排桌位,为等待就餐的顾客按序排队是必要的。领班由所有顾客“共享”,不能被任何特定顾客占用太多时间。当顾客在一张桌子入坐后,有一个侍应生专门为这张桌子服务。
下面来看一个使用 spserver 实现的简单的 line echo server 。
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 中可以使用同步操作来编程,可以直接使用阻塞型 I/O 。
在发生 error 和 timeout 事件之后,close 紧跟着这两个方法之后被调用。
如果是程序正常指示结束连接,那么在主线程中直接调用 close 方法。
4.高级功能--MsgDecoder
这个 line echo server 比起常见的 echo server 有一点不同:只有在读到一行时才进行 echo。
这个功能是通过一个称为 MsgDecoder 的接口来实现的。不同的 TCP server 在应用层的传输格式上各不相同。
比如在 SMTP/POP 这一类的协议中,大部分命令是使用 CRLF 作为分隔符的。而在 HTTP 中是使用 Header + Body 的形式。
为了适应不同的 TCP server,在 spserver 中有一个 MsgDecoder 接口,用来处理这些应用层的协议。
比如在这个 line echo server 中,把传输协议定义为:只有读到一行时将进行 echo 。
那么相应地就要实现一个 SP_LineMsgDecoder ,这个 LineMsgDecoder 负责判断目前的输入缓冲区中是否已经有完整的一行。
MsgDecoder 的接口如下:
class SP_MsgDecoder { public: virtual ~SP_MsgDecoder(); enum { eOK, eMoreData }; virtual int decode( SP_Buffer * inBuffer ) = 0; };
decode 方法对 inBuffer 里面的数据进行检查,看是否符合特定的要求。如果已经符合要求,那么返回 eOK ;如果还不满足要求,那么返回 eMoreData。比如 LineMsgDecoder 的 decode 方法的实现为:
int SP_LineMsgDecoder :: decode( SP_Buffer * inBuffer ) { if( NULL != mLine ) free( mLine ); mLine = inBuffer->getLine(); return NULL == mLine ? eMoreData : eOK; }
spserver 默认提供了几个 MsgDecoder 的实现:
SP_DefaultMsgDecoder :它的 decode 总是返回 eOK ,即只要有输入就当作是符合要求了。
如果应用不设置 SP_Request->setMsgDecoder 的话,默认使用这个。
SP_LineMsgDecoder : 检查到有一行的时候,返回 eOK ,按行读取输入。
SP_DotTermMsgDecoder :检查到输入中包含了特定的 <CRLF>.<CRLF> 时,返回 eOK。
具体的使用例子可以参考示例:testsmtp 。
5.高级功能--实现聊天室
spserver 还提供了一个广播消息的功能。使用消息广播功能可以方便地实现类似聊天室的功能。具体的实现可以参考示例:testchat 。
6.libevent
spserver 使用 c++ 实现,使用了一个第三方库--libevent,以便在不同的平台上都能够使用最有效的事件驱动机制(Currently, libevent supports /dev/poll, kqueue(2), select(2), poll(2) and epoll(4). )。
评论
56 楼
gisupc
2011-12-26
博主你好,请问你在visual studio2008 x64环境下编译过spserver没?求指导
55 楼
iunknown
2007-08-14
引用
好像没看到调用event_base_free,是不是因为没办法取消所有注册的event? 我刚好也遇到这个问题,总是不能很好地处理退出。
SPServer内部保存了所有注册的 struct event,可以取消所有注册的 event 的。不过 libevent 1.1 和 1.2 中有 bug ,在 event_init 的时候,libevent 自身注册了一些 event ,这些 event 在 event_base_free 的时候,没有取消,导致 event_base_free 会被挂住。另外 event_base_free 是从 1.2 开始才有的,1.1 都没有 event_base_free ,所以目前在 SPServer 中都把 event_base_free 调用注释掉了。
54 楼
qiezi
2007-08-14
好像没看到调用event_base_free,是不是因为没办法取消所有注册的event? 我刚好也遇到这个问题,总是不能很好地处理退出。
53 楼
iunknown
2007-08-12
byjove 写道
在这里重新分配了内存,有没有更好的办法?
想到了一个方法,就是为 SP_Buffer 增加一个 take 的方法,如下
class SP_Buffer { public: SP_Buffer * take(); };
通过这个方法,可以直接把 SP_Buffer 底层控制的内存块拿出来用,同时 SP_Buffer 自身重新分配控制块。这个控制块很小,大概只有 50 bytes 。这样可以减少内存的复制操作。
使用的例子可以参考 spserver/openssl/sptunnelimpl.cpp 文件中的
int SP_TunnelDecoder :: decode( SP_Buffer * inBuffer ) 函数。
52 楼
iunknown
2007-08-08
byjove 写道
int SP_DotTermMsgDecoder :: decode( SP_Buffer * inBuffer ) { if( NULL != mBuffer ) free( mBuffer ); const char * pos = (char*)inBuffer->find( "\r\n.\r\n", 5 ); if( NULL == pos ) { pos = (char*)inBuffer->find( "\n.\n", 3 ); } if( NULL != pos ) { int len = pos - (char*)inBuffer->getBuffer(); mBuffer = (char*)malloc( len + 1 ); memcpy( mBuffer, inBuffer->getBuffer(), len ); mBuffer[ len ] = '\0'; inBuffer->erase( len );
在这里重新分配了内存,有没有更好的办法?
还是就是如果有两个数据包同时被接收,后面一个包怎么处理?
不知道我说的对不对,还有几个问题想要请救,方便留个MSN?
我的是yinaesop@msn.com
对于一个 fd,如果有两个数据包同时被读入 inBuffer 里面,后面的数据包将在前一个数据包被处理完之后,继续被处理。这两个数据包不会被并行处理,因为 SP_Handler 和 fd 是一一对应的,如果并行处理数据包,将导致 SP_Handler 可能同时被多个线程调用,这样将使得 SP_Handler 非常难于实现。
关于重新分配内存的问题:inBuffer 是使用 read 系统调用从 fd 里面读入的。使用 read 的时候,是尽可能地读入数据,因此 inBuffer 里面的数据就不一定是逻辑上的一个数据包。由于 inBuffer 里面的数据不一定正好是一个逻辑上的数据包,也有可能多于一个逻辑上的数据包,而为了能够提供给应用层(SP_Handler::handle)方便地使用,因此不得不做了一次数据 copy 。如果 inBuffer 里面的数据不需要做逻辑上的 decode 操作,那么这次 copy 的确有浪费之嫌。如果 inBuffer 里面的数据需要经过 decode 操作(比如解压缩,解密,或者协议解释),那么这次 copy 操作就是无法避免的了。比如 http msg decoder 的实现
int SP_HttpRequestDecoder :: decode( SP_Buffer * inBuffer ) { if( inBuffer->getSize() > 0 ) { int len = mParser->append( inBuffer->getBuffer(), inBuffer->getSize() ); inBuffer->erase( len ); return mParser->isCompleted() ? eOK : eMoreData; } else { return eMoreData; } }
另外有一点,上面的 decode( SP_Buffer * inBuffer ) 的实现是用一种最简单,但内存使用最多的方式来实现的。其实可以在 else 中把 inBuffer 复制到 mBuffer 中,不过前面的判断是否已经完整读取到一个数据包的逻辑就变得比较复杂了。这样可以避免把内容一直放在 inBuffer 。
int SP_DotTermMsgDecoder :: decode( SP_Buffer * inBuffer ) { 。。。。。。 if( NULL != pos ) { } else { // 复制 inBuffer 的内容到 mBuffer,同时清空 inBuffer }
说到这里,可能就是一个关于 SPServer 适用范围的问题了,如果多出来的这次 copy 操作是无法接受的,那么可能就需要另外的专门的解决方案了。
关于 SP_MsgDecoder 的设计思路,当初主要是模仿 java 的 mina 框架。mina 框架号称
引用
如果想实现复杂的如LDAP这样的协议怎么办呢?它似乎是一个恶梦,因为IO层没有帮助你分离‘message解析’和‘实际的业务逻辑(比如访问一个目录数据库)’。MINA提供了一个协议层来解决这个问题。协议层将ByteBuffer事件转换成高层的POJO事件:
就像前面提到的,你只需撰写面向POJO的message而不是ByteBuffer的。ProtocolEncoder 将message对象解释成ByteBuffers以便IO层能够将他们输出到socket。
51 楼
byjove
2007-08-08
引用
是指解决什么问题?是说 SP_MsgDecoder 实现的时候,缓冲有问题?
int SP_DotTermMsgDecoder :: decode( SP_Buffer * inBuffer )
{
if( NULL != mBuffer ) free( mBuffer );
const char * pos = (char*)inBuffer->find( "\r\n.\r\n", 5 );
if( NULL == pos ) {
pos = (char*)inBuffer->find( "\n.\n", 3 );
}
if( NULL != pos ) {
int len = pos - (char*)inBuffer->getBuffer();
mBuffer = (char*)malloc( len + 1 );
memcpy( mBuffer, inBuffer->getBuffer(), len );
mBuffer[ len ] = '\0';
inBuffer->erase( len );
在这里重新分配了内存,有没有更好的办法?
还是就是如果有两个数据包同时被接收,后面一个包怎么处理?
不知道我说的对不对,还有几个问题想要请救,方便留个MSN?
我的是yinaesop@msn.com
50 楼
iunknown
2007-08-08
byjove 写道
SP_MsgDecoder 实现时,COPY缓冲能不能很好的解决?
是指解决什么问题?是说 SP_MsgDecoder 实现的时候,缓冲有问题?
49 楼
qiezi
2007-08-08
qiezi 写道
底层无论是使用select还是epoll,都无法在不加锁的情况下,在一个原子操作中完成取出事件并取消已经注册的事件。
经过测试,我这个说法是错误的。epoll有个EPOLLONESHOT可以完成一次性事件,可以让epoll_wait每次只取一个事件,从而达到没有线程锁的情况下实现LF模式。一个线程调用epoll_wait时还允许其它线程调用epoll_ctl来注册事件。多个线程在同一个epoll描述符上执行epoll_wait内核会进行排队,不会出现一个事件在有EPOLLONESHOT时还被多个线程得到的情况,所以可以在无锁的情况下完成整个过程,目前简单测试通过了。
48 楼
七猫
2007-08-08
linux网络内核没有实现aio
47 楼
byjove
2007-08-08
SP_MsgDecoder 实现时,COPY缓冲能不能很好的解决?
46 楼
iunknown
2007-07-01
引用
有没有测试多CPU?结果可能会不同吧。
测试的主要关键点是在测试案例的选取上。
就目前这个 testhttp.cpp 的案例来说,多 CPU 对性能的提升应该不大了。对于这种案例,如果要在多 CPU 上获得更好的性能,应该使用 memcached 的方法,每个线程使用一个 event_base 。
引用
底层无论是使用select还是epoll,都无法在不加锁的情况下
在 POSA2 书中,多次提到 win32 WaitForMultiObjects 函数允许线程池在同一个句柄集上同时等待来支持并发句柄集。
引用
可能还是要自己去整底层API,依赖libevent可能还是有局限性
对于 event-driven 编程方式的封装,libevent 其实做得很好了。libevent 主要的特点是使用 callback ,但这个也是由 event-driven 决定的。这种实现方式就相当于设计模式中的“被动迭代器”。
通过上面提到的方法可以把“被动迭代器”转换为“主动迭代器”。
引用
为了把 reactor/proactor 和线程池分开,可以用一个间接的方法。在 callback 中不直接处理事件,把事件保存到一个列表(eventList)里面,这样就可以使得 event_loop 要做的事情很简单。等到 event_loop 运行完之后,就可以使用相应的线程池策略来处理 eventList 了。
如果要改进,应该就是要考虑如何一步到位地把 event-driven 编程方式封装为“主动迭代器”的方式。
45 楼
qiezi
2007-07-01
有没有测试多CPU?结果可能会不同吧。
现在无论是使用LF还是HAHS,都有个锁的问题。底层无论是使用select还是epoll,都无法在不加锁的情况下,在一个原子操作中完成取出事件并取消已经注册的事件(select当然是操作fd_set),这就给LF方式带来了不方便,必须用线程锁了。我感觉要做出最高效的应用,可能还是要自己去整底层API,依赖libevent可能还是有局限性,自己做比较麻烦的是超时,有时间看看libevent是如何实现的。感觉每种方案都有不完善的地方,不知道linux内核中实现的aio怎么样。
现在无论是使用LF还是HAHS,都有个锁的问题。底层无论是使用select还是epoll,都无法在不加锁的情况下,在一个原子操作中完成取出事件并取消已经注册的事件(select当然是操作fd_set),这就给LF方式带来了不方便,必须用线程锁了。我感觉要做出最高效的应用,可能还是要自己去整底层API,依赖libevent可能还是有局限性,自己做比较麻烦的是超时,有时间看看libevent是如何实现的。感觉每种方案都有不完善的地方,不知道linux内核中实现的aio怎么样。
44 楼
iunknown
2007-07-01
引用
为了把 reactor/proactor 和线程池分开,可以用一个间接的方法。在 callback 中不直接处理事件,把事件保存到一个列表(eventList)里面,这样就可以使得 event_loop 要做的事情很简单。等到 event_loop 运行完之后,就可以使用相应的线程池策略来处理 eventList 了。
从目前想象到的运行过程来看,LF 的线程切换应该比 HAHS 少很多,性能应该会更好。
从目前想象到的运行过程来看,LF 的线程切换应该比 HAHS 少很多,性能应该会更好。
今天试着用这个思路来实现 Leader/Follower ,为 SPServer 增加了一个 SP_LFServer 的类,接口和 SP_Server 一样。
最新代码
http://spserver.googlecode.com/files/spserver-0.6.src.tar.gz
然后修改了 testhttp.cpp 的代码,使得可以在命令行指定使用的线程池模型,然后用 ab 做了一些简单的测试。
testhttp.cpp 的功能很简单,只是把收到的 http 请求解释出来,然后把解释到的内容作为 response 返回给客户端。由于 testhttp.cpp 的功能足够简单,所以用来测试线程的切换的消耗还是比较理想的。
从初步的测试结果来看,在 testhttp.cpp 这种应用中, LF 比 HAHS 快。
测试不是很严格,针对 HAHS 和 LF 分别测试使用 1 个线程和 2 个线程的情况,每个 case 运行 5 次,取中间的结果。性能最好的是 LF 使用 1 个线程的时候,因为这里完全没有线程的切换, 。
对比上次 测试memcached 得到的数据,LF 使用 1 线程的时候,处理请求的速度和 memcached 差不多。memcached 是单线程的。
对于 HAHS 模型的测试结果
bash-2.05a$ ./testhttp -s hahs -t 1 bash-2.05a$ ./ab -n 10000 -c 100 -k http://127.0.0.1:8080/ Requests per second: 3464.96 [#/sec] (mean)
bash-2.05a$ ./testhttp -s hahs -t 2 bash-2.05a$ ./ab -n 10000 -c 100 -k http://127.0.0.1:8080/ Requests per second: 2768.06 [#/sec] (mean)
对于 LF 模型的测试结果
bash-2.05a$ ./testhttp -s lf -t 1 bash-2.05a$ ./ab -n 10000 -c 100 -k http://127.0.0.1:8080/ Requests per second: 4576.40 [#/sec] (mean)
bash-2.05a$ ./testhttp -s lf -t 2 bash-2.05a$ ./ab -n 10000 -c 100 -k http://127.0.0.1:8080/ Requests per second: 2951.07 [#/sec] (mean)
43 楼
iunknown
2007-06-29
引用
LF线程切换会比HAHS少?目前你的HAHS实现是怎样的?我是做了一个同步队列,所以这个线池里面的线程是阻塞在同步队列上的,这些线程的关系应该也是LF。
目前是实现了一个 Executor ,这个 Executor 对象是一个主动对象,内部维持一个任务队列和一个线程池( worker )。创建 Executor 对象的时候,自动会创建一个线程(称为 scheduler 线程 ),这个线程复杂分派队列中的任务给线程池中的线程。
Executor 的使用场景类似这样:
void main_thread( .... ) { Executor executor; for( ; ; ) { Task * task = xxxxx; executor.execute( task ); } }
这样带来的好处是 Executor 可以用 lazy 的方式来创建线程池里面的线程,也可以在必要的时候回收线程。
另外就是使用者容易使用,接口容易理解。这种线程池的使用和普通的 connection pool 之类的池很类似。
过量的任务只会阻塞 scheduler ,不会阻塞 main_thread 。
坏处就是多了线程的切换。executor.execute 的时候,从 main_thread 切换到 scheduler 线程,然后 scheduler 线程分派任务,又从 scheduler 切换到 worker 线程。
引用
另一个LF方式的缺点,就是各个处理线程都忙时,不会再接受连接,而HAHS方式是先接下来再说,接下来的连接还可以先接收数据,至于哪种方式更合理,我也不能确定。
LF 的确有这种问题。所以可能要做到由用户来选择,把 HAHS 和 LF 封装起来,可以方便地切换。
42 楼
qiezi
2007-06-29
LF线程切换会比HAHS少?目前你的HAHS实现是怎样的?我是做了一个同步队列,所以这个线池里面的线程是阻塞在同步队列上的,这些线程的关系应该也是LF。这种情况下,HAHS可能同时运行的线程数比LF多一个,在多CPU上感觉差异并不太大。不过我那个实现用了同步队列,线程锁会影响性能。改用LF也会有锁的问题,必须保证在一个线程取到一个socket事件时,从libevent里面移除该socket的所有事件,以防止多个线程跑同一个socket,这时锁是免不了的。虽然libevent会在发生事件时移除该句柄上的事件,但似乎只是移除一个事件,而且这个过程也不是线程安全的,所以我对于实际性能会不会提高还有疑问。另一个LF方式的缺点,就是各个处理线程都忙时,不会再接受连接,而HAHS方式是先接下来再说,接下来的连接还可以先接收数据,至于哪种方式更合理,我也不能确定。
不知道我对于这2种模式有没有理解上的偏差。
我也订了一套POSA2,周末到货,看看再说。
不知道我对于这2种模式有没有理解上的偏差。
我也订了一套POSA2,周末到货,看看再说。
41 楼
iunknown
2007-06-29
qiezi 写道
现在最不自然的地方,就是Protocol来处理是否放入线程池,因为read请求也是它发起的,问题在于线程池在reactor里面,所以现在是通过protocol找到factory再找到reactor,虽然直接用指针就定位到了(factory->reactor->addTask(this)),不过感觉很不舒服。这类问题在twisted和ace里面可能是使用全局变量或singleton来访问,但我想在一个系统里面同时跑起多个reactor(虽然不一定有用),更重要的原因是我极度讨厌全局变量和singleton。
使用 libevent 和线程池,一般很自然地就把线程池和 reactor 混在一起了。
主要的原因是 libevent 的 event_loop 的实现,当底层的 select/poll/epoll 探测到有事件的时候,event_loop 是逐个处理所有的事件,在 event_loop 内部调用所有的 callback 。而一般就是在 callback 里面直接处理事件了。
为了把 reactor/proactor 和线程池分开,可以用一个间接的方法。在 callback 中不直接处理事件,把事件保存到一个列表(eventList)里面,这样就可以使得 event_loop 要做的事情很简单。等到 event_loop 运行完之后,就可以使用相应的线程池策略来处理 eventList 了。
在这里有类似的描述
Modification of the leader-follower pattern for Proactor
代码大概是这样的
class Reactor { public: int handle_events() { if( mEventList->getCount() <= 0 ) { // 把所有的 event 保存到 mEventList 中 event_loop( xxx ); } Event * event = mEventList->remove( 0 ); if( NULL != event ) { // process event } } private: List * mEventList; }; int main( ... ) { Reactor reactor; // 可以使用不同的线程池模型来处理这个循环 // 可以是 Half-Async/Half-Sync ,也可以是 Leader/Follower for( ; ; ) { reactor.handle_events(); } return 0; }
我也正准备按这个思路来尝试修改 spserver ,然后对比一下 HAHS 和 LF 线程池模型的性能差异。
从目前想象到的运行过程来看,LF 的线程切换应该比 HAHS 少很多,性能应该会更好。
关键的地方是
引用
(Please, see Collections of results below how to avoid/minimize the cost of operations on collections).
40 楼
qiezi
2007-06-29
现在好像默认就使用proactor方式了,用户在connectionMade里面写入requestRead。有一个int process()方法,返回值是1时表明用户已经处理,返回0表明用户想放入线程池处理,返回-1是错误,需要断开。所以想使用reactor方式时,必须要重写int process()了。我在想另一种方式,如果没有requestRead,则process直接调用dataReceived来作reactor方式的处理。细节上有些绕,还没想好,现在基本可以工作,从线程池里面回到libevent用的是event_msgqueue,还比较好用。
39 楼
qiezi
2007-06-29
现在最不自然的地方,就是Protocol来处理是否放入线程池,因为read请求也是它发起的,问题在于线程池在reactor里面,所以现在是通过protocol找到factory再找到reactor,虽然直接用指针就定位到了(factory->reactor->addTask(this)),不过感觉很不舒服。这类问题在twisted和ace里面可能是使用全局变量或singleton来访问,但我想在一个系统里面同时跑起多个reactor(虽然不一定有用),更重要的原因是我极度讨厌全局变量和singleton。
38 楼
qiezi
2007-06-28
目前是在reactor上模拟的,加入了线程池,这个线程池是和Reactor类绑定的,原来的Protocol类被修改了来适应Reactor的改变,目前改得很难看,原来的reactor方式已经变得不“纯”了,兼容上也有点小问题,需要再重构一下。
目前我使用的是bufferevent,它和event接口稍有不同,event是要在处理完事件以后再请求,bufferevent则持续添加事件,不需要时就要调用disable,使用它的原因是它提供了2个buffer,偷了一下懒。
open是没有问题的,等我修改一下并且在公司项目中用过并且基本架构确定再说。目前是C++版本的,由于没有委托,所以使用了static方法作为proactor方式的回调函数,有时间会考虑用D语言改进一下。做网络框架也是早有打算,D语言也尝试过,总是有些困难没解决,所以不了了之,这次想借助libevent看看效果。
目前我使用的是bufferevent,它和event接口稍有不同,event是要在处理完事件以后再请求,bufferevent则持续添加事件,不需要时就要调用disable,使用它的原因是它提供了2个buffer,偷了一下懒。
open是没有问题的,等我修改一下并且在公司项目中用过并且基本架构确定再说。目前是C++版本的,由于没有委托,所以使用了static方法作为proactor方式的回调函数,有时间会考虑用D语言改进一下。做网络框架也是早有打算,D语言也尝试过,总是有些困难没解决,所以不了了之,这次想借助libevent看看效果。
37 楼
iunknown
2007-06-28
引用
对于HTTP这种没有长度前缀的,最好还是用reactor方式,当然用上面的proactor方式也是可以的
对于不是使用真正的异步 IO (*nix aio 或者 win iocp) 实现的 proactor ,其实都可以认为是用 reactor 模拟出来的。
用 reactor 模拟 proactor ,在这篇文章中描述得很清楚
Comparing Two High-Performance I/O Design Patterns
在不使用真正的异步 IO 的情况下,reactor 还是 proactor ,差别只在于曝露出来的 handler 接口。
reactor 的接口体现的是 IO 句柄上发生了事件。(在 POSA2 的 page114)
class Event_Handler { pupblic: virtual void handle_input( HANDLE handle ) = 0; virtual void handle_output( HANDLE handle ) = 0; virtual void handle_timeout( const Time_Value & ) = 0; virtual void handle_close( HANDLE handle, Event_Type et ) = 0; };
proactor 的接口体现的是 IO 读写事件已经完成了。(在 POSA2 的 page139)
class Completion_Handler { public: virtual void handle_read( HANDLE handle, const Async_Result &result ) = 0; virtual void handle_write( HANDLE handle, const Async_Result &result ) = 0; };
用 reactor 模拟 proactor ,就是设计一个 Event_Handler 的子类,设计一套 Async_Result ,把 Event_Handler 接口适配为 Completion_Handler 。
在 SPServer 的设计中,SP_EventCallback 可以看成是这个适配子类,SP_MsgDecoder 可以看成是 read 的 Async_Result,SP_Message 可以看成是 write 的 Async_Result 。
引用
对于HTTP这种没有长度前缀的,最好还是用reactor方式,当然用上面的proactor方式也是可以的,因为有上readall参数可以设置为 false。好像和原来的reactor又兼容得不好了,我想是不是可以在收到数据后判断是否有读请求,如果没有则直接调用dataReceived。不过感觉和requestRead这种请求/回调的思想不一致。也不想再维护一个单线程的reactor、一个多线程reactor和一个多线程的 proactor。
对于 HTTP 这种协议,如果直接使用 blocking TCP socket 的话,很容易实现。
但无论是使用 reactor 还是 proactor ,都是为了避免 blocking TCP socket ,因此就不可避免需要实现一个面向流的 http message parser 了。有了这个面向流的 http message parser ,那么无论使用何种接口,都可以容易地实现功能了。
不是很明白这里 qiezi 提到的 “和原来的reactor又兼容得不好了”的意思。
PS:qiezi 是使用 D 语言在实现吧?不知道有没有计划 open 这个实现?
发表评论
-
把开源项目从googlecode转移到github
2015-03-14 23:07 1137前几天看到说 googlecode 准备关闭了,花一个晚上把以 ... -
spcached: memcached 的克隆实现,支持 windows 平台
2009-11-03 22:51 1673memcached 没有官方的 windows 发布版本,只有 ... -
一个轻量的 wire format 解释器(google protobuf 的二进制格式)
2009-10-07 21:38 5099google 的 protobuf 项目,底层的二进制格式设计 ... -
SPHiveDB: 基于 sqlite 的数据库服务器
2009-05-23 23:39 4286在 share nothing 的架构中,如果数据规模很大,为 ... -
spmemvfs: 在内存中加载/保存 sqlite3 数据库
2009-05-01 18:38 3524关于 sqlite3 有很多的介绍文章,这里就不提了。 说一 ... -
SPSmtpGate: SMTP 反垃圾邮件网关
2009-04-16 22:01 1466SPSmtpGate 是一个反垃圾 ... -
SPSmtpServer: 一个基于 SPServer 的 SMTP 服务器框架
2009-03-15 15:08 1562在 SPServer 中增加了一个 smtp 服务器框架。在框 ... -
SPDataPickle: c语言的结构体和 xml/json/protobuf 的自动转化
2009-01-20 00:20 7986SPDataPickle 是一种轻便高效的结构化数据和xml/ ... -
用 state pattern 简化 json 解释器的实现
2008-07-26 11:02 1561之前用 state pattern 实现过 xml 的解释器( ... -
SPNetKit:http/smtp/pop3/memcached 的客户端库
2008-01-13 17:41 3263SPNetKit 主要是一个常见应用层协议的客户端库,使用 C ... -
SPProcPool: Unix/Linux 上的进程池服务器框架
2007-12-09 11:30 4738SPProcPool 是一个 linux/unix ... -
sptalk:基于 spserver/spxml/spdict 实现的 jabber 服务器
2007-05-25 14:50 3252在 3 年前因为工作需要,搞过一段时间 jabberd 1.4 ... -
spcached : memcached 的多线程实现
2007-05-15 14:43 9909实现 spcached 的目的:不是与 memcached 进 ... -
SPWebServer:一个基于 SPServer 的 web 服务器框架
2007-05-10 16:07 14050看到这个题目,估计很多人会问:为什么要再实现一个 web 服务 ... -
spdict:红黑树(RedBlackTree),平衡树(BalancedTree),SkipList 的实现
2007-04-27 12:11 5417对着 MIT 的 《Introduction to Algor ... -
spxml:使用 state pattern 实现 xml pull/dom parser
2007-01-17 22:26 6161spxml 是一个实现了 pull 和 dom 两种解释模型 ... -
Build a thread pool in C
2006-12-11 16:27 15148想找个轻便的 thread pool ...
相关推荐
Oracle用户密码 hash加密 C#实现方式 Oracle版本:Oracle11g以前的版本
THRIFT 是一个开源的跨语言服务开发框架,由 Facebook 开发并贡献给了 Apache 基金会。它允许开发者定义数据类型和服务接口,然后自动生成多种编程语言的代码,简化了分布式系统之间的通信。在 THRIFT 开发教程中,...
在这个场景中,"Scripps paramanipulaçãode hahs"可能是指利用特定参数对哈希值进行处理或操纵的过程,这可能是为了安全或数据验证的目的。 在密码学中,哈希函数被广泛用于密码存储。当用户创建一个新密码时,...
基于Springboot的实验报告系统源码数据库文档.zip
GEE训练教程——Landsat5、8和Sentinel-2、DEM和各2哦想指数下载
基于springboot智能健康饮食系统源码数据库文档.zip
基于SpringBoot的校园服务系统源码数据库文档.zip
内容概要: IXIA测试仪的基本配置.doc ixia测试仪基础使用示例.doc IxNetwork如何进行抓包回放-V1.0.pdf IxNetwork如何自定义报文-V2.0.pdf ixia构造ip分片方法.txt IxNetwork使用简介.pdf 适用人群:网络协议造包、打流相关的测试工程技术人员,想要学习的同学可以下载哈 使用场景:构造pcap包,打流 Ixia简介 IXIA使用的是Server-client模式,Server端在测试仪表的主机上,在开机后会随着主机内的操作系统的启动而自动启动,一般情况下不需要人为的手工启动。因此在通常不需要为主机配置专用的显示器和键盘。 client端包括两个测试软件: Ixia Explorer和ScriptMate。这两个软件一般安装在测试用计算机上,在仪表自带的主机中也有这两个软件。根据测试项目的不同来选择使用不同的软件。Ixia Explorer主要提供数据流的测试,针对设备的功能进行测试; ScriptMate提供各种性能测试窗口,针对设备的性能进行测试。 Auto:自动分配;
基于Python+Django花卉商城系统源码数据库文档.zip
Umi-OCR-main.zip
基于微信小程序开发的促销抽奖小工具源码,适用于初学者了解小程序开发过程以及简单抽奖工具的实现。
GEE训练教程——Landsat5、8和Sentinel-2、DEM和各2哦想指数下载
以下是一个关于Spring Boot设计的资源描述及项目源码的简要概述: Spring Boot设计资源描述 Spring Boot是一个为基于Spring的应用提供快速开发工具的框架,其设计旨在简化Spring应用的初始搭建和开发过程。以下是一些关键资源: Spring Boot官方文档:详细阐述了Spring Boot的核心特性、自动配置原理、起步依赖、内嵌式服务器等关键概念。这是学习和掌握Spring Boot设计的首选资源。 在线教程与视频:各大在线教育平台提供了丰富的Spring Boot教程和视频课程,从基础入门到高级应用,帮助开发者全面了解和掌握Spring Boot设计。 书籍与电子资料:许多技术书籍和在线电子资料深入讲解了Spring Boot的设计原理、最佳实践和项目案例,为开发者提供了宝贵的学习资源。 项目源码示例 以下是一个简单的Spring Boot项目源码示例,用于演示Spring Boot的基本结构和自动配置功能: java // 引入Spring Boot依赖 @SpringBootApplication public class MySpri
基于springboot美妆领域管理系统源码数据库文档.zip
tables-3.7.0+gpl-cp37-cp37m-win_amd64.whl
算法是计算机科学的核心,它们在解决各种问题时发挥着关键作用。一个好的算法不仅可以提高程序的效率,还可以简化复杂的问题。下面我将通过一个具体的例子——快速排序算法(Quick Sort)——来展示算法的实现过程,包括资源描述和项目源码。 ### 快速排序算法简介 快速排序是一种高效的排序算法,采用分治法的思想。其基本步骤如下: 1. 从数列中挑出一个元素,称为“基准”(pivot)。 2. 重新排序数列,所有比基准值小的元素放到基准前面,所有比基准值大的元素放到基准后面(相同的数可以到任一边)。在这个分割结束之后,该基准就处于数列的中间位置。这个称为分割(partition)操作。 3. 递归地(recursive)把小于基准值的子数列和大于基准值的子数列排序。 ### 资源描述 快速排序算法因其高效性和简洁性,在实际应用中非常受欢迎。它的时间复杂度平均为 O(n log n),最坏情况下为 O(n^2),但这种情况很少发生。快速排序的空间复杂度为 O(log n),因为它使用了递归来实现。 快速排序的一个典型应用场景是在数据库系统中对大量数据进行排序。由于它的高效性,快速排序
基于springboot农场投入品运营线上管理系统源码数据库文档.zip
基于springboot个性化影院推荐系统源码数据库文档.zip
linux基础进阶笔记,配套视频:https://www.bilibili.com/list/474327672?sid=4493093&spm_id_from=333.999.0.0&desc=1
小程序 微信自动抢红包动态库.zip程序资源学习资料参考