网络服务器模型简介

一个服务器一个进程

这种模型是最原始的,服务器中只有一个进程,复制监听端口,建立链接以及处理请求,然后关闭socket。这种模型效率最低,而且不能够支持长连接,对于阻塞socket来说,效率更是底下,服务器会阻塞在一个socket上,导致其他的socket被饿死。

一个链接一个进程

服务器只负责监听端口,每当收到一个链接,就fork一个进程负责处理该链接请求。这使得服务器能够同时为多个客户服务,每一个进程一个客户,客户数量的唯一限制是操作系统对以其名义运行服务器的用户ID能够同时拥有多少子进程的限制。

预先派生子进程

相比于上一个模型,预先派生子进程可以在有链接时就可以直接的有进程为客户服务,而不需要先等到fork完成后,才能够为客户服务。派生子进程在服务器启动会的时候,会预先派生一定数量的进程,当客户链接到达时,这些进程就可以立即为客户服务。所有派生的子进程都直接监听端口,单当收到连接时,就负责为建立的链接服务。由于多个子进程同时监听一个端口,会导致惊群效应。该模式只适合在于Berkeley内核的系统,对于System V, 多个进程同时在应用同一个监听socket描述符上调用accept会出现错误。

预先派生子进程+accept上锁

该模式只是上一个模式的改进,在accept上进行加锁,使得只有一个进程可以访问accept。其他的都与上一个模式一样,只不过由于加锁,这里没有了惊群效应。(其实还是有的,只不过移到了等待锁上,有多个进程在等待加锁,如果此时锁可用,那么所有等到加锁的进程都会醒过来,再一次进行加锁竞争)

预先派生子进程+传递描述符

预先派生多个进程,但是所有子进程都关闭了监听socket,只有父进程负责监听socket,父进程与子进程通过管道进行通信。有多少个子进程就有多少个管道,父进程采用select模式,监听所有的管道文件描述符和监听socket,当有新链接时,父进程负责将新的文件描述符发给空闲的子进程。父进程维护一个子进程数组,记录子进程的空闲与否,父进程与子进程通过管道进行信息交互,子进程空闲时,写管道通知父进程,同时读管道,等待父进程写新的链接描述符给子进程。当select中的管道文件描述符可读时,父进程根据管道所属的子进程来标记子进程空闲,以方便有新连接时可以将链接分配给子进程处理。

预先派线程

该系列模型其实与预先派生子进程差不多,只不过采用的是一个客户一个线程的方式。

预先派生进程或是线程都有一个问题,就是不知道开始需要派生多少,还有,需要实时对线程或是进程数量进行监控,当数量不够的时候需要采取一定的措施。

基于事件驱动

一般地,I/O多路复用机制都依赖于一个事件多路分离器(Event Demultiplexer)。分离器对象可将来自事件源的I/O事件分离出来,并分发到对应的read/write事件处理器(Event Handler)。开发人员预先注册需要处理的事件及其事件处理器(或回调函数);事件分离器负责将请求事件传递给事件处理器。两个与事件分离器有关的模式是Reactor和Proactor。Reactor模式采用同步IO,而Proactor采用异步IO。

在Reactor中,事件分离器负责等待文件描述符或socket为读写操作准备就绪,然后将就绪事件传递给对应的处理器,最后由处理器负责完成实际的读写工作。

而在Proactor模式中,处理器或者兼任处理器的事件分离器,只负责发起异步读写操作。IO操作本身由操作系统来完成。传递给操作系统的参数需要包括用户定义的数据缓冲区地址和数据大小,操作系统才能从中得到写出操作所需数据,或写入从socket读到的数据。事件分离器捕获IO操作完成事件,然后将事件传递给对应处理器。比如,在windows上,处理器发起一个异步IO操作,再由事件分离器等待IOCompletion事件。典型的异步模式实现,都建立在操作系统支持异步API的基础之上,我们将这种实现称为“系统级”异步或“真”异步,因为应用程序完全依赖操作系统执行真正的IO工作。