当前位置:   article > 正文

服务器程序框架_服务器框架

服务器框架

本篇博客是根据《Linux高性能服务器编程》的第八章高性能服务器程序框架来写的,大部分内容都是来自书上,其中结合自己学习的心得和体会。
服务器主要可以分为I/O部分、逻辑处理、存储单元三部分。第一部分主要是I/O处理单元的四种I/O模式和两种高效的事件处理模式。分别是异步I/O模式、复用I/O模式、SIGIO模式、阻塞I/O模式四种I/O模式和Reactor、Proactor两种事件处理模式,以及使用同步I/O来模拟Proactor,提示:服务器程序通常要处理的三类事件:I/O事件、信号、定时事件,事件的处理模式对三类事件均适用。
逻辑单元将介绍两种高效的并发模式

一、服务器模型

1、C/S模式
 **C/S模式是客户端、服务器模式,所有的客户端可以通过服务器获得资源**。
  • 1

服务器处理客户端请求的大致过程如下:服务器启动之后创建一个或者多个socket,调用bind函数将socket和服务器端口绑定,调用listen函数监听客户端的请求。服务器运行稳定之后,客户端可以调用connect函数连接服务器。
由于客户端的请求是随时到达的异步事件,服务器要使用特殊的I/O模型监听这个事件,I/O模型有多种(比如I/O复用技术有select系统调用),以此来处理随时到达、异步的事件。在监听到事件的到达之后,调用accept函数进行接收连接,并分配逻辑单元,逻辑单元可以创建进程处理客户请求,最后将处理结果返回给客户端。在这次数据传输之后,还可以继续传输数据,也可以关闭连接。但是此时服务器可以同时监听多个客户端请求,这就会出现一个问题,服务器的负载太大导致资源不足,客户端的访问速度会下降,用户体验将会下降。
因此,有了P2P服务器模型。

2、P2P模型

P2P模型是一种对等模型,每台主机既是客户端又是服务器。每台机器使用别的机器服务的同时,还会给别的机器提供服务,这使得让网络中所有主机保持对等的关系。但是这个模型存在一个问题,主机之间很难互相发现,因此此模型一般还会有一个发现服务器,提供查找服务,使得每个客户尽快的找到自己需要的资源。

3、C/S模式和P2P模型的关系

P2P模式其实是C/S模式的一种扩展。

二、服务器编程框架

服务器框架基本分为I/O处理单元、逻辑单元、网络存储单元以及在I/O处理单元和逻辑单元、存储单元和逻辑单元之间的请求队列。下面介绍了单个服务器程序、服务器集群程序各个模块要实现的功能。
I/O处理单元:处理客户连接,读写数据(单个服务器程序),作为接入服务器,要实现负载均衡,将数据处理的任务合理的分配给逻辑单元(服务器集群程序)。
逻辑单元:业务的进程或者线程(单个服务器程序),逻辑服务器(服务器集群程序)。
存储单元:本地的数据库、文件、缓存(单个服务器程序),数据库服务器(服务器集群程序)。
请求队列:描述单元之间的通信方式、策略(单个服务器程序),各服务器之间的TCP连接,请求队列是各个服务器之间建立静态的、永久的TCP连接(服务器集群)。

三、I/O模型

I/O模型可以分为阻塞模型、I/O复用模型、SIGIO模型、异步I/O模型。

1、阻塞I/O和非阻塞I/O。

首先声明阻塞和非阻塞可以引用于所有的文件描述符,不只是socket。在socketAPI中,可以被阻塞你的系统调用包括accept、send、recv、connect。
阻塞的文件描述符为阻塞I/O,阻塞I/O可能是因为无法立即完成而被os挂起,直到等待的事件发生。
非阻塞I/O是立即返回,不管事件有没有发生。如果没有立即发生,那么就返回-1,那么就要根据errno来区分两种情况。对accept、recv、send来说,errno通常被设置为EAGAIN(再来一次),EWOULDBLOCK(期望阻塞)。对connect来说errno常被设置为EINPROGRESS(在处理中)。
非阻塞I/O是立即返回的,因此只有事件发生了,使用非阻塞I/O才是有用的。因此非阻塞I/O通常与I/O通知机制一起使用,通知到达时,就可以在处理函数中对目标文件描述符进行非阻塞I/O操作。
I/O的通知机制包括I/O复用SIGIO信号等等。I/O复用是指应用程序通过I/O复用函数向内核注册一组事件,内核通过I/O复用函数将其中就绪的事件通知给应用程序。
请添加图片描述

I/O复用函数本身是阻塞的,提高程序效率的原因在于能够同时监听多个I/O事件

2、SIGIO模型

SIGIO信号也是I/O的通知机制,可以用来报告I/O事件。例如,当我们为一个目标文件描述符指定宿主进程时,那么指定的宿主进程将捕获到SIGIO信号。当文件描述符有事件发生,SIGIO信号将被触发,就可以在处理函数对目标文件描述符执行非阻塞I/O操作。

3、总结

阻塞I/O、I/O复用、SIGIO都是同步的I/O模型,因为I/O的都写操作都是在I/O事件发生之后,由应用程序来完成。
对于异步I/O,用户可以直接对I/O执行读写操作,这些操作告诉内核用户读写缓冲区的位置,以及I/O操作完成之后内核通知应用程序的方式。异步I/O的读写总是操作总是立即返回,而不论I/O是否阻塞,因为真正的读写已经由内核接管。总的来说就是异步机制是由内核来执行I/O操作,而同步则是由用户的代码来执行I/O操作。
说白了,异步I/O就是向应用程序通知完成事件,而同步则向应用程序通知就绪事件。

四、两种高效的事件处理模式Reactor和Proactor

同步I/O模型常用于实现Reactor模式,异步I/O模型常用于Proactor。当然,也可以使用同步I/O模式模拟出Proactor模式。

1、Reactor

Reactor模式要求主线程(I/O处理单元)只负责监听文件描述符上是否有事件发生,有的话立即通知工作线程(逻辑单元来处理),比如 读写数据、处理客户请求等等。
使用同步I/O模型实现Reactor模式的步骤是:
1)主线程epoll内核事件表中注册socket上的读就绪事件,并调用epoll——wait等待socket上有数据可读。
2)当socket上有数据可读,epoll——wait通知主线程,主线程将socket可读事件放入请求队列。
3)睡眠在请求队列上某个工作线程被唤醒,工作线程从socket中读取数据,并处理客户的请求,之后往epoll内核事件表中注册该socket上写就绪事件,主线程调用epoll——wait等待socket可写。
4)当socket可写时,epoll——wait通知主线程,主线程将可写事件放入请求队列中,睡眠在请求队列上的某个工作线程被唤醒,它往socket上写入服务器处理客户请求的结果。

具体的流程图如下:

请添加图片描述
总结:

  • socket:I/O处理的地方,数据读取、处理结果返回的地方。

  • 主线程:调用epoll_wait,调用epoll内核事件表,

  • epoll_wait:等待socket上有数据可读、可写,事件到达时通知主进程。

  • 工作线程:读取socket上的数据,处理客户的请求、调用epoll内核事件表

  • epoll内核事件表:存放事件,例如读就绪事件、写就绪事件。

  • 请求队列:存放可写事件、可读事件。

2、Proactor模式

Proactor模式将所有的I/O操作都交给了主线程和内核来处理,工作线程仅仅负责业务逻辑。下面是异步I/O模型实例Proactor模式的工作流程:
1)主线程调用aio_read注册读完成事件,并告诉内核用户读缓冲区位置,读完成之后如何通知应用程序。
2)主线程继续处理其他逻辑。
3)将socket上的数据读取到用户缓冲区,内核向应用程序发送一盒信号以通知应用程序数据可以使用了。
4)应用程序预先定义好的信号处理函数选择一个工作线程处理客户请求,处理完请求之后凋也aio_write向内核注册写完成事件,并告诉内核用户读缓冲区位置,读完成之后如何通知应用程序。
5)主线程继续处理其他逻辑。
6)当用户缓冲区的数据写入到socket之后,内核向应用程序发送一个信号以通知数据发送完毕。
7)应用程序预先定义好的处理函数选择一个工作线程来做善后处理。
注意:在此模式下,是通过aio_read和aio_write来向内核注册的,通过信号来向应用程序报告连接socket上的读写事件。主线程上的epoll_wait只能监听socket的连接请求事件,不能检测socket上的读写事件。

3、模拟Proactor

使用同步I/O模拟Profrctor。工作流程如下:按照同步I/O注册事件、epoll——wait等待和通知,不过此时使用主线程对数据进行读取再将数据封装成对象插入带请求队列中,而不是通过工作线程,此时工作线程只需要处理客户请求。然后想事件表注册写就绪事件。主进程调用epoll——wait等待socket可写。socket可写,epoll——wait通知主进程,主进程往socket写入处理结果。

4、心得

我觉得在这里同步I/O和异步I/O的主要区别是I/O操作是谁来做,异步就是主线程和内核来做,而同步只要求主线程监听文件描述符是否有事件发生,主线程不做任何其他实质性工作,大部分工作是由工作线程,也就是逻辑单元来做。说白了就是内核向应用程序通知什么I/O事件,是就绪事件(同步I/O),是完成事件(异步I/O)。以及谁来完成I/O读写,应用程序(同步I/O)还是内核(异步I/O)。
使用同步I/O模拟Proactor就体现的很明显,整体的步骤是按照同步I/O来进行的,只是在读取socket数据上是主进程进行读取封装成对象插入到工作队列中。

五、两种高效的并发模式

并发模式是指I/O处理单元多个逻辑单元之间协调完成任务的方法。其目的是让程序”同时“执行多个任务。并发编程主要有多进程和多线程两种方式。服务器主要有半同步/半异步、领导者/追随者两种并发编程模式。

1、半同步/半异步模式

半同步/半异步模式中同步和异步的概念和同步I/O异步I/O不同,此处的同步是指程序完全按照代码序列顺序执行,异步是指程序的执行需要由系统事件来驱动,这里的系统事件是指终端、信号等等。

同步线程:按照同步方式运行的线程。特点:执行效率高、实时性强,编写程序复杂,难于调试和扩展,不适合大量的并发。
异步线程:按照异步方式运行的线程。特点:执行效率低,实时性差,但是逻辑简单。
服务器要求:实时强,并发性。因此使用两种模式的结合,即半同步/半异步模式将会达到最佳的结果。
半同步/半异步模式的工作分配同步线程用于处理客户逻辑异步线程用于处理I/O事件。异步线程监听到客户请求之后将其封装成请求对象插入到请求队列中,请求队列将通知工作在同步模式下的某个线程来读取并处理请求对象,具体选择哪一个工作线程将取决于请求队列的设计,比如简单的轮流选取工作线程的Round Robin算法。

1、1半同步/半反映堆模式

异步线程只有一个,由主线程来充当,负责监听所有的socket上的事件。监听的socket上有可读事件发生,连接socket,往事件注册表上注册,连接上的socket有读写事件,主线程将socket事件插入到请求队列中,所有工作线程都睡眠在请求队列上,当任务来了,他们通过竞争来获得任务接管权。竞争之后的工作线程从socket上读取数据和写入数据,因此称为half-reactive。

缺点:

  • 主线程和工作线程共享请求队列,主线程向共享进程添加任务和工作线程向请求队列获取任务需要加锁保护,这样浪费了CPU时间。
  • 每个工作线程在同一时间只能处理一个客户请求。如果通过增加工作线程来解决的话,那么工作线程之间的切换也会浪费CPU时间。
2、2高效的半同步/半异步模式

主线程指监听socket,连接socket由工作线程来处理。当有新的连接来临,主线程接收并返回给工作线程,然后此连接的任何I/O工作都是由这个工作线程来处理,直到客户关闭连接。主线程向工作线程返回socket最简单的方式是向和工作线程的管道里写数据,工作线程检测到由数据写入就将新的读写事件注册到自己的epoll内核事件表中。可见,这里的每个工作线程独自监听不同的时间,工作在异步模式

2、领导者/追随者模式

半同步/半异步模式是多个工作线程轮流获得事件源集合,轮流监听、分发并处理事件的一种模式,在任意实时间点都有一个领导者,负责监听I/O事件,而其他的线程都是追随者,他们睡眠在线程池中,等待成为领导者。当领导者检测到由I/O事件,先从线程池中选择一个新的线程作为领导者,之后再处理I/O事件。此时,新的领导者在等待I/O事件,而之前的领导者在处理I/O事件,这就实现了并发。

领导者/追随者模式主要由句柄集、线程集、事件处理器和具体的事件处理器组成
句柄集是由句柄组成,句柄集是由wait——for——event来监听每个句柄上的I/O事件,并将其中就绪事件通知给领导者,领导者调用绑定在句柄集上的事件处理器来处理,事件处理器包括一个或者多个回调函数(handle——event ),这些回调函数用于处理事件对于的业务逻辑。
线程集:线程集是所有工作线程的管理者,负责线程之间的同步,以及领导者线程的推选。线程的状态属于Leader、Follower、Processing三种之一。Leader是领导者,负责监听句柄集上的I/O事件。Follwer是伴随者,可能通过线程集的join方法等待成为新的领导者,也可能被当前领导者来指定处理新的任务。Processing表示线程正处理事件,当领导者检测到由I/O事件,可以转移到Processing状态处理事件,并调用promote——new——leader方法推选出新的领导者;也可以调用追随者来处理事件,自己继续当领导者。当Processing处理之后,没有领导者,那么它成为领导者,否则成为追随者。

六、有限状态机

有限状态机是逻辑单元内部的一种高效编程方法。有的应用层协议头部包含数据包类型字段,每种类型可以映射为逻辑单元的一种执行状态。服务器可以根据这个执行状态编写相应的处理逻辑。主状态机负责监听当前状态实现状态的转移,从状态机负责处理逻辑。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/235116
推荐阅读
相关标签
  

闽ICP备14008679号