当前位置: 首页 > news >正文

网络编程 之 从BIO到 NIO加多线程高性能网络编程实战

网络编程 之 从BIO到 NIO加多线程高性能网络编程实战

阻塞非阻塞/同步异步

概念

image-20241120205736037

阻塞与非阻塞是对同一个线程来说的,在某个时刻,线程要么处于阻塞,要么处于非阻塞

同步和异步是指:发送方和接收方是否协调步调一致

  • 阻塞(blocking) IO:资源不可用时,IO请求一直阻塞,直到反馈结果(有数据或超时)。
  • 非阻塞(non-blocking)IO: 资源不可用时,IO请求离开返回,返回数据标识资源不可用。
  • 同步(synchronous) IO:应用阻塞在发送或接收数据的状态,直到数据传输成功或返回结果。
  • 异步(asynchronous) IO:应用发送或接收数据后立刻返回,处理结果通过回调来通知。

阻塞和非阻塞是进程在访问数据的时候,数据是否准备就绪的一种处理方式,当数据没有准备的时候。

阻塞:往往需要等待缓冲区中的数据准备好过后才处理其他的事情,否则一直等待在那里。

非阻塞:当我们的进程访问我们的数据缓冲区的时候,如果数据没有准备好则直接返回,不会等待。如果数据已经准备好,也直接返回。

同步和异步都是基于应用程序和操作系统处理 IO 事件所采用的方式。比如同步:是应用程序要直接参与 IO 读写的操作。异步:所有的 IO 读写交给操作系统去处理,应用程序只需要等待通知。

同步方式在处理 IO 事件的时候,必须阻塞在某个方法上面等待我们的 IO 事件完成(阻塞 IO 事件或者通过轮询 IO事件的方式),对于异步来说,所有的 IO 读写都交给了操作系统。这个时候,我们可以去做其他的事情,并不需要去完成真正的 IO 操作,当操作完成 IO 后,会给我们的应用程序一个通知。

同步 : 阻塞到 IO 事件,阻塞到 read 或则 write。这个时候我们就完全不能做自己的事情。让读写方法加入到线程里面,然后阻塞线程来实现,对线程的性能开销比较大。

代码中使用的API:ServerSocket#accept、InputStream#read 都是阻塞的API。操作系统底层API中,默认Socket操作都是Blocking型,send/recv等接口都是阻塞的。

带来的问题: 阻塞导致在处理网络I/O时,一个线程只能处理一个网络连接。

组合

同步阻塞

同步非阻塞

异步阻塞

异步非阻塞

IO模型

5种IO模型

  • 阻塞IO模型:Socket Read 面向字节流InputStream、OutPutStream
  • 非阻塞IO模型:不再面向字节流,面向缓冲 API,Java NIO 面向缓冲实现
  • IO复用模型
  • 信号驱动IO模型
  • 异步IO模型:AIO Windows的IOCP支持比较好,目前linux没有好的支持

linux底层IO多路复用事件模型

select、poll,返回后遍历文件描述符来获取已经就绪的socket,描述符越多效率越低

epoll、kqueue,不需要遍历了,注册了一个事件函数,效率比poll要高

windows aio 事件模型:IOCP

JAVA NIO

始于Java 1.4,提供了新的JAVA IO操作非阻塞API。用意是替代Java IO 和 Java Networking相关的API。

NIO中的三个核心组件

  • Buffer:缓冲区
  • Channel:通道
  • Selector:选择器

Buffer 缓冲区

Buffer 缓冲区介绍

缓冲区本质上是一个可以写入数据的内存块(类似数组),然后可以再次读取。此内存块包含在NIO Buffer对象中,该对象提供了一组方法,可以更轻松地使用内存块。

相比较直接对数组的操作,Buffer API更加容易操作和管理。

使用Buffer进行数据写入与读取,需要进行如下四个步骤:

  1. 将数据写入缓冲区

  2. 调用buffer.flip(),转换为读取模式

  3. 缓冲区读取数据

  4. 调用buffer.clear()或buffer.compact()转为写模式

Buffer 缓冲区工作原理

Buffer三个重要属性:

capacity 容量: 作为一个内存块,Buffer具有一定的固定大小,也称为“容量” 。

position 位置: 写入模式时代表写数据的位置。读取模式时代表读取数据的位置。

limit 限制: 写入模式,限制等于buffer的容量。读取模式下,limit等于写入的数据量。

image-20241120210854757

ByteBuffer内存类型

介绍

ByteBuffer为性能关键型代码提供了直接内存(direct堆外)和非直接内存(heap堆)两种实现。

堆内内存获取的方式:

ByteBuffer directByteBuffer = ByteBuffer.allocate(noBytes);

堆外内存获取的方式:

ByteBuffer directByteBuffer = ByteBuffer.allocateDirect(noBytes);

好处:

1、 进行网络IO或者文件IO时比heapBuffer少一次拷贝。(file/socket ---- OS memory ---- jvm heap)

GC会移动对象内存,在写file或socket的过程中, JVM的实现中,会先把数据复制到堆外,再进行写入。

2、 GC范围之外,降低GC压力,但实现了自动管理。DirectByteBuffer中有一个Cleaner对象(PhantomReference),

Cleaner被GC前会执行clean方法,触发DirectByteBuffer中定义的Deallocator

建议:

1、 性能确实可观的时候才去使用;分配给大型、长寿命; (网络传输、文件读写场景)

2、 通过虚拟机参数MaxDirectMemorySize限制大小,防止耗尽整个机器的内存;

使用
堆内
public class BufferDemo {public static void main(String[] args) {// 构建一个byte字节缓冲区,容量是4ByteBuffer byteBuffer = ByteBuffer.allocate(10);// 默认写入模式,查看三个重要的指标System.out.println(String.format("初始化:capacity容量:%s, position位置:%s, limit限制:%s", byteBuffer.capacity(),byteBuffer.position(), byteBuffer.limit()));// 写入2字节的数据byteBuffer.put((byte) 1);byteBuffer.put((byte) 2);byteBuffer.put((byte) 3);// 再看数据System.out.println(String.format("写入3字节后,capacity容量:%s, position位置:%s, limit限制:%s", byteBuffer.capacity(),byteBuffer.position(), byteBuffer.limit()));// 转换为读取模式(不调用flip方法,也是可以读取数据的,但是position记录读取的位置不对)System.out.println("#######开始读取");byteBuffer.flip();byte a = byteBuffer.get();System.out.println(a);byte b = byteBuffer.get();System.out.println(b);System.out.println(String.format("读取2字节数据后,capacity容量:%s, position位置:%s, limit限制:%s", byteBuffer.capacity(),byteBuffer.position(), byteBuffer.limit()));// 继续写入3字节,此时读模式下,limit=3,position=2.继续写入只能覆盖写入一条数据// clear()方法清除整个缓冲区。compact()方法仅清除已阅读的数据。转为写入模式byteBuffer.compact(); // buffer : 1 , 3byteBuffer.put((byte) 3);byteBuffer.put((byte) 4);byteBuffer.put((byte) 5);System.out.println(String.format("最终的情况,capacity容量:%s, position位置:%s, limit限制:%s", byteBuffer.capacity(),byteBuffer.position(), byteBuffer.limit()));// rewind() 重置position为0// mark() 标记position的位置// reset() 重置position为上次mark()标记的位置}
}
堆外
public class DirectBufferDemo {public static void main(String[] args) {// 构建一个byte字节缓冲区,容量是4ByteBuffer byteBuffer = ByteBuffer.allocateDirect(4);// 默认写入模式,查看三个重要的指标System.out.println(String.format("初始化:capacity容量:%s, position位置:%s, limit限制:%s", byteBuffer.capacity(),byteBuffer.position(), byteBuffer.limit()));// 写入2字节的数据byteBuffer.put((byte) 1);byteBuffer.put((byte) 2);byteBuffer.put((byte) 3);// 再看数据System.out.println(String.format("写入3字节后,capacity容量:%s, position位置:%s, limit限制:%s", byteBuffer.capacity(),byteBuffer.position(), byteBuffer.limit()));// 转换为读取模式(不调用flip方法,也是可以读取数据的,但是position记录读取的位置不对)System.out.println("#######开始读取");byteBuffer.flip();byte a = byteBuffer.get();System.out.println(a);byte b = byteBuffer.get();System.out.println(b);System.out.println(String.format("读取2字节数据后,capacity容量:%s, position位置:%s, limit限制:%s", byteBuffer.capacity(),byteBuffer.position(), byteBuffer.limit()));// 继续写入3字节,此时读模式下,limit=3,position=2.继续写入只能覆盖写入一条数据// clear()方法清除整个缓冲区。compact()方法仅清除已阅读的数据。转为写入模式byteBuffer.compact();byteBuffer.put((byte) 3);byteBuffer.put((byte) 4);byteBuffer.put((byte) 5);System.out.println(String.format("最终的情况,capacity容量:%s, position位置:%s, limit限制:%s", byteBuffer.capacity(),byteBuffer.position(), byteBuffer.limit()));byteBuffer.array();// rewind() 重置position为0// mark() 标记position的位置// reset() 重置position为上次mark()标记的位置}
}

Channel通道

详解

image-20241121073629317

Channel的API涵盖了UDP/TCP网络和文件IO

FileChannel

DatagramChannel

SocketChannel

ServerSocketChannel

和标准IO Stream操作的区别

在一个通道内进行读取和写入,可以非阻塞读取和写入通道,通道始终读取或写入缓冲区,而stream通常是单向的(input或output)

SocketChannel 客户端

SocketChannel用于建立TCP网络连接,类似java.net.Socket。有两种创建socketChannel形式:

  1. 客户端主动发起和服务器的连接。

  2. 服务端获取的新连接

// 客户端主动发起连接的方式
SocketChannel socketChannel = SocketChannel.open(); 
socketChannel .configureBlocking( false ); // 设置为非阻塞模式
socketChannel.connect(new InetSocketAddress(“127.0.0.1”,8080));socketChannel.write(byteBuffer); // 发送请求数据 – 向通道写入数据
int bytesRead = socketChannel.read( byteBuffer ); // 读取服务端返回 – 读取缓冲区的数据
socketChannel.close(); // 关闭连接

write写: write()在尚未写入任何内容时就可能返回了。需要在循环中调用write()。

read读: read()方法可能直接返回而根本不读取任何数据,根据返回的int值判断读取了多少字节。

ServerSocketChannel 服务端

ServerSocketChannel可以监听新建的TCP连接通道,类似ServerSocket。

// 创建网络服务端
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false); // 设置为非阻塞模式
serverSocketChannel.socket().bind(new InetSocketAddress(8080)); // 绑定端口
while(true){SocketChannel socketChannel = serverSocketChannel.accept(); // 获取新tcp连接通道if(socketChannel != null){ // tcp请求 读取/响应}
}

**serverSocketChannel.accept():**如果该通道处于非阻塞模式,那么如果没有挂起的连接,该方法将立即返回null。 必须检查返回的 SocketChannel是否为null

使用Channel写一个服务器

模拟服务端NIOServer
/*** 直接基于非阻塞的写法*/
public class NIOServer {public static void main(String[] args) throws Exception {// 创建网络服务端ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.configureBlocking(false); // 设置为非阻塞模式serverSocketChannel.socket().bind(new InetSocketAddress(8080)); // 绑定端口System.out.println("启动成功");while (true) {SocketChannel socketChannel = serverSocketChannel.accept(); // 获取新tcp连接通道// tcp请求 读取/响应if (socketChannel != null) {System.out.println("收到新连接 : " + socketChannel.getRemoteAddress());socketChannel.configureBlocking(false); // 默认是阻塞的,一定要设置为非阻塞try {ByteBuffer requestBuffer = ByteBuffer.allocate(1024);while (socketChannel.isOpen() && socketChannel.read(requestBuffer) != -1) {// 长连接情况下,需要手动判断数据有没有读取结束 (此处做一个简单的判断: 超过0字节就认为请求结束了)if (requestBuffer.position() > 0) break;}if(requestBuffer.position() == 0) continue; // 如果没数据了, 则不继续后面的处理requestBuffer.flip();byte[] content = new byte[requestBuffer.limit()];requestBuffer.get(content);System.out.println(new String(content));System.out.println("收到数据,来自:"+ socketChannel.getRemoteAddress());// 响应结果 200String response = "HTTP/1.1 200 OK\r\n" +"Content-Length: 11\r\n\r\n" +"Hello World";ByteBuffer buffer = ByteBuffer.wrap(response.getBytes());while (buffer.hasRemaining()) {socketChannel.write(buffer);// 非阻塞}} catch (IOException e) {e.printStackTrace();}}}// 用到了非阻塞的API, 在设计上,和BIO可以有很大的不同.继续改进}
}
模拟客户端NIOClient
public class NIOClient {public static void main(String[] args) throws Exception {SocketChannel socketChannel = SocketChannel.open();socketChannel.configureBlocking(false);socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080));while (!socketChannel.finishConnect()) {// 没连接上,则一直等待Thread.yield();}Scanner scanner = new Scanner(System.in);System.out.println("请输入:");// 发送内容String msg = scanner.nextLine();ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());while (buffer.hasRemaining()) {socketChannel.write(buffer);}// 读取响应System.out.println("收到服务端响应:");ByteBuffer requestBuffer = ByteBuffer.allocate(1024);while (socketChannel.isOpen() && socketChannel.read(requestBuffer) != -1) {// 长连接情况下,需要手动判断数据有没有读取结束 (此处做一个简单的判断: 超过0字节就认为请求结束了)if (requestBuffer.position() > 0) break;}requestBuffer.flip();byte[] content = new byte[requestBuffer.limit()];requestBuffer.get(content);System.out.println(new String(content));scanner.close();socketChannel.close();}}
模拟服务端NIOServer的升级版1

升级前的版本是一直轮询通道的方式,接收到一个客户端channel就处理一个

缺点:只能一个挨着一个执行,轮询通道的方式,低效,浪费CPU

升级后的版本也是一直轮询通道的方式,接收一个客户端channel,先缓存到一个列表中,当没有客户端channel的时候再去遍历列表处理

缺点:虽然能在少量的请求中,解决轮询浪费CPU,但是请求量大了导致要么一直在接受channel,添加到列表中,无法处理请求,要么一直在处理请求无法接收请求

*** 直接基于非阻塞的写法,一个线程处理轮询所有请求*/
public class NIOServer1 {/*** 已经建立连接的集合*/private static ArrayList<SocketChannel> channels = new ArrayList<>();public static void main(String[] args) throws Exception {// 创建网络服务端ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.configureBlocking(false); // 设置为非阻塞模式serverSocketChannel.socket().bind(new InetSocketAddress(8080)); // 绑定端口System.out.println("启动成功");while (true) {SocketChannel socketChannel = serverSocketChannel.accept(); // 获取新tcp连接通道// tcp请求 读取/响应if (socketChannel != null) {System.out.println("收到新连接 : " + socketChannel.getRemoteAddress());socketChannel.configureBlocking(false); // 默认是阻塞的,一定要设置为非阻塞channels.add(socketChannel);} else {// 没有新连接的情况下,就去处理现有连接的数据,处理完的就删除掉Iterator<SocketChannel> iterator = channels.iterator();while (iterator.hasNext()) {SocketChannel ch = iterator.next();try {ByteBuffer requestBuffer = ByteBuffer.allocate(1024);if (ch.read(requestBuffer) == 0) {// 等于0,代表这个通道没有数据需要处理,那就待会再处理continue;}while (ch.isOpen() && ch.read(requestBuffer) != -1) {// 长连接情况下,需要手动判断数据有没有读取结束 (此处做一个简单的判断: 超过0字节就认为请求结束了)if (requestBuffer.position() > 0) break;}if (requestBuffer.position() == 0) continue; // 如果没数据了, 则不继续后面的处理requestBuffer.flip();byte[] content = new byte[requestBuffer.limit()];requestBuffer.get(content);System.out.println(new String(content));System.out.println("收到数据,来自:" + ch.getRemoteAddress());// 响应结果 200String response = "HTTP/1.1 200 OK\r\n" +"Content-Length: 11\r\n\r\n" +"Hello World";ByteBuffer buffer = ByteBuffer.wrap(response.getBytes());while (buffer.hasRemaining()) {ch.write(buffer);}iterator.remove();} catch (IOException e) {e.printStackTrace();iterator.remove();}}}}// 用到了非阻塞的API, 再设计上,和BIO可以有很大的不同// 问题: 轮询通道的方式,低效,浪费CPU}
}
模拟服务器NIOServer的升级版2
/*** 结合Selector实现非阻塞服务器*/
public class NIOServerV2 {public static void main(String[] args) throws Exception {// 1. 创建服务端的channel对象ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.configureBlocking(false); // 设置为非阻塞模式// 2. 创建SelectorSelector selector = Selector.open();// 3. 把服务端的channel注册到selector,注册accept事件SelectionKey selectionKey = serverSocketChannel.register(selector, 0);selectionKey.interestOps(SelectionKey.OP_ACCEPT);// 4. 绑定端口,启动服务serverSocketChannel.socket().bind(new InetSocketAddress(8080)); // 绑定端口System.out.println("启动成功");while (true) {// 5. 启动selector(管家)selector.select();// 阻塞,直到事件通知才会返回Set<SelectionKey> selectionKeys = selector.selectedKeys();Iterator<SelectionKey> iterator = selectionKeys.iterator();while (iterator.hasNext()) {SelectionKey key = iterator.next();iterator.remove();if (key.isAcceptable()) {SocketChannel socketChannel = ((ServerSocketChannel) key.channel()).accept();socketChannel.configureBlocking(false);socketChannel.register(selector, SelectionKey.OP_READ);System.out.println("收到新连接:" + socketChannel);} else if (key.isReadable()) {// 客户端连接有数据可以读时触发try {SocketChannel socketChannel = (SocketChannel) key.channel();ByteBuffer requestBuffer = ByteBuffer.allocate(1024);while (socketChannel.isOpen() && socketChannel.read(requestBuffer) != -1) {// 长连接情况下,需要手动判断数据有没有读取结束 (此处做一个简单的判断: 超过0字节就认为请求结束了)if (requestBuffer.position() > 0) break;}if (requestBuffer.position() == 0) continue; // 如果没数据了, 则不继续后面的处理requestBuffer.flip();byte[] content = new byte[requestBuffer.remaining()];requestBuffer.get(content);System.out.println(new String(content));System.out.println("收到数据,来自:" + socketChannel.getRemoteAddress());// TODO 业务操作 数据库 接口调用等等// 响应结果 200String response = "HTTP/1.1 200 OK\r\n" +"Content-Length: 11\r\n\r\n" +"Hello World";ByteBuffer buffer = ByteBuffer.wrap(response.getBytes());while (buffer.hasRemaining()) {socketChannel.write(buffer);}} catch (Exception e) {e.printStackTrace();key.cancel();}}}}}
}
模拟客户端NIOClient的升级版2
public class NIOClientV2 {public static void main(String[] args) throws Exception {SocketChannel socketChannel = SocketChannel.open();Selector selector = Selector.open();socketChannel.configureBlocking(false);socketChannel.register(selector, SelectionKey.OP_CONNECT);socketChannel.connect(new InetSocketAddress("localhost", 8080));while (true) {selector.select();Set<SelectionKey> selectionKeys = selector.selectedKeys();Iterator<SelectionKey> iterator = selectionKeys.iterator();while (iterator.hasNext()) {SelectionKey selectionKey = iterator.next();iterator.remove();if (selectionKey.isConnectable()) { // 连接到远程服务器try {if (socketChannel.finishConnect()) { // 完成连接// 连接成功System.out.println("连接成功-" + socketChannel);ByteBuffer buffer = ByteBuffer.allocateDirect(20480);// 切换到感兴趣的事件selectionKey.attach(buffer);selectionKey.interestOps(SelectionKey.OP_WRITE);}} catch (IOException e) {// 连接失败e.printStackTrace();return;}} else if (selectionKey.isWritable()) {// 可以开始写数据ByteBuffer buf = (ByteBuffer) selectionKey.attachment();buf.clear();Scanner scanner = new Scanner(System.in);System.out.print("请输入:");// 发送内容String msg = scanner.nextLine();scanner.close();buf.put(msg.getBytes());buf.flip();while (buf.hasRemaining()) {socketChannel.write(buf);}// 切换到感兴趣的事件selectionKey.interestOps(SelectionKey.OP_READ);} else if (selectionKey.isReadable()) {// 可以开始读数据// 读取响应System.out.println("收到服务端响应:");ByteBuffer requestBuffer = ByteBuffer.allocate(1024);while (socketChannel.isOpen() && socketChannel.read(requestBuffer) != -1) {// 长连接情况下,需要手动判断数据有没有读取结束 (此处做一个简单的判断: 超过0字节就认为请求结束了)if (requestBuffer.position() > 0) break;}requestBuffer.flip();byte[] content = new byte[requestBuffer.remaining()];requestBuffer.get(content);System.out.println(new String(content));
//                    selectionKey.interestOps(SelectionKey.OP_WRITE);}}}}}
模拟服务器NIOServer的升级版2的改动

服务器端读事件后,不直接写了,改为关注写事件,然后在写事件中,进行写回复客户端的相应信息

public class NIOServerV2 {public static void main(String[] args) throws IOException {ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.configureBlocking(false);Selector selector = Selector.open();SelectionKey selectionKey = serverSocketChannel.register(selector, 0);selectionKey.interestOps(SelectionKey.OP_ACCEPT);serverSocketChannel.socket().bind(new InetSocketAddress(8080));System.out.println("服务启动成功");while (true) {selector.select();//阻塞Set<SelectionKey> selectionKeys = selector.selectedKeys();Iterator<SelectionKey> iterator = selectionKeys.iterator();while (iterator.hasNext()) {SelectionKey key = iterator.next();iterator.remove();if (key.isAcceptable()) {SocketChannel channel = ((ServerSocketChannel) key.channel()).accept();channel.configureBlocking(false);channel.register(selector, SelectionKey.OP_READ);System.out.println("收到新连接:" + channel.getRemoteAddress());} else if (key.isReadable()) {SocketChannel channel = (SocketChannel) key.channel();ByteBuffer byteBuffer = ByteBuffer.allocate(2014);while(channel.isOpen() && channel.read(byteBuffer) != -1){if(byteBuffer.position() > 0) break;}if (byteBuffer.position() == 0) continue; // 如果没数据了, 则不继续后面的处理byteBuffer.flip();byte[] content = new byte[byteBuffer.remaining()];byteBuffer.get(content);System.out.println("收到新连接:" + channel.getRemoteAddress() + " 数据:"+new String(content));channel.register(selector,SelectionKey.OP_WRITE);} else if (key.isWritable()){System.out.println("开始写");SocketChannel channel = (SocketChannel) key.channel();String response = "HTTP/1.1 200 OK\r\n" +"Content-Length: 11\r\n\r\n" +"Hello World";ByteBuffer byteBuffer = ByteBuffer.wrap(response.getBytes());while(byteBuffer.hasRemaining()){channel.write(byteBuffer);}//写完后,一定取消写的关注,否则轮询中就会一直写key.interestOps(0);}}}}
}

Selector 选择器

详解

Selector是一个Java NIO组件,可以检查一个或多个NIO通道,并确定哪些通道已准备好进行读取或写入。实现单个线程可以管理多个通道,从而管理多个网络连接

一个线程使用Selector监听多个channel的不同事件:四个事件分别对应SelectionKey四个常量。

  1. Connect 连接(SelectionKey.OP_CONNECT)
  2. Accept 准备就绪(OP_ACCEPT)
  3. Read 读取(OP_READ)
  4. Write 写入(OP_WRITE)

实现一个线程处理多个通道的核心概念理解:事件驱动机制

非阻塞的网络通道下,开发者通过Selector注册对于通道感兴趣的事件类型,线程通过监听事件来

触发相应的代码执行。(拓展:更底层是操作系统的多路复用机制)

Selector selector = Selector.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ); //注册感兴趣的事件
while(true) { // 由accept轮询,变成了事件通知的方式。int readyChannels = selector.select(); // select 收到新的事件,方法才会返回if(readyChannels == 0) continue;Set<SelectionKey> selectedKeys = selector.selectedKeys();Iterator<SelectionKey> keyIterator = selectedKeys.iterator();while(keyIterator.hasNext()) {SelectionKey key = keyIterator.next();// 判断不同的事件类型,执行对应的逻辑处理// key.isAcceptable() / key.isConnectable() / key.isReadable() / key.isWritable()}keyIterator.remove();}
}

:更底层是操作系统的多路复用机制)

Selector selector = Selector.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ); //注册感兴趣的事件
while(true) { // 由accept轮询,变成了事件通知的方式。int readyChannels = selector.select(); // select 收到新的事件,方法才会返回if(readyChannels == 0) continue;Set<SelectionKey> selectedKeys = selector.selectedKeys();Iterator<SelectionKey> keyIterator = selectedKeys.iterator();while(keyIterator.hasNext()) {SelectionKey key = keyIterator.next();// 判断不同的事件类型,执行对应的逻辑处理// key.isAcceptable() / key.isConnectable() / key.isReadable() / key.isWritable()}keyIterator.remove();}
}
http://www.xdnf.cn/news/586045.html

相关文章:

  • 嵌入式学习笔记 - Void类型的指针
  • FFmpeg解码器配置指南:为什么--enable-decoders不能单独使用?
  • YOLOv11 性能评估与横向对比
  • Vault应用广吗?我是否有学习使用的必要,难不难?
  • 解码工业转型密码,R‘AIN SUITE赋能制造业价值跃迁
  • labview设计一个虚拟信号发生器
  • 齿轮,链轮,同步轮,丝杆传动sw画法
  • 训练一个线性模型
  • Linux 线程(中)
  • 基于FPGA控制电容阵列与最小反射算法的差分探头优化设计
  • 使用pm2 部署react+nextjs项目到服务器
  • (Java基础笔记vlog)Java中常见的几种设计模式详解
  • java接口自动化(四) - 企业级代码管理工具Git的应用
  • 理解全景图像拼接
  • 动态网页爬取:Python如何获取JS加载的数据?
  • Jenkins与Maven的集成配置
  • C++中的string(1)简单介绍string中的接口用法以及注意事项
  • Web前端开发 - 制作简单的焦点图效果
  • 单例模式的运用
  • UniApp+Vue3微信小程序二维码生成、转图片、截图保存整页
  • uniapp实现的简约美观的票据、车票、飞机票模板
  • ffmpeg 转换视频格式
  • 【Windows】FFmpeg安装教程
  • 「Python教案」运算符的使用
  • 中国计算机学会——2024年9月等级考试5级——第四题、森森快递(贪心+线段树)
  • JavaScriptAPIs学习day3--事件高级
  • 破局制造业转型: R²AIN SUITE 提效实战教学
  • Unity3D 异步加载材质显示问题排查
  • Python安全密码生成器:告别弱密码的最佳实践
  • TRC20代币创建教程指南