IO多路复用

IO多路复用技术

什么叫IO多路复用?
在了解IO多路复用之前我们先来了解一下阻塞IO和非阻塞IO

什么是程序的阻塞呢?

举个简单的例子,我们要买网络编程一本书,打电话问书店老板,老板的回复是等待一下,他去找书,这个时候我们就只能

拿着电话静静的等,直到老板找到这本书。

什么是程序的非阻塞呢?

还是上面的例子,我们要买网络编程这本书,打电话给老板,老板的回复是他去找书,这个时候情况就不同了,

我们隔一会再给老板打电话问他找到了没有 ,如果老板回复没有找到,我们过一会再打电话询问,直到老板回复找到了。

通过上面的例子我们来总结一下
阻塞IO:当请求的数据没有到来时,程序会一直阻塞在这里,等待数据的到来。
非阻塞IO:当请求的数据没有到来时,会返回一个数据没准备好的结果,然后采用轮询的方式继续询问,直到数据准备好。
阻塞IO和非阻塞IO图解如下:


阻塞IO存在的问题是,当数据没有到来时,程序会一直阻塞,什么事情也做不了。

非阻塞IO虽然会及时返回一个结果,但是需要不停地轮询IO请求的状态。


这个时候我们引入了IO多路复用

在上面买书的例子基础上,我们不需要再持续等待,也不需要不断地给老板打电话询问,我们有了一个收件箱

这个收件箱里面有我们请求的信息,我们只需要监听这个收件箱,当老板找到书时我们就会收到信息。

IO多路复用技术分为:select、poll、epoll


select原理

收件箱就好比引入的一个中间人物,我们不需要自己去监听轮询数据是否准备好,我们只需要创建一个select监听队列,把我们需要

监听的IO事件(文件描述符)放进该队列中,让它去帮助我们监听数据的准备状态,当有数据请求时我们再去处理。但是select存在
一些问题,当监听到有IO事件发生时,我们并不知道具体是哪个文件描述符,这个时候我们需要轮询所有文件描述符,确定是某一个
流准备好了,然后对其进行IO操作。所以select就具有O(n)的复杂度,当处理的流越多时,轮询时间就越长。

epoll原理:

epoll和select相似,但不同之处在于epoll会在监听的时候确定是哪个流发生了什么IO事件,避免了select中的轮询,它的时间复杂度

O(1),大大的提高了效率。

select和epoll对比

对比一下我们可以看到,select只是能知道有几个文件描述符发生了IO事件,但是他并不能知道是哪几个,发生了什么IO时间,但是epoll就能
定位到确定的几个流,并且知道发生了什么IO事件,相比一下epoll比select要高效许多,可以说epoll是牺牲空间节省时间的思想。
那么问题来了,为什么叫做IO多路复用呢?
首先select和epoll也是一种阻塞式的模型,然后因为传统阻塞IO同一时间只是对一个针对某一个IO进行的,而IO多路复用是同一时间针对多个IO。
### 原理 在传统的阻塞IO模型中,每个IO操作都会导致线程阻塞,直到数据准备完毕或发送完毕,且每个线程只能处理一个IO操作,当面临大量并发连接时,需创建大量线程,消耗大量系统资源。而IO多路复用通过使用操作系统提供的IO复用机制,如selectpollepoll,可同时监听多个文件描述符上的IO事件,实现高效的IO处理。例如select函数和poll函数模型,进程告诉多路复用器(内核)所有的socket号,多路复用器去获取每个socket的状态,当程序获取到某个socket号有事件发生时,就去该socket号上处理对应的事件,如read事件或者recived事件。不过select函数底层是数组,有最大连接数的限制,poll函数底层是链表,无最大连接数的限制。这两种模型都存在需要遍历所有socket(O(N)复杂度)以及重复传递数据(内核无状态,每次都要从用户态向内核态传递所有的socket号去遍历获取状态)的缺点 [^2][^3]。 ### 应用 IO多路复用广泛应用于需要同时处理大量网络连接的场景中,如高并发服务器、即时通讯系统等。在高并发服务器中,可能会同时有大量客户端连接,使用IO多路复用可以高效地处理这些连接上的IO事件;在即时通讯系统里,也需要同时处理多个用户的消息收发,IO多路复用能提升系统的处理效率 [^1]。 ### 实现方式 常见的实现方式有selectpollepoll。 - **select**:进程将所有需要监听的socket号告知内核,内核会检查这些socket的状态,当有事件发生时通知进程。但由于其底层使用数组存储socket号,有最大连接数的限制,且每次调用都要将所有socket号从用户态复制到内核态,遍历所有socket,效率较低 [^3]。 - **poll**:与select类似,也是让内核检查多个socket的状态。不同的是,poll底层使用链表存储socket号,没有最大连接数的限制,但同样存在遍历所有socket和重复传递数据的问题 [^3]。 - **epoll**:是一种更为高效的IO多路复用实现方式,它使用事件驱动的机制,只关注有事件发生的socket,避免了遍历所有socket,并且使用内存映射(mmap)技术避免了数据的重复复制,提高了效率。 ### 示例代码(Java中使用NIO模拟简单的IO多路复用) ```java import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Set; public class NioServer { public static void main(String[] args) throws IOException { // 创建选择器 Selector selector = Selector.open(); // 打开监听通道 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 绑定端口 serverSocketChannel.socket().bind(new InetSocketAddress(8080)); // 设置为非阻塞模式 serverSocketChannel.configureBlocking(false); // 将通道注册到选择器上,并监听连接事件 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while (true) { // 等待事件发生 int readyChannels = selector.select(); if (readyChannels == 0) continue; // 获取所有就绪的选择键 Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> keyIterator = selectedKeys.iterator(); while (keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if (key.isAcceptable()) { // 处理新的连接 ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel(); SocketChannel socketChannel = serverChannel.accept(); socketChannel.configureBlocking(false); // 注册读事件 socketChannel.register(selector, SelectionKey.OP_READ); } else if (key.isReadable()) { // 处理读事件 SocketChannel socketChannel = (SocketChannel) key.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); int bytesRead = socketChannel.read(buffer); if (bytesRead > 0) { buffer.flip(); byte[] data = new byte[buffer.remaining()]; buffer.get(data); System.out.println("Received: " + new String(data)); } } // 移除处理过的键 keyIterator.remove(); } } } } ```
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值