Linux高性能服务器笔记--4
Linux高性能服务器笔记--4
1. 高性能服务器程序框架
1.1服务器模型
1.1.1 C/S模型
服务器启动后,首先创建一个或多个socket,并调用bind函数将其绑定到服务器感兴趣的端口上,然后调用listen函数等待客户端连接。服务器稳定运行后,客户端可以调用connect函数向服务器发起连接。
服务器使用的I/O复用技术是select系统调用。当监听到连接请求后,服务器调用accept函数接受它,并分配一个逻辑单元为新的连接服务。逻辑单元可以是创建新的子进程、子线程或者其他。逻辑单元读取客户请求,读取该请求,并将处理结果返回给客户端。 C/S模型是和资源相对集中的场合,但服务器是通信的中心,当访问量过大时,客户可能都会得到较慢的响应。
1.1.2 P2P模型
1.2服务器编程框架
1.3I/O模型
2.俩种高效的事件处理模式
服务器程序通常需要处理三类事件:IO事件、信号及定时事件。我们将在后续章节依次讨论这三种类型的事件,这一节先从整体上介绍一下两种高效的事件处理模式: Reactor 和Proactor.
2.1 Reactor模式(适合同步I/O模型)
Reactor是这样一种模式,它要求主线程(I/O处理单元)只负责监听文件描述上是否有事件发生,有的话就立即将该事件通知工作线程(逻辑单元)。
除此之外,主线程不做任何其他实质性的工作。
读写数据,接受新的连接,以及处理客户请求均在工作线程中完成。
使用同步IO模型(以epoll_wait为例)实现的 Reactor模式的工作流程是:
-
主线程往epoll 内核事件表中注册socket上的读就绪事件。 2)主线程调用epoll_wait 等待socket上有数据可读。 3)当socket上有数据可读时,epoll_wait通知主线程。主线程则将socket可读事件放入请求队列。 4)睡眠在请求队列上的某个工作线程被唤醒,它从socket读取数据,并处理客户请求,然后往epoll内核事件表中注册该socket上的写就绪事件。(应该有关线程池) 5)主线程调用epoll_wait等待socket可写。 6)当socket可写时,epoll_wait通知主线程。主线程将socket可写事件放入请求队列。
(如何通知可写呢? 可能需要反应堆? 后面再看)
7)睡眠在请求队列上的某个工作线程被唤醒,它往 socket上写入服务器处理客户请求的结果。
2.2 Proactor模式(适合异步I/O模型)
Proactor模式将所有的I/O操作都交给主线程和内核,工作线程仅负责业务逻辑。
1)主线程调用aio_read 函数向内核注册socket 上的读完成事件,并告诉内核用户读缓冲区的位置,以及读操作完成时如何通知应用程序(这里以信号为例,详情请参考sigevent的 man手册)。 2)主线程继续处理其他逻辑。 3)当socket 上的数据被读入用户缓冲区后,内核将向应用程序发送一个信号,以通知应用程序数据已经可用。 4)应用程序预先定义好的信号处理函数选择一个工作线程来处理客户请求。.工作线程处理完客户请求之后,调用aio_write 函数向内核注册socket上的写完成事件,并告诉内核用户写缓冲区的位置,以及写操作完成时如何通知应用程序(仍然以信号为例)。 5)主线程继续处理其他逻辑。 6)当用户缓冲区的数据被写人socket之后,内核将向应用程序发送一个信号,以通知应用程序数据已经发送完毕。 7〉应用程序预先定义好的信号处理函数选择一个工作线程来做善后处理,比如决定是否关闭socket。
2.3比较
使用Proactor框架和Reactor框架都可以极大的简化网络应用的开发,但它们的重点却不同。
Reactor框架中用户定义的操作是在实际操作之前调用的。比如你定义了操作是要向一个SOCKET写数据,那么当该SOCKET可以接收数据的时候,你的操作就会被调用;
而Proactor框架中用户定义的操作是在实际操作之后调用的。比如你定义了一个操作要显示从SOCKET中读入的数据,那么当读操作完成以后,你的操作才会被调用。
Proactor和Reactor都是并发编程中的设计模式。在我看来,他们都是用于派发/分离IO操作事件的。这里所谓的IO事件也就是诸如read/write的IO操作。"派发/分离"就是将单独的IO事件通知到上层模块。两个模式不同的地方在于,Proactor用于异步IO,而Reactor用于同步IO。
主动和被动
以主动写为例: Reactor将handle放到select(),等待可写就绪,然后调用write()写入数据;写完处理后续逻辑; Proactor调用aoi_write后立刻返回,由内核负责写操作,写完后调用相应的回调函数处理后续逻辑;
可以看出,Reactor被动的等待指示事件的到来并做出反应;它有一个等待的过程,做什么都要先放入到监听事件集合中等待handler可用时再进行操作; Proactor直接调用异步读写操作,调用完后立刻返回;
实现
Reactor实现了一个被动的事件分离和分发模型,服务等待请求事件的到来,再通过不受间断的同步处理事件,从而做出反应;
Proactor实现了一个主动的事件分离和分发模型;这种设计允许多个任务并发的执行,从而提高吞吐量;并可执行耗时长的任务(各个任务间互不影响)
优点
Reactor实现相对简单,对于耗时短的处理场景处理高效; 操作系统可以在多个事件源上等待,并且避免了多线程编程相关的性能开销和编程复杂性; 事件的串行化对应用是透明的,可以顺序的同步执行而不需要加锁; 事务分离:将与应用无关的多路分解和分配机制和与应用相关的回调函数分离开来,
Proactor性能更高,能够处理耗时长的并发场景;
缺点
Reactor处理耗时长的操作会造成事件分发的阻塞,影响到后续事件的处理;
Proactor实现逻辑复杂;依赖操作系统对异步的支持,目前实现了纯异步操作的操作系统少,实现优秀的如windows IOCP,但由于其windows系统用于服务器的局限性,目前应用范围较小;而Unix/Linux系统对纯异步的支持有限,应用事件驱动的主流还是通过select/epoll来实现;
适用场景
Reactor:同时接收多个服务请求,并且依次同步的处理它们的事件驱动程序; Proactor:异步接收和同时处理多个服务请求的事件驱动程序;
2.3 模拟Proactor
主线程执行数据读写操作,读写完成后,主线程向工作线程通知这一完成事件,从工作线程的角度,它们就直接获得了数据读写的结果,只需要对读写结果进行逻辑处理。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
