使用NIO编写服务端代码,客户端关闭后,服务端无限循环获取读事件

问题描述

服务端代码如下:

package com.ethan.nio;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.channels.spi.SelectorProvider;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Set;

/**
 * 关于Buffer的Scattering与Gathering
 */
@Slf4j
public class NioTest12 {

    public static void main(String[] args) throws IOException {

        int[] ports = new int[] {10000, 10001, 10002, 10003, 10004};

        Selector selector = Selector.open();

        for (int i = 0; i < ports.length; i++) {
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false);
            ServerSocket serverSocket = serverSocketChannel.socket();
            serverSocket.bind(new InetSocketAddress(ports[i]));

            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            log.info("监听端口:{}", ports[i]);
        }

        while (true) {
            int numbers = selector.select();
            log.info("numbers:{}", numbers);

            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                iterator.remove();
                log.info("selectionKey interestOps:{}", selectionKey.interestOps());
                if (selectionKey.isAcceptable()) {
                    ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector, SelectionKey.OP_READ);
                    log.info("获得客户端连接:{}", socketChannel);
                } else if (selectionKey.isReadable()) {
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                    int bytesRead = 0;
                    while (true) {
                        ByteBuffer byteBuffer = ByteBuffer.allocate(512);
                        byteBuffer.clear();
                        int read = socketChannel.read(byteBuffer);
                        if (read <= 0) {
                            break;
                        }
                        byteBuffer.flip();
                        socketChannel.write(byteBuffer);
                        bytesRead += read;
                    }
                    log.info("读取{}个字符串,来自于:{}", bytesRead, socketChannel);
                }
            }
            selectionKeys.clear();
        }
    }

}

通过nc或telnet命令,连接任意服务端口,如10000,当端口连接后,服务端不断接受客户端的读事件。

问题分析

通过调试,发现当客户端关闭后,从channel中读取的内容长度为-1,经过查阅资料,等值内容长度为-1时,表示客户端关闭,需要将服务端保留的channel关闭或取消关注SelectKey.OP_READ事件。

解决办法

判断读取长度是否为-1,若为-1,则手动关闭channel,完整代码如下:

package com.ethan.nio;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.channels.spi.SelectorProvider;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Set;

/**
 * 关于Buffer的Scattering与Gathering
 */
@Slf4j
public class NioTest12 {

    public static void main(String[] args) throws IOException {

        int[] ports = new int[] {10000, 10001, 10002, 10003, 10004};

        Selector selector = Selector.open();

        for (int i = 0; i < ports.length; i++) {
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false);
            ServerSocket serverSocket = serverSocketChannel.socket();
            serverSocket.bind(new InetSocketAddress(ports[i]));

            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            log.info("监听端口:{}", ports[i]);
        }

        //TODO 如果解决selector.select 一直有返回值,并且是最后关闭的socket
        while (true) {
            int numbers = selector.select();
            log.info("numbers:{}", numbers);

            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                iterator.remove();
                log.info("selectionKey interestOps:{}", selectionKey.interestOps());
                if (selectionKey.isAcceptable()) {
                    ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector, SelectionKey.OP_READ);
                    log.info("获得客户端连接:{}", socketChannel);
                } else if (selectionKey.isReadable()) {
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                    int bytesRead = 0;
                    while (true) {
                        ByteBuffer byteBuffer = ByteBuffer.allocate(512);
                        byteBuffer.clear();
                        int read = socketChannel.read(byteBuffer);
                        if (read == -1) {
                            // 当读取长度为-1时,表示客户端断开连接,服务端应当关闭channel,或解除对 {@link SelectionKey.OP_READ} 的监听
                            socketChannel.close();
                            break;
                        }
                        if (read == 0) {
                            break;
                        }
                        byteBuffer.flip();
                        socketChannel.write(byteBuffer);
                        bytesRead += read;
                    }
                    log.info("读取{}个字符串,来自于:{}", bytesRead, socketChannel);
                }
            }
            selectionKeys.clear();
        }
    }

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值