Repeated triggering of read (write) events in NIO network programming

I. Introduction

  Recently, the company will build a TCP communication framework based on Netty. Because Netty is based on NIO, in order to learn and use Netty better, I deliberately went through the previously recorded NIO data, and re-implemented the NIO network communication. I don't know, I found a lot of details that I didn't pay attention to, which led to some very inexplicable problems in the communication between the client and the server. Here I recorded the problems that took me all night~

2. Text

  Not much nonsense, let's go to the problem code first~

  Server:

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( "Connection ready" );
                    ServerSocketChannel server = (ServerSocketChannel)key.channel();
                    System.out.println( "Waiting for client to connect......" );
                    SocketChannel channel = server.accept();
                    channel.configureBlocking(false);
                    channel.register(selector,SelectionKey.OP_READ);
                }
                else if(key.isReadable()){
                    System.out.println( "Ready to read, start reading.........." );
                    SocketChannel channel = (SocketChannel)key.channel();
                    System.out.println( "The client data is as follows:" );
                    
                    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(( "Client, the data you sent is: "+ 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);
    }
}

  Client:

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( "Data returned by the server: "+ new String(bf.array()));
                    }
                }
                selector.selectedKeys().clear();
            }
        }
    }
}

  Server running result:

 

   Client running result:

  Here we can see that the client outputs twice. When I debugged, I found that the server only wrote data to the client once, but the client printed the data twice, and the two data were different. Strange! Then I searched all kinds of things and tortured me all night. When I came this morning, I thought about how to solve the problem, and inadvertently found an article: Does java nio use horizontal triggering or edge triggering? , the article points out that Nio's Selector.select() is a "horizontal trigger" (also called "conditional trigger"), as long as the conditions are always met, it will always trigger, so far I'm in a daze: Is the data in my channel the first Did not read clean at a time? Caused the client to trigger multiple reads? Later, after verification, I found that this is indeed the problem. Readers can look at my server-side code. What I return is the number of data bytes: "The data returned by the server: ".length()+bf.array().length=26 +1024, and the client just reads this data into a ByteBuffer of 1024 size, and there are 26 bytes that have not been read cleanly, so the second read event is triggered! ! !

  Now that the problem is found, it is now to solve how to read the data in the channel cleanly. The modified code is as follows, pay special attention to the red part:

  Server:

  

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( "Connection ready" );
                    ServerSocketChannel server = (ServerSocketChannel)key.channel();
                    System.out.println( "Waiting for client to connect......" );
                    SocketChannel channel = server.accept();
                    channel.configureBlocking(false);
                    channel.register(selector,SelectionKey.OP_READ);
                }
                else if(key.isReadable()){
                    System.out.println( "Ready to read, start reading.........." );
                    SocketChannel channel = (SocketChannel)key.channel();
                    System.out.println( "The client data is as follows:" );
                    
                    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((
"Client, the data you sent is: "+ 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); } }

  Client:

  

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

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324842809&siteId=291194637