CppExplore

一切像雾像雨又像风

  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  29 随笔 :: 0 文章 :: 280 评论 :: 0 Trackbacks

作者:CppExplore 网址:http://www.cppblog.com/CppExplore/
本章主要列举服务器程序的各种网络模型,示例程序以及性能对比后面再写。
一、分类依据。服务器的网络模型分类主要依据以下几点
(1)是否阻塞方式处理请求,是否多路复用,使用哪种多路复用函数
(2)是否多线程,多线程间如何组织
(3)是否多进程,多进程的切入点一般都是accept函数前
二、分类。首先根据是否多路复用分为三大类:
(1)阻塞式模型
(2)多路复用模型
(3)实时信号模型
三、详细分类。
1、阻塞式模型根据是否多线程分四类:
(1)单线程处理。实现可以参见http://www.cppblog.com/CppExplore/archive/2008/03/14/44509.html后面的示例代码。
(2)一个请求一个线程。
主线程阻塞在accept处,新连接到来,实时生成线程处理新连接。受限于进程的线程数,以及实时创建线程的开销,过多线程后上下文切换的开销,该模型也就是有学习上价值。
(3)预派生一定数量线程,并且所有线程阻塞在accept处。
该模型与下面的(4)类似与线程的领导者/追随者模型。
传统的看法认为多进程(linux上线程仍然是进程方式)同时阻塞在accept处,当新连接到来时会有“惊群”现象发生,即所有都被激活,之后有一个获取连接描述符返回,其它再次转为睡眠。linux从2.2.9版本开始就不再存在这个问题,只会有一个被激活,其它平台依旧可能有这个问题,甚至是不支持所有进程直接在accept阻塞。
(4)预派生一定数量线程,并且所有线程阻塞在accept前的线程锁处。
一次只有一个线程能阻塞在accept处。避免不支持所有线程直接阻塞在accept,并且避免惊群问题。特别是当前linux2.6的线程库下,模型(3)没有存在的价值了。另有文件锁方式,不具有通用性,并且效率也不高,不再单独列举。
(5)主线程处理accept,预派生多个线程(线程池)处理连接。
类似与线程的半同步/半异步模型。
主线程的accept返回后,将clientfd放入预派生线程的线程消息队列,线程池读取线程消息队列处理clientfd。主线程只处理accept,可以快速返回继续调用accept,可以避免连接爆发情况的拒绝连接问题,另加大线程消息队列的长度,可以有效减少线程消息队列处的系统调用次数。
(6)预派生多线程阻塞在accept处,每个线程又有预派生线程专门处理连接。
3)和(4)/(5)的复合体。
经测试,(5)中的accept线程处理能力非常强,远远大于业务线程,并发10000的连接数也毫无影响,因此该模型没有实际意义。
总结:就前五模型而言,性能最好的是模型(5)。模型(3)/(4)可以一定程度上改善模型(1)的处理性能,处理爆发繁忙的连接,仍然不理想。。阻塞式模型因为读的阻塞性,容易受到攻击,一个死连接(建立连接但是不发送数据的连接)就可以导致业务线程死掉。因此内部服务器的交互可以采用这类模型,对外的服务不适合。优先(5),然后是(4),然后是(1),其它不考虑。
2、多路复用模型根据多路复用点、是否多线程分类:
以下各个模型依据选用select/poll/epoll又都细分为3类。下面个别术语采用select中的,仅为说明。
(1)accept函数在多路复用函数之前,主线程在accept处阻塞,多个从线程在多路复用函数处阻塞。主线程和从线程通过管道通讯,主线程通过管道依次将连接的clientfd写入对应从线程管道,从线程把管道的读端pipefd作为fd_set的第一个描述符,如pipefd可读,则读数据,根据预定义格式分解出clientfd放入fd_set,如果clientfd可读,则read之后处理业务。
此方法可以避免select的fd_set上限限制,具体机器上select可以支持多少个描述符,可以通过打印sizeof(fd_set)查看,我机器上是512字节,则支持512×8=4096个。为了支持多余4096的连接数,此模型下就可以创建多个从线程分别多路复用,主线程accept后平均放入(顺序循环)各个线程的管道中。创建5个从线程以其对应管道,就可以支持2w的连接,足够了。另一方面相对与单线程的select,单一连接可读的时候,还可以减少循环扫描fd_set的次数。单线程下要扫描所有fd_set(如果再最后),该模型下,只需要扫描所在线程的fd_set就可。
(2)accept函数在多路复用函数之前,与(1)的差别在于,主线程不直接与从线程通过管道通讯,而是将获取的fd放入另一缓存线程的线程消息队列,缓存线程读消息队列,然后通过管道与从线程通讯。
目的在主线程中减少系统调用,加快accept的处理,避免连接爆发情况下的拒绝连接。
(3)多路复用函数在accept之前多路复用函数返回,如果可读的是serverfd,则accept,其它则read,后处理业务,这是多路复用通用的模型,也是经典的reactor模型。
4)连接在单独线程中处理。
以上(1)(2)(3)都可以在检测到cliendfd可读的时候,把描述符写入另一线程(也可以是线程池)的线程消息队列,另一线程(或线程池)负责read,后处理业务。

(5)业务线程独立,下面的网络层读取结束后通知业务线程。
以上(1)(2)(3)(4)中都可以将业务线程(可以是线程池)独立,事先告之(1)、(2)、(3)、(4)中read所在线程(上面1、2、4都可以是线程池),需要读取的字符串结束标志或者需要读取的字符串个数,读取结束,则将clientfd/buffer指针放入业务线程的线程消息队列,业务线程读取消息队列处理业务。这也就是经典的proactor模拟。
总结:模型(1)是拓展select处理能力不错选择;模型(2)是模型(1)在爆发连接下的调整版本;模型(3)是经典的reactor,epoll在该模型下性能就已经很好,而select/poll仍然存在爆发连接的拒绝连接情况;模型(4)(5)则是方便业务处理,对模型(3)进行多线程调整的版本。带有复杂业务处理的情况下推荐模型(5)。根据测试显示,使用epoll的时候,模型(1)(2)相对(3)没有明显的性能优势,(1)由于主线程两次的系统调用,反而性能下降。
3、实时信号模型:
使用fcntl的F_SETSIG操作,把描述符可读的信号由不可靠的SIGIO(SYSTEM V)或者SIGPOLL(BSD)换成可靠信号。即可成为替代多路复用的方式。优于select/poll,特别是在大量死连接存在的情况下,但不及epoll。
四、多进程的参与的方式
(1)fork模型。fork后所有进程直接在accept阻塞。以上主线程在accept阻塞的都可以在accept前fork为多进程。同样面临惊群问题。
(2)fork模型。fork后所有进程阻塞在accept前的线程锁处。同线程中一样避免不支持所有进程直接阻塞在accept或者惊群问题,所有进程阻塞在共享内存上实现的线程互斥锁。
(3)业务和网络层分离为不同进程模型。这个模型可能是受unix简单哲学的影响,一个进程完成一件事情,复杂的事情通过多个进程结合管道完成。我见过进程方式的商业协议栈实现。自己暂时还没有写该模型的示例程序测试对比性能。
(4)均衡负载模型。起多个进程绑定到不同的服务端口,前端部署lvs等均衡负载系统,暴露一个网络地址,后端映射到不同的进程,实现可扩展的多进程方案。
总结:个人认为(1)(2)没什么意义。(3)暂不评价。(4)则是均衡负载方案,和以上所有方案不冲突。
以上模型的代码示例以及性能对比后面给出。

posted on 2008-03-21 17:16 cppexplore 阅读(11142) 评论(16)  编辑 收藏 引用

评论

# re: 【原创】系统设计之 网络模型(二) 2008-03-21 19:17 sgsoft
现在的高性能服务器多采用异步IO,在Windows上,使用IOCP,在AIX上,使用AIO 系统对象,在Solaris,HP-UX上,都有不同实现。但大多使用了OS的异步事件队列来防止连接阻塞。

是否缺异步通信服务器模型呢?Win和*inx不一样的实现。  回复  更多评论
  

# re: 【原创】系统设计之 网络模型(二) 2008-03-21 22:05 cppexplore
@sgsoft
win上实现了socket上真正的AIO,*inx上基本上都没有针对socket实现真正的AIO。本文主要针对linux平台,其他平台不很熟悉,并且没机会接触,也无法写代码进行测试,因此AIO的模型就没涉及。
另详细分类2里的模型(5)模拟了AIO的实现,也就是proactor的模拟。  回复  更多评论
  

# re: 【原创】系统设计之 网络模型(二)[未登录] 2008-03-22 12:27
我一直采用的sever处理模型是,父进程创建出监听socket,然后fork出子进程,子进程中在accept出阻塞,处理IO时采用多路复用模型(select之类的),个人感觉效率还是不错的.

BTW:看来兄台也是server程序员,有时间多多交流:)
  回复  更多评论
  

# re: 【原创】系统设计之 网络模型(二) 2008-03-24 12:16 cppexplore
@创
你的模型是不是和细分类2中(1)的模型类似啊,直接使用线程不更好嘛。如果是用epoll的话,直接单线程在epoll处wait就好。

以前去你blog上逛过,呵呵,多多交流!  回复  更多评论
  

# re: 【原创】系统设计之 网络模型(二) 2008-03-24 15:24 ecopgm
在下是新手,想问几个问题:
1. 上面的模型,都是将accept独立处理,这样可以接受爆发连接,然后读写和逻辑处理都是交给下级线程池。那这样的模型跟主线程里accept并读写,然后下级线程池处理逻辑,在性能上区别有多大呢?
2. accept即使接受了所有爆发连接,但是生产者快,消费者慢,加上队列的流控,这样还是没什么用啊,连接还是会被丢掉
3. 消息队列和管道,这两种通信方式,在性能上有什么差异?管道更好吗,比消息队列更少的同步操作?  回复  更多评论
  

# re: 【原创】系统设计之 网络模型(二)[未登录] 2008-03-24 19:32 CppExplore
@ecopgm
1、上面的模型并不是都将accept独立处理啊?各种情况都有的。“下级线程池处理逻辑”这个就多说了,这个和accept/读写是否在同一线程不冲突,不同线程的时候也可以把逻辑独立出来。各种模型性能上的数据对比后面的文章会给出具体的数据。概括说下,不单独处理accept,除epoll方式外,爆发连接都会出现拒绝连接情况,比如ab(apache带的工具)并发1w的时候。当然并非所有的服务器能是要处理这种爆发的断连接。在不拒绝连接的情况下,比如并发1000,性能还是差不多的。
2、连接被拒绝是在三路握手阶段,accept接受的所有连接,连接就不会被丢掉了啊,生产者的确是非常快,消费者很慢,但不影响client把数据发送到这边的接受缓冲区。服务器没有发送reset的必要,client也不会发fin分节,连接自然不会被丢掉。
3、消息队列自然比管道快,这里的消息队列不是ipc的消息队列,全部实现在用户态,可以看下上一篇《线程二》有消息队列的实现,比涉及系统调用的管道快,管道怎么也是属于进程间通讯的方式。但是消息队列不能象管道一样把文件描述符放到fd_set里,供select监控。  回复  更多评论
  

# re: 【原创】系统设计之 网络模型(二) 2008-03-24 22:11 ecopgm
@CppExplore
我先前理解错了,能不能接受爆发连接,是取决于accept的速度。我对并发的概念没想清楚,呵呵,谢谢。  回复  更多评论
  

# re: 【原创】系统设计之 网络模型(二)[未登录] 2008-03-24 22:34 CppExplore
@ecopgm
这么晚还没睡觉啊。
这里说的爆发的连接,也可以说是单点并发。你想的并发是不是同时在线的连接啊?一般都是追求的同时在线连接数。爆发连接的场景毕竟也不多。尤其是对音频视频等多媒体的应用服务器,限于网络带宽,也不可能有爆发的连接出现,有的话直接拒绝连接就可以接受,呵呵。  回复  更多评论
  

# re: 【原创】系统设计之 网络模型(二) 2008-03-24 22:41 ecopgm
对的,我想成请求数了,觉得如果1万个连接到了,每个发个消息过来,逻辑层处理不了这么多,而队列又装不下,那这个请求就丢失了,请求不是连接,呵呵  回复  更多评论
  

# re: 【原创】系统设计之 网络模型(二) 2008-03-25 10:11 游客
好文章,赞一个!!  回复  更多评论
  

# re: 【原创】系统设计之 网络模型(二)[未登录] 2008-04-30 20:05 true
下面这个假想的模型在上面有体现,但不太好归类,描述如下:
1.主线程一直accept
2.有一个线程在监听epoll事件
3.accept后将连接加入2中的epoll监听(在epoll中add 这个fd时是线程安全的吗?)
4.epoll检测到某fd可读时,交给业务逻辑线程t1处理(这时需要在epoll中del这个fd吗),在t1中根据请求的类型给fd发送了一些数据,然后再加入到epoll中?。即2中的epoll只监听可读  回复  更多评论
  

# re: 【原创】系统设计之 网络模型(二)[未登录] 2008-05-01 07:53 CppExplore
@true
:)
这个就是文中2(1)的模型,主线程accept,将新的clientfd通过管道传递给epoll线程,管道的读端也在epoll的监控之下。不使用管道的话,sockpair也可以,不过它也是基于管道实现。
管道的连接是不可回避的,因为epoll线程在epoll_wait处等待,要实时的告诉epoll监控某fd,一定要epoll_wait返回,执行加入操作,再继续epoll_wait。
后面的业务线程处理也就是2(4)和2(5)模型。
2(3)是单线程的模型,就epoll而言,单线程的性能最好,也就是epoll+单线程=高性能。业务线程当然是根据需要后接线程,测试的数据一直没时间发出来,过几天发下。  回复  更多评论
  

# re: 【原创】技术系列之 网络模型(二)[未登录] 2008-06-19 12:12 Jeff
想问一下,不可以用UDP来接收请求数据吗,为什么非要用TCP,UDP不可靠?  回复  更多评论
  

# re: 【原创】技术系列之 网络模型(二)[未登录] 2008-06-19 13:26 CppExplore
@Jeff
udp也可以啊,模型简单,在一个端口上复用就可以了,没什么可写的。  回复  更多评论
  

# re: 【原创】技术系列之 网络模型(二) 2009-04-19 20:49 蛙蛙
你怎么2W连接就满足了呀,下面这个人弄了单机50W连接。
http://blog.sina.com.cn/s/blog_466c66400100cfrj.html  回复  更多评论
  

# re: 【原创】技术系列之 网络模型(二) 2015-03-27 17:13 ckw
文中2(1)的模型 使用select时也无法突破最大fd=1024的限制啊!  回复  更多评论
  


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理