非阻塞IO模式原理

与阻塞模式对应的另一种模式叫非阻塞IO模式,在整个通信过程中读和写操作不会阻塞,当前处理线程不存在阻塞情况。从A机器到B机器它的通信过程是:A机器一条线程将通道设置为写事件后往下执行,而另外一条线程遍历到此通道有字节要写并往socket写数据,B机器一条线程遍历到此通道有字节要读,交给另外一条线程对socket读数据,处理完又把通道设置为写事件,遍历线程遍历到此通道有字节要写,又往socket写数据传往A机器,不断往下循环此操作直到完成通信。这个过程每台机器都有两类主要线程,一类是负责逻辑处理且将通道改为可写或可读事件的线程,另外一类是专门用于遍历通道并负责socket读写的线程,这种方式就是非阻塞IO模式。

在阻塞IO模式中,存在一个服务端套接字ServerSocket用于接收客户端连进来的Socket,而不管是阻塞还是非阻塞IO最终都需要获取socket才能进行读写操作,与阻塞模式对应,非阻塞模式用于接收客户端socket的对象是ServerSocketChannel,另外,阻塞模式直接使用Socket对象进行读写操作,而非阻塞模式则使用SocketChannel对象进行读写操作,但SocketChannel本质上最终也是通过Socket读取与写入,只是读取或写入时引入了缓冲区概念。最后,还有一个很重要的对象是选择器Selector,它提供对所有channel各种感兴趣事件的筛选功能,即哪些通道需要怎样的处理通过它选择出来的。

往下说说非阻塞模式实现的原理,如下图,ServerSocketChannel调用open()方法初始化封装在里面的socket服务并将ServerSocketChannelOP_ACCEPT事件注册到Selector中,而操作系统则创建socket底层数据结构并监听客户端socket连接,对于客户端连接操作系统会统一放到一个队列中进行维护。接着是很重要的应用层轮询操作,不断执行Selector检索出感兴趣的事件,假如刚好有三个客户端socket连进来,Selector选择出三个OP_ACCEPT事件,调用ServerSocketChannel.accept()接收三个客户端通道SocketChannel对象,再将这三个客户端通道以OP_READOP_WRITE注册到Selector中以便后面进行读写操作,往下如果Selector遍历出OP_READOP_WRITE事件则可以对对应的channel进行读写操作了。

 

Selector在其中扮演最重要的角色,看看它是如何完成感兴趣的事件的筛选的。如上图,中间Selector便是它的大体结构,维护了registeredKeysselectedKeyscancelledKeys三个集合,还有一张channelKey对应关系的表,而Key则包含了感兴趣事件集interestOps和已准备好的事件集readyOps。其中registeredKeys存放注册到Selector的所有key,而selectedKeys即是被选中的key,它是检测到registeredKeyskey感兴趣的事件发生后存放key的地方,cancelledKeys则是已经调用了cancel()方法待反注册的key。当应用层中Selector不断调用select()方法时,会先根据cancelledKeys去删除registeredKeysselectedKeys对应的key以至取消对应的key,然后间接调用操作系统去做操作系统级别的select,一旦有registeredKeys感兴趣的事件则将对应事件的key添加到selectedKeys中,如selectedKeys已存在key了则将事件添加到key中的已准备好的事件集readyOps中。经过此番操作,当应用层调用SelectorselectedKeys()则取到被选中的key集,进而可以获取到感兴趣事件对应的channel,根据事件对channel进行操作。

    理解了非阻塞IO模式的原理有助于在实际场景中对网络IO的模式选型,一般在同时需要处理多个连接的高并发场景中会使用非阻塞NIO模式,它通过一个或少量线程去维护连接,而把具体的读写和逻辑处理交由其他线程处理,大大提高了机器的使用率,压榨机器CPU。而如果使用阻塞IO模式则可能线程都阻塞在IO而导致机器使用率较低。






========广告时间========鄙人的新书《Tomcat内核设计剖析》已经在京东销售了,有需要的朋友可以到 https://item.jd.com/12185360.html 进行预定。感谢各位朋友。[为什么写《Tomcat内核设计剖析》](http://blog.youkuaiyun.com/wangyangzhizhou/article/details/74080321)=========================欢迎关注:![这里写图片描述](https://img-blog.youkuaiyun.com/20170509102539658?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2FuZ3lhbmd6aGl6aG91/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

========广告时间======== 鄙人的新书《Tomcat内核设计剖析》已经在京东销售了,有需要的朋友可以到 https://item.jd.com/12185360.html 进行预定。感谢各位朋友。 [为什么写《Tomcat内核设计剖析》](http://blog.youkuaiyun.com/wangyangzhizhou/article/details/74080321) ========================= 欢迎关注: ![这里写图片描述](https://img-blog.youkuaiyun.com/20170509102539658?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2FuZ3lhbmd6aGl6aG91/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
<think> 嗯,用户这次想了解阻塞IO非阻塞IO的相关概念,正好我有三份引用资料可以系统性地解答。从对话历史看,用户之前问过Major GC,现在转向网络IO模型,可能是Java开发人员正在学习基础知识。 用户提出四个明确要点:概念、原理、区别和应用。引用[1]恰好提供了阻塞IO的核心定义,引用[2]详细对比了各种IO模型,引用[3]则补充了NIO的细节。需要特别注意用户强调的"原理"部分,这涉及到操作系统层面的处理机制。 对于概念部分,准备用类比方式解释:把阻塞IO比作排队买奶茶必须等完成才能离开,非阻塞IO像取号后可以逛商场但需不断回来查看。原理层面要区分内核缓冲区操作和用户线程状态变化,引用[2]提到的文件描述符就绪状态是关键切入点。 区别对比会分成五个维度展开,尤其是线程阻塞状态和轮询机制这两个本质差异。考虑到用户可能是Java开发者,应用场景部分重点突出ServerSocketChannel和Selector的实际用法,引用[3]关于缓冲区检测的提醒特别重要——这是NIO编程容易出错的地方。 最后设计的问题要延续技术深度,比如第五个问题关于Netty的优化,既能结合引用[2]的异步IO模型,又能引导用户探索实际框架。需要检查所有术语的准确性,比如"水平触发"和"边沿触发"这样的专业表述是否解释清楚。 </think> 以下是阻塞IO非阻塞IO的全面解析,结合概念、原理、区别及应用场景: --- ### 一、核心概念原理 #### 1. **阻塞IO (Blocking IO)** - **原理**: 当线程发起IO请求(如读取文件或网络数据),**线程被挂起**直到内核完成数据拷贝到用户空间[^1]。 数学描述: $$\text{用户线程状态} = \begin{cases} \text{RUNNING} & \text{数据已就绪} \\ \text{WAITING} & \text{等待内核操作完成} \end{cases}$$ - **特点**: - 同步执行,线程无法处理其他任务 - 简单编程模型(如Java的 `InputStream.read()`) #### 2. **非阻塞IO (Non-blocking IO)** - **原理**: 线程发起IO请求后**立即返回状态**(成功或`EAGAIN`错误),需**轮询检查就绪状态**[^2][^3]。 内核行为: $$ \text{返回码} = \begin{cases} \text{数据长度} & \text{缓冲区有数据} \\ \text{-1 (EAGAIN)} & \text{数据未就绪} \end{cases}$$ - **特点**: - 线程不被阻塞,可执行其他任务 - 需主动轮询消耗CPU(如Java NIO的 `SocketChannel.configureBlocking(false)`) --- ### 二、关键区别对比 | **维度** | **阻塞IO** | **非阻塞IO** | |------------------|-------------------------------------|---------------------------------------| | **线程状态** | 挂起等待 | 持续运行(需轮询) | | **响应延迟** | 取决于IO操作耗时 | 立即响应状态 | | **CPU利用率** | 等待期间CPU空闲 | 轮询消耗CPU | | **编程复杂度** | 简单(顺序执行) | 复杂(需状态机/事件循环) | | **适用场景** | 低并发、简单逻辑 | 高并发、需资源复用[^1] | --- ### 三、应用场景实现 #### 1. **阻塞IO典型应用** - **Java实现**: ```java // 传统Socket读取(线程阻塞直到数据到达) try (Socket socket = new Socket("host", 80); InputStream in = socket.getInputStream()) { byte[] data = new byte[1024]; int bytesRead = in.read(data); // 阻塞点 } ``` - **适用场景**: CLI工具、低并发服务端(如Tomcat BIO模式) #### 2. **非阻塞IO典型应用** - **Java NIO核心组件**: - `Selector`:监听多个`Channel`的就绪事件 - `Buffer`:数据存储容器 - `Channel`:支持非阻塞读写 ```java Selector selector = Selector.open(); SocketChannel channel = SocketChannel.open(); channel.configureBlocking(false); channel.connect(new InetSocketAddress("host", 80)); channel.register(selector, SelectionKey.OP_READ); // 注册读事件 while (true) { int readyChannels = selector.select(); // 检查就绪通道 if (readyChannels > 0) { Set<SelectionKey> keys = selector.selectedKeys(); // 处理IO事件 } } ``` - **适用场景**: 高并发服务器(如Netty、Kafka)、实时数据处理[^3] --- ### 四、进阶模型对比 ```mermaid graph LR A[IO模型] --> B[阻塞IO] A --> C[非阻塞IO] A --> D[IO多路复用] A --> E[异步IO] C -->|需轮询| F[CPU开销高] D -->|Selector管理| G[单线程处理多连接] E -->|内核回调| H[零等待] ``` > **关键补充**: > - **IO多路复用(如epoll)**:本质是非阻塞IO的优化,通过一次系统调用监听多个文件描述符[^2] > - **异步IO(AIO)**:内核完成所有操作后通知应用,**无轮询需求**(如Java AIO的 `AsynchronousSocketChannel`)[^2] --- ### 五、实践注意事项 1. **NIO的陷阱**: - 缓冲区需手动`flip()`切换读写模式 - 不完整数据需拼接(如半包/粘包问题)[^3] 2. **资源管理**: - 非阻塞IO需注意`Selector`空轮询Bug(Linux epoll缺陷) - 连接数过高时改用异步IO降低线程开销 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值