NIO的使用心得

笔者使用NIO的背景–话说在前

笔者为何会使用上NIO呢?回顾自己的这次使用经历:直到最后去找老师请教后,才直到自己仍未有足以使用NIO的理由。当初笔者是在编程一个小的网络java application,用单线程的通讯,即与应用同一线程。在调试运行中,发现由于通讯的监听input阻塞,应用界面没有响应,这令笔者很快的放弃了这个程序模型,去追求一种不阻塞的通讯模式。后来笔者按照网上的一些范例基本,完成了NIO雏形的模块。然而,这次使用还是阻塞,这令笔者很气恼,于是被逼学习多线程。(特别说明:笔者使用javafx,而fx它另有自己的一个concurrent包,与普通的java多线程有特别之处)。而结果就是有了笔者如下的NIO使用心得。(老师指出笔者的错误是:在最初单线程BIO监听循环结构中不使用thread.sleep,导致界面无响应)(忽略各种纠结逻辑)

NIO概念

据笔者的学习理解,IO有BIO、NIO、AIO,分别对应同步阻塞IO、同步非阻塞IO、异步IO。BIO在java.io包中,而NIO和AIO在java.nio目录下。
三者的区别可以这样比喻:你要收一个快件(读操作)

  • BIO:你要在楼下等快件,一直死等,直到快递员来了你才收到快件
  • NIO:你待在家里边做其他事情边等快件,当快递员到了,收到短信通知你到楼下的柜里提取快递
  • AIO:你待在家里边做其他事情边等快件,当快递员到了,快递员不把快件放柜里,还按定好的详细地址,上门派送

NIO的体系结构

以下仅为使用层面的介绍,并不会介绍详细的uml关系
以TCP/IP为例,原来BIO的客户端服务器模型是使用socket系列,server先new一个serversocket并指定端口,然后使用serversocket.accept()等待链接返回一个socket;client需要new一个socket并指定server的地址和端口。至此连接成立。之后双方可以使用使用channel对象的configureblocking()设置是否阻塞工作。数据交流使用stream系列。从socket.getInputStream()/socket.getOutputStream()得到对应流,然后可以再用其他流封装得到的基础流来使用。

对此,NIO则有很大变化。首先NIO的C/S模型使用channel系列。channel是个抽象类,基本上nio.channel目录下的都是抽象类,其实现子类在sun包里,笔者暂时看不到sun包的源代码。server首先new一个inetsocketaddress指定地址和端口,然后使用serversocketchannel静态方法open()得到该类的实现类对象,再使用该类bind()指定刚才定义的socketaddress以绑定。client需要使用socketchannel的静态方法open()得到它的实现类对象。

    //服务器端准备工作
    InetSocketAddress isa = new InetSocketAddress("127.0.0.1", PORT);  
    ServerSocketChannel server = ServerSocketChannel.open();  
    server.bind(isa);  
    server.configureBlocking(false);  
    //客户端准备工作
    InetSocketAddress isa = new InetSocketAddress("127.0.0.1", PORT);  
    SocketChannel sc = SocketChannel.open();  
    sc.configureBlocking(false);  

至此,连接未成立,而NIO的新异之处才开始。

Selector-SelectKey ——非阻塞核心

NIO引入选择器selector概念。原本BIO的阻塞现象是因为线程自身要循环监听,而NIO则使用selector,利用通知/回调机制,线程会在收到通知或受到唤醒才进行监听(然而这监听还是阻塞现象,“非阻塞”的称呼并没有因此失去意义的原因,待体系介绍末尾再解释)。然后,谁能通知或者唤醒它呢?这可以是它自己,或者选择键selectkey。
selectkey是用来跟踪channel情况的一个key条目,而selector充当一个集中管理key条目的角色。channel可以向selector注册登记自己,让selector管理channel的工作行程。selectkey包含着channel的引用和一个可有可无、特殊具体要求才起作用的附加对象Object。selectkey有四种用静态int表示的状态,可以进行或运算以叠加状态:OP_ACCEPT、OP_CONNECT、OP_READ、OP_WRITE,分别表示服务器连接就绪、客户端连接就绪、读就绪、写就绪,前两个状态分别只有服务器和客户端的channel可以使用。

    //selector也是抽象的。两端各使用静态方法创建选择器对象
    //服务器端准备工作
    Selector sel = Selector.open();
    server.register(sel, SelectionKey.OP_ACCEPT);
    //客户端准备工作
    Selector sel = Selector.open();
    sc.connect(isa);//通常上,客户端若无其他准备工作就可直接连接服务器,跳过选择器管理的连接就绪 

Selector工作

selector的工作是监听已register()注册的channel的情况,观测由selectkey指示状态,它有划分keys、selectedkeys、cancelledkeys三个set,前两个可以访问。在selectkey设定了感兴趣的事件集(即就绪事件集)后,若执行selector的选择方法,若无就绪的channel,则会有因执行的具体选择方法而不同的动作:

  • select():线程阻塞,selector沉默
  • select(long):线程在指定毫秒内阻塞,时间结束后返回
  • selectNow():选择后立即返回

若有则selector会将有就绪事件的selectkey加入selectedkeys集中,并将selectkey的感兴趣集清空。
selector选择后形成的selectedkeys集的意义是:集中给出的键都是就绪的,程序员可以对它们进行不需等待的操作,即连接、读写操作。
selector管理的通常代码结构

    while(true){
        while(sel.select() > 0){
            for(SelectKey sk : sel.selectedKeys()){
                if(sk.isReadable()){
                    //具体的写操作
                }
                if(sk.isWritable()){
                    //具体的写操作
                }
            }
        }
    }

这是一种selector的轮询工作。需要注意的是:

  • 在选择后对通道进行了具体的操作后,若无后续操作,应该手动从selectedKeys集中删除该键,否则,下次的选择后该键仍在该已选择键集中,有出错的风险。//sel.selectedKeys().remove(sk);
  • 在选择器选择某键后,某键的感兴趣集会清空,若有后续的操作,应在对该键重设感兴趣集。//sk.interestOps(int ops),该语句的位置灵活,不一定要在非选择器线程上
  • 该轮询是循环选择,一次性的工作应不套用该while(true)条件。

使用ByteBuffer作为信息媒体

channel.read()的返回值和channel.write()的参数都是ByteBuffer类型,使用字节为单位。不过可以使用ByteBuffer对象的asDoubleBUffer()等等获得支持的有限种基本数据类型封装后的Buffer,方便其他数据类型的读写。具体的Buffer系列使用方法这里不详尽。

笔者对NIO的认识

首先解释上文笔者对选择器的工作仍然是阻塞,但NIO未失去意义的原因:原本BIO的读写监听都由程序的读写模块直接负责,而使用NIO的选择器则可将此阻塞的监听转交给选择器承担。就如上文的比喻一样,你真的不需等待,待通知到来就可获得所需。

笔者的老师对我谈过他的经历:他学了NIO后其实以后没有多少使用的机会。由笔者再提炼老师的想法就是:就阻塞监听任务的转移上,只有转移,没有省去。因此只是模块上的再划分,在管理上的便利化,但照样可以完成读写的结果没有变化。一般的程序没有NIO的需求,除非那程序的读写、连接任务比重非常大。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值