什么是I/O多路复用

本文介绍了BIO、NIO和I/O多路复用的概念及其在处理并发连接时的优缺点。BIO是一对一的连接方式,而NIO通过线程池实现了多客户端并发处理。I/O多路复用,特别是Epoll,通过红黑树和事件驱动机制,提高了系统在高并发情况下的效率,减少了系统资源的消耗。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

要想知道什么是I/O多路复用,首先我们要来了解一下BIO和NIO

1、BIO

BIO又叫阻塞IO。一般我们传统的JDK内置的Socket编程就是阻塞IO。一次只能有一个客户端连接。

其基本实现流程如下:服务端:首先创建socket接口,通过bind函数将接口号与端口号进行绑定,然后通过listen函数进行监听,服务端进入了监听状态后,通过调用accept函数从内核获取客户端的连接,如果没有客户端连接,就会阻塞等待客户端的连接到来。

客户端:首先创建好Socket,然后调用connect函数发起连接,然后通过tcp三次握手进行连接。

这个IO模型简单的解决了我们服务端和客户端的通信连接,但是他有一个很大的弊端,就是只能进行一对一的连接,不能服务更多的客户,那么为了解决这个问题,服务更多的客户,于是就有了NIO

2、NIO

NIO又叫非阻塞I/O模型,

当服务器与客户端 TCP 完成连接后,通过 pthread_create() 函数创建线程,然后将「已连接 Socket」的文件描述符传递给线程函数,接着在线程里和客户端进行通信,从而达到并发处理的目的。

如果每来一个连接就创建一个线程,线程运行完后,还得操作系统还得销毁线程,虽说线程切换的上写文开销不大,但是如果频繁创建和销毁线程,系统开销也是不小的。

那么,我们可以使用线程池的方式来避免线程的频繁创建和销毁,所谓的线程池,就是提前创建若干个线程,这样当由新连接建立时,将这个已连接的 Socket 放入到一个队列里,然后线程池里的线程负责从队列中取出已连接 Socket 进程处理。

这种模型虽然解决了服务多个用户的问题,但是每次新到来一个TCP连接,就需要分配一个进行或者线程,当同时到来的连接过多时,我们系统也是维护不过来的。

所以为了提高我们的性能,就出现了I/O的多路复用

3、I/O多路复用

通过一个线程就可以管理多个 socket,这个线程不断去轮询多个 socket 的状态,只有当socket 真正有读写事件发生才会占用资源来进行实际的读写操作。实现I/O多路复用有三种方式:

1.select

将已连接的 Socket 都放到一个文件描述符集合,然后调用 select 函数将文件描述符集合拷贝到内核里,让内核来检查是否有网络事件产生,检查的方式很粗暴,就是通过遍历文件描述符集合的方式,当检查到有事件产生后,将此 Socket 标记为可读或可写, 接着再把整个文件描述符集合拷贝回用户态里,然后用户态还需要再通过遍历的方法找到可读或可写的 Socket,然后再对其处理。

2.poll

实现方式和select没有太大的本质区别。

3.epoll

epoll 通过两个方面,很好解决了 select/poll 的问题。

第一点,epoll 在内核里使用红黑树来跟踪进程所有待检测的文件描述字,把需要监控的 socket 通过 epoll_ctl() 函数加入内核中的红黑树里,红黑树是个高效的数据结构,增删查一般时间复杂度是 O(logn),通过对这棵黑红树进行操作,这样就不需要像 select/poll 每次操作时都传入整个 socket 集合,只需要传入一个待检测的 socket,减少了内核和用户空间大量的数据拷贝和内存分配。

第二点, epoll 使用事件驱动的机制,内核里维护了一个链表来记录就绪事件,当某个 socket 有事件发生时,通过回调函数内核会将其加入到这个就绪事件列表中,当用户调用 epoll_wait() 函数时,只会返回有事件发生的文件描述符的个数,不需要像 select/poll 那样轮询扫描整个 socket 集合,大大提高了检测的效率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值