zoukankan      html  css  js  c++  java
  • NIO网络编程中重复触发读(写)事件

    一、前言

      公司最近要基于Netty构建一个TCP通讯框架, 因Netty是基于NIO的,为了更好的学习和使用Netty,特意去翻了之前记录的NIO的资料,以及重新实现了一遍NIO的网络通讯,不试不知道,一试发现好多细节没注意,导致客户端和服务端通讯的时候出现了一些非常莫名其妙的问题,这边我记录下耗了我一晚上的问题~

    二、正文

      废话不多说,先上问题代码~

      服务端:

    package com.nio.server;
    
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.SelectionKey;
    import java.nio.channels.Selector;
    import java.nio.channels.ServerSocketChannel;
    import java.nio.channels.SocketChannel;
    import java.util.Iterator;
    
    public class NIOServer {
        private static Selector selector;
        private static ServerSocketChannel serverSocketChannel;
        private static ByteBuffer bf = ByteBuffer.allocate(1024);
        public static void main(String[] args) throws Exception{
            init();
            while(true){
                selector.select();
                Iterator<SelectionKey> it = selector.selectedKeys().iterator();
                while(it.hasNext()){
                    SelectionKey key = it.next();
                    if(key.isAcceptable()){
                        System.out.println("连接准备就绪");
                        ServerSocketChannel server = (ServerSocketChannel)key.channel();
                        System.out.println("等待客户端连接中........................");
                        SocketChannel channel = server.accept();
                        channel.configureBlocking(false);
                        channel.register(selector,SelectionKey.OP_READ);
                    }
                    else if(key.isReadable()){
                        System.out.println("读准备就绪,开始读.......................");
                        SocketChannel channel = (SocketChannel)key.channel();
                        System.out.println("客户端的数据如下:");
                        
                        int readLen = 0;
                        bf.clear();
                        StringBuffer sb = new StringBuffer();
                        while((readLen=channel.read(bf))>0){
                            sb.append(new String(bf.array()));
                            bf.clear();
                        }
                        if(-1==readLen){
                            channel.close();
                        }
                        channel.write(ByteBuffer.wrap(("客户端,你传过来的数据是:"+sb.toString()).getBytes()));
                    }
                    it.remove();
                }
            }
        }
        private static void init() throws Exception{
            selector = Selector.open();
            serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.socket().bind(new InetSocketAddress(8080));
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        }
    }

      客户端:

    package com.nio.client;
    
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.SelectionKey;
    import java.nio.channels.Selector;
    import java.nio.channels.SocketChannel;
    import java.util.Iterator;
    
    public class NIOClient {
        private static Selector selector;
        public static void main(String[]args) throws Exception{
            selector = Selector.open();
            SocketChannel sc = SocketChannel.open();
            sc.configureBlocking(false);
            sc.connect(new InetSocketAddress("127.0.0.1",8080));
            sc.register(selector,SelectionKey.OP_READ);
            
            ByteBuffer bf = ByteBuffer.allocate(1024);
            bf.put("Hi,server,i'm client".getBytes());
            
            
            if(sc.finishConnect()){
                bf.flip();
                while(bf.hasRemaining()){
                    sc.write(bf);
                }
                
                while(true){
                    selector.select();
                    Iterator<SelectionKey> it = selector.selectedKeys().iterator();
                    while(it.hasNext()){
                        SelectionKey key = it.next();
                        
                        if(key.isReadable()){
                            bf.clear();
                            SocketChannel othersc  = (SocketChannel)key.channel();
                            othersc.read(bf);
                            System.out.println("服务端返回的数据:"+new String(bf.array()));
                        }
                    }
                    selector.selectedKeys().clear();
                }
            }
        }
    }

      服务端运行结果:

       客户端运行结果:

      这边我们可以看到,客户端输出了两次,笔者调试的时候发现,服务端只往客户端写过一次数据,但是客户端却打印了两次数据,而且两次的数据不一样,挺诡异的!然后我就各种查,折磨了我一夜,今早一来,又想着怎么解决问题,不经意间发现了一篇文章:java nio使用的是水平触发还是边缘触发?,文章中指出Nio的Selector.select()是“水平触发”(也叫“条件触发”),只要条件一直满足,那么就会一直触发,至此我如醍醐灌顶:是不是我通道里面的数据第一次没有读取干净?导致客户端触发了多次读取?后来验证之后,发现确实是这个问题,读者可以看我服务器端的代码,我返回的是数据字节数是:"服务端返回的数据:".length()+bf.array().length=26+1024,而客户端只是将这个数据读入1024大小的ByteBuffer中,还有26字节没有读取干净,所以就触发了第二次的读事件!!!

      既然问题找到了,现在就是要解决如何将通道内的数据读取干净了,修改之后的代码如下, 特别注意红色部分:

      服务端:

      

    package com.nio.server;
    
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.SelectionKey;
    import java.nio.channels.Selector;
    import java.nio.channels.ServerSocketChannel;
    import java.nio.channels.SocketChannel;
    import java.util.Iterator;
    
    public class NIOServer {
        private static Selector selector;
        private static ServerSocketChannel serverSocketChannel;
        private static ByteBuffer bf = ByteBuffer.allocate(1024);
        public static void main(String[] args) throws Exception{
            init();
            while(true){
                selector.select();
                Iterator<SelectionKey> it = selector.selectedKeys().iterator();
                while(it.hasNext()){
                    SelectionKey key = it.next();
                    if(key.isAcceptable()){
                        System.out.println("连接准备就绪");
                        ServerSocketChannel server = (ServerSocketChannel)key.channel();
                        System.out.println("等待客户端连接中........................");
                        SocketChannel channel = server.accept();
                        channel.configureBlocking(false);
                        channel.register(selector,SelectionKey.OP_READ);
                    }
                    else if(key.isReadable()){
                        System.out.println("读准备就绪,开始读.......................");
                        SocketChannel channel = (SocketChannel)key.channel();
                        System.out.println("客户端的数据如下:");
                        
                        int readLen = 0;
                        bf.clear();
                        StringBuffer sb = new StringBuffer();
                        while((readLen=channel.read(bf))>0){
                            bf.flip();
                            byte [] temp = new byte[readLen];
                            bf.get(temp,0,readLen);
                            sb.append(new String(temp));
                            bf.clear();
                        }
                        if(-1==readLen){
                            channel.close();
                        }
                System.out.println(sb.toString()); channel.write(ByteBuffer.wrap((
    "客户端,你传过来的数据是:"+sb.toString()).getBytes())); } it.remove(); } } } private static void init() throws Exception{ selector = Selector.open(); serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.configureBlocking(false); serverSocketChannel.socket().bind(new InetSocketAddress(8080)); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); } }

      客户端:

      

    package com.nio.client;
    
    import java.io.ByteArrayOutputStream;
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.SelectionKey;
    import java.nio.channels.Selector;
    import java.nio.channels.SocketChannel;
    import java.util.Iterator;
    
    public class NIOClient {
        private static Selector selector;
        public static void main(String[]args) throws Exception{
            selector = Selector.open();
            SocketChannel sc = SocketChannel.open();
            sc.configureBlocking(false);
            sc.connect(new InetSocketAddress("127.0.0.1",8080));
            sc.register(selector,SelectionKey.OP_READ);
            
            ByteBuffer bf = ByteBuffer.allocate(1024);
            bf.put("Hi,server,i'm client".getBytes());
            
            
            if(sc.finishConnect()){
                bf.flip();
                while(bf.hasRemaining()){
                    sc.write(bf);
                }
                
                while(true){
                    selector.select();
                    Iterator<SelectionKey> it = selector.selectedKeys().iterator();
                    while(it.hasNext()){
                        SelectionKey key = it.next();
                        
                        
                        if(key.isReadable()){
                            ByteArrayOutputStream bos = new ByteArrayOutputStream();
                            bf.clear();
                            SocketChannel othersc  = (SocketChannel)key.channel();
                            while(othersc.read(bf)>0){
                                bf.flip();
                                while(bf.hasRemaining()){
                                    bos.write(bf.get());
                                }
                                bf.clear();
                            };
                            System.out.println("服务端返回的数据:"+bos.toString());
                        }
                    }
                    selector.selectedKeys().clear();
                }
            }
        }
    }

      客户端的输出:

       

    三、参考链接

           https://www.zhihu.com/question/22524908

    四、联系本人

      为方便没有博客园账号的读者交流,特意建立一个企鹅群(纯公益,非利益相关),读者如果有对博文不明之处,欢迎加群交流:261746360,小杜比亚-博客园。

  • 相关阅读:
    sqlserver2005 新建批处理作业的步骤
    sql server 排除名字重复数据,获取最新的数据
    c#.net常用函数列表
    vs2008 ajax:AjaxPanel 页面无刷新操作
    解决asp.net中listbox的SelectedIndex的取值为1
    AjaxPro.Utility.RegisterTypeForAjax DropDownList三级联动
    我来了!
    《暗时间》读后感
    js判断是安卓手机还是ios
    angular各个版本下载
  • 原文地址:https://www.cnblogs.com/xdouby/p/8942083.html
Copyright © 2011-2022 走看看