问题描述
服务端代码如下:
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();
}
}
}