随笔 - 1162  文章 - 0  评论 - 16  阅读 - 59万 

一、Selector 基本介绍

(1)Java的NIO,用非阻塞的IO方式。可以用一个线程,处理多个的客户端连接,就会使用到 Selector(选择器)

(2)Selector 能够检测多个注册的通道上是否有事件发生(注意:多个Channel以事件的方式可以注册到同一个Selector), 如果有事件发生, 便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道, 也就是管理多个连接和请求。

(3)只有在 连接/通道 真正有读写事件发生时,才会进行读写,就大大地减少 了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程。

(4)避免了多线程之间的上下文切换导致的开销。

二、Selector 示意图和特点说明

特点再说明:

(1)Netty 的 IO 线程 NioEventLoop 聚合了 Selector(选择器,也叫多路复用器),可以同时并发处理成百上千个客户端连接。

(2)当线程从某客户端 Socket 通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务。

(3)线程通常将非阻塞 IO 的空闲时间用于在其他通道上执行 IO 操作,所以单独的线程可以管理多个输入和输出通道。

(4)由于读写操作都是非阻塞的,这就可以充分提升IO线程的运行效率,避免由于频繁 I/O 阻塞导致的线程挂起。

(5)一个 I/O 线程可以并发处理 N 个客户端连接和读写操作,这从根本上解决了传统同步阻塞 I/O 一连接一线程模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。

三、Selector 类相关方法

Selector 类是一个抽象类, 常用方法和说明如下:

public abstract class Selector implements Closeable {
  //得到一个选择器对象
  public static Selector open();
  
  //判断此选择器是否打开
  public abstract boolean isOpen(); 
  
  //返回创建此频道的提供者
  public abstract SelectorProvider provider(); 
  
  //返回此选择器的键集
  public abstract Set<SelectionKey> keys(); 
  
  //返回此选择器的选定键集,从内部集合中得到所有的 SelectionKey
  public abstract Set<SelectionKey> selectedKeys();
  
  //不阻塞,立马返还,选择一组键,其对应的通道已准备好进行 IO 操作
  public abstract int selectNow() throws IOException;
  
  //监控所有注册的通道,当其中有IO操作可以进行时,将对应的 SelectionKey 加入到内部集合中并返回,
  //参数用来设置超时时间,即阻塞一定毫秒, 在指定毫秒后返回
  public abstract int select(long timeout) throws IOException;
  
  //选择一组键,其对应的通道已准备好进行 IO 操作
  //此方法执行阻塞选择操作
  public abstract int select() throws IOException;
  
  //唤醒selector
  public abstract Selector wakeup();
  
  //关闭此选择器
  public abstract void close() throws IOException;
}

注意事项:NIO中的 ServerSocketChannel功能类似ServerSocket,SocketChannel功能类似Socket

三、NIO 非阻塞 网络编程原理分析图

NIO 非阻塞 网络编程相关的(Selector、 SelectionKey、 ServerScoketChannel和SocketChannel) 关系梳理图

说明

  1. 当有客户端连接时,会通过ServerSocketChannel 得到 SocketChannel
  1. Selector 进行监听 select 方法, 返回有事件发生的通道的个数.

   3. 将socketChannel通过register(Selector sel, int ops) 方法注册到Selector上,一个selector上可以注册多个SocketChannel

  1. 注册后返回一个 SelectionKey, 会和该 Selector 关联(集合)
  1. 进一步得到各个 SelectionKey (有事件发生)
  1. 再通过 SelectionKey 的 channel()方法反向获取SocketChannel
  1. 可以通过得到的 channel , 完成业务处理

四、NIO 非阻塞 网络编程快速入门

案例要求:

  1. 编写一个 NIO 入门案例, 实现服务器端和客户端之间的数据简单通讯(非阻塞)
  2. 目的: 理解NIO非阻塞网络编程机制

服务器端:

public class NIOServer {
    public static void main(String[] args) throws Exception{

        //创建ServerSocketChannel -> ServerSocket
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        //得到一个 Selector 对象
        Selector selector = Selector.open();

        //绑定一个端口6666, 在服务器端监听
        serverSocketChannel.socket().bind(new InetSocketAddress(6666));
        //设置为非阻塞
        serverSocketChannel.configureBlocking(false);

        //把 serverSocketChannel 注册到  selector 关心 事件为 OP_ACCEPT
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("注册后的selectionkey 数量=" + selector.keys().size());


        //循环等待客户端连接
        while (true) {
            //这里我们等待1秒,如果没有事件发生, 返回
            //没有事件发生
            if (selector.select(1000) == 0) {
                System.out.println("服务器等待了1秒,无连接");
                continue;
            }

            //如果返回的>0, 就获取到相关的 selectionKey集合
            //1.如果返回的>0, 表示已经获取到关注的事件
            //2. selector.selectedKeys() 返回关注事件的集合
            //   通过 selectionKeys 反向获取通道
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            System.out.println("selectionKeys 数量 = " + selectionKeys.size());

            //遍历 Set<SelectionKey>, 使用迭代器遍历
            Iterator<SelectionKey> keyIterator = selectionKeys.iterator();

            while (keyIterator.hasNext()) {
                //获取到SelectionKey
                SelectionKey key = keyIterator.next();

                //根据key 对应的通道发生的事件做相应处理
                //如果是 OP_ACCEPT, 有新的客户端连接
                if (key.isAcceptable()) {
                    //为该客户端生成一个 SocketChannel
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    System.out.println("客户端连接成功 生成了一个 socketChannel " + socketChannel.hashCode());

                    //将 SocketChannel 设置为非阻塞
                    socketChannel.configureBlocking(false);

                    //将socketChannel 注册到selector, 关注事件为 OP_READ,
                    //同时给 socketChannel 关联一个Buffer
                    socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));

                    System.out.println("客户端连接后 ,注册的selectionkey 数量=" + selector.keys().size());
                }

                //发生 OP_READ
                if (key.isReadable()) {

                    //通过key 反向获取到对应channel
                    SocketChannel socketChannel = (SocketChannel)key.channel();
                    System.out.println("正在从socketChannel 中读取数据:" + socketChannel.hashCode());
                    //获取到该channel关联的buffer
                    ByteBuffer buffer = (ByteBuffer) key.attachment();
                    socketChannel.read(buffer);
                    System.out.println("form 客户端 " + new String(buffer.array()));
                }

                //手动从集合中移动当前的selectionKey, 防止重复操作
                keyIterator.remove();
            }
        }
    }
}

客户端:

public class NIOClient {

    public static void main(String[] args) throws Exception{
        //得到一个网络通道
        SocketChannel socketChannel = SocketChannel.open();
        //设置非阻塞
        socketChannel.configureBlocking(false);
        //提供服务器端的ip 和 端口
        InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1"6666);

        //连接服务器
        if (!socketChannel.connect(inetSocketAddress)) {

            while (!socketChannel.finishConnect()) {
                System.out.println("因为连接需要时间,客户端不会阻塞,可以做其它工作..");
            }
        }

        //如果连接成功,就发送数据
        String str = "Hello,中国~~~";
        //Wraps a byte array into a buffer
        ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());
        //发送数据,将 buffer写入到 channel中
        socketChannel.write(buffer);
        System.in.read();

    }
}

  Tips:这部分代码可能不好理解,自己 Debug 一下会更容易理解。 

 

posted on   格物致知_Tony  阅读(95)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· 面试官:你是如何进行SQL调优的?
历史上的今天:
2021-03-06 Java 常用类——String类
2021-03-06 第四节:职责链模式——总结
2021-03-06 第三节:职责链模式——在SpringMVC源码分析
2021-03-06 第二节:职责链模式——基本介绍&应用
2021-03-06 第一节:职责链模式——需求说明&传统实现
2020-03-06 URI、URL 和 URN 的区别
点击右上角即可分享
微信分享提示

目录导航