详解从网络IO到IO多路复用
一、前言
这篇文章,会重点介绍linux的BIO、NIO和IO多路复用。
二、Netcat软件的基本使用
Netcat(简写nc)是一个强大的网络命令工具,能够在linux中执行与TCP、UDP相关的操作,例如端口扫描,端口重定向、端口监听甚至远程连接
在这里,我们使用 nc 来模拟一台接收message的服务器,和一台发送message的客户端
1、安装 nc 软件
sudo yum install -y nc
2、使用 nc 创建一台监听9999端口的服务器
nc -l -p 9999 # -l表示listening,监听
启动成功后 nc 进行阻塞
3、新建一个bash,使用 nc 创建一个发送message的客户端
nc localhost 9999
在控制台上输入要发送的信息,查看服务端是否接收到
4、查看上面的nc进程中的文件描述符
ps -ef | grep nc # 查看nc的进程号,这里假设是2603ls /proc/2603/fd # 查看2603进程下的文件描述符
可以看到这个进程下有一个socket,这就是nc的客户端和服务端之间创建的一个socket
经过这一系列的操作,相信我们对Netcat软件有了基本的了解,下面来介绍BIO
三、strace追踪系统调用
strace软件说明: 它是一个可以追踪系统调用和信号的软件,通过它我们来了解BIO
环境说明: 这里演示的都是基于老版本的linux,因为新版本的linux都不用BIO了,演示不出来
1、使用strace来追踪系统调用
sudo yum install -y strace # 安装strace软件mkdir ~/strace # 新建一个目录,存放追踪的信息cd ~/strace # 进入到这个目录strace -ff -o out nc -l -p 8080 # 使用strace追踪后边的命令进行的系统调用# -ff 表示追踪后面命令创建的进程及子进程的所有系统调用,# 并根据进程id号分开输出到文件# -o 表示追踪到的信息输出到指定名称的文件,这里是out
2、查看服务端创建的系统调用
在上一步进入的目录下,出现了一个 out.pid 文件,里的内容都是 nc -l -p 9999 这个命令执行后的系统调用过程,使用vim命令来查看
vim out.92459 # nc进程id为92459
这里accept()方法进行了阻塞,它要等待其他socket对它进行连接
3、客户端连接,查看系统调用
退出vim,使用tail来进行查看
tail -f out.92459
-f 参数:当文件有追加的内容,可以实时地打印在控制台,这样就能很方便来查看客户端连接后进行的系统调用
nc localhost 8080
查看系统调用
- 这里客户端连接后,accept() 方法获取到客户端连接并返回文件描述符4,这个4就是服务端新创建的socket,用于和这个客户端进行通信
- 之后使用多路复用器poll来监听服务端上文件描述符4和0,0是标准输入文件描述符,哪个有事件发生就读取哪个文件描述符,如果都没有事件发生就进行堵塞
4、客户端发送message,查看系统调用
客户端向服务端发送数据,服务端就能从socket中监听到有事件发生,就能进行相应的处理,处理完继续堵塞,等待下一个事件发生
5、服务端发送数据到客户端,查看系统调用
服务端发数据,肯定从键盘输入,也就是标准输入0,从0中读取到数据发送给socket 4
分享更多关于C/C++ Linux后端开发网络底层原理知识学习提升 学习资料,完善技术栈,内容知识点包括Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,音视频开发,Linux内核,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等等。
视频学习资料点击:C/C++Linux服务器开发/Linux后台架构师-学习视频
四、BIO(阻塞式IO)
在我们第三节中,我们使用 strace 工具查看了 nc 软件使用过程中的系统调用,其实上一节中体现的就是BIO,我们把上面的一系列系统调用总结一下,根据直观的理解BIO
1、单线程模式
1.1、过程演示
1、服务端启动
启动服务端,等待socket连接,accept()方法阻塞
2、客户端连接,未发送数据
连接客户端,accept() 方法执行,未收到client1发送的数据,read()方法阻塞
3、另一个客户端连接
由于read()方法阻塞,无法执行到accept()方法,所以这样cpu一次只能处理一个socket
1.2、存在的问题
上面的模型存在很大的问题,如果客户端与服务端建立了连接,客户端迟迟不发数据,进程就会一直堵塞在read()方法上,这样其他客户端也不能进行连接,也就是一次只能处理一个客户端,对客户很不友好
1.3、如何解决
其实要解决这个问题很简单,利用多线程就可以,只要连接了一个socket,操作系统分配一个线程来处理,这样read()方法堵塞在每个线程上,不堵塞主线程,就能操作多个socket了,有哪个线程中的socket有数据,就读哪个socket
2、多线程模式
1.1 过程演示
- 程序服务端只负责监听是否有客户端连接,使用 accept() 阻塞
- 客户端1连接服务端,就开辟一个线程(thread1)来执行 read() 方法,程序服务端继续监听
- 客户端2连接服务端,也开辟一个线程,执行read()方法
- 任何一个线程上的socket有数据发送过来,read()就能立马读到,cpu就能进行处理
1.2、存在的问题
上面这个多线程模型,看似已经十分的完美,其实也有很大的问题。每来一个客户端,就要开辟一个线程,如果来1万个客户端,那就要开辟1万个线程。在操作系统中,用户态不能直接开辟线程,需要调用cpu的80软中断,让内核来创建的一个线程,这其中还涉及到用户状态的切换(上下文的切换),十分耗资源。
1.3、如何解决
第一个办法:使用线程池,这个在客户端连接少的情况下可以使用,但是用户量大的情况下,你不知道线程池要多大,太大了内存可能不够,也不可行
第二个办法:因为read()方法堵塞了,所有要开辟多个线程,如果什么方法能使read()方法不堵塞,这样就不用开辟多个线程了,这就用到了另一个IO模型,NIO(非阻塞式IO)
五、NIO(非阻塞式IO)
1、过程演示
1、服务端刚创建,没有客户端连接
在NIO中,accept()方法也是非阻塞的,它在一个while死循环中
2、当有一个客户端进行连接时
3、当有第二个客户端进行连接时
2、总结
在NIO模式中,一切都是非阻塞的:
- accept()方法是非阻塞的,如果没有客户端连接,就返回error
- read()方法是非阻塞的,如果read()方法读取不到数据就返回error,如果读取到数据时只阻塞read()方法读数据的时间
在NIO模式中,只有一个线程:
- 当一个客户端与服务端进行连接,这个socket就会加入到一个数组中,隔一段时间遍历一次,看这个socket的read()方法能否读到数据
- 这样一个线程就能处理多个客户端的连接和读取了
3、存在的问题
NIO成功的解决了BIO需要开启多线程的问题,NIO中一个线程就能解决多个socket,看似已经 perfect,但是还存在问题。
这个模型在客户端少的时候十分好用,但是客户端如果很多,比如有1万个客户端进行连接,那么每次循环就要遍历1万个socket,如果一万个socket中只有10个socket有数据,也会变量一万个socket,就会做很多无用功。而且这个遍历过程是在用户态进行的,用户态判断socket是否有数据还是调用内核的read()方法实现的,这就涉及到用户态和内核态的切换,每遍历一个就要切换一次,开销很大
因为这些问题的存在,IO多路复用应运而生
分享更多关于C/C++ Linux后端开发网络底层原理知识学习提升 学习资料,完善技术栈,内容知识点包括Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,音视频开发,Linux内核,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等等。
视频学习资料点击:C/C++Linux服务器开发/Linux后台架构师-学习视频
六、IO Multiplexing(IO多路复用)
IO多路复用有三种实现方式,select、poll、epoll,现在让我们来看看这三种实现的真面目吧
1、select
这里还有select代码实现的代码例子
1.1 优点
select 其实就是把NIO中用户态要遍历的 fd 数组拷贝到了内核态,让内核态来遍历,因为用户态判断socket是否有数据还是要调用内核态的,所有拷贝到内核态后,这样遍历判断的时候就不用一直用户态和内核态频繁切换了
从代码中可以看出,select系统调用后,返回了一个置位后的&rset,这样用户态只需进行很简单的二进制比较,就能很快知道哪些socket需要read数据,有效提高了效率
1.2 存在的问题
1、bitmap最大1024位,一个进程最多只能处理1024个客户端
2、&rset不可重用,每次socket有数据就相应的位会被置位
3、文件描述符数组拷贝到了内核态,仍然有开销
4、select并没有通知用户态哪一个socket有数据,仍然需要O(n)的遍历
2、poll
2.1 代码例子
在poll中,文件描述符有一份独立的数据结构pollfd,传入poll中的是pollfd的数组,其他的实现逻辑和select一样
2.2 优点
1、poll使用pollfd数组来代替select中的bitmap,数组没有1024的限制,可以一次管理更多的client
2、当pollfds数组中有事件发生,相应的revents置位为1,遍历的时候又置位回0,实现了pollfd数组的重用
2.3 缺点
poll 解决了select缺点中的前两条,其本质原理还是select的方法,还存在select中原来的问题
1、pollfds数组拷贝到了内核态,仍然有开销
2、poll并没有通知用户态哪一个socket有数据,仍然需要O(n)的遍历
3、epoll
3.1 代码例子
3.2 事件通知机制
1、当有网卡上有数据到达了,首先会放到DMA(内存中的一个buffer,网卡可以直接访问这个数据区域)中
2、网卡向cpu发起中断,让cpu先处理网卡的事
3、中断号在内存中会绑定一个回调,哪个socket中有数据,回调函数就把哪个socket放入就绪链表中
3.3 详细过程
首先epoll_create创建epoll实例,它会创建所需要的红黑树,以及就绪链表,以及代表epoll实例的文件句柄,其实就是在内核开辟一块内存空间,所有与服务器连接的socket都会放到这块空间中,这些socket以红黑树的形式存在,同时还会有一块空间存放就绪链表;红黑树存储所监控的文件描述符的节点数据,就绪链表存储就绪的文件描述符的节点数据;
epoll_ctl添加新的描述符,首先判断是红黑树上是否有此文件描述符节点,如果有,则立即返回。如果没有, 则在树干上插入新的节点,并且告知内核注册回调函数。当接收到某个文件描述符过来数据时,那么内核将该节点插入到就绪链表里面。
epoll_wait将会接收到消息,并且将数据拷贝到用户空间,清空链表。
3.4 水平触发和边沿触发
Level_triggered(水平触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait()时,它还会通知你在上没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会一直通知你!!!如果系统中有大量你不需要读写的就绪文件描述符,而它们每次都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率!!!
Edge_triggered(边缘触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你!!!这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符!!!
3.5 优点
epoll是现在最先进的IO多路复用器,Redis、Nginx,linux中的Java NIO都使用的是epoll
1、一个socket的生命周期中只有一次从用户态拷贝到内核态的过程,开销小
2、使用event事件通知机制,每次socket中有数据会主动通知内核,并加入到就绪链表中,不需要遍历所有的socket
详解从网络IO到IO多路复用相关推荐
- [Pytorch系列-61]:循环神经网络 - 中文新闻文本分类详解-3-CNN网络训练与评估代码详解
作者主页(文火冰糖的硅基工坊):文火冰糖(王文兵)的博客_文火冰糖的硅基工坊_CSDN博客 本文网址:https://blog.csdn.net/HiWangWenBing/article/detai ...
- 十二、详解计算网络中的流量控制和差错控制、HDLC
十二.详解计算网络中的流量控制和差错控制 提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加 例如:第一章 Python 机器学习入门之pandas的使用 提示:写完文章后,目录可以自动 ...
- 【Linux服务器开发系列】详解多线程网络编程丨百分百干货分享丨学到就是赚到
90分钟搞懂多线程网络编程模型 1. 网络编程关注的问题 2. 网络编程的几种模型reactor,one loop per thread及其变种 3. skynet,redis,nginx,memca ...
- 【技术篇】详解,网络穿透,P2P,打洞的核心原理丨NAT,穿透的原理丨实现网络穿透
[技术篇]详解,网络穿透,P2P,打洞的核心原理丨NAT,穿透的原理丨实现网络穿透 那些你肯定不理解的技术,网络穿透,P2P,打洞的核心原理 1. NAT的原理 2. 穿透的原理 3. 实现网络穿透 ...
- 网络安全nmap扫描端口命令详解linux网络探测
简介: nmap是一个网络连接端扫描软件,用来扫描网上电脑开放的网络连接端.确定哪些服务运行在哪些连接端,并且推断计算机运行哪个操作系统(这是亦称 fingerprinting).它是网络管理员必用的 ...
- unet网络python代码详解_python网络编程详解
最近在看<UNIX网络编程 卷1>和<FREEBSD操作系统设计与实现>这两本书,我重点关注了TCP协议相关的内容,结合自己后台开发的经验,写下这篇文章,一方面是为了帮助有需要 ...
- 连接linux工具Mtr,mtr命令_Linux mtr命令使用详解:网络连通性判断工具
1.Mtr介绍 mtr是Linux中有一个非常棒的网络连通性判断工具,它结合了ping, traceroute,nslookup 的相关特性. 安装mtr工具 apt-get install mtr ...
- ResNet网络结构详解,网络搭建,迁移学习
前言: 参考内容来自up:6.1 ResNet网络结构,BN以及迁移学习详解_哔哩哔哩_bilibili up的代码和ppt:https://github.com/WZMIAOMIAO/deep-le ...
- Network in Network(NIN)网络结构详解,网络搭建
一.简介 Network in Network,描述了一种新型卷积神经网络结构. LeNet,AlexNet,VGG都秉承一种设计思路:先用卷积层构成的模块提取空间特征,再用全连接层模块来输出分类结果 ...
- linux网络服务详解,Linux网络服务器配置基础详解 (3)
Linux网络服务器配置基础详解 (3) Linux网络服务器配置基础详解 (3) 第三步:编辑"inetd.conf"文件(vi /etc/inetd.conf),禁止所有不需要 ...
最新文章
- python数据结构与算法:单向链表
- html的分类与特点
- 使用Python批量处理行、列和单元格
- Unet项目解析(4): ./src/RetinaNN_predict.py
- MFC入门示例之静态文本框、编辑框
- 项目管理与项目组合管理的不同
- html5动画在线制作工具,KoolShow-KoolShow(HTML5动画制作工具) v2.4.4 官方版-CE安全网...
- django-spaghetti-and-meatballs 0.2.0 : Python Package Index
- 六年级下计算机课ppt课件ppt课件,小学信息技术浙摄影版六年级上册第1课 走进计算机说课ppt课件...
- CAN总线波特率的计算方法
- 步科触摸屏程序上传 程序解密步骤方法
- Linux电脑怎么接入arm开发板,PC机与ARM开发板之间实现NFS共享
- python runner.daemonrunner_python3.3.4:pythondaemon3K;如何使用runn
- deepin更新linux内核,修改deepin启动内核
- Java POI解析Word提取数据存储在Excel
- squirrelSql小松鼠数据库连接工具的安装以及连接informix(系列3,squirrelSql作为客户端连接)
- 网络图标出现小地球,但可以正常上网的解决方法
- 白帽汇赵武:我们来聊一聊实名制
- 机器人正运动学DH参数表示法
- 链路分析(Link analysis)
热门文章
- Web3 Api学习
- jQuery的animate()的scrollTop属性在iPad Safari上不起作用
- 监控Linux性能的18个命令行工具
- SlikSvn的使用(命令版 )一
- 【MATLAB项目实战】基于Morlet小波变换的滚动轴承故障特征提取研究
- 使用sourcTree完成项目的hotfix操作步骤
- source insight php-styles.cf3,【转】SourceInsight 中集成Artistic Style 格式化代码
- 网络断网服务器未响应,断网服务器未响应
- 用MeCab解决日文汉字的排序问题
- 3个开关与3盏灯的问题