Netty学习之理解selector原理示例

 更新时间:2023年07月13日 11:10:53   作者:pq217  
这篇文章主要为大家介绍了Netty学习之理解selector原理示例使用分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪<BR>

BIO的弊端

BIO既是Blocking IO,也叫同步阻塞模型,BIO模型如下

如果所示,多个客户端连接一个服务端, 每出现一个客户端就开一个handler(一般对应一个线程)处理

对应的服务端代码如下

public class BioServer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(9000);
        // 任务处理线程池
        ExecutorService pool = Executors.newFixedThreadPool(10);
        while (true) {
            System.out.println("server start");
            //阻塞方法等待客户端连接
            Socket clientSocket = serverSocket.accept();
            System.out.println("new client");
            pool.execute(()->{
                handler(clientSocket);
            });
        }
    }
    private static void handler(Socket clientSocket) {
        try {
            byte[] bytes = new byte[1024];
            System.out.println("reading");
            //接收客户端的数据,阻塞方法,没有数据可读时就阻塞
            int read = clientSocket.getInputStream().read(bytes);
            System.out.println("read over");
            if (read != -1) {
                System.out.println("received data:" + new String(bytes, 0, read));
            }
            clientSocket.getOutputStream().write("HelloClient".getBytes());
            clientSocket.getOutputStream().flush();
        } catch (Exception e) {
        }
    }
}

上面的代码使用了线程池来处理客户端业务,整个代码有两步阻塞

  • 一步主线程阻塞等待客户端连接
  • 一步线程池的线程阻塞等待客户端传输数据准备好

其实第一步阻塞到没什么,作为一个服务没有客户的情况下不阻塞也无事可做,主要是第二个阻塞,上面代码线程池size是10,假如当前有10个客户端连接都不发送数据,那么10个线程都阻塞,此时再来一个客户端发送完数据也无法及时处理

假设现在有个澡堂子,一共雇佣10个搓澡工,BIO就好比来一个客人就分配一个搓澡师傅,这个师傅就死死盯着客人洗澡,啥时候洗澡完了就开始安排搓澡,如果十个客户在池子里洗澡洗不完,这时有11号客户来了,没有空闲的搓澡师傅(都在等待客户洗完澡),那这个客户只能傻傻的等着,实际上十个搓澡工都没有实际干活,但却处于傻傻的等待不可用状态

解决思路

通过上面的例子可以看出,BIO的模型明显是反人类的,合理的工作流程是这样

10个搓澡师傅都待着,什么时候有人洗完澡了才分配搓澡师傅进行搓澡,这样搓澡师傅的工作更加合理,充分的压榨了搓澡师傅

客户只会因为搓澡师傅都在搓澡而等待,而不是因为搓澡师傅都在傻等而等待

这样设计显然更加合理,也更加符合现实的工作流程,相当于事件驱动,当发生洗完澡(接收到数据)事件后,再安排搓澡工(线程)进行处理

回到代码怎么才能做到以事件驱动呐?更确切的说,作为java代码,如何得知数据传输的事件发生呐?

显然java本身肯定是做不到的,我们可以想一下数据的来源,一个网络数据的传输,一台机器通过网线或无线等形式发送二进制数据到一台电脑,硬件的驱动必然能感知到,那么操作系统也一定能感知到,所以一个链接是否有数据传输,操作系统最知道!

epoll

linux 提供了epoll系列函数

上层代码可以通过调用该系列函数订阅感兴趣的事件,后续即可感知到注册事件的发生,主要方法如下

  • epoll_create: 创建一个epoll实例
  • epoll_ctl: 订阅事件
  • epoll_wait: 阻塞等待订阅事件的发生

有个这系列函数,java代码就可以:

  • 调用epoll_create创建一个epoll实例
  • 调用epoll_ctl订阅可读事件(相当于有数据传输事件)
  • 调用epoll_wait阻塞并等待时间发生

当然不能直接调用,而是调用底层C++再调用linux函数

NIO

有了操作系统的支持,java就可以实现一个不阻塞的IO服务,这就是NIO模型(Non Blocking IO)

JDK1.4开始引入java.nio包,在linux系统中底层就是使用epoll实现的(windows中基于winsock2,不开源)

先看一下NIO实现服务的代码

public class NioServer {
    public static void main(String[] args) throws IOException, InterruptedException {
        // 创建NIO ServerSocketChannel
        ServerSocketChannel serverSocket = ServerSocketChannel.open();
        serverSocket.socket().bind(new InetSocketAddress(9000));
        serverSocket.configureBlocking(false);
        // 打开Selector处理Channel,底层调用epoll_create
        Selector selector = Selector.open();
        // 把ServerSocketChannel注册到selector上,并且selector对客户端accept连接操作感兴趣,底层调用epoll_ctl
        serverSocket.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("服务启动成功");
        while (true) {
            // 阻塞等待需要处理的事件发生,即调用epoll_wait
            selector.select();
            // 获取selector中注册的全部事件的 SelectionKey 实例
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            // 遍历SelectionKey对事件进行处理
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                // 如果是OP_ACCEPT事件,则进行连接获取和事件注册
                if (key.isAcceptable()) {
                    ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    SocketChannel socketChannel = server.accept();
                    socketChannel.configureBlocking(false);
                    // 这里只注册了读事件,如果需要给客户端发送数据可以注册写事件,底层调用epoll_ctl
                    socketChannel.register(selector, SelectionKey.OP_READ);
                    System.out.println("客户端连接成功");
                } else if (key.isReadable()) {  // 如果是OP_READ事件,则进行读取和打印
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    ByteBuffer byteBuffer = ByteBuffer.allocate(128);
                    int len = socketChannel.read(byteBuffer);
                    // 如果有数据,把数据打印出来
                    if (len > 0) {
                        System.out.println("接收到消息:" + new String(byteBuffer.array()));
                    } else if (len == -1) { // 如果客户端断开连接,关闭Socket
                        System.out.println("客户端断开连接");
                        socketChannel.close();
                    }
                }
                //从事件集合里删除本次处理的key,防止下次select重复处理
                iterator.remove();
            }
        }
    }
}

上面及是一个典型的NIO代码,其中(liunx中):

  • Selector.open() 底层调用liunx的epoll_create
  • socketChannel.register(selector, SelectionKey.OP_READ)相当于调用liunx的epoll_ctl(实际上只是缓存起来,下一步再真正执行epoll_ctl)
  • selector.select() 底层调用epoll_wait,阻塞并等待订阅事件发生

总结

selector 是java.nio包中一个nio解决方案,主要可以实现

  • 订阅IO事件,如连接事件和有数据传输事件
  • 阻塞并等待任何订阅事件的发生

在linux内核中,selector就是对epoll函数的一种封装,或者说epoll是linux针对java selector的实现

以上就是Netty学习之理解selector原理示例的详细内容,更多关于Netty selector原理的资料请关注脚本之家其它相关文章!

相关文章

  • Java字段Stream排序常用方式

    Java字段Stream排序常用方式

    这篇文章主要给大家介绍了关于Java字段Stream排序常用方式的相关资料,我们在处理数据的时候经常会需要进行排序后再返回给前端调用,比如按照时间升序排序,前端展示数据就是按时间先后进行排序,需要的朋友可以参考下
    2023-09-09
  • Java+OpenCV实现图片中的人脸识别

    Java+OpenCV实现图片中的人脸识别

    这篇文章主要介绍了如何利用java opencv实现人脸识别功能,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-03-03
  • java提高篇(二三)-----HashMap详解

    java提高篇(二三)-----HashMap详解

    HashMap基于哈希表的 Map 接口的实现,本篇文章主要讲诉了java中HashMap,有兴趣的可以了解一下。
    2016-11-11
  • 详解Java的Spring框架中的注解的用法

    详解Java的Spring框架中的注解的用法

    这篇文章主要介绍了Java的Spring框架中的注解的用法,包括对Java bean的定义的作用介绍,需要的朋友可以参考下
    2015-11-11
  • 深入理解JVM自动内存管理

    深入理解JVM自动内存管理

    对于Java虚拟机在内存分配与回收的学习,本文主要介绍了JVM自动内存管理,文中通过图文示例介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-08-08
  • SpringBoot结合FreeMarker视图渲染的实现

    SpringBoot结合FreeMarker视图渲染的实现

    FreeMarker它允许开发人员使用模板和数据来生成输出文本,如HTML网页、电子邮件、配置文件和源代码等,本文主要介绍了SpringBoot结合FreeMarker视图渲染的实现,感兴趣的可以了解一下
    2024-03-03
  • 详解Java动态代理的实现机制

    详解Java动态代理的实现机制

    这篇文章主要为大家详细介绍了Java动态代理的实现机制,感兴趣的小伙伴们可以参考一下
    2016-03-03
  • 浅谈Java高并发解决方案以及高负载优化方法

    浅谈Java高并发解决方案以及高负载优化方法

    这篇文章主要介绍了浅谈Java高并发解决方案以及高负载优化方法,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • java调用oracle分页存储过程示例

    java调用oracle分页存储过程示例

    这篇文章主要介绍了java调用oracle分页存储过程,需要的朋友可以参考下
    2014-03-03
  • 详谈Java枚举、静态导入、自动拆装箱、增强for循环、可变参数

    详谈Java枚举、静态导入、自动拆装箱、增强for循环、可变参数

    下面小编就为大家带来一篇详谈Java枚举、静态导入、自动拆装箱、增强for循环、可变参数。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-08-08

最新评论