使用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();
        }
    }

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章