NIO问题记录-客户端断开连接,服务端循环读取到read事件
问题: client连接上server后日志正常,我想要再次发送请求,所以重启了client。重启后 server无限监听到read事件,buffer读取长度为-1
日志截图:
客户端代码
package org.example.io.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
/**
* @Des NIO服务端练习 buffer、channel、selector
*/
public class NIOServer {
public static void main(String[] args) throws IOException {
//创建serverSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//创建一个selector
Selector selector = Selector.open();
//绑定一个端口 6666,在服务器端监听
serverSocketChannel.socket().bind(new InetSocketAddress(6666));
//设置为非阻塞
serverSocketChannel.configureBlocking(false);
//serverSocketChannel 注册到selector
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//循环等待客户端连接
while (true) {
//判断是否有事件发生
if (selector.select(1000) == 0) {
System.out.println("服务器等待了1秒,无连接。");
continue;
}
//如果有accept事件发生 注册到selector 类型是 读数据
//获取selectKey
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectionKeys.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
//判断事件类型 如果是accept
if (key.isAcceptable()) {
//该客户端生成 SocketChannel
SocketChannel socketChannel = serverSocketChannel.accept();
//将 SocketChannel 设置为非阻塞
socketChannel.configureBlocking(false);
//注册到selector 关注事件是 OP_READ 给SocketChannel关联一个Buffer
socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
} else if (key.isReadable()) {
//如果是 read 类型 通过key获取channel
SocketChannel socketChannel = (SocketChannel) key.channel();
//获取该channel关联的buffer
ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
//从channel里读数据
int len = socketChannel.read(byteBuffer);
System.out.println("from 客户端 读取数据长度:" + len +
" 数据内容:" + new String(byteBuffer.array()));
}
//手动从集合中移除 SelectionKey,防止重复操作
it.remove();
}
}
}
}
通过测试发现,只要中断client就会发生这种情况
经过查找原因了解到,当客户端主动连接断开时,为了让服务器知道断开了连接,会产生OP_READ事件。所以需要判断读取长度,当读到-1时,关闭channel。
改正后的代码如下
...
while (it.hasNext()) {
SelectionKey key = it.next();
//判断事件类型 如果是accept
if (key.isAcceptable()) {
//该客户端生成 SocketChannel
SocketChannel socketChannel = serverSocketChannel.accept();
//将 SocketChannel 设置为非阻塞
socketChannel.configureBlocking(false);
//注册到selector 关注事件是 OP_READ 给SocketChannel关联一个Buffer
socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
} else if (key.isReadable()) {
//如果是 read 类型 通过key获取channel
SocketChannel socketChannel = (SocketChannel) key.channel();
//获取该channel关联的buffer
ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
//从channel里读数据
int len = socketChannel.read(byteBuffer);
// !!!注意这里
if(len == -1){
//说明连接断开
socketChannel.close();
it.remove();
continue;
}
System.out.println("from 客户端 读取数据长度:" + len +
" 数据内容:" + new String(byteBuffer.array()));
}
//手动从集合中移除 SelectionKey,防止重复操作
it.remove();
}
···