单线程可以配合 Selector 完成对多个 Channel 可读写事件的监控,这称之为多路复用
好处
开并行,多个客户端测试服务端
package com.jtc.test;
import java.io.IOException;
import java.net.Socket;
public class Client1 {
public static void main(String[] args) {
try (Socket socket = new Socket("localhost", 8080)) {
System.out.println(socket);
socket.getOutputStream().write("world".getBytes());
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
}
监听accept与read事件并处理
package com.jtc.test;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
import static com.jtc.Utils.ByteBufferUtil.debugAll;
@Slf4j
public class Server1 {
public static void main(String[] args) {
try (ServerSocketChannel channel = ServerSocketChannel.open()) {
channel.bind(new InetSocketAddress(8080));
System.out.println(channel);
log.debug("sssssss");
Selector selector = Selector.open();
//注册事件,绑定的事件 selector 才会关心
/*channel 必须工作在非阻塞模式
* FileChannel 没有非阻塞模式,因此不能配合 selector 一起使用
* 绑定的事件类型可以有
* connect - 客户端连接成功时触发
* accept - 服务器端成功接受连接时触发
* read - 数据可读入时触发,有因为接收能力弱,数据暂不能读入的情况
* write - 数据可写出时触发,有因为发送能力弱,数据暂不能写出的情况*/
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
//监听 Channel 事件
//方法1,阻塞直到绑定事件发生
int count = selector.select();
//方法2,阻塞直到绑定事件发生,或是超时(时间单位为 ms)
//int count = selector.select(long timeout);
//方法3,不会阻塞,也就是不管有没有事件,立刻返回,自己根据返回值检查是否有事件
//int count = selector.selectNow();
log.debug("select count: {}", count);
// 获取所有事件
Set<SelectionKey> keys = selector.selectedKeys();
// 遍历所有事件,逐一处理
Iterator<SelectionKey> iter = keys.iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
// 判断事件类型
if (key.isAcceptable()) {
ServerSocketChannel c = (ServerSocketChannel) key.channel();
// 必须处理
SocketChannel sc = c.accept();
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_READ);
log.debug("连接已建立: {}", sc);
} else if (key.isReadable()) {
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(128);
int read = sc.read(buffer);
if(read == -1) {
//cancel 会取消注册在 selector 上的 channel,
//并从 keys 集合中删除 key 后续不会再监听事件
key.cancel();
sc.close();
} else {
buffer.flip();
debugAll(buffer);
}
}
// 处理完毕,必须将事件移除
iter.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
为什么要iter.remove():
因为 select 在事件发生后,就会将相关的 key 放入 selectedKeys 集合,但不会在处理完后从 selectedKeys 集合中移除,需要我们自己编码删除。例如