问题 本篇将梳理前面讲过的所有网络模型,并在此基础上探究这些模型所涉及的设计思路。相当于前面只讲了What和How部分,这里专门来补充Why部分。因为若是一开始就从原理讲起难免枯燥乏味且晦涩难通,而有了前面的铺垫再学起来就能增加可预知性,这时你的知识网络里已经构建了网络编程的基本框架,已经建立了它们之间的联系。 这篇文章就希望在此基础上,帮助你一步一步对新东西进行扩展重构,加深理解。这样不论学起来还是使用起来,都能游刃有余,左右逢源。 那么首先就从最开始的基本Socket说起。 这个阶段所开发的网络程序都是同步程序,一个客户端连接进来,服务器阻塞地使用 accept 接受,之后阻塞地发送,阻塞地接收。 当一个客户端的数据没有处理完时,其它客户端都得阻塞地等待连接,这样的服务器设计同时只能处理一位客户。若处理操作很短,那么其它客户端倒不会感到不满,因为速度很快它们根本察觉不到差异。而一但处理耗时较长,其它客户端就满心不悦,尤其是处于后面连接的客户端,只有等到前面的所有客户端处理完成才能被轮到。 这就好像在一家只有一个窗口的餐馆排除买餐,先来者先买,排在最后面的只有等到前面所有人买完了之后才能轮到。若是速食,那么很快就能买到,所以轮到的也快;而若是慢食,第一个人就要2分钟才能买到,那么第十个人就要20分钟。 所以这种方式效率极低,基本满足不了多少需求,而优点则是开发最简单。 问题总是倒逼着解决方案,所以一个个的思路堪堪而现。 多进程服务器 第一种做法是创建多个进程来对连接的客户端提供服务,这种方式称为 多进程服务器。比如在Linux上你可以使用 fork 函数创建一个调用的进程副本,它会复制正在运行的调用 fork 函数的进程。调用 fork 函数的主体称为 父进程,而通过父进程复制出来的进程称为子进程,它们都将执行 fork 函数调用后的语句。 所以这种方式就是每有一个客户端连接,都由父进程创建一个子进程来处理。这就好似每有一位顾客来买餐,都为其提供一个专门的餐馆,所以需要付出的代价极大,需要进行大量的运算和内存空间,而且各个进程间的数据交换也需要复杂的方法来完成。 多线程服务器 第二种做法是每有一位客户端连接,都开启一条线程来为其服务,这种方式称为「多线程服务器」。 比如在之前所写的HTTP服务器中,我们就对每一个并发连接的客户端开启了一条专门的线程。在服务下一条请求之前,这个线程同步地完成一个请求操作。当客户端请求访问指定文件时,在线程中同步地读取文件,再发送给客户端。 这就像每有一位顾客来买餐,都为单独地开一个窗口。而服务人员是有限的(即CPU核心数),若有4位服务人员,那么你开8个窗口,这4个人就得在这些窗口之间来回切换。虽说开了8个窗口,其实处理速度并没有得到提升。 这时,若是让窗口的数量和服务人员一致,那么效率往往更好。还有在结账时,只能由一位服务人员去处理(共享资源),若是一人处理一半又有另一个人来处理就造成收了二次收费。 所以总结这个模式的缺点就是: 增加了性能开销(CPU频繁地进行上下文切换) 增加了同步复杂度 不可移植(并非所有系统都支持多线程,且系统之间实现可能有很大不同,难以达到行为一致) Reactor模式 为了提高服务器的效率,就得想办法用最少的消耗来处理更多的请求。在前面两种方法中,要么消耗大量内存,要么增加CPU开销,由于CPU性能和内存空间都有瓶颈,所以就造成了极大的浪费。 若能将所有请求都放到一个管理中心去统一管理,处理完成后再通知相应的客户那么将可节省很多资源。 举个例子,由于顾客日益增多,所以餐馆便安装了一个点餐系统。此时,顾客无需等待,可以直接点餐,点餐系统负责记录每位顾客的点餐信息,并分派给厨师去做。当餐做好后,再由点餐系统对相应顾客发出通知“请xxx到柜台取餐”,顾客只需竖起耳朵听着就好。 这就是「I/O多路复用」(I/O Multiplexing)的思路,也叫「事件驱动模型」(Event-driven)。 但是因为只有一位厨师,所以对于同时点餐的顾客,就不能同时处理,此时就要将点餐时间划分为非重叠空隙,在不同的时间段对用户进行处理。这就叫做「时分多路复用」(Time Division Multiplexing,TDM)。 若是通知一次比较费时,那么点餐系统不必在只有一位客户点餐时就通知厨师,可以一次性发送几位顾客的需求,之后厨师再分析出这几位顾客的需求。这就叫做「频分多路复用」(Frequency Division Multiplexing,FDM)。 前面说过,当餐做好后,点餐系统需要发出通知,那么此时必须从已记录的客户信息中解析出对应的客户,都能通知给正确的用户。那么这部分操作就叫做「多路分解」(Demultiplexing),多路复用与多路分解的关系可参考这张图: 可以看到,通过引用复用技术,减少了进程数与线程数,只需一条线程或进程来处理请求。 通过这个思路,经过不断探索与经验积累,形成了一套开发服务器的模式,而这个模式就是著名的「Reactor模式」(中译为反应器模式)。 Reactor模式允许请求事件被应用到多路复用并分派到服务请求中,所有请求被从一个或多个客户端投递到Reactor(即前面所述之管理中心),如此便反转了程序的控制流,程序无需主动去监听客户端的消息,只需等待Reactor通知便好。 那么现在来看看Reactor模式的UML结构图。… Continue Reading 各种网络模型背后的设计思路