赞
踩
阻塞模式下,相关方法都会导致线程暂停
ServerSocketChannel.accept 会在没有连接建立时让线程暂停
SocketChannel.read 会在没有数据可读时让线程暂停
阻塞的表现其实就是线程暂停了,暂停期间不会占用 cpu,但线程相当于闲置
单线程下,阻塞方法之间相互影响,几乎不能正常工作,需要多线程支持
但多线程下,有新的问题,体现在以下方面
服务端事例代码:
- // 使用 nio 来理解阻塞模式, 单线程
- // 0. ByteBuffer
- ByteBuffer buffer = ByteBuffer.allocate(16);
- // 1. 创建了服务器
- ServerSocketChannel ssc = ServerSocketChannel.open();
-
- // 2. 绑定监听端口
- ssc.bind(new InetSocketAddress(8080));
-
- // 3. 连接集合
- List<SocketChannel> channels = new ArrayList<>();
- while (true) {
- // 4. accept 建立与客户端连接, SocketChannel 用来与客户端之间通信
- log.debug("connecting...");
- SocketChannel sc = ssc.accept(); // 阻塞方法,线程停止运行
- log.debug("connected... {}", sc);
- channels.add(sc);
- for (SocketChannel channel : channels) {
- // 5. 接收客户端发送的数据
- log.debug("before read... {}", channel);
- channel.read(buffer); // 阻塞方法,线程停止运行
- buffer.flip();
- debugRead(buffer);
- buffer.clear();
- log.debug("after read...{}", channel);
- }
- }
客户端:
- SocketChannel sc = SocketChannel.open();
- sc.connect(new InetSocketAddress("localhost", 8080));
- System.out.println("waiting...");
非阻塞模式下,相关方法都会不会让线程暂停
在 ServerSocketChannel.accept 在没有连接建立时,会返回 null,继续运行
SocketChannel.read 在没有数据可读时,会返回 0,但线程不必阻塞,可以去执行其它 SocketChannel 的 read 或是去执行 ServerSocketChannel.accept
写数据时,线程只是等待数据写入 Channel 即可,无需等 Channel 通过网络把数据发送出去
但非阻塞模式下,即使没有连接建立,和可读数据,线程仍然在不断运行,白白浪费了 cpu
数据复制过程中,线程实际还是阻塞的(AIO 改进的地方)
服务器端demo代码,客户端代码不变
- // 使用 nio 来理解非阻塞模式, 单线程
- // 0. ByteBuffer
- ByteBuffer buffer = ByteBuffer.allocate(16);
- // 1. 创建了服务器
- ServerSocketChannel ssc = ServerSocketChannel.open();
- ssc.configureBlocking(false); // 非阻塞模式
- // 2. 绑定监听端口
- ssc.bind(new InetSocketAddress(8080));
- // 3. 连接集合
- List<SocketChannel> channels = new ArrayList<>();
- while (true) {
- // 4. accept 建立与客户端连接, SocketChannel 用来与客户端之间通信
- SocketChannel sc = ssc.accept(); // 非阻塞,线程还会继续运行,如果没有连接建立,但sc是null
- if (sc != null) {
- log.debug("connected... {}", sc);
- sc.configureBlocking(false); // 非阻塞模式
- channels.add(sc);
- }
- for (SocketChannel channel : channels) {
- // 5. 接收客户端发送的数据
- int read = channel.read(buffer);// 非阻塞,线程仍然会继续运行,如果没有读到数据,read 返回 0
- if (read > 0) {
- buffer.flip();
- debugRead(buffer);
- buffer.clear();
- log.debug("after read...{}", channel);
- }
- }
- }
单线程可以配合 Selector 完成对多个 Channel 可读写事件的监控,这称之为多路复用。
多路复用仅针对网络 IO、普通文件 IO 没法利用多路复用
如果不用 Selector 的非阻塞模式,线程大部分时间都在做无用功,而 Selector 能够保证
有可连接事件时才去连接
有可读事件才去读取
有可写事件才去写入
限于网络传输能力,Channel 未必时时可写,一旦 Channel 可写,会触发 Selector 的可写事件
一个线程配合 selector 就可以监控多个 channel 的事件,事件发生线程才去处理。避免非阻塞模式下所做无用功
让这个线程能够被充分利用
节约了线程的数量
减少了线程上下文切换
Selector selector = Selector.open();
也称之为注册事件,绑定的事件 selector 才会关心
- channel.configureBlocking(false);
- SelectionKey key = channel.register(selector, 绑定事件);
channel 必须工作在非阻塞模式
FileChannel 没有非阻塞模式,因此不能配合 selector 一起使用
绑定的事件类型可以有
connect - 客户端连接成功时触发
accept - 服务器端成功接受连接时触发
read - 数据可读入时触发,有因为接收能力弱,数据暂不能读入的情况
write - 数据可写出时触发,有因为发送能力弱,数据暂不能写出的情况
可以通过下面三种方法来监听是否有事件发生,方法的返回值代表有多少 channel 发生了事件
方法1,阻塞直到绑定事件发生 (常用)
int count = selector.select();
方法2,阻塞直到绑定事件发生,或是超时(时间单位为 ms)
int count = selector.select(long timeout);
方法3,不会阻塞,也就是不管有没有事件,立刻返回,自己根据返回值检查是否有事件
int count = selector.selectNow();
事件发生时
客户端发起连接请求,会触发 accept 事件
客户端发送数据过来,客户端正常、异常关闭时,都会触发 read 事件,另外如果发送的数据大于 buffer 缓冲区,会触发多次读取事件
channel 可写,会触发 write 事件
在 linux 下 nio bug 发生时
调用 selector.wakeup()
调用 selector.close()
selector 所在线程 interrupt
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。