NIO 服务器端不阻塞的一个Bug解决

NIO 服务器端不阻塞的一个Bug解决

@author:Jingdai
@date:2021.06.27

今天用 NIO 时出现了一个Bug,搞了半天,现在记录一下。

使用 NIO 进行通信时,在客户端正常关闭时(即调用close() 方法关闭),会触发服务器的一个读事件,此时服务器的 read 方法会返回 -1,根据此我们就可以做一些处理,如下代码。

int n = socketChannel.read(byteBuffer);
if (n == -1) {
    selectionKey.cancel();
}

但是今天写代码发现客户端正常关闭后服务器端总是读不到 -1,无法取消这个 key,导致服务器端无法阻塞,一直空转。示例代码如下。

Server.java


import java.io.IOException;
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.nio.charset.Charset;
import java.util.Iterator;

public class Server {

    public static void main(String[] args) {

        int port = 8888;

        try {
            ServerSocketChannel ssc = ServerSocketChannel.open();
            ssc.configureBlocking(false);
            ssc.bind(new InetSocketAddress(port));
            Selector selector = Selector.open();
            ssc.register(selector, SelectionKey.OP_ACCEPT);
            
            while (true) {
                selector.select();
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    if (selectionKey.isAcceptable()) {
                        SocketChannel sc =  ssc.accept();

                        sc.configureBlocking(false);
                        sc.register(selector, SelectionKey.OP_READ);
                    } else if (selectionKey.isReadable()) {
                        SocketChannel sc = (SocketChannel) selectionKey.channel();
                        ByteBuffer byteBuffer;
                        if (selectionKey.attachment() == null) {
                            byteBuffer = ByteBuffer.allocate(512);
                            selectionKey.attach(byteBuffer);
                        } else {
                            byteBuffer = (ByteBuffer) selectionKey.attachment();
                        }
                        int n = sc.read(byteBuffer);
                        System.out.println(n);
                        if (n == -1) {
                            selectionKey.cancel();
                        } else {
                            byteBuffer.flip();
                            System.out.println(Charset.defaultCharset().decode(byteBuffer));
                            // byteBuffer.clear();
                        }
                    }
                    iterator.remove();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}       

Client.java


import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

public class Client {

    public static void main(String[] args) {

        String ip = "localhost";
        int port = 8888;

        try (Socket socket = new Socket(ip, port)) {
            OutputStream os = socket.getOutputStream();
            os.write("Hello Server!".getBytes());
            os.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

最后发现在客户端正常关闭时,如果 read(ByteBuffer dst) 方法的 dst 里面没有数据,则 read() 方法就会正常的返回 -1;而如果 dst 里面已经有了数据的话,那么 read() 方法就会返回 0 。所以上面代码中就无法进入到 n == -1 的那个分支,即无法取消这个 key,这就导致 select 无法阻塞,一直空转。知道了原因,也就很好解决了,只要将上面代码中那行注释的代码去掉注释就行,读取完之后 clear 会重置ByteBuffer ,就可以使 read 方法返回 -1了,当然每次读取都使用一个新的 ByteBuffer 也是可以的。

结论

当客户端正常关闭时,会触发服务器的读事件。如果服务器端的 read(ByteBuffer dst) 方法的 dst 里面没有数据,则 read() 方法就会正常的返回 -1;而如果 dst 里面已经有了数据的话,那么 read() 方法就会返回 0 。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值