后端必读:传说中的网络I/O复用,到底是怎么回事?

大家好呀,我是码财同行。

在网络开发中,I/O复用是经常被提及的一个技术名词。今天,我们就来聊聊它。

来看一组I/O模型的示意图:

上图中,I/O 模型有3种,他们是

  • 阻塞 I/O
  • 非阻塞 I/O
  • I/O 复用

无论哪种类型,都分为2个阶段:

  1. 等待网卡数据到达
  2. 将数据从内核空间拷贝到程序调用方的用户空间

现在思考一下,这里的阻塞是阻塞谁?

答案是调用方的线程或进程。

假如有个客户端连接过来,看看调用方的线程或进程是什么表现。

第一种,阻塞I/O,全程阻塞,线程只要调用 socket 的I/O 函数(如read),线程或进程就卡住了,其他什么事情都干不了:

  • 做不了其他逻辑,如协议包的逻辑处理、发送Ack包等;
  • 服务不了其他连接,读不了连接数据。这对于一个服务器而言是不可接受的,服务器的基本宗旨是服务大量的连接(玩家),不能让一个玩家做无效的独占。

那这个卡住(阻塞)会持续多久?

不知道。可能很快,如这条连接上立即有新的数据过来;也可能很慢,客户端用户半天没什么操作,也就没有交互数据。

实际上,这种情况一个进程或线程只能为一个连接服务,是对系统资源的很大浪费。

事实上一个连接上,大部分时间是没有数据需要处理的

因此,一个线程或者进程资源,服务多个连接完全没有问题。这样,也能节约服务器的系统资源。这相当于通信上的时分复用,一段时间服务这个连接,一段时间服务那个连接,交替进行,看起来像是同时(并发)服务多个连接。

要达到时分复用的效果,I/O方案就必须是:

第二种,非阻塞I/O。这个时候,如果线程调用 socket 的I/O 函数(如read),有数据就读取,没有数据就返回。

这种方式能解决卡住(阻塞)的问题么?

可以。因为读不到数据就返回了嘛,调用方线程可以处理其他逻辑,如读取其他连接上的数据或者做逻辑处理。

现在,假如有多个连接,都被设置成了非阻塞I/O,服务器同时服务这些连接。

那应该采取什么样的方式及时得读取所有连接上过来的请求数据呢?

这时候,有几种方案可以解决这个问题:

1)轮询

第一种方法,对多个连接进行人工轮询,读到数据就处理,读不到就遍历下一个。

那这种方式最大的问题是什么?

效率低。

效率为何低呢?因为I/O操作(如read)一般是系统调用,轮询时候需要不停的在用户空间和内核空间进行切换;此外,轮询本身消耗 CPU,连接数如果成千上万,依次遍历的效率可想而知。

所以非阻塞I/O一般不单独使用。

2)I/O复用

第二种方法是让操作系统帮我们探测有数据的连接,通知我们,然后我们去处理。这就是I/O 复用,也是服务器主流的处理高并发的方案。

I/O复用本质上,是让操作系统对多个连接上I/O事件的并发探测,所谓复用,就是复用同一个调用方资源(如同一个线程或进程)来并发服务多个连接,也类似时分复用的思想。 如果不复用,像前面说的阻塞I/O模式,那只能让一个调用方资源对应一个连接了。

这里的处理只是数据上的并发通知,实际数据的读取还不是并发的,当然我们可以用多个工作线程或工作进程来并发读数据和处理消息。

最后再提一点,回过头看文章一开始的图,细心的同学可能发现了:

I/O复用虽然从使用上是两个阶段(探测有无数据,读取数据),但也都是阻塞的啊?如果一直阻塞,什么时候做逻辑处理?

其实,这里的阻塞是对所有连接的阻塞,当所有连接上都没有数据时,才是阻塞的。

即使所有连接上都没有新数据传输过来,也有几种方案可以解决这个问题:

  1. 设置依次I/O复用的超时,短时间I/O超时(没有数据)之后,就可以做逻辑处理了;
  2. 可以把一些逻辑转化成I/O的形式,例如linux上定时器就可以用文件fd来实现(muduo);
  3. 可以把I/O操作放在单独的一个网络线程中,逻辑处理再开一个线程或进程处理,这就是大名鼎鼎的 Reactor 模式;

这篇文章到这里就结束啦。

好了,看了这么多,一定很费脑力吧。来个笑话放松一下 :)

【笑话一则】我每天都会做仰卧起坐,每天晚上一个仰卧,每天早上一个起坐。

感谢您花时间阅读这篇文章!如果觉得有趣或有收获,请来个关注、评论、点赞吧,您的鼓励是我持续创作的动力,蟹蟹!

| 往期推荐

# 协程没有秘密(二):几张图文快速入门协程,推荐收藏

基于本地知识库,定制一个私有GPT助手,不能再简单了

【建议收藏】服务注册与发现原理+踩坑,一文包教会

【技术·真相】谈一谈游戏AI - 真的搞懂寻路(一)

【技术·真相】谈一谈K8S的存储(一)

相关推荐
蓝田~4 分钟前
SpringBoot-自定义注解,拦截器
java·spring boot·后端
theLuckyLong6 分钟前
SpringBoot后端解决跨域问题
spring boot·后端·python
.生产的驴7 分钟前
SpringCloud Gateway网关路由配置 接口统一 登录验证 权限校验 路由属性
java·spring boot·后端·spring·spring cloud·gateway·rabbitmq
小扳11 分钟前
Docker 篇-Docker 详细安装、了解和使用 Docker 核心功能(数据卷、自定义镜像 Dockerfile、网络)
运维·spring boot·后端·mysql·spring cloud·docker·容器
v'sir20 分钟前
POI word转pdf乱码问题处理
java·spring boot·后端·pdf·word
李少兄25 分钟前
解决Spring Boot整合Redis时的连接问题
spring boot·redis·后端
码上一元5 小时前
SpringBoot自动装配原理解析
java·spring boot·后端
枫叶_v7 小时前
【SpringBoot】22 Txt、Csv文件的读取和写入
java·spring boot·后端
杜杜的man8 小时前
【go从零单排】Closing Channels通道关闭、Range over Channels
开发语言·后端·golang
java小吕布8 小时前
Java中Properties的使用详解
java·开发语言·后端