常见的Linux并发服务器模型有:多进程并发服务器、多线程并发服务器、select多路I/O转接模型、poll多路I/O转接模型、epoll多路I/O转接模型。
1、多进程并发服务器、多线程并发服务器
多进程并发服务器考虑因素:
- 父进程最大文件描述符个数(父进程中需要close关闭accept返回的新文件描述符)
- 系统内存创建进程个数(与内存大小相关)
- 进程创建过多是否降低整体服务性能(进程调度)
多线程并发服务器考虑因素:
- 调用进程内最大文件描述符上限
- 如果线程中需要共享数据,则需要考虑线程同步
- 服务于客户端线程退出时,推出处理(退出值、分离态)
- 系统负载,随着客户端连接数的增加,会导致其他线程不能及时得到CPU
多进程和多线程模型在实现过程相对简单,但其系统开销和CPU使用率过高
2、select多路I/O转接模型、poll多路I/O转接模型
多路I/O复用:指内核一旦发现指定的一个或者多个IO条件准备读取,它进通知该进程。适用于以下场合:
- 当客户处理多个描述符时(多见于交互式输入和网络套接字),必须适用I/O复用
- 当一个客户同时处理多个套接字时(少见)
- 如果一个TCP服务器既要处理监听套接字,又要处理已连接套接字
- 一个服务器程序既要处理TCP,又要处理UDP
- 一个服务器要处理多个服务或多个协议
于多进程和多线程相比,多路I/O复用最大优势在于系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而减小系统开销
select多路I/O转接模型:
- select能监听的文件描述符个数受限于FD_SETSIZE,一般为1024。单纯改变进程打开的文件描述符个数并不能改变select监听文件个数
- 适用于解决少于1024个客户端链接的情况
select缺点:
- 单个进程能够监视的文件描述符的最大数量有限值,通常为1024。select采用的是轮询的方式扫描文件描述符,文件描述符越多,性能越低
- 内核/用户空间内存拷贝问题,select需要复制大量的句柄结构数据,会产生巨大的开销
- select返回的是含有整个句柄的数组,需要遍历整个数组才能找到处于活跃状态的链接
- select的触发方式为水平触发,如果没有完成对一个已经就绪的文件描述符进行IO,那么之后再次select调用还是会将这些文件描述符通知进程
poll多路I/O转接模型:
相比于select模型,poll模型适用链表保存文件描述符,因此没有1024的限制,但select模型的其他的三个缺点依然存在
select和poll模型I/O事件工作方式:电平触发(Level Triggered)
3、epoll多路I/O转接模型
epoll是Linux下select/poll模型的增强版,能显著提高程序在大量并发链接中只有少数活跃的情况下的系统CPU使用率,原因有二:(1)epoll会复用文件描述集合来传递结果而不用迫使开发者每次等待事件之前都必须重新准备要被监听的文件描述符集合;(2)epoll在获取时间的时候,无需遍历整个被监听的描述符集合,只需要遍历那些被内核I/O事件异步唤醒而加入Ready队列的描述符集合即可。
epoll模型除了电平触发,还支持边沿触发(Edge Trigered)。
4、工作方式:
LT(level triggered):水平触发,缺省方式,同时支持block和no-block socket,在这种做法中,内核告诉我们一个文件描述符是否被就绪了,如果就绪了,你就可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错的可能性较小。传统的select\poll都是这种模型的代表。
ET(edge-triggered):边沿触发,高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪状态时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如:你在发送、接受或者接受请求,或者发送接受的数据少于一定量时导致了一个EWOULDBLOCK错误)。但是请注意,如果一直不对这个fs做IO操作(从而导致它再次变成未就绪状态),内核不会发送更多的通知。
区别:LT事件不会丢弃,而是只要读buffer里面有数据可以让用户读取,则不断的通知你。而ET则只在事件发生之时通知。