记一次JavaNIO重复读消费

本文详细分析了一个Java NIO服务端在处理客户端连接时遇到的死循环问题,原因是客户端断开连接后,服务端仍触发读就绪事件。通过在服务端代码中添加对`channel.read()`返回值的判断,当读取到-1时关闭通道或注销OP_READ事件,可以有效避免这种问题。同时,文章提供了验证和解决问题的代码示例。
摘要由CSDN通过智能技术生成

在一次服务器端接收客户端发送的消息时,服务器端一直触发读就绪事件,导致服务端代码出现死循环,代码如下

客户端
public class Client1 {
    public static void main(String[] args) throws IOException, InterruptedException {
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.configureBlocking(false);
        InetSocketAddress localhost = new InetSocketAddress("localhost", 8888);
        socketChannel.connect(localhost);
        while (!socketChannel.isConnected()) {
            socketChannel.finishConnect();
        }
        socketChannel.write(ByteBuffer.wrap("客户端数据".getBytes()));
    }
}
服务端
public class Server1 {
    public static void main(String[] args) throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        InetSocketAddress inetSocketAddress = new InetSocketAddress(8888);
        serverSocketChannel.bind(inetSocketAddress);
        serverSocketChannel.configureBlocking(false);
        Selector selector = Selector.open();
        serverSocketChannel.register(selector, 16);
        while (true) {
            int select = selector.select();
            System.out.println("当前有" + select + "个操作就绪");
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = keys.iterator();
            while (iterator.hasNext()) {
                SelectionKey next = iterator.next();
                if (next.isAcceptable()) {
                    System.out.println("accept就绪++++++++++++++");
                    ServerSocketChannel server = (ServerSocketChannel) next.channel();
                    SocketChannel socketChannel = server.accept();
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector, SelectionKey.OP_READ);
                } else if (next.isReadable()) {
                    System.out.println("读就绪+++++++++++++++++");
                    ByteBuffer allocate = ByteBuffer.allocate(10240000);
                    StringBuffer stringBuffer = new StringBuffer();
                    SocketChannel channel = (SocketChannel) next.channel();
                    int read = channel.read(allocate);
                    while (read > 0) {
                        allocate.flip();
                        stringBuffer.append(new String(allocate.array(), 0, read));
                        allocate.clear();
                        read = channel.read(allocate);
                    }
                    System.out.println(stringBuffer);
                }
            }
            iterator.remove();
        }
    }
}
执行结果

执行结果

最后在一个帖子里看到了答案https://segmentfault.com/q/1010000019694209

原因
  • 如果客户端的socket断开连接后,会一直向服务端发送一个读就绪,即channel.read(allocate)=-1,这个时候服务端应该断开这个连接,或者至少注销掉OP_READ。
验证
  • 在客户端的代码最后加一个线程睡眠,Thread.sleep(1000000L);
    可以看到,在客户端代码执行结束之前,服务端不会收到这个读就绪事件
    等待客户端数据
解决

服务器端代码加上判断

public class Server1 {
    public static void main(String[] args) throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        InetSocketAddress inetSocketAddress = new InetSocketAddress(8888);
        serverSocketChannel.bind(inetSocketAddress);
        serverSocketChannel.configureBlocking(false);
        Selector selector = Selector.open();
        serverSocketChannel.register(selector, 16);
        while (true) {
            int select = selector.select();
            System.out.println("当前有" + select + "个操作就绪");
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = keys.iterator();
            while (iterator.hasNext()) {
                SelectionKey next = iterator.next();
                if (next.isAcceptable()) {
                    System.out.println("accept就绪++++++++++++++");
                    ServerSocketChannel server = (ServerSocketChannel) next.channel();
                    SocketChannel socketChannel = server.accept();
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector, SelectionKey.OP_READ);
                } else if (next.isReadable()) {
                    System.out.println("读就绪+++++++++++++++++");
                    ByteBuffer allocate = ByteBuffer.allocate(10240000);
                    StringBuffer stringBuffer = new StringBuffer();
                    SocketChannel channel = (SocketChannel) next.channel();
                    int read = channel.read(allocate);
                    while (read > 0) {
                        allocate.flip();
                        stringBuffer.append(new String(allocate.array(), 0, read));
                        allocate.clear();
                        read = channel.read(allocate);
                    }
                    System.out.println(stringBuffer);

                    // socket已经断开
                    if (read == -1) {
                        //增加注销操作
                        // int readyOps = next.readyOps();
                        //&~xx 代表取消事件,取反 按位与
                        // next.interestOps(next.interestOps() & ~readyOps);
                        
                        // 或者断开服务端的这个socket
                        channel.close();
                    }
                }
            }
            iterator.remove();
        }
    }
}

正确执行结果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值