服务器的框架其实很多,但是基本都是根据如下三个模块进行设计:
1:I/O处理单元,处理客户连接,读写网络数据
2:逻辑单元,业务进程或线程
3:存储单元,本地数据库、文件或缓存
一、C/S模型
C/S模型即客户端(Client)和服务器(Server)模型,它的逻辑很简单,服务器启动后,首先创建一个或多个监听(socket),调用bind函数等待客户端连接,服务器运行稳定后,就可以调用connect函数向服务器发起连接。
由于客户连接请求是随机到达的(异步事件),所以服务器需要某种I/O模型来监听该事件
二、P2P模型
Peer to Peer模型,点对点,它比C/S更符合网络通信的实际情况,摒弃了以服务器为中心的格局,让网络上所有主机重新回归对等的地位,P2P模型使得每台机器在消耗服务的同时也给别人提供服务,这样资源能够充分、自由共享,但是当用户的请求过多,网络负载会加重,点对点模型通常会带一个发现服务器,这个发现服务器通常提供查找服务,使得每个客户都能够尽快找到自己的资源
三、I/O模型
Socket在创建的时候是默认阻塞的,所以在系统调用时可能无法立即完成而被系统挂起,知道等待的事情发生位置
针对非阻塞I/O执行的系统会立即返回,如果没有立即发生,会返回-1。我们只有在事件已经发生的情况下操作非阻塞I/O(读写等),才能提高程序的效率,因此非阻塞I/O通常 要和其他I/O通知机制一起使用,比如I/O复用、SIGIO信号
Linux上常用的I/O复用函数是select、poll和epoll_wait,它本身是阻塞,但是它能够同时监听多个I/O事件的能力
SIGIO信号也可以用来报告I/O事件,当目标文件有事件发生,SIGIO信号的信号处理函数被触发,我们也就知道该信号处理函数对目标文件执行非阻塞I/O操作
理论上说,阻塞I/O、I/O复用和信号驱动I/O都是同步I/O模型
四、两种高效事件处理模式
服务器程序通常要处理三类事件:I/O事件、信号、定时事件
同步I/O模型通常用于实现Reactor模式,异步I/O模型用于实现Proactor模式
1:Reacotr模式
工作流程:
主线程往epoll内核时间表中注册socket上的读就绪事件。
主线程调用epoll_wait等待socket上有数据可读。
当socket上有数据可读,epoll_wait通知主线程,主线程将socket可读事件放入请求队列
睡眠唤醒,工作线程读取socket,处理客户请求。
主线程往epoll内核事件表中注册该socket上的写就绪事件。
主线程调用epoll_wait等待socket有数据可写。
当socket可写,epoll_wait通知主线程,主线程将socket可写事件放入请求队列
睡眠唤醒,工作线程往socket上写入服务器处理客户请求结果。
2:Proactor模式
该模式把所有的I/O操作交给主线程和内核处理,工作线程仅处理业务逻辑
工作流程:
主线程调用aio_read函数向内核注册socket上读完成事件,告诉内核用户度缓冲区的位置
主线程继续处理其他逻辑
当socket数据被读入用户缓冲区,内核向应用程序发送信号,通知应用程序数据可用。
应用程序预先定义好的信号处理函数选择一个工作线程来处理用户请求
工作线程处理完用户请求后,调用aio_write函数向内核注册socket上的写完成事件,告诉内核用户写缓冲区的位置
主线程继续处理其他逻辑
当用户缓冲区的数据被写入socket,内核向应用程序发送一个信号,通知应用程序数据发送完毕
应用程序预先定义好的信号处理函数选择一个工作线程来善后,比如是否关闭socket