IO多路复用p1

本文深入探讨了BIO的局限性及其在高并发场景下的挑战,介绍了NIO如何通过非阻塞I/O解决多客户端问题,以及多路复用技术如select、poll和epoll的工作原理和性能考量。

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


听了小刘讲源码的小刘老师视频整理,侵删~

BIO

BIO是什么,有什么缺点?

accept和socket读写都会阻塞线程,所以需要开多线程进行C/S交互,这样很难做到C10K。

BIO的B是blocking(阻塞),服务端开发,使用SeverSocket()绑定完端口,使用listen监听,等待accept(),accept会阻塞主线程。收到accept,程序就会收到一个客户端与当前服务端链接的socket,可针对该socket中进行读写。但是socket读写都会阻塞当前的线程,可以采用多线程的方法进行C/S交互,但这样很难做到C10K。
要1W个客户端链接服务端,那么1W个线程也难以支持,包括上下文切换,没办法执行。

NIO

NIO如何解决多客户端问题?

NIO包给我们提供一套非阻塞的接口,这样我们就不需要为每一个C/S长连接保留单独的处理线程,阻塞IO之所以要给每一个长连接保留一个线程,是因为它阻塞。NIO API具备非阻塞特性,就可以用一个线程去检测n个socket。NIO包提供了一个选择器selector,把需要检查的socket注册到selector中,主线程阻塞在selector#select方法里,当选择器发现某个socket就绪了,就会唤醒主线程,就可以通过selector获得就绪状态的socket并进行相对应的处理。

多路复用

select函数实现原理?

每次调用kernel里的select函数,都会涉及到用户态和内核态的切换,还需要传递需要检查的socket集合,也就是需要检查的(文件描述符ID)fd——Linux系统下一切皆文件,fd就是文件系统中socket生成的对应文件描述符。select函数被调用后,首先按照fd集合去检查内存中的socket状态,复杂度O(N),如果有就绪状态的socket就直接返回,不会阻塞当前调用的线程,否则就说明没有就绪状态的socket,那么阻塞当前线程,直到有就绪状态的socket。

select函数监听socket数量限制?

默认最大1024,实际小于1024。因为fd_set的结构是bitmap位图结构,默认长度是1024。另外一点可能是处于性能的考虑,因为select在检查到就绪状态socket后做了两件事,一标记当前的socket就绪,第二会返回select函数,唤醒Java线程,在Java层面,收到一个int值,表示有几个socket处于就绪状态。接着又是一个O(N)检查fd—set集合中有哪几个是就绪的。涉及到用户态和内核态的来回切换。

socket会一直占用cpu轮询检查吗?

补充(select第一次未发现就绪状态的socket,过段时间又反馈了就绪socket的状态,socket会一直占用cpu轮询检查吗?)

操作系统调度:单核CPU在同一时刻只能运行一个进程,操作系统最主要的功能就是系统调度,就是有N个进程,让N个进程在CPU上切换进行。未挂起的进程都在工作队列内,都有机会获得CPU的执行权,挂起的进程会从工作队列内移除,反应到Java层面就是线程阻塞了。Linux系统下的线程其实就是轻量级的进程。

操作系统中断:键盘打字,按下一个键之后会发生电流信号,主板感应到之后,就会触发CPU中断,所谓中断就是让正在执行的进程保留进程上下文,避让出CPU给中断让道。中断就会拿到CPU执行权,执行响应的程序。

select函数第一遍轮询没有发现就绪状态的socket,就会把当前进程保留给需要检查的socket等待队列中。socket有三大核心,读缓存、写缓存和等待队列,select函数会把当前进程保留到每个需要检查的socket等待队列之后,会把当前进程从工作队列移出。移出其实就是挂起当前进程,select函数也不会再运行。下一个阶段,客户端发送数据到服务端,数据通过网线到网卡,网卡到DMA硬件的方式将数据直接写在内存中,整个过程CPU不参与。当数据传输完之后,就会触发网络数据传输完毕的中断程序,中断程序会顶掉CPU当前执行的进程。
根据内存中的数据包,分析出数据包是哪个socket的数据,TCP/IP协议保证传输时候有端口号,所以数据包是有端口号的。根据端口号找到socket实例,将数据导入读缓存里,导入完成后就检查socket等待队列是不是有等待者,如果有就把等待者移入工作队列中,中断执行到这里就完成了。进程又在一起进入工作队列,获取到CPU时间片。当前进程再执行select函数,发现有socket准备就绪,就会标记当前就绪文件描述符fd,select函数执行完毕,返回Java层面。就会涉及内核态和用户态的转换。之后轮询检查fd是否被标记,处理被标记的socket。

poll与select的区别?

二者最大的不一样就是传参。select使用的bitmap,表示需要检查的socket集合,而poll使用数组结构,数组没有1024这个限制,可以监听多个线程。

epoll的产生背景?

select和poll的缺陷:
1.每次调用都需要提供所有需要监听的socket文件描述符集合,线程死循环调用select&poll函数,涉及到用户空间到内核空间的数据拷贝过程,虽然监听的socket集合数据变化非常小,但是该函数就较耗费性能,每次调用都进行数据拷贝。
2.返回值都是int型,需要再次循环检查哪个socket是就绪状态的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值