收藏等会学会!IO多路复用,从来没遇到过能讲得这么明白的了

很多对技术有追求的读者朋友,做到一定阶段后都希望技术有所精进。有些读者朋友可能会研究一些中间件的技术架构和实现原理。比如,Nginx为什么能同时支撑数万乃至数十万的连接?为什么单工作线程的Redis性能比多线程的Memcached还要强?Dubbo的底层实现是怎样的,为什么它的通信效率非常高?

实际上,上面的一些问题都和网络模型相关。本文从基础的概念和网络编程开始,循序渐进讲解Linux五大网络模型,包括耳熟能详的多路复用IO模型。相信读完本文,大家会对网络编程和网络模型有一个较清晰的理解。

基本概念


我们先来了解几个基本概念。

什么是I/O?

所谓的I/O(Input/Output)操作实际上就是输入输出的数据传输行为。程序员最关注的主要是磁盘IO和网络IO,因为这两个IO操作和应用程序的关系最直接最紧密。

磁盘IO:磁盘的输入输出,比如磁盘和内存之间的数据传输。

网络IO:不同系统间跨网络的数据传输,比如两个系统间的远程接口调用。

下面这张图展示了应用程序中发生IO的具体场景:

通过上图,我们可以了解到IO操作发生的具体场景。一个请求过程可能会发生很多次的IO操作:

1,页面请求到服务器会发生网络IO

2,服务之间远程调用会发生网络IO

3,应用程序访问数据库会发生网络IO

4,数据库查询或者写入数据会发生磁盘IO

阻塞与非阻塞

所谓阻塞,就是发出一个请求不能立刻返回响应,要等所有的逻辑全处理完才能返回响应。

非阻塞反之,发出一个请求立刻返回应答,不用等处理完所有逻辑。

内核空间与用户空间

在Linux中,应用程序稳定性远远比不上操作系统程序,为了保证操作系统的稳定性,Linux区分了内核空间和用户空间。可以这样理解,内核空间运行操作系统程序和驱动程序,用户空间运行应用程序。Linux以这种方式隔离了操作系统程序和应用程序,避免了应用程序影响到操作系统自身的稳定性。这也是Linux系统超级稳定的主要原因。

所有的系统资源操作都在内核空间进行,比如读写磁盘文件,内存分配和回收,网络接口调用等。所以在一次网络IO读取过程中,数据并不是直接从网卡读取到用户空间中的应用程序缓冲区,而是先从网卡拷贝到内核空间缓冲区,然后再从内核拷贝到用户空间中的应用程序缓冲区。对于网络IO写入过程,过程则相反,先将数据从用户空间中的应用程序缓冲区拷贝到内核缓冲区,再从内核缓冲区把数据通过网卡发送出去。

Socket(套接字)

Socket可以理解成,在两个应用程序进行网络通信时,一个应用程序将数据写入Socket,然后通过网卡把数据发送到另外一个应用程序的Socket中。

所有的网络协议都是基于Socket进行通信的,不管是TCP还是UDP协议,应用层的HTTP协议也不例外。这些协议都需要基于Socket实现网络通信。5种网络IO模型也都要基于Socket实现网络通信。实际上,HTTP协议是建立在TCP协议之上的应用层协议。HTTP协议负责如何包装数据,而TCP协议负责如何传输数据。

绝大部分编程语言,都支持Socket编程,例如Java,Php,Python等等。而这些语言的Socket SDK都是基于操作系统提供的 socket() 函数来实现的。不管是Linux还是windows,都提供了相应的 socket() 函数。

Socket 编程过程


我们来看看Socket 编程过程是怎样的。

不管Java、Python还是Php,很多编程语言都支持Socket编程。Linux,Windows等操作系统都开放了网络编程接口。只不过,各种编程语言对底层操作系统提供的网络编程接口做了封装而已。

从服务端开始,服务端首先调用 socket() 函数,按指定的网络协议和传输协议创建 Socket ,例如创建一个网络协议为 IPv4,传输协议为 TCP 的Socket。接着调用 bind() 函数,给这个 Socket 绑定一个 IP 地址和端口,绑定这两个的目的是什么?

  • 绑定端口的目的:当内核收到 TCP 报文,通过 TCP 头里的端口号,来找到我们的应用程序,然后把数据传递给我们
  • 绑定 IP 地址的目的:一台机器可能有多个网卡,每个网卡都对应一个 IP 地址,只有绑定一个网卡对应的IP时,内核在收到该网卡上的包,才会发给我们的应用程序

绑定完 IP 地址和端口后,就可以调用 listen() 函数进行监听。如果我们要判定服务器上某个网络程序有没有启动,可以通过 netstat 命令查看对应的端口号是否被监听。

服务端进入了监听状态后,通过调用 accept() 函数,来从内核获取客户端的连接,如果没有客户端连接,则会阻塞等待客户端连接的到来。

那客户端是怎么发起连接的呢?客户端在创建好 Socket 后,调用 connect()函数发起连接,该函数的参数要指明服务端的 IP 地址和端口号,然后众所周知的 TCP 三次握手就开始了。

连接建立后,客户端和服务端就开始相互传输数据了,双方可以通过 read()和 write() 函数来读写数据。

基于TCP 协议的 Socket 编程过程就结束了,整个过程如下图所示:

网络IO模型


5种Linux网络IO模型包括:同步阻塞IO、同步非阻塞IO、多路复用IO、信号驱动IO和异步IO。

同步阻塞IO

我们先看一下传统阻塞IO。在Linux中,默认情况下所有socket都是阻塞模式的。当用户线程调用系统函数read(),内核开始准备数据(从网络接收数据),内核准备数据完成后,数据从内核拷贝到用户空间的应用程序缓冲区,数据拷贝完成后,请求才返回。从发起read请求到最终完成内核到应用程序的拷贝,整个过程都是阻塞的。为了提高性能,可以为每个连接都分配一个线程。因此,在大量连接的场景下就需要大量的线程,会造成巨大的性能损耗,这也是传统阻塞IO的最大缺陷。

同步非阻塞IO

用户线程在发起Read请求后立即返回,不用等待内核准备数据的过程。如果Read请求没读取到数据,用户线程会不断轮询发起Read请求,直到数据到达(内核准备好数据)后才停止轮询。非阻塞IO模型虽然避免了由于线程阻塞问题带来的大量线程消耗,但是频繁地重复轮询大大增加了请求次数,对CPU消耗也比较明显。这种模型在实际应用中很少使用。

多路复用IO模型

多路复用IO模型,建立在多路事件分离函数select,poll,epoll之上。在发起read请求前,先更新select的socket监控列表,然后等待select函数返回(此过程是阻塞的,所以说多路复用IO并非完全非阻塞)。当某个socket有数据到达时,select函数返回。此时用户线程才正式发起read请求,读取并处理数据。这种模式用一个专门的监视线程去检查多个socket,如果某个socket有数据到达就交给工作线程处理。由于等待Socket数据到达过程非常耗时,所以这种方式解决了阻塞IO模型一个Socket连接就需要一个线程的问题,也不存在非阻塞IO模型忙轮询带来的CPU性能损耗的问题。多路复用IO模型的实际应用场景很多,比如大家耳熟能详的Java NIO,Redis,Nginx以及Dubbo采用的通信框架Netty都采用了这种模型。

下图是基于select函数Socket编程的详细流程。

用一句话解释多路复用模型。多路:可以理解成多个网络连接(TCP连接)。复用:服务端反复使用同一个线程去监听所有网络连接中是否有IO事件(如果有IO事件就交给工作线程从对应的连接中读取并处理数据)。

信号驱动IO模型

信号驱动IO模型,应用进程使用sigaction函数,内核会立即返回,也就是说内核准备数据的阶段应用进程是非阻塞的。内核准备好数据后向应用进程发送SIGIO信号,接到信号后数据被复制到应用程序进程。

采用这种方式,CPU的利用率很高。不过这种模式下,在大量IO操作的情况下可能造成信号队列溢出导致信号丢失,造成灾难性后果。

异步IO模型

异步IO模型的基本机制是,应用进程告诉内核启动某个操作,内核操作完成后再通知应用进程。在多路复用IO模型中,socket状态事件到达,得到通知后,应用进程才开始自行读取并处理数据。在异步IO模型中,应用进程得到通知时,内核已经读取完数据并把数据放到了应用进程的缓冲区中,此时应用进程

直接使用数据即可。

很明显,异步IO模型性能很高。不过到目前为止,异步IO和信号驱动IO模型应用并不多见,传统阻塞IO和多路复用IO模型还是目前应用的主流。Linux2.6版本后才引入异步IO模型,目前很多系统对异步IO模型支持尚不成熟。很多应用场景采用多路复用IO替代异步IO模型。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容