Reactor 线程模型
一、传统线程模型与NIO模型
1. BIO 模型
传统 I/O 模型为同步阻塞式 I/O 模型,对于网络I/O而言,需要赋予每个连接一个线程,同时该线程会阻塞于I/O事件,无法操作执行其他任务
1.1 处理模型
每当有新连接到来时,创建一个新的线程以对新连接进行处理,可用***线程池***进行优化,但仍然无法应对高并发服务
1.2 服务端代码
public class BIOServer {
private ServerSocket serverSocket;
private ExecutorService executorService;
// 初始化 server 端
public BIOServer() {
try {
System.out.println("服务器开始启动");
init();
} catch (IOException e) {
System.out.println("服务器启动失败");
e.printStackTrace();
// 启动失败,退出服务器
System.exit(0);
}
}
private void init() throws IOException {
// 创建 serverSocket
serverSocket = new ServerSocket(9999);
// 实例化线程池,优化BIO的线程使用效率
executorService = Executors.newFixedThreadPool(4);
System.out.println("服务器初始化成功");
}
public void start() {
while (true) {
try {
final Socket socket = serverSocket.accept();
System.out.println("收到来自客户端的连接");
// 使用 lambda 表达式
executorService.execute(() -> {
handler(socket);
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 对连接的处理
private void handler(Socket socket) {
System.out.println("线程:"+Thread.currentThread().getName());
System.out.println("读取数据 ==> start");
byte[] bytes = new byte[1024];
int read;
try {
InputStream inputStream = socket.getInputStream();
while ((read = inputStream.read(bytes)) != -1) {
System.out.print(new String(bytes, 0, read));
}
System.out.println("读取数据 ==> finish");
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2. NIO 模型
NIO 为同步非阻塞式 IO,属于事件驱动模式,类似于 java的swing 或者时html页面的事件。一个线程管理多个 I/O 通道(简单的单线程reactor模型),每当检测出某些通道有事件发生时,才对其触发的事件进行处理。
1. 处理模型
- 新连接到来时,即serverSocketChannel的连接事件发生时,接收连接,并将客户端管道注册到selector中进行监听
- 当客户端通道有读事件发生时,对读事件进行处理
2. 服务端代码
public class NIOServer {
ServerSocketChannel serverSocketChannel;
Selector selector;
// 默认端口
private static final int DEFAULT_PORT = 12345;
public NIOServer() {
this(DEFAULT_PORT);
}
public NIOServer(int port) {
try {
init(port);
} catch (IOException e) {
e.printStackTrace();
System.out.println("初始化异常");
System.exit(0);
}
}
private void init(int port) throws IOException {
// 创建服务端管道
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(port));
// 设置为非阻塞
serverSocketChannel.configureBlocking(false);
selector = Selector.open();
// 将serverSocketChannel注册到selector,监听accept操作
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
}
public void start() {
try {
inStart();
} catch (Exception e) {
System.out.println("服务器无法正常启动");
e.printStackTrace();
}
}
private void inStart() throws IOException, InterruptedException {
while (true) {
// 非阻塞式获取发生事件的管道
if (selector.select(1000) == 0) {
// System.out.println("server 无事件");
// 每一秒检查一次
Thread.sleep(1000);
} else {
// 获取有事件发生的SelectionKey
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
// 移除当前key, 防止重复操作
iterator.remove();
if (key.isAcceptable()) {
acceptHandler();
}
if (key.isReadable()) {
readHandler(key);
}
}
}
}
}
/**
* 对读事件进行处理
*/
private void readHandler(SelectionKey key) throws IOException {
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer attachment = (ByteBuffer) key.attachment();
// 进行读操作
System.out.println("接收到客户端[ " +
socketChannel.getRemoteAddress() + " ]的信息:");
while (true) {
int l = socketChannel.read(attachment);
if (l == 0) {
break;
}
if (l == -1) {
System.out.println("客户端[ " + socketChannel.getRemoteAddress() + " ]断开连接");
key.cancel();
break;
}
attachment.flip();
System.out.print(new String(attachment.array(), 0, attachment.limit()));
attachment.clear();
}
System.out.println();
}
/**
* 对读事件进行处理
*/
private void acceptHandler() throws IOException {
SocketChannel socketChannel;
while ((socketChannel = serverSocketChannel.accept()) == null) ;
// 将socketChannel设置为非阻塞
socketChannel.configureBlocking(false);
// 将SocketChannel注册到selector,并绑定buffer
socketChannel.register(selector,
SelectionKey.OP_READ, ByteBuffer.allocate(1024));
System.out.println("成功连接客户端:" + socketChannel.getRemoteAddress());
}
}
3. 客户端代码
public class NIOClientApplication {
public static void main(String[] args) throws IOException, InterruptedException {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
// 连接到服务器,十秒内连接不上则退出客户端
if (!socketChannel.connect(new InetSocketAddress(12345))) {
for (int i = 0; i < 10; i++) {
Thread.sleep(1000);
if (socketChannel.finishConnect()) {
break;
}
}
if (!socketChannel.finishConnect()) {
System.out.println("连接出错,请稍后重连");
System.exit(0);
}
}
String msg = "hello, I am ccy";
System.out.println(msg.getBytes().length);
ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
socketChannel.write(buffer);
Thread.sleep(4000);
String msg1 = "hello, I am ccy2";
ByteBuffer buffer1 = ByteBuffer.wrap(msg1.getBytes());
socketChannel.write(buffer1);
Thread.sleep(2000);
socketChannel.close();
}
}
二、Reactor线程模型
Reactor线程模型基于NIO事件驱动进行实现,每当有事件发生时,将其进行分发。有三种实现,分别是***单线程 Reactor模型 、多线程 Reactor模型 、主从多线程 Reactor模型***
1. 单线程 Reactor 模型
与NIO模型相同
2. 多线程 Reactor 模型
当客户端连接到服务器时,reactor负责读取数据并将数据交由工作线程处理,待处理完成之后由reactor将数据会送给客户端
3. 主从 Reactor 模型
主Reactor负责监听serverSocketChannal的连接事件,当收到客户端连接时,将连接注册到从reactor中,由从reactor负责处理客户端的读写事件