前面的代码在服务器端都是单线程配合一个 Selector 选择器来管理多个 channel 上的事件
这样有两个缺点
- 现在都是多核CPU,一个线程只能用一个核心,其他的核心会白白浪费
- 单线程是可以处理多个事件,但是如果某个事件耗时较长,就会造成其他事件的等待
如果让你设计一种较为合理的架构,你会怎样设计呢?
首先一点要把多核CPU 充分利用起来,第二就是每个线程对应自己的职责,例如,店小二负责接待,厨师负责炒菜,服务员负责记录菜单
代码实现
服务器
package com.zhao.c1;
import com.zhao.io.ByteBufferUtil;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
/**
* @Auther: HackerZhao
* @Date: 2021/11/2 12:05
* @Description: 多线程管理多个 channel
*/
@Slf4j
public class MultiThreadServer {
public static void main(String[] args) throws IOException {
// 设置main 名称为 Boss
Thread.currentThread().setName("boss");
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
Selector boss = Selector.open();
SelectionKey bossKey = ssc.register(boss, 0, null);
bossKey.interestOps(SelectionKey.OP_ACCEPT);
ssc.bind(new InetSocketAddress(8080));
// 1. 创建固定数量的 work 并初始化
Work work = new Work("work0");
work.register();
while (true) {
// 判断是否有事件发生,如果四种事件其中一种发生了,会向下运行。没有发生,则阻塞
boss.select();
Iterator<SelectionKey> iter = boss.selectedKeys().iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
iter.remove();
if (key.isAcceptable()) {
// accept 建立与客户端连接,SocketChannel 用来与客户端之间通信(菜单)
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
log.debug("connected...{}",sc.getRemoteAddress());
// 如果 work 创建在连接里边,那么每创建一个连接就会创建一个work,太浪费资源了(work应该是独立的,它只负责自己的读写事件)
// 2. 关联 selector,此时就不能关联 boss 这个 selector 了,应当关联对象 work 中的 selector
log.debug("before register...{}",sc.getRemoteAddress());
sc.register(work.selector,SelectionKey.OP_READ,null);
log.debug("after register...{}",sc.getRemoteAddress());
}
}
}
}
static class Work implements Runnable {
private Thread thread; // 独立的线程
private Selector selector; // 独立的监听器
private String name; // work 的名字
private volatile boolean start; // 刚开始线程还未初始化
public Work(String name) {
this.name = name;
}
// 初始化 Thread 和 Selector
public void register() throws IOException {
if (!start) {
thread = new Thread(this, name); // 创建一个新线程,就是当前类
thread.start();
selector = Selector.open();
start = true;
}
}
@Override
public void run() {
while (true){
try {
selector.select();
Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
while (iter.hasNext()){
SelectionKey key = iter.next();
iter.remove();
if (key.isReadable()){
ByteBuffer buffer = ByteBuffer.allocate(16);
SocketChannel channel = (SocketChannel) key.channel();
// 读取到了 channel 中的数据
log.debug("read...{}",channel.getRemoteAddress());
channel.read(buffer);
// 切换至读模式
buffer.flip();
ByteBufferUtil.debugAll(buffer);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
客户端
package com.zhao.c1;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
/**
* @Auther: HackerZhao
* @Date: 2021/11/2 13:17
* @Description: 客户端
*/
public class TestClient {
public static void main(String[] args) throws IOException {
SocketChannel sc = SocketCha