一起学netty(2)nio模型及多路复用器

weblog 770 0 0

一、最简单的nio程序

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

    List<SocketChannel> list = new ArrayList<>();

    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    //绑定9000端口
    serverSocketChannel.bind(new InetSocketAddress(9000));
    //配置非阻塞(nio程序也可以设置成阻塞io,效果和bio一样)
    serverSocketChannel.configureBlocking(false);
    System.out.println("服务启动完成");

    while (true){
        //非阻塞模式,accept方法不会阻塞 accept函数
        //Nio的非阻塞是由操作系统内部实现的,底层调用操作系统的
        SocketChannel channel = serverSocketChannel.accept(); // sign:1
        if(channel != null){
            System.out.println("链接成功");
            //SocketChannel设置为非阻塞
            channel.configureBlocking(false);
            list.add(channel);
        }

        Iterator<SocketChannel> iterator = list.iterator();
        //遍历所有的链接,读取数据
        while (iterator.hasNext()){  // sign:3
            SocketChannel sc = iterator.next();
            ByteBuffer allocate = ByteBuffer.allocate(128);
            int read = sc.read(allocate); //sign:2
            if(read > 0){
                //读取到消息
                System.out.println("收到消息:"+new String(allocate.array()));
            }else if(read == -1){
                //链接断开
                System.out.println("有链接断开");
                list.remove(sc);
            }
        }
    }
}

在nio的程序中,默认是阻塞模式的,当然也可以通过配置,配置成非阻塞模式。当配置了非阻塞模式,上面程序的sign:1位置,和sign:2位置都不会阻塞。那么外层的while循环就会一直循环(空转)。

上述程序只是一个简单的写法,还是存在很多问题,比如sign:3位置,每次都需要遍历所有的客户端链接,读取数据。那么如果客户端链接很多但是真正有数据发送的客户端很少的情况下,那么代码执行的效率是很低的。
因为依然需要处理没有发送数据的客户端。

二、以上程序该怎么优化?

上述的程序的问题已经很明确了,就是当客户端链接数很大,而且真正发送数据的客户端很少的情况下,依然要遍历所有的客户端去读取数据。造成效率低下。

那么有没有办法使得程序在读取客户端信息的时候只读取有消息发送的客户端呢?也就是说比如有一万个链接,但是只有三个给服务器发送了数据,那么我仅仅只读取这三个客户端的数据就可以了,而不是所有的客户端都要遍历一遍。

三、Selector多路复用器

什么是多路复用器?

为了快速理解多路复用技术,我们以生活中的小案例进行说明。老张开大排档,刚刚起步的时候,客人比较少。
接待,炒菜,上菜都是老张一个人负责。老张的手艺不错,炒出来的菜味道可以。客人越来越多,每来个客人,老张都得花时间去接待,忙不过来。

于是老张就招了服务员,服务员收集每桌需要点的菜,然后把菜单交给老张,老张只负责做菜即可。在这里,服务员就充当了选择器,客户把自己的要求告诉服务员,服务员告诉老张。

那么对应我们的java程序也是一样,需要定义一个Selector,然后将ServerSocketChannel和SocketChannel注册到Selector上,当有客户端链接或者有客户端发送消息给服务端的时候,Selector就会通知程序来处理。
 

程序案例:

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

    List<SocketChannel> list = new ArrayList<>();

    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    //绑定9000端口
    serverSocketChannel.bind(new InetSocketAddress(9000));
    //配置非阻塞(nio程序也可以设置成阻塞io,效果和bio一样)
    serverSocketChannel.configureBlocking(false);

    //打开selector处理channel,即创建epoll
    Selector selector = Selector.open();
    //把ServerSocketChannel注册到Selector上
    serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); //sign:1

    System.out.println("服务启动完成");

    while (true){

        //阻塞等待需要处理的事件发生
        selector.select(); //sign:3

        Set<SelectionKey> selectionKeys = selector.selectedKeys();

        Iterator<SelectionKey> iterator = selectionKeys.iterator();
        //遍历所有的链接,读取数据
        while (iterator.hasNext()){
            SelectionKey sk = iterator.next();
            if(sk.isAcceptable()){
                System.out.println("有客户端链接");
                //建立客户端链接
                ServerSocketChannel sc = (ServerSocketChannel)sk.channel();
                SocketChannel accept = sc.accept();
                //设置为非阻塞
                accept.configureBlocking(false);
                //将链接注册到selector上,监听消息发送事件
                accept.register(selector,SelectionKey.OP_READ); //sign:2
            }else if(sk.isReadable()){
                //客户端发送消息
                SocketChannel socketChannel = (SocketChannel)sk.channel();
                ByteBuffer allocate = ByteBuffer.allocate(128);
                int read = socketChannel.read(allocate);
                if(read > 0){
                    //读取到消息
                    System.out.println("收到消息:"+new String(allocate.array()));
                }else if(read == -1){
                    //链接断开
                    System.out.println("有链接断开");
                    socketChannel.close();
                }
            }
            iterator.remove();
        }
    }
}

可以重程序中看到,sign:1是将监听客户端链接的事件注册到Selector,sign:2是将读取客户端发送消息的事件注册到Selector。

那么这样无论是有客户端链接,还是有客户端发送消息,都会在sign:3位置监听到。从而处理对应事件。
至于为什么效率要比bio或者没有Selector的时候效率高,接下来的文章会介绍。

至此我们通过上述代码我们得到了一个很重要的结论,那就是nio不再需要为每个客户端链接创建一个线程去处理读取数据的问题。因为nio是非阻塞的,可以在一个线程内完成创建客户端链接,和读取客户端发送的消息的任务。


猜你喜欢
official 1556 篇《netty(2)nio》中已经简单介绍了nio,以的概念,并了解nio是非阻塞的网络,以与bio的区别。本篇将继续深入理解nio,以select
official 917 在上节《netty(6)》的文章中,简要说明了nio原生代码写程序的些不足和问题,以netty在nio的基础上大致做了那些工作。其中提到点就是当活跃客户端的数量太,单线程处理时所带
official 1057 之前的文章中提到过,单线程的nio任然有定缺点。在上节《netty(7)netty的线程》中也提到,netty的出现,封装了nio杂的代码,并且介入线程来处理事件,最大限度的提
official 849 、什么是bio?bio即:同步阻塞式IO。就是传统的javaio网络。javabio有两个阻塞的地方,第个地方是需要阻塞的监听端口,等待客户端链接。第二个需要阻塞Socket的read方法
official 761 、epoll等各种的出现,我们通过原生的nio已经可以写出些高性能的程序。但是深入分析,我们还是可以从中找出很问题:1.原生的nio服务端程序需要我们自己去解决客户端的连接、客户端收发数据,异常等问
official 981 之前的文章中提到了java中的nio是同步非阻塞的网络io,本文就主要说明下同步、异步、阻塞、非阻塞的概念来帮助理解nio。io操作IO分两阶段(旦拿到数据后就变成了数据操作,不再是IO
official 1090 编码和解码在网络应中需要实现某种编解码,将原始字节数据与自定义的消息对象进行互相转换。网络中都是以字节码的数据形式来传输数据的,服务编码数据后发送到客户端,客户端需要对数据进行解码
official 813 UpdaterequestHTTP包建立连接,之后的通信全部使websocket自己的协议,就和http没啥关系了。有兴趣的同可以了解下websocket协议报文的详细信息。Netty实现websoc
目录
没有一个冬天不可逾越,没有一个春天不会来临。最慢的步伐不是跬步,而是徘徊,最快的脚步不是冲刺,而是坚持。