大家好,我是涛哥。

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

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

本文2800字,阅读需要10分钟。百度一百篇,不如收藏这一篇。

基本概念


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

什么是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模型。

I/O多路复用,从来没遇到过这么明白的文章相关推荐

  1. Q4,IT人从来没输过!

    一不留神,又到Q4了 咱IT人 Q4从来没输过 Q4第一单,10月8号就开标了 销售带着售前 自信满满步入开标现场 ↓ 经过前3Q的铺垫和放假7天的努力 他们已经给友商挖好了大坑 ↓ 研发部正在努力把 ...

  2. 今天的我从来没想到的飞鸽传书2009

    今天的我从来没想到的飞鸽传书2009,小猴子要回家了,我的笔丢了,富贵把蛋下在我的鞋子里,我哪会知道,我来到办公室找爸爸,来到新丰橱窗前,经过了一次手术,一不小心掉进了河里,我告诉他我的笔丢了. 太阳 ...

  3. accp8.0 网页编程_某程序员月入上万!为何却说:我希望自己从来没有学过编程? - C/C++爱好者...

    我是一名程序员.你可能会觉得我是专业的软件工程师,但实际上,软件工程不只是一个专业,还是一种生活方式.连帽衫.乒乓球.吃不完的零食和苏打水--都是这种生活的一部分.但虽然这个职业可以给人带来那么多回报 ...

  4. 你想过没有,学校从来没教过你怎么赚钱

    你想过没有,学校从来没教过你怎么赚钱,学校告诉你努力学习一门技能,将来找一份工作,然后安安稳稳. 打工对于绝大多数人来说这就足够了,但如果你想摆脱被束缚的人生想对自己的命运有更多的掌控,那就只有创业. ...

  5. 程序员从来没给丈母娘买礼物,真相竟然令人沉默

    来源 :对影成三Great 同事A,程序员,今年三十左右,已婚,有一女儿,技术好,开发经验足,平时工作也很勤奋,来公司两年,头发逐渐秃顶. 一日闲聊,A和我们抱怨,快要过年了,要给丈母娘准备礼物了.丈 ...

  6. 计算机方向键是哪个键,你可能从来没碰过的键,电脑键盘方向键上面的3个按键有什么用?...

    Hello大家好,我是兼容机之家的小牛. 我们在使用标准108键键盘的时候,经常只使用左边的英文字符区域和右边的小键盘区域,而方向键上方的3个按键大家可能从来没碰过,这些按键你知道它有哪些用处吗? 1 ...

  7. 回力说从来没想要创新,那它电商年增长1000%的秘诀是啥?

    它拥有千家线上线下的授权店铺,却没有一家店铺是一样的,玩的到底是什么运营模式? (1934年,诞生初期回力在<申报>上刊登的广告.) 文/天下网商记者 陈之琰 33岁的张恩祈出现在人们面前 ...

  8. JVM难学?那是因为你没认真看完这篇文章

    JVM难学?那是因为你没认真看完这篇文章 一:虚拟机内存图解 JAVA程序运行与虚拟机之上,运行时需要内存空间.虚拟机执行JAVA程序的过程中会把它管理的内存划分为不同的数据区域方便管理. 虚拟机管理 ...

  9. 为什么你从来没做过发起人?

    读MBA的时候,曾经听过一堂创业课,是一个来自MIT创业研究室的资深教授的课. 当时我正在筹备自己的第一个公司,举手提问,大概是问 "怎样才能确定自己是不是一个好的创业者". 教授 ...

最新文章

  1. 目标检测|SSD原理与实现
  2. 一篇文章一张思维导图看懂Android学习最佳路线
  3. AtCoder Beginner Contest 198 (A ~ F)题解
  4. C语言用char数组存储一串整数时的一个陷阱
  5. Property with #39;retain (or strong)#39; attribute must be of object type
  6. linux 字符串加入中括号,方括号及其在命令行中的不同用法介绍
  7. React全家桶构建一款Web音乐App实战(九):皮肤切换
  8. pytorch实现图像分类代码实例
  9. 为什么现在越来越多的人不愿换新机?最后一个原因扎心了
  10. mysql在同一台机器上实现主从_MySQL 5.7主从搭建(同一台机器)
  11. 计算机屏幕调节亮度,电脑屏幕亮度怎么调最好 有没有调节电脑屏幕亮度的软件...
  12. linux中批量创文件夹的方法
  13. 不会写Java面试简历?看这一篇就够了(项目经历,个人技能)
  14. 汽车营销与保险【1】
  15. Newton-Raphson法求解非线性方程复根
  16. RGB转HDMI模块解决方案
  17. 图片放大后不清楚怎么办?
  18. 重新认识融云,「不止即时通讯」
  19. 双十一大促淘宝主图设置优化方法
  20. java jar 最大内存大小_Java运行Jar包内存配置的操作

热门文章

  1. DFA在C#中的实现:过滤敏感词
  2. 工作中使用计算机的经验,事业单位考试中计算机工作经历是什么意思
  3. HDU1878(判断一个无向图是否存在欧拉回路)
  4. 威尔逊定理 ---- [hdu-6608] Fansblog 威尔逊定理 质数的密度分布 快速乘优化快速幂防止中间爆longlong
  5. 下行物理信道rs_信道估计与均衡
  6. matlab图像定位分割,車牌定位matlab程序:通過hsv彩色分割方式定位車牌
  7. 0x14.基础数据结构 — hash表与字符串hash
  8. cs通信查询mysql数据库_C#访问和操作MYSQL数据库
  9. 前端网页、php与mysql数据库字符编码(解决中文等乱码问题
  10. 函数对象、 函数对象与容器、函数对象与算法