I/O 多路复用技术

概述

在 I/O 编程过程中,当需要同时处理多个客户端请求时,可以利用多线程或 I/O 多路复用技术进行处理。本篇博客我就来简单介绍 I/O 多路复用相关知识。


什么是 I/O 多路复用技术

I/O 多路复用技术是指多个网络 I/O 复用一个或少量的线程来处理这些请求。其中它通过把多个 I/O 的阻塞复用到同一个 select 阻塞上,从而使系统在单线程的情况下可以同时处理多个客户端请求。

下面我通过抽象示例简单描述 I/O 多路复用技术:

假设你是一名老师,让30个学生完成一道题目,并检查他们的结果是否正确,现在有以下几种情况:
1、按顺序逐个检查,先检查A,然后B,之后C、D。。,这中间如果有一个学生卡住,后面的同学都会被耽搁
2、找来30个助教,每个助教负责检查一个学生
3、站在讲台上,哪个学生做完了举手示意,谁举手我去检查谁

  • 上述场景一对应传统单线程 socket 模型,它的缺点非常明显:同时只能处理一个客户端请求,多余的请求可以通过队列的方式保存起来,后续依次遍历处理。但如果处理某个请求时阻塞了,那么后续所有请求的处理都会“阻塞”。

  • 上述场景二对应传统多线程 socket 模型,一般都是通过主线程阻塞等待客户端连接,每个客户端连接创建新的工作线程来处理请求的方式实现。当并发量不是很大时,这种处理方式还可以使用。一旦并发量很大,频繁创建的线程会带来巨大的资源消耗以及上下文切换消耗。

  • 上述场景三就可以理解为 I/O 多路复用技术:将客户端对应 socket 的 fd(文件描述符) 注册到 select 或 poll 或 epoll 上,当 socket 流就绪时,select 线程就会执行:轮询找到就绪的 socket,将它返回给应用,执行相应流处理。

从这里也就可以看出,I/O 多路复用技术的核心是减少服务端线程的创建,通过使用较少线程处理所有请求的方式提高整体效率。


I/O 多路复用与非阻塞

一般情况下,I/O 多路复用也叫 非阻塞I/O,JAVA NIO 就是基于多路复用实现的。这里的 NIO 也就是我们常说的 Non-block I/O。然而通过上篇博客我们可以看出,无论是传统阻塞 I/O 模型,还是多路复用模型,都存在阻塞情况,那么为什么多路复用被称之为非阻塞 I/O 呢?

个人理解这里的非阻塞是针对服务端处理方式来说的:

  • 在传统多线程 socket 模型中,对于每个客户端连接,服务端都需要创建线程进行跟踪处理。当线程进行阻塞调用时,该线程阻塞。每个客户端所对应的线程都有可能会阻塞。当连接的客户端过多时,大量的线程资源消耗以及线程间的上线文切换会给服务端带来巨大的资源消耗,整体性能急剧下降。

  • 在 I/O 复用 socket 模型中,对于每个客户端连接,将它所对应的 socket 注册到一个 select 线程上,后续监听该 select 上注册所有 socket 的文件描述符,当存在 socket 就绪时,就将它返回。在整个过程中,服务端不用创建大量线程、某个客户端请求阻塞时,它的 socket 会阻塞到 select 线程上,服务端还可以处理其它客户端已经就绪的流,此时对服务端来说,这个处理方式就是非阻塞的。

从这里也就可以看出,I/O 多路复用非常适用于大量客户端 TCP 连接,流传输的场景。如果客户端连接数本身就比较少,多路复用可能还不如传统socket模型,因为它可能不能发挥多核cpu的优势。


I/O 多路复用技术的实现

目前常见的支持 I/O 多路复用的系统调用有 select、poll、epoll。其中 epoll 是为了克服 select 缺陷所实现的优化版本,下面我们主要看 epoll 相比 select 的优势:

1、epoll 可以注册的 socket 描述符不受限制

select 最大的缺陷就是单个进程所能注册的描述符有一定限制,默认是1024,这对于海量 tcp 连接应用来说显然是不够的。虽然我们可以通过修改配置的方式提高上限,不过这会带来网络效率的下降。 epoll 所支持的 FD 的上限是操作系统最大文件句柄数,这个数字远大于1024。

2、epoll 效率不会随着 FD 的提升而降低

当注册的 FD 过多时,select 和 poll 的效率会急剧下降。这是由于 select 和 poll 对于注册的 socket 集合采用遍历的方式找出就绪的 socket,如果 socket 数量很大,每次遍历就需要耗费不少时间,拿上文中的抽象案例解释就是说:但凡有一个学生举手,我就遍历所有学生,询问他是否举手,这显然是低效的。而 epoll 直接处理就绪的 socket,无须遍历集合,因此整体效率不会随着 FD 的提高而降低。

造成这种情况的主要原因是 epoll 在内核层面根据每个 fd 的回调函数实现,只有就绪的 socket才会调用回调函数,因此无须遍历所有集合。

3、使用 mmap 加速内核与用户空间的消息传递

无论 select、poll 还是 epoll,都需要内核把 FD 的消息通知给用户空间,epoll 在这里通过内核和用户空间 mmap 同一块内存来实现。

mmap:mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。

4、epoll 的 API 更加简单

无论是创建描述符,添加监听事件,阻塞等待监听事件的发生,关闭epoll描述符等操作,epoll 的 API 使用更加简单。


参考
Netty 权威指南第二版
https://blog.csdn.net/wangxindong11/article/details/78591308
https://www.zhihu.com/question/32163005
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值