CC BY 4.0 (除特别声明或转载文章外)
如果这篇博客帮助到你,可以请我喝一杯咖啡~
服务器模型
C/S模型
所有客户端都通过访问服务器来获取所需的资源。
P2P模型
P2P(Peer to Peer,点对点)模型比C/S模型更符合网络通信的实际情况。它摒弃了以服务器为中心的格局,让网络上所有主机重新回归对等的地位。
使得每台机器在消耗服务的同时也给别人提供服务,这样 资源能够充分、自由地共享,当用户之间传输的请求过多时,网络 的负载将加重。
主机之间很难互相 发现。所以实际使用的P2P模型通常带有一个专门的发现服务器。
从编程角度来讲,P2P模型可以看作C/S模型的扩展:每台主机既是客户端,又是服务器。
服务器编程框架
I/O模型
阻塞IO,非阻塞IO,IO复用,信号驱动IO,异步IO
两种高效的事件处理模型
Reactor模式
要求主线程(I/O处理单元,下同)只负责监听文件描述上是否有事件发生,有的话就立即将该事件通知工作线程(逻辑单元,下同)。除此之外,主线程不做任何其他实质性的工作。读写数据,接受新的连接,以及处理客户请求均在工作线程中完成。
Proactor模式
Proactor模式将所有I/O操作都交给主线程和内核来处理,工作线程仅仅负责业务逻辑。
使用异步I/O模型(以aio_read和aio_write为例)实现的Proactor模 式的工作流程是:
模拟Proactor模式
使用同步I/O方式模拟出Proactor模式的一种方 法。其原理是:主线程执行数据读写操作,读写完成之后,主线程向 工作线程通知这一“完成事件”。那么从工作线程的角度来看,它们就 直接获得了数据读写的结果,接下来要做的只是对读写的结果进行逻辑处理。
两种高效的并发模式
并发模式是指I/O处理单元和多个逻辑单元之间协调完成任务的方法。
服务器主要有两种并发编程模式:半同步/半异步(half-sync/halfasync)模式和领导者/追随者(Leader/Followers)模式。
半同步/半异步
半同步/半异步模式中的“同步”和“异步”与前面讨论的I/O模 型中的“同步”和“异步”是完全不同的概念。
在I/O模型中,“同步”和“异 步”区分的是内核向应用程序通知的是何种I/O事件(是就绪事件还是完 成事件),以及该由谁来完成I/O读写(是应用程序还是内核)。
在并发模式中,“同步”指的是程序完全按照代码序列的顺序执行;“异步”指 的是程序的执行需要由系统事件来驱动。
半同步/半异步模式中,同步线程用于处理客户逻辑;异步线程用于处理I/O事件。
异步线程监听到客户请求后,就将其封装成请求对象并插入请求队列中。请求队列将通知某个工作在同步模式的工作线程来读取并处理该请求对象。具体选择哪个工作线程来为新的客户请求服务,则 取决于请求队列的设计。
半同步/半反应堆(half-sync/half-reactive)模式:
缺点:
- 主线程和工作线程共享请求队列。主线程往请求队列中添加任 务,或者工作线程从请求队列中取出任务,都需要对请求队列加锁保 护,从而白白耗费CPU时间。
- 每个工作线程在同一时间只能处理一个客户请求。如果客户数 量较多,而工作线程较少,则请求队列中将堆积很多任务对象,客户 端的响应速度将越来越慢。如果通过增加工作线程来解决这一问题, 则工作线程的切换也将耗费大量CPU时间。
相对高效的半同步/半异步模式:
主线程只管理监听socket,连接socket由工作线程来管理。当有新的连接到来时,主线程就接受之并将新返回的连接socket派 发给某个工作线程,此后该新socket上的任何I/O操作都由被选中的工 作线程来处理,直到客户关闭连接。主线程向工作线程派发socket的简单的方式,是往它和工作线程之间的管道里写数据。工作线程检测 到管道上有数据可读时,就分析是否是一个新的客户连接请求到来。 如果是,则把该新socket上的读写事件注册到自己的epoll内核事件表中。
每个线程(主线程和工作线程)都维持自己的事件循环,它们各自独立地监听不同的事件。因此,在这种高效的半同 步/半异步模式中,每个线程都工作在异步模式,所以它并非严格意义上的半同步/半异步模式。
领导者/追随者模式
领导者/追随者模式是多个工作线程轮流获得事件源集合,轮流监听、分发并处理事件的一种模式。在任意时间点,程序都仅有一个领 导者线程,它负责监听I/O事件。而其他线程则都是追随者,它们休眠 在线程池中等待成为新的领导者。
当前的领导者如果检测到I/O事件, 首先要从线程池中推选出新的领导者线程,然后处理I/O事件。此时, 新的领导者等待新的I/O事件,而原来的领导者则处理I/O事件,二者实现了并发。
领导者线程推选新的领导者和追随者等待成为新领导者这两个操作都将修改线程集,因此线程集提供一个成员 Synchronizer来同步这两个操作,以避免竞态条件。
池
池是一组资源的集合,这组资源在服务器启动之初就被完全创建好并初始化,这称为静态资源分配。当服务器进入正式运行阶段,即开始处理客户请求的时候,如果 它需要相关的资源,就可以直接从池中获取,无须动态分配。很显然,直接从池中取得所需资源比动态分配资源的速度要快得多,因为 分配系统资源的系统调用都是很耗时的。当服务器处理完一个客户连 接后,可以把相关的资源放回池中,无须执行系统调用来释放资源。 从最终的效果来看,池相当于服务器管理系统资源的应用层设施,它避免了服务器对内核的频繁访问。
并发程序需要考虑的另外一个问题是共享资源的加锁保护。锁通 常被认为是导致服务器效率低下的一个因素,因为由它引入的代码不 仅不处理任何业务逻辑,而且需要访问内核资源。因此,服务器如果 有更好的解决方案,就应该避免使用锁。
如 果服务器必须使用“锁”,则可以考虑减小锁的粒度,比如使用读写锁。当所有工作线程都只读取一块共享内存的内容时,读写锁并不会 增加系统的额外开销。只有当其中某一个工作线程需要写这块内存时,系统才必须去锁住这块区域。