linux 高性能服务器编程---高性能程序框架


http://illxx.com/?p=232

http://blog.csdn.net/AAA20090987/article/month/2013/01

 

8.1 服务器模型:

     8.1.1 C/S模型:

      C/S模型的逻辑很简单。服务器启动后,首先创建一个(或多个)监听socket,并调用bind函数将其绑定到服务器感兴趣的端口上,然后调用listen函数等待客户连接。服务器运行稳定后,客户端就可以调用connect函数向服务器发起连接了。由于客户连接请求是随机到达的异步事件,服务器需要某种I/O模型来年监听这一事件。客户端接受到服务器返回的结果之后,可以继续向服务器发送请求,也可以立即主动关闭连接。如果客户端主动关闭连接,则服务器执行被动关闭连接。

      image 

     C/S模型非常适用于资源相对集中的场合,并且他的实现也很简单,其缺点也很明显:服务器是通信的中心,当访问量过大时,可能所有客户都将得到很慢的响应。

     8.1.2 P2P模型:

     P2P模型比C/S模型更符合网络通信的实际情况。它摒弃了以服务器为中心的格局,让网络所有的主机回归对等的地位。P2P模型的确定明显,当用户之间的传输的请求过多时,网络的负载将加重。一般来说,实际中所用的P2P模型通常带有一个专门的发现服务器,这个发现服务器还提供查找服务(甚至还提供内容服务),使每个客户都能尽快的找到自己需要的资源

    image

     从编程角度来讲,P2P模型可以看做C/S模型的扩展,每台主机既是客户端,又是服务器。



8.2 服务器编程框架

      虽然服务器程序种类繁多,但其基本框架都一样,不同之处在逻辑处理。

      image

       I/O处理单元负责处理客户连接,读写网络数据,逻辑单元是业务进程或线程,分析处理客户数据,然后将结果传递给I/O处理单元,网络存储单元可是是本地数据库,缓存或文件,请求队列是各个单元之间的通信方式。

      image



8.3 两种高效的事件处理模式

      服务器程序通常处理三类事件:I/O事件,信号及定时事件。同步I/O模型通常用于实现Reactor模式,异步I/O模型则用于实现Proactor模式,也可以用同步I/O方式模拟出Proactor模式。

     8.3.1 Reactor模式:(read, write)

      Reactor是这样一种模式,它要求主线程(I/O处理单元,下同)只负责监听文件描述符上是否有事件发生,有的话立即将该事件通知工作线程(逻辑单元),除此之外,主线程不做任何实质性的工作。读写数据,接收新的连接,以及处理客户请求均在工作线程中完成。

      使用同步I/O模型(以epoll_wait为例)实现的Reactor模式的工作流程是:

1)主线程往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上写入服务器处理客户请求的结果;

    image

      8.3.2 Proactor模式(aio_read, aio_write)

      与Reactor模型不同,Proactor模式将所有的I/O操作都交给主线程和内核来处理。工作现场仅仅负责业务逻辑,因此,Proactor模式更符合8-4中描述的服务器编程框架;

      使用异步I/O模型(以aio_read和aio_write为例)实现的Proactor模式的工作流程是:

1) 主线程调用aio_read函数向内核注册socket上的读完成事件,并告诉内核用户读缓冲区的位置,以及读操作完成时如何通知应用程序;

2)主线程继续处理其他逻辑;

3)当socket上的数据被读入用户缓冲区后,内核向应用数据发送一个信号,以通知应用数据已经可用;

4)应用程序预先定义好的信号处理函数选择一个工作线程来处理客户请求(只负责业务逻辑处理,不负责实际的IO读写,实际的IO读写由主线程进行)。工作线程处理客户请求之后,调用aio_write函数向内核注册socket上写完成事件,并告诉内核用户写缓冲区的位置,以及写操作完成时如何通知应用程序;

5)主线程继续处理其他逻辑;

6)当用户缓冲区的数据被写入socket之后,内核将向应用数据发送一个信号,以通知应用程序已经发送完毕;

7)应用程序预先定义好的信号处理函数选择一个工作线程来做善后处理,比如决定是否关闭socket;

     image

     8.3.3 模拟Proactor模式

     可以使用同步I/O方式模拟出Proactor模式,其原理是:主线程执行数据读写操作,读写完成之后,主线程向工作线程通知这一“完成事件”。那么从工作线程的角度来看,它们就直接获得了数据读写的结果,接下来要做的是对读写的结果进行逻辑处理。

     使用同步I/O模型(仍然以epoll_wait为例)模拟出的Proactor模式的工作流程如下:

1)主线程往epoll内核事件表中注册socket上读就绪事件;

2)主线程调用epoll_wait等待socket上有数据可读;

3)当socket上有数据可读时,epoll_wait通知主线程。主线程从socket循环读取数据,直到没有更多数据可读,然后将读取到的数据封装成一个请求对象并插入请求队列;

4)睡眠在请求队列上的某个工作线程被唤醒,它获取请求对象并处理客户请求,然后往epoll内核事件表中注册socket上的写就绪事件;

5)主线程调用epoll_wait等待socket可写;

6)当socket可写时,epoll_wait通知主线程。主线程往socket上写入服务器处理客户请求的结果;

     image



8.4 两种高效的并发模式

     并发编程的目的是让“程序”执行多个任务。如果程序是“计算密集型”的,并发编程并没有优势,但如果程序时I/O密集型的,则并发编程则可以挺高CPU的利用率;

     从实现来看,并发编程主要有多进程和多线程两种方式。这一章主要讨论并发模式,对应于图8-4,并发模式是指I/O处理单元和多个逻辑单元之间协调完成任务的方法。服务器主要有两种并发编程模式:半同步/半异步(half-sync/half-async)模式和领导者/追随者(Leader/Followers)。

    8.4.1 半同步/半异步(half-sync/half-async)模式:

      这里所谓的“同步”和“异步”和I/O模型中的“同步”和“异步”是不同的概念。在I/O模型中,“同步”和“异步”区分的是内核向应用程序通知的何种I/O事件(是就绪事件还是完成事件),以及谁来完成I/O事件。在并发模式中,“同步”指的是程序完全按照代码的序列执行:“异步”指的是程序的执行需要由系统事件来驱动。常见的系统事件包括中断,信号等。

     image

      异步线程的执行率高,实时性强,但编写以异步方式执行的程序相对复杂,难以调试和扩展,而且不适于大量的并发。而同步线程,执行效率相对较低,但实时性较差。

      半同步/半异步模式中,同步线程用于处理客户逻辑,相当于图8-4的逻辑单元;异步线程用于处理I/O事件,相当于8-4中的I/O处理单元。异步线程监听到客户请求后,将其封装成请求对象并插入请求队列中。请求队列将通知某个工作在同步模式下的工作线程来读取并处理请求对象。具体选择哪个线程来作为新的客户请求服务。

      image

     在服务器程序中,如果结合考虑两种事件处理模式和几种I/O模型,则半同步/半异步模式就存在多种变体。其中有一种变体成为半同步/半反应堆模式,如图8-10所示;

     image

      图8-10中,异步线程只有一个,由主线程来充当。它负责听所有socket上的事件。如果监听socket上有可读事件发生,既有新的连接请求到来,主线程就接受之,以得到新的连接socket,然后往epoll内核事件表中注册该socket上的读写事件。如果连接socket上有读写事件,即新的客户请求到来或有数据要发送至客户端,主线程就将该连接socket插入请求队列中。所有工作线程都睡眠在请求队列中,当有任务来时,它们将通过竞争(比如申请互斥锁)获取任务的接管权。这种竞争机制使得只有空闲的工作线程才有机会来处理新任务这是合理的。

      主线程插入请求队列中的任务是就绪的连接socket。这说明图8-10所示的半同步/半异步反应堆模式采用的事件处理模式是Reactor模式:它要求工作线程自己从socket上读取客户请求和往socket写入服务器应答

      图8-11描述了一种相对高效的半同步/半异步模式,它的每个工作线程都能同时处理多个客户连接

      image

      主线程只管理监听socket,连接socket由工作线程来管理。当有新的连接到来时,主线程就接受之并将新返回的连接socket派发给某个工作线程,此后该新socket上的任何I/O操作都由被选中的的工作线程处理,直到客户关闭连接。主线程向工作线程派发socket的最简单的方式,是往它和工作线程之间的管道里写数据。工作线程检测到管道上有数据可读时,就分析是否是一个新的客户连接请求到来。如果是,则将该新socket上的读写事件注册到自己的epoll内核时间表中。

      8.4.2 领导者/追随者模式

      领导者/追随者模式是多个工作线程轮流获得事件源集合,轮流监听,分发并处理事件的一种模式。在任何时间点,程序都仅有一个领导者线程,它负责监听I/O事件,而其他线程则都是追随者,它们休眠在线程池中等待成为新的领导者。当前的领导者如果检测到I/O事件,首先要从线程池推选出新的领导者线程,然后处理I/O事件。此时,新的领导者等待新的I/O事件,而原来的领导者则处理I/O事件,两者实现了并发。

     领导者/追随者模式包含如下几个组件:句柄集(HandleSet),线程集(ThreadSet),事件处理器(EventHandle )和具体的时间处理器(ConcreteEventHandler),它们的关系如下:

     image

       句柄集:句柄用于表示I/O资源,在Linux下通常就是一个文件描述符。句柄集管理众多句柄,它使用wait_for_event方法来监听这些句柄上的I/O事件。领导者将Handle和事件处理器绑定是通过调用句柄集中的register_handle方法实现的。

       线程集:这个组件是所有工作线程的管理者。它负责各线程之间的同步,以及新领导者线程的推选

       事件处理器和具体的事件处理器:根据上面的讨论,我们将领导者/追随者模式的工作流程总结于8-14

       image


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM