1、服务器模型
1.1C/S模型
【1】C/S(客户端/服务器)模型:所有客户端都通过访问服务器来获取所需的资源
【2】C/S模型逻辑:服务器启动后,先创建一个或者多个监听socket,并调用bind函数将其绑定到服务器的端口上,然后调用listen函数等待客户连接。服务器稳定运行后,客户端就可以调用connect函数向服务器发起连接。由于客户连接请求时随机到达的异步事件,服务器需要使用某种I/O模型来监听这一事件,(图中使用的是I/O复用技术之一的select系统调用),当监听到连接请求后,服务器就调用accept函数接受它了,并且分配一个逻辑单元为新的连接服务。逻辑单元读取客户请求,处理该请求,然后将结果反馈给客户端
【3】C/S模型适合资源相对集中的场合,但是服务器是通信的中心,当访问量过大时,可能所有客户都将得到很慢的响应。
2.1P2P模型
【1】P2P模型不在是以服务器为中心的格局,它使得每台机器在消耗服务的同时也给别人提供服务,这样资源能充分、自由的分享。
【2】问题:a图中,主机之间很难互相发现。所以实际的P2P模型通常还带有一个专门的发现服务器;如图b,这个发现服务器提供查找服务(甚至还可提供内容服务),使每个客户都能尽快找到自己需要的资源。
【3】缺点:当用户之间传输的请求过多时,网络负载加重。
2、服务器编程框架
服务器种类繁多,但是大体框架都一样,不同点在于逻辑处理
模块 | 单个服务器程序 | 服务器集群 |
---|---|---|
I/O处理单元 | 处理客户连接,读写网络数据 | 作为接入服务器,实现负载均衡 |
逻辑单元 | 业务进程或线程 | 逻辑服务器 |
网络存储单元 | 本地数据库,文件或缓存 | 数据库服务器 |
请求队列 | 各单元之间的通信方式 | 各服务器之间的永久TCP连接 |
3、I/O模型
【1】称阻塞的文件描述符为阻塞I/O,称非阻塞的文件描述符为非阻塞I/O。
【2】针对阻塞I/O执行的系统调用可能因为无法立即完成而被操作系统挂起,直到等待的事件发生为止。针对非阻塞I/O执行的系统调用则总是立即返回,而不管时间是否已经发生。所以,只有在事件已经发生的情况下操作非阻塞I/O,才能提高程序的效率。因此,非阻塞I/O通常要和其他I/O通知机制一起使用,比如I/O复用和SIGIO信号。
【3】I/O复用是最常使用的I/O通知机制:应用程序通过I/O复用函数向内核注册一组事件,内核通过I/O复用函数把其中就绪的事件通知给应用程序。I/O复用函数本身是阻塞的,它能提高程序效率的原因在于它具有同时监听多个I/O事件的能力
【4】SIGIO信号:为一个目标文件描述符指定宿主进程,那么被指定的宿主进程讲捕获到SIGIO信号。这样,当目标文件描述符上有事件发生时,SIGIO信号的信号处理函数将被触发,也就可以在该信号处理函数中对目标文件描述符执行非阻塞I/O操作
理论上,阻塞I/O,I/O复用和信号驱动I/O都是同步I/O模型。因为三种模型中,I/O的读写操作都是在I/O事件发生之后,由应用程序来完成的。而对于异步I/O模型,用户可以直接对I/O执行读写操作,会告诉内核读写缓冲区的位置,以及I/O操作完成之后内核通知应用程序的方式。异步I/O的读写操作总是立即返回,而不论I/O是否阻塞,因为真正的读写操作已经由内核接管。
I/O模型 | 读写操作和阻塞阶段 |
---|---|
阻塞I/O | 程序阻塞与读写函数 |
I/O复用 | 程序阻塞于I/O复用系统调用,但可同时监听多个I/O事件、对I/O本身的读写操作是非阻塞的 |
SIGIO信号 | 信号触发读写事件就绪,用户程序执行读写操作,程序没有阻塞阶段 |
异步I/O | 内核执行读写操作并触发读写完成事件,程序没有阻塞阶段 |
4、两种高效的事件处理模式
4.1Reactor模式
【1】Reactor模式:主线程(I/O处理单元)只负责监听文件描述符上是否有事件发生,有的话就立即将事件通知工作线程(逻辑单元)。除此之外,主线程不做任何其他实质性的工作;读写数据以及处理客户请求均在工作线程中完成。
【2】使用同步I/O模型(以epoll_wait为例)实现的Reactor模式的工作流程:
- 主线程往epoll内核事件表里面注册socket上的读事件。
- 主线程调用epoll_wait等待socket上有数据可读。
- 当socket上有数据可读时,epoll_wait通知主线程。主线程将socket可读事件放入请求队列。
- 睡眠在请求队列上的某个工作线程被唤醒,它从socket上读取数据,并处理客户请求,然后往epoll内核事件表中注册该socket上的写事件。
- 主线程调用epoll_wait等待socket可写。
- 当socket可写时,epoll_wait通知主线程。主线程将socket上的可写事件放入请求队列。
- 睡眠在请求队列上的某个工作线程被唤醒,它往socket上写入服务器处理客户请求的结果。
4.2Proactor模式
【1】Proactor模式:讲所有I/O操作都交给主线程和内核来处理,工作线程只负责业务逻辑。
【2】使用异步I/O模型(aio_read和aio_write为例)实现的Proactor模式的工作流程:
- 主线程调用aio_read函数向内核注册socket上的读事件,并告诉内核用户缓冲区的位置,和缓冲区大小,以及读操作完成时如何通知应用程序。(以信号为例)
- 主线程继续处理其他逻辑。
- 当socket上的数据被读入用户缓冲区之后,内核将向应用程序发送一个信号,告诉应用程序数据已经可用。
- 应用程序预先定义好的信号处理函数选择一个工作线程来处理客户请求。工作线程处理完成之后,调用aio_write函数向内核注册socket上的写事件,并告诉内核用户写缓冲区的位置和大小,以及写操作完成时如何通知应用程序。
- 主线程继续处理其他逻辑。
- 当用户缓冲区的数据被写入到socket之后,内核将向应用程序发送一个信号,通知应用程序写事件已经完毕。
- 应用层序预先定义好的信号处理函数选择一个工作线程来做善后处理。