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,小杜比亚-博客园。

  • 相关阅读:
    【leetcode】1295. Find Numbers with Even Number of Digits
    【leetcode】427. Construct Quad Tree
    【leetcode】1240. Tiling a Rectangle with the Fewest Squares
    【leetcode】1292. Maximum Side Length of a Square with Sum Less than or Equal to Threshold
    【leetcode】1291. Sequential Digits
    【leetcode】1290. Convert Binary Number in a Linked List to Integer
    【leetcode】1269. Number of Ways to Stay in the Same Place After Some Steps
    【leetcode】1289. Minimum Falling Path Sum II
    【leetcode】1288. Remove Covered Intervals
    【leetcode】1287. Element Appearing More Than 25% In Sorted Array
  • 原文地址:https://www.cnblogs.com/xdouby/p/8942083.html
Copyright © 2011-2022 走看看