完整代码见最后
1. 主线程(AcceptorThread)
职责:专门处理客户端连接请求
java
- 创建主Selector,监听ACCEPT事件 - 绑定8080端口,等待客户端连接 - 使用轮询算法分配连接到读工作线程 - 维持ServerSocketChannel的生命周期
2. 读工作线程(ReadWorker)
职责:专门处理数据读写业务
java
- 每个ReadWorker拥有独立的Selector - 处理注册到本线程的客户端Channel的READ/WRITE事件 - 管理数据缓冲区,处理半包、粘包问题 - 执行具体的业务逻辑处理
3. 线程选择器(ReadWorkerSelector)
职责:实现负载均衡
java
- 维护4个ReadWorker实例的数组 - 采用轮询(Round-Robin)算法分配连接 - 确保各工作线程负载相对均衡
核心执行流程
阶段一:服务启动与监听
-
初始化主Selector,注册ServerSocketChannel监听ACCEPT事件
-
创建工作线程组,初始化4个ReadWorker(但尚未启动)
-
进入事件循环,主线程在
selector.select()阻塞等待事件
阶段二:连接建立与分配
java
客户端连接 → 触发ACCEPT事件 → 主线程处理 → 轮询选择ReadWorker → 注册到工作线程
关键步骤:
-
主线程调用
serverSocketChannel.accept()获取客户端Channel -
通过
ReadWorkerSelector.getReadWorker()获取下一个工作线程 -
调用
readWorker.register(clientChannel)注册客户端连接
阶段三:工作线程注册机制
采用任务队列 + wakeup的线程安全注册模式:
java
// 1. 主线程提交注册任务
queue.add(() -> {
clientChannel.register(workerSelector, OP_READ, buffer);
// 初始数据发送等操作
});
// 2. 唤醒工作线程
workerSelector.wakeup();
// 3. 工作线程处理注册
Processor task = queue.poll();
if(task != null) {
task.process(); // 在工作线程中执行注册
}
设计优势:
-
所有Selector操作都在同一线程执行,避免线程安全问题
-
通过wakeup确保新连接及时被处理
-
任务队列保证注册操作的顺序性
阶段四:数据读写处理
读事件处理流程:
java
触发READ事件 → 读取数据到ByteBuffer → 处理消息边界 → 业务处理 → 缓冲区管理
关键技术点:
-
动态缓冲区扩容
java
if(byteBuffer.position() == byteBuffer.limit()) {
// 缓冲区已满,扩容2倍
ByteBuffer newBuffer = ByteBuffer.allocate(byteBuffer.capacity() * 2);
byteBuffer.flip();
newBuffer.put(byteBuffer);
key.attach(newBuffer);
}
-
消息边界处理
java
// 按行解析消息,处理TCP粘包/半包问题
while(source.hasRemaining()) {
// 查找换行符作为消息边界
if(source.get(i) == '\n') {
// 提取完整消息行
String message = new String(lineBytes, StandardCharsets.UTF_8);
}
}
-
写事件处理
java
// 大消息分批次发送
if(sentBuffer.hasRemaining()) {
// 注册WRITE事件,继续发送剩余数据
key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
key.attach(sentBuffer);
}
Server端:
public class Server {
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Server.class);
public static void main(String[] args) throws IOException {
Thread.currentThread().setName("AcceptorThread");
// 基于 selector 的非阻塞 IO 服务器端 单线程 配合 selector 实现对多个channel的可读写事件的管理 实现多路复用
Selector selector = Selector.open();
// 创建服务端 Socket
ServerSocketChannel serversocketChannel = ServerSocketChannel.open();
serversocketChannel.configureBlocking(false); // 设置为非阻塞模式 影响在 accept() 方法上 如果没有链接建立,返回的是none值
// 将 channel 注册到 selector 上,监听 ACCEPT 事件
// SelectionKey 是用来记录发生的事件 和 事件的channel 的对应关系 这里只会登记一些channel 和 事件的关系 到集合中 不可变的
SelectionKey sscKey = serversocketChannel.register(selector, SelectionKey.OP_ACCEPT, null);
// sscKey.interestOps(SelectionKey.OP_ACCEPT); // 也可以这么写 让 selector 监听 ACCEPT 事件
// 客户端会发生的事件: connect
// 可读、可写事件 我们的ssc只需要关心 ACCEPT 事件
// 绑定端口
serversocketChannel.bind(new InetSocketAddress(8080));
// 采用轮询选择readWorker线程处理readable事件
// ReadWorker readWorker = new ReadWorker("readWorer0");
// 监听连接
while (true) {
// 没有事件发生 select会阻塞在这里
// 发生事件后,selector 会将对应的 channel 放入 selectedKeys 集合中 如果事件没被处理,那下次调用 select 不会再次阻塞
log.info("Listening on port 8080");
selector.select(); //每次会新增事件 到集合当中 SelectedKeys Set 维护触发事件的key 不会自己删除,需要手动取消
Iterator<SelectionKey> selectionKeyIterator = selector.selectedKeys().iterator();
while (selectionKeyIterator.hasNext()) {
SelectionKey selectionKey = selectionKeyIterator.next();
log.info("Selected key: {}", selectionKey); // 打印出发生的事件
// 从selectedKeys集合中移除该事件
selectionKeyIterator.remove();
// 处理 ACCEPT 事件
if (selectionKey.isAcceptable()) {
SelectableChannel channel = selectionKey.channel();// 获取事件对应的 channel
// 从集合中移除该事件
// selectionKey.cancel();
if (channel instanceof ServerSocketChannel) {
try {
SocketChannel clientChannel = ((ServerSocketChannel) channel).accept();
log.info("Accepted connection from {}", clientChannel.getRemoteAddress());
clientChannel.configureBlocking(false); // 设置为非阻塞模式
// 将 channel 注册到 selector 上,监听读写事件
// 给每一个客户端的socketChannel 绑定一个缓冲区 附件为ByteBuffer
// ByteBuffer byteBuffer = ByteBuffer.allocate(16);
// SelectionKey clientKey = clientChannel.register(selector, SelectionKey.OP_READ, byteBuffer);
// 让客户端的读事件绑定到另一个线程的selector上 处理读事件
log.info("注册读事件到读线程");
ReadWorkerSelector.getReadWorker().register(clientChannel);
log.info("注册读事件到读线程完成");
// 如果向客户端发送大量消息 返回的是发送的字节数
// ByteBuffer sentbuffer = StandardCharsets.UTF_8.encode("a".repeat(3000));
// // 可能一次不会发送完 所以需要循环发送 如果用while就会阻塞在这里
// int written = clientChannel.write(sentbuffer);
// log.info("此次发送的字节数 :{} byte", written);
// if (sentbuffer.hasRemaining()) {
// // 缓冲区没有写完, 不需要死等, 可以给这个channel 的key关注写事件,下次有写事件时,会调用对应的处理方法
// clientKey.interestOps(clientKey.interestOps() | SelectionKey.OP_WRITE);
// // 往哪儿写? 此次大消息的buffer
// clientKey.attach(sentbuffer);
// }
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
}
else if (selectionKey.isReadable()) {
SocketChannel clientChannel = (SocketChannel) selectionKey.channel(); // 获取事件对应的 channel
try {
// 获取缓冲区
ByteBuffer byteBuffer = (ByteBuffer) selectionKey.attachment();
if (byteBuffer == null) {
byteBuffer = ByteBuffer.allocate(16);
selectionKey.attach(byteBuffer);
log.info("重新创建读缓冲区");
}
int read = clientChannel.read(byteBuffer); // 如果数据量大于缓冲区大小,会再次发送读事件 直到读完
if (read == -1) {
// 这里我们分配的是heap ByteBuffer 所以不需要手动释放
selectionKey.cancel(); // 客户端正常断开 反回-1 取消该事件
}
else {
// byteBuffer.flip();
// log.info("读取到数据:{}", StandardCharsets.UTF_8.decode(byteBuffer));
// byteBuffer.clear();
handleMessage(byteBuffer);
// 消息量超过了缓冲区大小,需要扩容处理,然后拷贝到新的缓冲区中
if (byteBuffer.position() == byteBuffer.limit()) {
byteBuffer.flip(); // 让position=0 让缓冲区可读
ByteBuffer newBuffer = ByteBuffer.allocate(byteBuffer.capacity() * 2);
newBuffer.put(byteBuffer);
selectionKey.attach(newBuffer);
}
}
} catch (IOException e) {
log.error(e.getMessage(), e);
// 异常断开
selectionKey.cancel(); // 会将映射关系 先添加到cancelledKeys集合中 下一次select() 会从keys 和 selectedKeys集合中移除该事件
}
// 不能在这里移除该事件,因为对于正常读完数据之后,也会将原先的映射关系key删除
// finally {
// // 移除该事件
// selectionKey.cancel();
// }
} else if (selectionKey.isWritable()) {
// 拿到sentbuffer
ByteBuffer sentBuffer = (ByteBuffer) selectionKey.attachment();
SocketChannel clientChannel = (SocketChannel) selectionKey.channel();
int written = clientChannel.write(sentBuffer);
if (written == -1) {
selectionKey.cancel(); // 客户端异常断开 取消该事件
}
log.info("此次发送的字节数 :{} byte", written);
// 清理sentbuffer
if (!sentBuffer.hasRemaining()) {
selectionKey.attach(null); // 发送完毕 取消大buffer 注意处理写事件的时候 要回填缓冲区
selectionKey.interestOps(selectionKey.interestOps()&~SelectionKey.OP_WRITE);
log.info("发送完毕, 取消关注写事件: {}", selectionKey);
}
}
}
}
}
private static void handleMessage(ByteBuffer source) throws IOException {
// 如果消息大小超出了缓冲区大小 ,需要扩容处理,然后拷贝到新的缓冲区中
source.flip();
try {
while (source.hasRemaining()) {
// 查找换行符
int originalPosition = source.position();
int lineEnd = -1;
for (int i = originalPosition; i < source.limit(); i++) {
if (source.get(i) == '\n') {
lineEnd = i;
break;
}
}
if (lineEnd == -1) {
// 没有完整行,重置位置等待更多数据
source.position(originalPosition);
break;
}
// 提取一行消息
int length = lineEnd - originalPosition;
byte[] lineBytes = new byte[length];
source.get(lineBytes);
source.get(); // 跳过换行符
// 处理消息
String message = new String(lineBytes, StandardCharsets.UTF_8);
System.out.println("收到消息: " + message);
// 可以在这里添加业务逻辑处理
}
} finally {
source.compact();
}
}
// 使用多线程优化 acceptable事件主线程处理 readable 另一个线程处理
static class ReadWorker implements Runnable {
private Thread thread;
private Selector selector;
private String name;
private volatile boolean running = false;
private ConcurrentLinkedQueue<Processor> queue = new ConcurrentLinkedQueue<>();
public ReadWorker(String name) {
this.name = name;
}
// 注册到selector上,监听readable事件
public void register(SocketChannel clientChannel) throws IOException {
if (!running) {
this.selector = Selector.open();
this.thread = new Thread(this, name);
thread.start();
running = true;
}
queue.add(() -> {
try {
SelectionKey clientKey = clientChannel.register(this.selector, SelectionKey.OP_READ, null);
ByteBuffer sentbuffer = StandardCharsets.UTF_8.encode("a".repeat(3000));
// 可能一次不会发送完 所以需要循环发送 如果用while就会阻塞在这里
int written = clientChannel.write(sentbuffer);
log.info("此次发送的字节数 :{} byte", written);
if (sentbuffer.hasRemaining()) {
// 缓冲区没有写完, 不需要死等, 可以给这个channel 的key关注写事件,下次有写事件时,会调用对应的处理方法
clientKey.interestOps(clientKey.interestOps() | SelectionKey.OP_WRITE);
// 往哪儿写? 此次大消息的buffer
clientKey.attach(sentbuffer);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
});
// 由 acceptor 线程 唤醒 worker线程
selector.wakeup();
}
@Override
public void run() {
while (true) {
try {
// 在主线程注册之前 就已经阻塞住了,新注册的事件不会唤醒当前worker线程
this.selector.select();
// 一定要由reader线程自己处理channel的注册
Processor polled = queue.poll();
if (polled!= null) {
polled.process();
}
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if (key.isReadable()) {
log.info("read worker 处理readable事件");
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
if (byteBuffer == null) {
byteBuffer = ByteBuffer.allocate(16);
key.attach(byteBuffer);
}
int read = channel.read(byteBuffer);
if (read == -1) {
key.cancel(); // 客户端断开 取消该事件
} else {
handleMessage(byteBuffer);
// 消息量超过了缓冲区大小,需要扩容处理,然后拷贝到新的缓冲区中
if (byteBuffer.position() == byteBuffer.limit()) {
byteBuffer.flip(); // 让position=0 让缓冲区可读
ByteBuffer newBuffer = ByteBuffer.allocate(byteBuffer.capacity() * 2);
newBuffer.put(byteBuffer);
key.attach(newBuffer);
}
}
}
//TODO: 处理写事件
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
@FunctionalInterface
interface Processor {
void process();
}
class ReadWorkerSelector {
private static Server.ReadWorker[] readWorkers = new Server.ReadWorker[]{
new Server.ReadWorker("readWorker0"),
new Server.ReadWorker("readWorker1"),
new Server.ReadWorker("readWorker2"),
new Server.ReadWorker("readWorker3"),
};
private static AtomicInteger index = new AtomicInteger(0);
public static Server.ReadWorker getReadWorker() {
if (index.get() == readWorkers.length) {
index.set(0);
}
System.out.println("获取读线程:" + index.get());
return readWorkers[index.getAndIncrement()];
}
}
719

被折叠的 条评论
为什么被折叠?



