zoukankan      html  css  js  c++  java
  • Netty快速入门(05)Java NIO 介绍-Selector

    Java NIO Selector

    Selector是Java NIO中的一个组件,用于检查一个或多个NIO Channel的状态是否处于可读、可写。如此可以实现单线程管理多个channels,也就是可以管理多个网络链接。

    file

    前面说过通道就是连接,比如同一时间有很多连接过来,这些连接,也就是channel都会注册到Selector中,Selector就会巡视这些channel,看看哪些可以操作(可读或者可写),并不会进行读写操作。而仅仅进行巡视,并不是很麻烦,所以即使Selector是单个线程也是很有效率的,大家不妨想象一下for循环几万次做一些简单的事,比如输出一个字符串,所花费的时间几乎很少就能明白。

    Selector之所以叫多路复用,是因为Selector这个线程可以在多个线程之间检查复用,不像bio那样一个线程只负责一个连接。通道连接来了以后要自己注册到Selector中,注册完会返回一个SelectionKey,表示Selector和被注册的channel之间的关系,也是一份凭证。

    Selector每次执行select就会返回就绪的通道个数,并更新所有就绪的SelectionKey的状态。下一步就可以迭代所有通道,进行处理了。

    file

    服务端代码

    上面的理论说完了,下面说一下服务端代码的流程。nio的编程比较复杂和麻烦,所以代码会比bio多很多。首先初始化的时候第一步要创建Selector:

    Selector selector = Selector.open();

    有了Selector后,服务端要专门创建一个 ServerSocketChannel 去接收客户端的请求连接,这个channel也需要注册到Selector中:

    ServerSocketChannel serverChannel = ServerSocketChannel.open(); //创建

    serverChannel.configureBlocking(false); //设置为非阻塞

    serverChannel.socket().bind(new InetSocketAddress(port), 1024); //绑定端口

    serverChannel.register(selector, SelectionKey.OP_ACCEPT); //设置类型为接收连接的线程,并注册到Selector中

    上面最后一行的SelectionKey.OP_ACCEPT表示serverChannel只对接收连接有兴趣,其它事不会做,也就是说在把通道注册到Selector中的时候,要告诉Selector通道专门负责做哪一类的事。一个通道可以做的事有四个类型:

    SelectionKey.OP_CONNECT //对请求有兴趣

    SelectionKey.OP_ACCEPT //对接收有兴趣

    SelectionKey.OP_READ //对读操作有兴趣

    SelectionKey.OP_WRITE //对写操作有兴趣

    初始化服务器的代码如下:

    file

    一旦向Selector注册了一个或多个channel后,就可以调用select方法来获取channel,select方法会返回所有处于就绪状态的channel,常用select方法具体如下:

    int select()

    int select(long timeout)

    int selectNow()

    select()方法的返回值是一个int,代表有多少channel处于就绪了。也就是自上一次select后有多少channel进入就绪。

    在调用select并返回了有channel就绪之后,可以通过选中的key集合来获取channel,这个操作通过调用selectedKeys()方法:

    Set selectedKeys = selector.selectedKeys();

    接下来看一下获取通道的代码:

    file

    上面的逻辑很简单,调用select和selectedKeys获取所有可以操作通道,然后迭代并交给handlerInput方法去处理。这就是selecto获取和r处理所有通道的循环流程。通道分为四种类型,所以通道处理的时候,可以进行四种判断:

    while(keyIterator.hasNext()){

    SelectionKey key = keyIterator.next();
    
    if(key.isAcceptable()) {
    
        // 判断通道是不是一个可以接收类型的通道,用在服务端处理的时候做判断
    
    } else if (key.isConnectable()) {
    
        // 是否是一个连接类型的通道
    
    } else if (key.isReadable()) {
    
        // 通道可读的时候处理
    
    } else if (key.isWritable()) {
    
        // 通道可写的时候处理
    
    }
    
    keyIterator.remove();
    

    }

    上面是根据通道四种类型总共要做的四种判断,最后一行注意,通道处理完后,一定要进行remove,否则还会继续处理。

    来看一下接收连接的请求处理:

    file

    上面的代码逻辑就是发现一个新请求后,使用SocketChannel接收,然后设置为非阻塞的,最后注册到selector中,设置类型为SelectionKey.OP_READ,最后的sk.attach(num++);表示设置连接的编号。

    当连接可读的时候,处理代码如下:

    file

    这个代码虽然多,但是逻辑简单,就是读取数据打印到控制台,最后的doWrite方法就是返回信息到客户端:

    file

    这里服务端根据实际情况作了两种判断,实际开发中channel存在几种类型就要做几种判断。这里的nio服务端的程序只是一个简单的版本,很多问题比如半包都没有考虑,但是即使这样,也比bio要复杂很多。

    客户端代码

    客户端代码和服务端流程上差不多,

    file

    这里使用的是SocketChannel,注册的时候,客户端要连接服务端,所以类型为SelectionKey.OP_CONNECT,循环获取channel的流程和服务端一样:

    file

    看一下处理的方法:

    file

    上面判断连接成功后,会发一个消息到服务端,前面看到服务端接收到消息后会自动给客户端返回一个消息,所以客户度也要写一个读的操作:

    file

    读的逻辑也是把读到的内容写到控制台。

    NIO特性

    NIO使用事件驱动模型,以前一个线程只能处理一个连接,现在单个线程可以处理很多channel,相同请求数量的情况下避免线程过多的缺点。NIO是非阻塞IO,IO读写不再阻塞,而是返回0。基于block块的传输,通常比基于流的传输更高效,并且有更高级的IO函数,zero-copy零拷贝等特性,IO多路复用大大提高了java网络应用的可伸缩性和实用性。

    NIO只能说在特定的情况下,比如并发量大的时候有优势,但是如果连接数只有几百,并发也不高的情况下,nio并不一定比bio好,速度也不一定比bio快。

    NIO屏蔽了底层实现,不同的操作系统多路复用模型也是不同的,比如Linux的epoll,FreeBSD的Kqueue等等,NIO基于各个操作系统的IO系统实现,在不同的平台运行还是有差异存在的,而且编程很困难,容易出各种问题,有很多陷阱,熟练掌握nio需要很多经验和技术功底。

    通过上面的总结也说明了我们为什么需要netty这样的框架,会把各种复杂的问题屏蔽掉,让高性能的网络编程变得简单可靠。

    代码地址:https://gitee.com/blueses/netty-demo 04

    本文由博客一文多发平台 OpenWrite 发布!

  • 相关阅读:
    组装query,query汇总,query字段
    POJ 1276, Cash Machine
    POJ 1129, Channel Allocation
    POJ 2531, Network Saboteur
    POJ 1837, Balance
    POJ 3278, Catch That Cow
    POJ 2676, Sudoku
    POJ 3126, Prime Path
    POJ 3414, Pots
    POJ 1426, Find The Multiple
  • 原文地址:https://www.cnblogs.com/guos/p/12187734.html
Copyright © 2011-2022 走看看