服务器端网络编程之 IO 模型

引言 linux

     从 T 跳槽到 A 以后,个人编程语言也从 C++ 转为 了 Java。在 T 作的偏服务器端开发,而在 A 更偏向于业务开发。上周在 A 公司组内作了一个《服务器端高性能网络编程》的分享,我讶异于组内的十我的居然没有一我的作过直接基于 TCP/IP 协议的开发,更多的是 Web 后台的业务开发。连 Java 最强大的网络库 Netty,用过的人也只有一个。但也不难理解---A 公司的中间件平台,将业务与底层进行了隔离,让程序员能够专心于业务开发。程序员

    孰优孰劣?不能一律而论,还记得跳槽 A 公司以前面试过 N 公司,面试官问我 HTTP 协议、RPC 框架等知识,我也是只知其一;不知其二。要什么 HTTP 协议、要什么 RPC ,咱们直接 TCP/IP。其实这也是 T 公司基础设施不够完善的一种表现,这些在个人另外一篇文章《我在腾讯和阿里的见闻》中谈过,感兴趣的能够移步。面试

    做为 Java Web 程序员,你有想过 Web 服务器(Nginx,Tomcat,Jetty等)是如何接受你的 HTTP 请求的吗?你知道 其实 HTTP 也是基于 TCP/IP 的文本协议吗?之因此取这样的标题,但愿 Java Web 程序员对服务器端的某些工做原理有一些简单的了解,也不至于在面试的时候一问三不知。编程

 

概念windows

    在编写服务器端网络程序时,咱们最多见到阻塞、非阻塞、同步和异步这四个词。它们的解释分别以下:服务器

    阻塞: 阻塞调用是指调用返回以前,当前线程会被挂起,只有当调用获得结果后才返回。网络

    非阻塞:与阻塞相反,非阻塞调用是指在不能当即获得结果以前,该函数不会将当前线程阻塞,而是当即返回。多线程

 

    同步:所谓同步,就是在发出一个功能调用时,在没有获得结果以前,该调用就不返回。等前一件作完了才能作下一件事。 架构

    异步:异步的概念和同步相对。当一个异步过程调用发出后,调用者不能马上获得结果。实际处理这个调用的部件在完成后,经过状态、通知和回调来通知调用者。并发

    经常有人弄不清阻塞/非阻塞与同步/异步之间的关系,容易将他们混为一谈。阻塞/非阻塞更多的用来形容某次调用的属性(好比 read(),write() 是不是阻塞/非阻塞 )因此应用范围比较窄;而同步/异步则更上层,一般指各个功能/线程之间的关系(好比 Thread1 和 Thread2 是同步执行仍是异步执行)。

 

IO 模型

    服务器端 IO 主要分为两种:磁盘 IO 和网络 IO,在讲服务器端高性能网络编程时更多时候咱们讲的是网络 IO 模型。一次完整的服务器端处理网络请求流程图以下(简化版,以 Web 服务器为例):

    这张图比较简单,可是不少人在没看到这张图以前确定都觉得每次网络读(recvfrom())或者写(sendto())都是在网卡与用户进程之间进行操做,其实不是。从上图能够看出,数据不管从网卡到用户空间仍是从用户空间到网卡都须要通过内核。从磁盘上读写数据也是如此。因此就有了 mmap 技术,感兴趣的能够自行百度。应用进程(Web 服务器也属于应用进程,这里须要再统一几个概念:用户进程、应用程序、Web 服务器程序,它们相对于内核来讲都是应用进程,因此后面文章中统一成应用进程)须要经过系统调用(recvfrom/sendto)向内核读写数据,内核再进一步操做网卡。

    根据应用进程系统调用方式的阻塞、非阻塞,操做系统在处理应用程序请求时处理方式的同步、异步处理的不一样,参考《UNIX 网络编程卷 I》能够分为 5 种 IO 模型: 

一、阻塞 IO 模型(blocking IO)

    描述:应用程序进行 recvfrom 系统调用时将阻塞在此调用,直到该套接字上有数据而且复制到用户空间缓冲区。该模式通常配合多线程使用,应用进程每接收一个链接,为此链接建立一个线程来处理该链接上的读写以及业务处理。

    优势:编程简单,适合教学。《UNIX网络编程卷I》上不少例子都是基于这种模式。

    缺点:若是套接字上没有数据,进程将一直阻塞。这时其余套接字上有数据也不能进行及时处理。若是是多线程方式,除非链接关闭不然线程会一直存在,而线程的建立、维护和销毁很是消耗资源,因此能创建的链接数量很是有限。

 

二、非阻塞 IO 模型(nonblocking IO)

    

  描述:应用进程每次调用 recvfrom 即便没有数据准备好也不会阻塞,会继续往下执行,避免了进程阻塞在某个链接上的弊端。

    优势:代码编写相对简单,进程不会阻塞,能够在同一线程中处理全部链接。

    缺点:须要频繁的轮询,比较耗 CPU,在并发量很大的时候将花费大量时间在没有任何数据的链接上轮询。因此该模型只在专门提供某种功能的系统中才会出现。

 

三、IO 复用模型(IO multiplexing)

   

    描述:应用进程阻塞于 select/poll/epoll 等系统函数等待某个链接变成可读(有数据过来),再调用 recvfrom 从链接上读取数据。虽然此模式也会阻塞在 select/poll/epoll 上,但与阻塞IO 模型不一样它阻塞在等待多个链接上有读(写)事件的发生,明显提升了效率且增长了单线程/单进程中并行处理多链接的可能。

    优势:统一管理链接,不必定采用多线程的方式,同时也不须要轮询。只须要阻塞于 select 便可,能够同时管理多个链接。

    缺点:当 select/poll/epoll 管理的链接数过少时,这种模型将退化成阻塞 IO 模型。而且还多了一次系统调用:一次 select/poll/epoll 一次 recvfrom。

 

四、信号驱动 IO 模型(signal-driven IO)

   

    描述:应用进程建立 SIGIO 信号处理程序,此程序可处理链接上数据的读写和业务处理。并向操做系统安装此信号,进程能够往下执行。当内核数据准备好会向应用进程发送信号,触发信号处理程序的执行。再在信号处理程序中进行 recvfrom 和业务处理。

    优势:非阻塞

    缺点:在前一个通知信号没被处理的状况下,后一个信号来了也不能被处理。因此在信号量大的时候会致使后面的信号不能被及时感知。

 

五、异步 IO 模型(asynchronous IO)

    描述:应用进程经过 aio_read 告知内核启动某个操做,而且在整个操做完成以后再通知应用进程,包括把数据从内核空间拷贝到用户空间。信号驱动 IO 是内核通知咱们什么时候能够启动一个 IO 操做,而一部 IO 模型是由内核通知咱们 IO 操做什么时候完成。

    注:前 4 种模型都是带有阻塞部分的,有的阻塞在等待数据准备好,有的阻塞在从内核空间拷贝数据到用户空间。而这种模型应用进程从调用 aio_read 到数据被拷贝到用户空间,不用任何阻塞,因此该种模式叫异步 IO 模型。这五种模型的取名和并列方式我是保留意见的,感受容易迷惑读者。 

    优势:没有任何阻塞,充分利用系统内核将 IO 操做与计算逻辑并行。

    缺点:编程复杂、操做系统支持很差。目前只有 windows 下的 iocp 实现了真正的 AIO。linux 下在 2.6 版本中才引入,目前并不完善,因此 Linux 下通常采用多路复用模型。

 

  对比

     前四种模型的主要区别于第一阶段,由于他们的第二阶段都是同样的:在数据从内核拷贝到应用进程的缓冲区期间,进程阻塞于 recvfrom 调用。相反,异步 IO 模型在这两个阶段都须要处理,从而不一样于其余四种模型。

   

  以上图片全部原型都来自于《UNIX网络编程卷 I》,里面有不少跟网络编程有关的知识点和例子,是程序员必备书籍,即便你是业务程序员也应该购买一本,知其然,知其因此然!

 

    总结

    JDK 的网络编程相关的类、接口虽然不像 C++ 是直接依赖于操做系统的,但它的 IO 模型是离不开以上五种模型的。毕竟这是模型,与语言、操做系统无关。 IO 模型只是高性能网络编程中的基础部分,光有好的 IO 模型还不行,咱们还须要好的架构(线程模型)。线程模型是高性能网络编程的核心部分,在后面的文章中应该还会分析。

相关文章
相关标签/搜索