問題描述
服務端代碼如下:
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();
}
}
}