【网络编程(二)】NIO快速入门

NIO

Java NIO 三大核心组件

  1. Buffer(缓冲区):每个客户端连接都会对应一个Buffer,读写数据通过缓冲区读写。
  2. Channel(通道):每个channel用于连接Buffer和Selector,通道可以进行双向读写。
  3. Selector(选择器):一个选择器对应多个通道,用于监听多个通道的事件。Selector可以监听所有的channel是否有数据要读取,当某个channel有数据时,就去处理,所有channel都没有数据时,线程可以去执行其他任务。

使用 NIO 模型操作 Socket 步骤:

  1. 创建 ServerSocketChannel 服务器;
  2. 创建多路复用器 Selector(每个操作系统创建出来的是不一样的 ,Windows创建的是 WindowsSelectorImpl)
  3. ServerSocketChannel 将建立连接事件注册到 Selector中(register 方法往 EPollArrayWrapper 中添加元素)
  4. 处理事件
    1. 如果是建立连接事件,则把客户端的读写请求也注册到Selector中;
    2. 如果是读写事件则按业务处理。

案例代码:

java 复制代码
public class NioServer {

    public static void main(String[] args) throws IOException {
        // 创建服务器
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(8888));
        serverSocketChannel.configureBlocking(false); // 配置成非阻塞式的channel
        // 创建一个IO多路复用选择器
        Selector selector = Selector.open();
        // 注册
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            // 阻塞的方法,返回值代表发生事件的通道的个数
            // 返回值 0 超时
            // -1 错误
            // select方法可以传递超时时间,如果不传的话是timeout最后会为-1表示不会超时
            selector.select();// 如果不设的话客户端不操作会一直阻塞在这
            // 只要走到这里,必然说明,发送了事情,有可读可写可连接的channel
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                // 这个事件处理完就删除
                if (selectionKey.isAcceptable()) {
                    // 有客户端来连接了
                    // 三次握手建立连接
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                    System.out.println("某某客户端连接来啦");
                }

                if (selectionKey.isReadable()) {
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                    ByteBuffer buffer = (ByteBuffer) selectionKey.attachment();
                    buffer.clear();
                    int read = socketChannel.read(buffer);
                    if (read == -1) { // 如果是可读事件,然后又没有数据,说明是客户端与服务器端的连接断开了,
                        // 这个时候我们关闭通道,不然选择器会一直监听通道,导致不必要的业务执行
                        socketChannel.close();
                    } else {
                        System.out.println(new String(buffer.array(), 0, buffer.position()));
                        System.out.println("有信息需要读取");
                    }
                }
                iterator.remove();
            }
        }

    }
}

doSelect 方法是由 WindowsSelectorImpl 类去实现的,这是select方法最后执行的方法,因为加了互斥锁,也是为什么说这里同步阻塞的原因。

俩问题:

  • 当 Selector.select() 方法返回后,它会返回一组 SelectionKey 对象,这些对象代表了已经就绪的 I/O 通道,即对应的文件描述符上有事件发生。这些 SelectionKey 对象Key用来处理对应的事件。但是,如果不将已经处理过的 SelectionKey 对象从 Selector 中删除,下次调用 Selector.select() 方法时,这些已经处理过的 SelectionKey 对象扔然会被返回,导致多余的事件处理,影响性能问题。

  • 删除的话,我们可以通过 SelectionKey.cancel() 方法来实现,并且使得对应的通道(即文件描述符)不再被Selector监视。(这是有问题的,这样的话以后这个 SelectionKey 就不会再被监听了)可以在迭代器使用的时候对其进行删除。

Netty 封装好后就帮我们解决了这种问题,不会出现事件处理完后续还会一直处理的现象。

相关推荐
X7x51 小时前
网络基石:深入浅出路由交换技术,构建高效通信世界
网络·网络协议·交换技术
@insist1231 小时前
网络工程师-实战配置篇(二):精通 ACL 与策略路由,实现智能流量管控
大数据·网络·网络工程师·软考·软件水平考试
QH139292318802 小时前
KEYSIGHT E5071C 端网络分析仪
网络·功能测试·嵌入式硬件·物联网·单元测试·集成测试·模块测试
念何架构之路3 小时前
图解常见网络I/O复用模型
服务器·网络·php
2401_841495643 小时前
Linux C++ TCP 服务端经典的监听骨架
linux·网络·c++·网络编程·ip·tcp·服务端
551只玄猫3 小时前
【计算机网络 实验报告5】IP层协议分析
网络·网络协议·计算机网络·课程设计·ip·实验报告
Zn_lunar3 小时前
autodl tizi+codex cli
运维·服务器·网络
@insist1233 小时前
网络工程师-实战配置篇(一):深入 BGP 与 VRRP,构建高可靠网络
服务器·网络·php·网络工程师·软件水平考试
静听夜半雨4 小时前
万字长文——基于CANoe/CAPL的UDS Bootloader上位机实现(附完整可运行代码及工程文件)
网络·上位机·canoe·can总线·ecu刷写·uds升级·capl编程
ulias2125 小时前
进程初识(1)
linux·运维·服务器·网络·c++