一、前言
二、NIO引入
2.1 客户端代码
public class NioClient {
public static void main(String[] args) throws Exception {
//得到一个网络通道
SocketChannel socketChannel = SocketChannel.open();
//设置成非阻塞
socketChannel.configureBlocking(false);
//提供服务器Ip和端口
InetSocketAddress inetSocketAddress = new InetSocketAddress(9091);
//连接服务器端
if (!socketChannel.connect(inetSocketAddress)) { //如果连接不上
while (!socketChannel.finishConnect()){
System.out.println("Nio 非阻塞");
}
}
new Thread(new MyRunnable(socketChannel)).start();
while (true) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
int read = socketChannel.read(buffer);
byte[] array = new byte[buffer.position()];
if (array.length >= 0) System.arraycopy(buffer.array(), 0, array, 0, array.length);
if (read > 0) {
System.out.println(new String(array));
}
}
}
static class MyRunnable implements Runnable{
SocketChannel socketChannel;
public MyRunnable(SocketChannel socketChannel) {
this.socketChannel = socketChannel;
}
@Override
public void run() {
while (true) {
//创建一个Buffer对象并存入数据
Scanner scanner = new Scanner(System.in);
String message = scanner.nextLine();
ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
//发送数据
try {
socketChannel.write(buffer);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
2.2 服务端代码
public class NioServer {
public static void main(String[] args) throws Exception {
//得到serverSocketChannel对象
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//得到Selector对象
Selector selector = Selector.open();
//绑定端口
serverSocketChannel.bind(new InetSocketAddress(9091));
//设置为非阻塞式
serverSocketChannel.configureBlocking(false);
//把ServerSocketChannel注册给Selector
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);//监听连接
while (true) {
if (selector.select(2000) == 0) { // selector轮询
//System.out.println("2秒内没有客户端来连接我");
continue;
}
//得到SelectionKey对象,判断事件
Set<SelectionKey> selectionKeys = selector.selectedKeys();
for (SelectionKey selectionKey : selectionKeys) {
if (selectionKey.isAcceptable()) { //连接事件
System.out.println("有人来连接");
//获取网络通道
SocketChannel clientSocket = serverSocketChannel.accept();
//设置非阻塞式
clientSocket.configureBlocking(false);
//连接上了 注册读取事件
clientSocket.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
if (selectionKey.isReadable()) {//读取客户端数据事件
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
ByteBuffer byteBuffer = (ByteBuffer) selectionKey.attachment();
socketChannel.read(byteBuffer); // 读入
// 逻辑 关于array开始
byte[] array = new byte[byteBuffer.position()]; // 新建array
if (array.length >= 0)
System.arraycopy(byteBuffer.array(), 0, array, 0, array.length); // 复制元素放到array中
System.out.println(new String(array)); // 打印array
// 逻辑 关于array结束
byteBuffer.put("你好".getBytes()); // put放入
byteBuffer.flip(); // 翻转,写出或打印的前奏
socketChannel.write(byteBuffer); // 写出
//写完过后要记得clear一下,不然position的值和limit的是一样,下次就会报异常。
byteBuffer.clear(); // 这个buffer同时用于读写,每次循环要清空
}
//手动从当前集合将本次运行完的对象删除
selectionKeys.remove(selectionKey); // iterator或者foreach需要remove
}
}
}
}
2.3 金手指
金手指:重点看NIOServer代码,NIOClient和IOClient是一样的,甚至可以不需要IOClient,直接用命令行模拟,对于NIOServer,后面的单线程Reactor就是对其封装并分类。
三、基于NIO的Reactor单线程模式
3.1 手写Reactor单线程模式
3.1.1 项目结构
项目结构为 Main+Reactor分发器+Acceptor处理连接请求+Handler处理读写请求+Client客户端
3.1.2 项目详细
Main类:仅包含一个main()方法,为程序入口。
package com.reactor1;
public class Main {
// Reactor就是对NIO封装,按照该功能分为不同类,对于开发者来说,只要按照Main类中的,
// 新建TcpReactor对象,传入ip:port,调用run()方法就好了,底层就是一个nio good
// Reactor 用于客户端连接和分发(acceptor和read/write)
// Acceptor accept接收连接请求
// Handler read接收请求 write写回给客户端
// 三种模型都是这样
// Reactor:把IO事件分配给对应的handler处理
// Acceptor:处理客户端连接事件
// Handler:处理非阻塞的任务
public static void main(String[] args) {
Reactor reactor = null;
try {
reactor = new Reactor(1333);
reactor.run(); // 为什么设置里使用run 不是start 不需要实现Runnable接口,造成误解,
// run()方法:
// 是在主线程中执行方法,和调用普通方法一样;(按顺序执行,同步执行)
// start()方法:
// 是创建了新的线程,在新的线程中执行;(异步执行)
} catch (Exception e) {
e.printStackTrace();
}
}
}
TcpReactor类:是一个实现Runnable接口的具有多线程功能的类,用于接收accept连接请求,包括构造方法和run()方法和dispatch()分发方法,子线程处理逻辑在run()方法中,dispatch()方法为调度方法,根据事件绑定的对象开新线程。
package com.reactor1;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.util.Iterator;
import java.util.Set;
public class Reactor {
private final ServerSocketChannel serverSocketChannel; // 类变量
// 三要素都要是类变量 selector--channnel--buffer(读写才要buffer,连接不需要buffer)
private final Selector selector; // 类变量
public Reactor(int port) throws Exception { // Reactor和之前的nio一样,就是对其封装一次
// 初始五句话:初始化selector 初始化服务端channel 服务端channel绑定端口 服务端channel设置为非阻塞 服务端channel注册到selector上面,返回在selector中表示这个服务端channel的key
// 最后一句:服务端这个channel去绑定一个服务端acceptor对象,用来接收请求的,参数为服务端channel和服务端selector
selector = Selector.open(); // good 这一步不能少 初始化selector 直接静态方法新建
// 只有一个serverSocketChannel
serverSocketChannel = ServerSocketChannel.open(); // 初始化服务端channel 直接静态方法新建
//在ServerSocketChannel绑定监听端口
serverSocketChannel.socket().bind(new InetSocketAddress(port)); // 服务端channel绑定端口
//设置ServerSocketChannel为非阻塞
serverSocketChannel.configureBlocking(false); // 服务端channel设置为非阻塞
// ServerSocketChannel向selector注册一个OP_ACCEPT事件,然后返回该通道的key SelectionKey.OP_ACCEPT 16
SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); // 服务端channel注册到selector上面,返回在selector中表示这个服务端channel的key
//给定key一个附件的Acceptor对象
selectionKey.attach(new Acceptor(serverSocketChannel, selector)); // 服务端这个channel去绑定一个服务端acceptor对象,用来接收请求的,参数为服务端channel和服务端selector
}
public void run() {
//在线程被中断前持续运行
while (!Thread.interrupted()) { // 当服务端线程没有被打断的时候
System.out.println("Waiting for new event on port:" + serverSocketChannel.socket().getLocalPort() + "..."); // 在服务端端口号等待客户端连接
int len=-1;
//这一句打印表示服务端已经启动,或已经处理完成一次请求,服务端目前处于空闲状态
try {
//若没有事件就绪则不往下执行,NIO的底层是linux非阻塞io,轮询
if ((len = selector.select()) == 0) { // 这里启动的时候阻塞,连接客户端才通过,没有客户端连接,这里就是0,也可以通过唤醒来处理
continue; // 既然方法还没执行完就阻塞,就完成不会返回0,那么还需要continue;干什么
}
} catch (IOException e) {
e.printStackTrace();
}
// System.out.println(len); // 这里我想打印一下
//取得所有已就绪事件的key集合
Set<SelectionKey> selectionKeys = selector.selectedKeys(); // 当前类变量selector中的key集合 selectedKey 被选择的指定的key Accept read write
Iterator<SelectionKey> it = selectionKeys.iterator(); // 迭代iterator() while
while (it.hasNext()) {
dispatch(it.next()); //根据事件的key进行分发调度
it.remove(); // 每次删除一个,和Reactor之前的nio一样,就是对其封装一次
}
}
}
//调度方法,根据事件绑定的对象开新线程
private void dispatch(SelectionKey key) {
//根据事件之key绑定的对象开启线程
Runnable r = (Runnable) key.attachment(); // 三个绑定 服务端启动的使用绑定Accept 客户端连接的时候使用
// 客户端连接的时候绑定READ 客户端输入的时候使用
// 客户端输入的时候绑定WRITE 输出给客户端的时候调用
// 输出给客户端的时候绑定READ 下一次客户端输入的时候使用
// 得到传递多来的selector中的key,所attach绑定的Runnable对象,就是Acceptor implements Runnable
if (r != null) {
r.run(); // 调用Acceptor的run()方法 调用 主线程 两个都是主线程
}
}
}
Acceptor类:是一个实现Runnable接口的具有多线程功能的类,用于接收accept连接请求,包括构造方法和run()方法,子线程处理逻辑在run()方法中。
package com.reactor1;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
//接受连接请求线程
public class Acceptor implements Runnable {
private final ServerSocketChannel serverSocketChannel; // 两个类变量,都是被TcpReactor的类变量赋值(TcpReactor类的构造函数)
private final Selector selector; // 两个类变量,都是被TcpReactor的类变量赋值(TcpReactor类的构造函数)
public Acceptor(ServerSocketChannel serverSocketChannel, Selector selector) {
this.serverSocketChannel = serverSocketChannel; // 仅仅设置而已,acceptor的run什么时候调用
this.selector = selector;
}
public void run() {
try {
//接受client连接请求
SocketChannel socketChannel = serverSocketChannel.accept(); // 既然调用就接收请求,
System.out.println(socketChannel.getRemoteAddress().toString() + " is connected."); // 这一句作用是表示启动服务端之后,
if (socketChannel != null) { // 不为空
//设置成非阻塞 good
socketChannel.configureBlocking(false);
//SocketChannel向selector注册一个OP_READ事件,然后返回该通道的key good
//还是注册到同一个selector上面 等待输入
SelectionKey selectionKey = socketChannel.register(selector, SelectionKey.OP_READ); //注册读 因为服务端是先读后写
//使一个阻塞住的selector操作立即返回
// selector.wakeup(); //wakeup 唤醒selector中的其中一个,为什么要唤醒,在哪里阻塞,
// selector.wakeup主要是为了唤醒阻塞在selector.select上的线程,在selector.select()后线程会阻塞
//给定可以一个附加的TCPHandler对象
selectionKey.attach(new Handler(selectionKey, socketChannel)); // 这个key attach一个TcpHandler
// 传入两个参数 selectionKey是客户端channel注册到selector的时候获取到,客户端channel是连接得到的
// Acceptor建立连接,TcpHandler就要处理读写请求了
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
TcpHandler类:也是一个多线程类,用来处理客户端发过来的读写请求,包含构造方法、run()方法、send()方法、read()方法、closeChannel()方法和process()方法。构造方法用户初始化selectionKey和客户端连接socketchannel,run()方法是子线程具体逻辑,通过判断当前state的值决定服务端是读还是写,从客户端读就是调用read()方法,写出到客户端就调用send()方法,实际上,底层都是调用socketChannel.read()方法和socketChannel.write()方法,所以说,是对NIO的封装,是基于NIO实现的。剩下两个,closeChannel()方法,仅仅封装了selectionKey.cancel(); socketChannel.close();表示因服务大异常引起的连接关闭操作;process()方法,仅调用了线程休眠,模拟服务端处理耗时。
package com.reactor1;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
public class Handler implements Runnable { // 不是多线程类 不是答应
private final SelectionKey selectionKey; // 两个类变量,是在Accetpor的run方法中设置的,
// 传入两个参数 selectionKey是客户端channel注册到selector的时候获取到,客户端channel是连接得到的
private final SocketChannel socketChannel;
int state;
public Handler(SelectionKey selectionKey, SocketChannel socketChannel) {
this.selectionKey = selectionKey;
this.socketChannel = socketChannel;
//初始的状态设定为Reading
state = 0; // state表示read还是send run用来判断 先读取客户端的数据,
// 然后三步一体:将标志位修改为state=1,注册写事件,唤醒一个selector,执行run()方法,将数据写到客户端
}
public void run() { // 这个run()方法什么时候调用 知道了 逻辑就是读写
try {
if (state == 0) {
//读取网络数据
read(); //read send 向对应
} else {
//发送网络数据
send(); // read send 相对应
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("[Warning!] A client has been closed");
closeChannel(); // 发生错误,关闭连接
}
}
private void closeChannel() {
try {
selectionKey.cancel(); //
socketChannel.close(); // selectionKey是客户端channel注册到selector的时候获取到,客户端channel是连接得到的
} catch (IOException e) {
e.printStackTrace();
}
}
private void send() throws Exception {
String str = "Your message has sent to" + socketChannel.socket().getLocalSocketAddress().toString() + "\r\n"; //这是服务端要发送给客户端的响应
//wrap自动把buf的position设为0,所以不需要在flip() good wrap和flip 就是要打印或者write
ByteBuffer buf = ByteBuffer.wrap(str.getBytes());
while (buf.hasRemaining()) {
//回传给client回应字符串,发送buf的position位置到limit位置为止之间的内容
socketChannel.write(buf); // 服务端使用channell来写,客户端使用两个stream读写,因为stream是单向的,
// 服务端使用一个channel读写,因为channel是双向的,api层面既有channel.read,也有channel.write
// 而且每次都是channel.write(buffer) channel.read(buffer) 所以 channel+buffer==stream selector是类变量,服务端全局选择
// 服务端的channel和stream如何联系起来 不用联系,直接静态方法新建channel
// 为什么分为服务端channel和客户端channel
// 服务端channel TcpReactor构造函数中,serverSocketChannel = ServerSocketChannel.open();
// 客户端channel Acceptor类中的run()方法,SocketChannel socketChannel = serverSocketChannel.accept(); // 启动服务端后阻塞在这里
// 服务端channel是打开来给客户端连接的,客户端channel表示服务端获取到的一个客户端连接,表示服务端获取到的一个客户端连接 理解了 很重要 good
// 所以,服务端channel是open 客户端channel是accept() 源码命名优美
}
// 写完成数据到客户端后,然后三步一体:将标志位修改为state=0,注册读事件,唤醒一个selector,执行run()方法,读取客户端的数据,就是一个循环
//改变状态
state = 0; // 改变状态为0,继续读取客户端的数据
//通过key改变通道注册的事件 SelectionKey.OP_READ 1
selectionKey.interestOps(SelectionKey.OP_READ); // 类变量selectionKey的感兴趣的操作设置为read 下一次使用 TCPHandlers selectedkey就是read
//使一个阻塞住的selector操作立即返回
// selectionKey.selector().wakeup(); // 类变量selectionkey 得到这个key关联的selector,只有一个类变量selector,然后唤醒这个selector
// 调用wakeup()的地方,read send
}
private void read() throws Exception {
//non-blocking下不可用Readers,因为Readers不支持non-blocking
byte[] arr = new byte[1024];
ByteBuffer buffer = ByteBuffer.wrap(arr);
//读取字符串
int numBytes = socketChannel.read(buffer); // 服务端使用channel来读取,
if (numBytes == -1) {
System.out.println("[Warning!] A client has been closed.");
closeChannel(); // 如果读完了,关闭连接,注意不是客户端发送-1,而是客户端发送exit
// 因为客户端发送exit会导致出现client.close,这个时候服务端可以感应到
return;
}
//将读取到的byte内容转为字符串类型
String str = new String(arr);
if ((str != null) && !str.equals(" ")) {
//模拟逻辑处理
process(); // 模拟逻辑处理 good
System.out.println(socketChannel.socket().getRemoteSocketAddress().toString() + ">" + str); // 打印客户端ip:port 及其 数据
// 然后三步一体:将标志位修改为state=1,注册写事件,唤醒一个selector,执行run()方法,将数据写到客户端
//改变状态
state = 1; // 已经读取完毕,就要修改状态,将将会结果发送给客户端
//通过key改变通道注册的事件 SelectionKey.OP_WRITE 4
selectionKey.interestOps(SelectionKey.OP_WRITE); // 类变量selectionKey的感兴趣的操作设置为write 下一次使用 TCPHandlers selectedkey就是write
//使一个阻塞住的selector操作立即返回
// selectionKey.selector().wakeup(); // 类变量selectionkey 得到这个key关联的selector,只有一个类变量selector,然后唤醒这个selector
}
}
void process() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Client类
package com.reactor1;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
public class Client { // 客户端没什么好看的
public static void main(String[] args) {
String hostname = "127.0.0.1";
int port = 1333; // 服务端ip:port
try {
//连接至目的地
Socket client = new Socket(hostname, port); // 建立socket连接
System.out.println("连接至目的地:" + hostname); // 第一,启动之后这里打印一句
PrintWriter out = new PrintWriter(client.getOutputStream()); // 源头时client.getOutputStream() 就是本地socket需要输出到服务端的东西
BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream())); // 源头是client.getInputStream() 就是本地socket从服务端读取到的东西
BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in)); // 源头时System.in 键盘输入
String input;
while ((input = stdIn.readLine()) != null) { //读取输入 输入不为空,循环不解释
out.println(input); //发送输入的字符串 输入的东西发送给服务端
out.flush();//强制将缓冲区的数据输出
if (input.equals("exit")) {
break; // 唯一退出条件
}
System.out.println("server:" + in.readLine()); // 打印从服务端读到的东西
}
client.close(); // 关闭socket连接 因为客户端发送exit会导致出现client.close,这个时候服务端可以感应到
System.out.println("client stop."); // 打印一下关闭
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.2 解释Reacter单线程模式
现在对手写Reactor单线程模式解释,Reactor单线程程序执行:Main --> Reactor–> Acceptor --> Handler。
对于Main类:
开发者只需要传入端口就可以初始化服务端Reactor类,然后调用Reactor类的run()方法在main线程中启动。
值得注意的是,这里的Reactor Acceptor Handler都不是多线程,都是调用run()不是调用start(),不要被骗了。
run()方法:在主线程中执行方法,和调用普通方法一样;(按顺序执行,同步执行)
start()方法:创建了新的线程,在新的线程中执行;(异步执行)
Reactor就是对NIO封装,按照该功能封装为不同类,对于开发者来说,只要按照Main类中的,新建Reactor对象,传入ip:port,调用run()方法就好了,底层就是一个NIO。
Reactor类:用于客户端连接和分发(acceptor和read/write)
Acceptor类:accept接收连接请求
Handler类:read接收请求 write写回给客户端
对于Reactor类:
变量:selector ServerSocketChannel
三要素都要是类变量 selector–channnel–buffer(读写才要buffer,连接不需要buffer)
方法1:Reactor类的构造函数:
初始五句:初始化selector,初始化服务端channel,服务端channel绑定端口,服务端channel设置为非阻塞,服务端channel注册到selector上面,返回在selector中表示这个服务端channel的key;
最后一句:服务端这个channel去绑定一个服务端acceptor对象,用来接收请求的,参数为服务端channel和服务端selector。
方法2:Reactor类的run()方法
(1)当服务端线程没有被打断的时候while (!Thread.interrupted()),进入循环,selector.select() 表示若没有事件就绪则不往下执行,NIO的底层是linux非阻塞IO;
(2)在while迭代循环中,轮询取得所有已就绪事件的key集合,即当前类变量selector中的key集合,selectedKey被选择的指定的key,包括accept、read、write;
(3)根据事件的key进行分发调度,每次删除一个,和Reactor之前的nio一样,就是对其封装一次。
方法3:Reactor类的dispatch()方法
dispatch()方法中,对于传递到selector中的key,attach绑定为Runnable对象,涉及到多个绑定:
第一,Reactor类中,服务端启动的使用绑定Accept,客户端连接的时候使用;
第二,Accept类中,客户端连接的时候绑定READ,客户端输入的时候使用;
第三,Handler类中,客户端输入的时候切换为WRITE,输出给客户端的时候调用;
第四,Handler类中,输出给客户端的时候切换为READ,下一次客户端输入的时候使用。
对于Acceptor类:
变量:两个类变量,都是被TcpReactor的类变量赋值(TcpReactor类的构造函数)
对于Handler类:
方法1:TcpHandler类的send()方法
服务端使用一个channel读写,因为channel是双向的,api层面既有channel.read也有channel.write,而且每次都是channel.write(buffer) channel.read(buffer) 所以 channel+buffer==stream selector是类变量,服务端全局选择。
问题1:服务端的channel和stream如何联系起来?
回答1:不用联系,直接静态方法新建channel。
问题2:为什么分为服务端channel和客户端channel?
回答2:服务端channel,TcpReactor构造函数中,serverSocketChannel = ServerSocketChannel.open();客户端channel,Acceptor类中的run()方法,SocketChannel socketChannel = serverSocketChannel.accept(); 启动服务端后阻塞在这里。
问题3:如何理解channel?
回答3:服务端channel是打开来给客户端连接的,客户端channel表示服务端获取到的一个客户端连接,表示服务端获取到的一个客户端连接,这个对于channel的理解比较重要,写两遍。此外,服务端channel是open(),客户端channel是accept(),由此可见源码命名之优美。
方法2:TcpHandler类的read()方法
如果读完了,关闭连接,注意不是客户端发送-1,而是客户端发送exit,因为客户端发送exit会导致出现client.close,这个时候服务端可以感应到。
四、基于NIO的Reactor多线程模式
4.1 手写Reactor多线程模式
4.1.1 项目结构
HandlerState三个实现类,WorkState用来修改状态,ReadState用来完成服务端读出客户端传输的数据的操作,WriteState用来完成服务端写数据到客户端操作,将HandlerState类型的引用注册到TCPHandler类里面,其run()方法中直接调用
state.handle(this, sk, sc, pool);
这个模型中,TCPReactor单线程,Acceptor和TCPHandler里面,虽然实现Runnable接口,但是都只是在TCPReactor类中调用它们的run()方法,还是在main线程中执行其run()方法,但是TCPHandler里面有一个线程池,state.handle(this, sk, sc, pool); 表示读写操作是线程池中完成,这是这个模式的关键。
4.1.2 项目详细
Main类
package com.reactor2;
import java.io.IOException;
public class Main {
// Reactor 多线程
// Acceptor 多线程,处理acceptor请求
// Handler 多线程,处理read,使用write写回去
// 三种模型都是这样
// Reactor:把IO事件分配给对应的handler处理
// Acceptor:处理客户端连接事件
// Handler HandlerState ReadState WriteState WorkState :处理非阻塞的任务
public static void main(String[] args) {
try {
Reactor reactor = new Reactor(1333); //使用者传入端口号,然后直接run
reactor.run(); // 这里是run
} catch (IOException e) {
e.printStackTrace();
}
}
}
Reactor类
package com.reactor2;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.util.Iterator;
import java.util.Set;
public class Reactor { // main线程
private final ServerSocketChannel ssc;
private final Selector selector;
public Reactor(int port) throws IOException {
selector = Selector.open(); // selector
ssc = ServerSocketChannel.open(); // ServerSocketChannel
// 在ServerSocketChannel绑定监听端口
ssc.socket().bind(new InetSocketAddress(port)); // ServerSocketChannel 绑定端口
// 设置ServerSocketChannel为非阻塞
ssc.configureBlocking(false); // ServerSocketChannel 阻塞设置为false
// ServerSocketChannel向selector注册一个OP_ACCEPT事件,然后返回该通道的key
SelectionKey sk = ssc.register(selector, SelectionKey.OP_ACCEPT); // ServerSocketChannel op_accept
sk.attach(new Acceptor(selector, ssc)); // 给定key一個附加的Acceptor对象
}
public void run() {
while (!Thread.interrupted()) { // 在编程被中断前持续运行
System.out.println("Waiting for new event on port: " + ssc.socket().getLocalPort() + "...");
try {
if (selector.select() == 0) // 若沒有事件就绪则不往下执行
continue;
} catch (IOException e) {
e.printStackTrace();
}
Set<SelectionKey> selectedKeys = selector.selectedKeys(); // 取得所有已就绪事件的key集合
Iterator<SelectionKey> it = selectedKeys.iterator();
while (it.hasNext()) {
dispatch((SelectionKey) (it.next())); // 根据事件的key进行调度
it.remove();
}
}
}
private void dispatch(SelectionKey key) {
Runnable r = (Runnable) (key.attachment()); // 根据事件之key绑定的对象开新线程
if (r != null)
r.run(); // Acceptor类run方法、
}
}
Acceptor类
package com.reactor2;
import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
public class Acceptor implements Runnable {
private final ServerSocketChannel ssc;
private final Selector selector;
public Acceptor(Selector selector, ServerSocketChannel ssc) {
this.ssc = ssc;
this.selector = selector;
}
@Override
public void run() {
try {
// 接受client连接请求
SocketChannel sc = ssc.accept();
System.out.println(sc.socket().getRemoteSocketAddress().toString() + " is connected.");
if (sc != null) {
//设置为非阻塞
sc.configureBlocking(false);
// SocketChannel向selector注册一个OP_READ事件,然后返回该通道的key
SelectionKey sk = sc.register(selector, SelectionKey.OP_READ);
// 使一个阻塞住的selector操作立即返回
selector.wakeup();
// 给定key一个附加的TCPHandler对象
sk.attach(new Handler(sk, sc)); // 继续绑定
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
HandlerState接口
package com.reactor2;
import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ThreadPoolExecutor;
public interface HandlerState {
void changeState(Handler h); // 没使用过,可以去掉了
void handle(Handler h, SelectionKey sk, SocketChannel sc,
ThreadPoolExecutor pool) throws IOException;
}
ReadState类
package com.reactor2;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ThreadPoolExecutor;
public class ReadState implements HandlerState { // HandlerState有一个handle()方法,ReadState就是读操作,WriteState就是写操作
private SelectionKey sk;
public ReadState() {
}
@Override
public void changeState(Handler h) {
// TODO Auto-generated method stub
h.setState(new WorkState());
}
@Override
public void handle(Handler h, SelectionKey sk, SocketChannel sc,
ThreadPoolExecutor pool) throws IOException { // read()
this.sk = sk;
// non-blocking下不可用Readers,因为Readers不支持non-blocking
byte[] arr = new byte[1024];
ByteBuffer buf = ByteBuffer.wrap(arr);
int numBytes = sc.read(buf); // 读取字符串
if (numBytes == -1) {
System.out.println("[Warning!] A client has been closed.");
h.closeChannel();
return;
}
String str = new String(arr); // 将读取到的byte內容转为字符串类型
if ((str != null) && !str.equals(" ")) {
h.setState(new WorkState()); // 改变状态(READING->WORKING) 设置读状态
pool.execute(new WorkerThread(h, str)); // do process in worker thread 交给线程池去处理
System.out.println(sc.socket().getRemoteSocketAddress().toString()
+ " > " + str); // 打印发送过来的
}
}
/*
* 执行逻辑处理之函数
*/
synchronized void process(Handler h, String str) {
// 三步一体
h.setState(new WriteState()); // 改变状态WORKING->SENDING)
this.sk.interestOps(SelectionKey.OP_WRITE); // 通过key改边通道的注册事件
this.sk.selector().wakeup(); // 使一个阻塞住的selector操作立即返回
}
/*
* 工作者线程
*/
class WorkerThread implements Runnable {
Handler h;
String str;
public WorkerThread(Handler h, String str) {
this.h = h;
this.str = str;
}
@Override
public void run() {
process(h, str);
} // 多线程要保证线程安全
}
}
WorkState类
package com.reactor2;
import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ThreadPoolExecutor;
public class WorkState implements HandlerState { // WorkState 就是用来设置state的
public WorkState() {
}
@Override
public void changeState(Handler h) {
h.setState(new WriteState());
}
@Override
public void handle(Handler h, SelectionKey sk, SocketChannel sc,
ThreadPoolExecutor pool) throws IOException {
}
}
WriteState类
package com.reactor2;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ThreadPoolExecutor;
public class WriteState implements HandlerState {
public WriteState() {
}
@Override
public void changeState(Handler h) {
h.setState(new ReadState());
} // 设置状态
@Override
public void handle(Handler h, SelectionKey sk, SocketChannel sc,
ThreadPoolExecutor pool) throws IOException { // send()
String str = "Your message has sent to "
+ sc.socket().getLocalSocketAddress().toString() + "\r\n"; // 写回给服务端的内容
ByteBuffer buf = ByteBuffer.wrap(str.getBytes()); // wrap自动把buf的position设为0,所以不需要再flip()
while (buf.hasRemaining()) {
sc.write(buf); // 回传给client回传字符串,发送buf的position位置到limit位置为止之间的內容
}
// 三步一体 good
h.setState(new ReadState()); // 改变状态(SENDING->READING)
sk.interestOps(SelectionKey.OP_READ); // 通过key改变通道注册的事件
sk.selector().wakeup(); // 使一个阻塞住的selector操作立即返回
}
}
Handler类
package com.reactor2;
import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Handler implements Runnable { // Handler 变成了 Reactor Thread Pool 因为他注入了一个state和pool 处理IO的线程池
private final SelectionKey sk;
private final SocketChannel sc;
private static final int THREAD_COUNTING = 10; // 处理IO的线程数为10
private static final ThreadPoolExecutor pool = new ThreadPoolExecutor(
THREAD_COUNTING, THREAD_COUNTING, 10, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>()); // 线程池 保证插入顺序的队列
HandlerState state; // 以状态模式实现Handler
public Handler(SelectionKey sk, SocketChannel sc) {
this.sk = sk;
this.sc = sc;
state = new ReadState(); // 初始状态设定为READING 还是初始读状态
pool.setMaximumPoolSize(32); // 设置线程池的最大线程数 默认线程数为10,最大线程数为32
}
@Override
public void run() {
try {
state.handle(this, sk, sc, pool); // 调用state的handle()方法
} catch (IOException e) {
System.out.println("[Warning!] A client has been closed.");
closeChannel(); // 出问题了关闭
}
}
public void closeChannel() {
try {
sk.cancel();
sc.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
public void setState(HandlerState state) {
this.state = state; // setState就是根据参数设置
}
}
Client:无变化,略。
4.2 解释Reactor多线程模式
金手指:Reactor多线程模式
(1)服务端用于接收客户端连接的是个 1 个单独的 NIO 线程,然后,一组 NIO 线程上面完成所有的 IO 操作;
(2)Reactor 多线程模型有专门一个NIO 线程——Acceptor 线程用于监听服务端,接收客户端的 TCP 连接请求; 网络 IO 操作-读、写等由一个 NIO 线程池负责,线程池可以采用标准的 JDK 线程池实现,它包含一个任务队列和 N个可用的线程,由这些 NIO 线程负责消息的读取、解码、编码和发送。
五、基于NIO的Reactor主从多线程模式
5.1 手写Reactor主从多线程模式
5.1.1 项目结构
5.1.2 项目详细
Main类
package com.reactor3;
import java.io.IOException;
public class Main {
// 三种模型都是这样
// Reactor SubReactor :把IO事件分配给对应的handler处理 主从Reactor 为什么一主一从
// Acceptor:处理客户端连接事件
// Handler HandlerState ReadState WriteState WorkState :处理非阻塞的任务
// 第一个good: 当Acceptor处理连接事件后,主reactor将连接分配给从Reactor good
// 从Reactor将连接加入到连接队列进行监听,并创建handler进行各种事件处理
// 当有新事件发生时,从reactor就会对用对应的handler处理
// handler读取数据后,分发给后面的worker线程处理
// worker线程池分配独立的worker线程进行处理并返回结果
// handler收到结果后再讲结果返回给客户端
// 第二个good mainReactor只处理连接事件,读写事件交给subReactor来处理。 懂了
// 业务逻辑还是由线程池来处理,mainRactor只处理连接事件,用一个线程来处理就好。
// 处理读写事件的subReactor个数一般和CPU数量相等,一个subReactor对应一个线程,业务逻辑由线程池处理
public static void main(String[] args) {
try {
Reactor reactor = new Reactor(1333); // 这里新建一个TCPReactor,新建一个Acceptor,新建8个TCPSubReactor并子线程运行,
// 所以对于8个TCPSubReactor,都会阻塞在run()中的 if (selector.select() == 0) 中的一句
// 但是只是8个子线程的阻塞(所以这就是Acceptor类中新建8个TCPSubReactor类使用start()运行的原因,
// 因为其run()中的selector.select()一定会阻塞,不要阻塞main线程) main线程可以继续执行,看下面
Thread thread = new Thread(reactor);
thread.start(); // 为什么这里需要TCPReactor.start(),为什么不在main线程中执行
// 表示新建子线程执行TCPReactor中的run()方法,但是TCPReactor的run()方法没有synchronized,
// 第一start 第一个synchronized 就是因为start才需要synchronized
} catch (IOException e) {
e.printStackTrace();
}
}
}
Reactor类
package com.reactor3;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.util.Iterator;
import java.util.Set;
public class Reactor implements Runnable {
private final ServerSocketChannel ssc;
private final Selector selector; // mainReactor用的selector
public Reactor(int port) throws IOException {
selector = Selector.open(); // selector
ssc = ServerSocketChannel.open(); // ServerSocketChannel
ssc.configureBlocking(false); // 设置ServerSocketChannel为非阻塞
ssc.socket().bind( new InetSocketAddress(port)); // 在ServerSocketChannel绑定监听端口
// 所有的都注册到同一个selector上面 这里是启动的时候注册到selector上面,等待连接过来 主线程服务端channel只要接收accept good
SelectionKey sk = ssc.register(selector, SelectionKey.OP_ACCEPT); // ServerSocketChannel向selector注册一个OP_ACCEPT事件,然后返回该通道的key
sk.attach(new Acceptor(ssc)); // 给定key一个附加的Acceptor对象
}
@Override
public void run() {
while (!Thread.interrupted()) { // 在线程被中断前持续运行
System.out.println("mainReactor waiting for new event on port: "
+ ssc.socket().getLocalPort() + "..."); // 第二,启动的时候这里打印一句
try {
if (selector.select() == 0) // 若沒有事件就绪则不往下执行 启动后这里一定会阻塞 ,有了连接过来之后才放开
continue;
} catch (IOException e) {
e.printStackTrace();
}
Set<SelectionKey> selectedKeys = selector.selectedKeys(); // 取得所有已就绪事件的key集合
Iterator<SelectionKey> it = selectedKeys.iterator();
while (it.hasNext()) {
dispatch((SelectionKey) (it.next())); // 根据事件的key进行调度
it.remove();
}
}
}
private void dispatch(SelectionKey key) {
Runnable r = (Runnable) (key.attachment()); // 根据事件之key绑定的对象开新线程
if (r != null)
r.run();
}
}
SubReactor类
package com.reactor3;
import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.util.Iterator;
import java.util.Set;
public class SubReactor implements Runnable { // 多线程
private final ServerSocketChannel ssc;
private final Selector selector;
private boolean restart = false;
int num;
public SubReactor(Selector selector, ServerSocketChannel ssc, int num) {
this.ssc = ssc;
this.selector = selector;
this.num = num;
}
//SubReactor Reactor 和 Accept 的构造函数中执行 good 被阻塞了 当有用户请求发送过来的时候,accept read write
// 再次打印了两三个 System.out.println("waiting for restart");
// 两三个突破阻塞 ,并进行请求分发,交给对应的Accpet ReadState WriteState来处理
@Override
public void run() { // 什么时候调用这个run方法
while (!Thread.interrupted()) { // 在线程被中断前持续运行
System.out.println("waiting for restart"); // 第一,启动的时候这里打印8个;第四,连接过来这里打印两句
while (!Thread.interrupted() && !restart) { // 在线程被中断前以及被指定重启前持续运行
try {
if (selector.select() == 0)
continue; // 若沒有事件就绪则不往下执行
} catch (IOException e) {
e.printStackTrace();
}
Set<SelectionKey> selectedKeys = selector.selectedKeys(); // 取得所有已就绪事件的key集合
Iterator<SelectionKey> it = selectedKeys.iterator();
while (it.hasNext()) {
dispatch((SelectionKey) (it.next())); // 根据事件的key进行调度
it.remove();
}
}
}
}
private void dispatch(SelectionKey key) {
Runnable r = (Runnable) (key.attachment()); // 根据事件之key绑定的对象开启线程
if (r != null)
r.run(); // 对于每一个TCPSubReactor,调用它们的run()
}
public void setRestart(boolean restart) {
this.restart = restart;
}
}
Acceptor类
package com.reactor3;
import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
public class Acceptor implements Runnable { // 之前的Accept变成了Accept线程池,因为这里面注入了一个TCPSubReactor[]
private final ServerSocketChannel ssc; // mainReactor监听的socket通道
private final int cores = Runtime.getRuntime().availableProcessors(); // 取得CPU核心数
private final Selector[] selectors = new Selector[cores]; // 创建核心数个selector给subReactor用
private int selIdx = 0; // 当前可使用的subReactor索引
private SubReactor[] r = new SubReactor[cores]; // subReactor线程 这里开辟CPU核心数个子线程
private Thread[] t = new Thread[cores]; // subReactor线程
public Acceptor(ServerSocketChannel ssc) throws IOException {
this.ssc = ssc;
// 创建多个selector以及多个subReactor线程
for (int i = 0; i < cores; i++) {
selectors[i] = Selector.open();
r[i] = new SubReactor(selectors[i], ssc, i); //Acceptor里面新建一个TCPSubReactor线程组
t[i] = new Thread(r[i]);
t[i].start(); //Acceptor构造函数中,这里是TCPSubReactor.start(),表示新建子线程执行TCPSubReactor的run()方法,
// 但是TCPSubReactor类的run()
}
}
@Override
public synchronized void run() { // run()方法要使用同步的,之前都没有用到同步啊 Acceptor的run()方法
// 为什么Acceptor的run上面使用synchronized 调用这个run的是TCPReactor里面的run()里面的dispatch()方法,直接调用run()方法
// 而TCPReactor里面的run(),使用Main类中的start()调用
try {
SocketChannel sc = ssc.accept(); // 接受client连接请求
System.out.println(sc.socket().getRemoteSocketAddress().toString()
+ " is connected."); // 第三,连接过来之后,这里打印一句
if (sc != null) {
sc.configureBlocking(false); // 设置为非阻塞 之前
// 安排新任务的核心就是将当前的SocketChannel sc注册到指定的selector[selIdx]上面去
r[selIdx].setRestart(true); // 暂停线程 将selIdx序号TCPSubReactor线程暂停,因为要给他安排新任务
selectors[selIdx].wakeup(); // 使一个阻塞住的selector操作立即返回,要使用selectors[selIdx],要将channel注册到这个selectors[selIdx]上面去,
// 所以要先唤醒它
SelectionKey sk = sc.register(selectors[selIdx],
SelectionKey.OP_READ); // SocketChannel向selector[selIdx]注册一个OP_READ事件,然后返回该通道的key,
// 这个很重要,表示客户端连接的channel,用来注册到线程池中第一个元素上面 selectors[selIdx]
// 到底是哪一个元素 selIdx 表示 当前可使用的subReactor索引 ,初始值为0
selectors[selIdx].wakeup(); // 使一个阻塞住的selector操作立即返回
r[selIdx].setRestart(false); // 重启线程,将selIdx序号TCPSubReactor线程重启,任务安排好了
// selectors[selIdx] 和 r[selIdx] 的关系是如何联系在一起的,
// 回答:不会怎样特别连接,反正Acceptor类的时候,就使用Selector.open();得到一个selector对象,使用 selectors[i] 引用
// 然后使用这个 selectors[i] 新建一个TcpSubReactor线程 r[i] = new SubReactor(selectors[i], ssc, i);
// 并执行这个线程 t[i] = new Thread(r[i]) t[i].start();
//然后,对于run()方法的执行,将新创建的 SocketChannel 注册到 IO 线程池(subReactor 线程池)的某个 IO 线程上,
// 由它负责SocketChannel 的读写和编解码工作,就是绑定到相应的TCPHandler上面 sk.attach(new Handler(sk, sc));
// TCPHandler两个参数 一个key 一个客户端channel 默认state为readState 线程最大数量为32
// TCPHandler里面注入state state就是读写
// 很重要1: Acceptor只接受请求,然后就是交给TCPHandler TCPHandler里面注入state state就是读写
// Acceptor 线程池仅仅只用于客户端的登陆、握手和安全
// 认证,一旦链路建立成功,就将链路注册到后端 subReactor 线程池的 IO 线程上,由 IO 线程负
// 责后续的 IO 操作。表示Acceptor只接受请求,然后就是交给TCPHandler TCPHandler里面注入state state就是读写
// 很重要2:上面我们注册sc就是为了得到一个key ,这个key就是为构造TCPHandler准备的
// 这里注册到TCPHandler,那么TCPHandler的run()什么时候执行呢 就是在
sk.attach(new Handler(sk, sc)); // 给定key一个附加的TCPHandler对象
if (++selIdx == selectors.length) // 两种情况解释:如果selIdx++== selectors.length 就是 selIdx== selectors.length -1,就是到了最后一个
// 就循环,如果没有到最后一个,不不会执行 selIdx = 0; 反正selIdx还是要++的
selIdx = 0;
// 之前就四句 这里加上而已 什么使用调用SubReactor的run()
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
HandlerState接口
package com.reactor3;
import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ThreadPoolExecutor;
public interface HandlerState {
void changeState(Handler h);
void handle(Handler h, SelectionKey sk, SocketChannel sc,
ThreadPoolExecutor pool) throws IOException;
}
ReadState类
package com.reactor3;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ThreadPoolExecutor;
public class ReadState implements HandlerState {
private SelectionKey sk;
public ReadState() {
}
@Override
public void changeState(Handler h) {
h.setState(new WorkState());
}
@Override
public void handle(Handler h, SelectionKey sk, SocketChannel sc,
ThreadPoolExecutor pool) throws IOException { // read()
this.sk = sk;
// non-blocking下不可用Readers,因为Readers不支持non-blocking
byte[] arr = new byte[1024];
ByteBuffer buf = ByteBuffer.wrap(arr);
int numBytes = sc.read(buf); // 读取字符串
if (numBytes == -1) {
System.out.println("[Warning!] A client has been closed.");
h.closeChannel();
return;
}
String str = new String(arr); // 将读取到的byte內容转为字符串类型
if ((str != null) && !str.equals(" ")) {
h.setState(new WorkState()); // 改变状态(READING->WORKING)
pool.execute(new WorkerThread(h, str)); // do process in worker thread
System.out.println(sc.socket().getRemoteSocketAddress().toString()
+ " > " + str);
}
}
/*
* 执行逻辑处理之函数
*/
synchronized void process(Handler h, String str) {
h.setState(new WriteState()); // 改变状态(WORKING->SENDING)
this.sk.interestOps(SelectionKey.OP_WRITE); // 通过key改变通道注册的事件
this.sk.selector().wakeup(); // 使一个阻塞住的selector操作立即返回
}
/*
* 工作者线程
*/
class WorkerThread implements Runnable {
Handler h;
String str;
public WorkerThread(Handler h, String str) {
this.h = h;
this.str = str;
}
@Override
public void run() {
process(h, str);
}
}
}
WorkState类
package com.reactor3;
import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ThreadPoolExecutor;
public class WorkState implements HandlerState {
public WorkState() {
}
@Override
public void changeState(Handler h) {
h.setState(new WriteState());
}
@Override
public void handle(Handler h, SelectionKey sk, SocketChannel sc,
ThreadPoolExecutor pool) throws IOException {
}
}
WriteState类
package com.reactor3;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ThreadPoolExecutor;
public class WriteState implements HandlerState {
public WriteState() {
}
@Override
public void changeState(Handler h) {
h.setState(new ReadState());
}
@Override
public void handle(Handler h, SelectionKey sk, SocketChannel sc,
ThreadPoolExecutor pool) throws IOException { // send()
String str = "Your message has sent to "
+ sc.socket().getLocalSocketAddress().toString() + "\r\n";
ByteBuffer buf = ByteBuffer.wrap(str.getBytes()); // wrap自动把buf的position设为0,所以不需要再flip()
while (buf.hasRemaining()) {
sc.write(buf); // 回传给client回传字符串,发送buf的position位置 到limit位置为止之间的內容
}
// 三步一体
h.setState(new ReadState()); // 改变状态(SENDING->READING)
sk.interestOps(SelectionKey.OP_READ); // 通过key改变通道注册的事件
sk.selector().wakeup(); // 使一个阻塞住的selector操作立即返回
}
}
Handler类
package com.reactor3;
import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Handler implements Runnable { // 这里就是处理IO的线程池 从线程池
private final SelectionKey sk; // 一个key
private final SocketChannel sc; // 一个客户端channel
private static final int THREAD_COUNTING = 10;
private static final ThreadPoolExecutor pool = new ThreadPoolExecutor(
THREAD_COUNTING, THREAD_COUNTING, 10, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>()); // 线程池
HandlerState state; // 以状态模式实现Handler
public Handler(SelectionKey sk, SocketChannel sc) {
this.sk = sk;
this.sc = sc;
state = new ReadState(); // 初始状态设定为READING
pool.setMaximumPoolSize(32); // 设置线程池的最大线程数
}
@Override
public void run() {
try {
state.handle(this, sk, sc, pool); //
} catch (IOException e) {
System.out.println("[Warning!] A client has been closed.");
closeChannel();
}
}
public void closeChannel() {
try {
sk.cancel();
sc.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
public void setState(HandlerState state) {
this.state = state;
}
}
Client:无变化,略。
5.2 解释Reactor主从多线程模式
金手指:Reactor主从线程池
含义:服务端用于接收客户端连接的是一个独立的 NIO 线程池,而且由一组 NIO 线程上面完成所有的 IO 操作。
解释:Acceptor 接收到客户端 TCP 连接请求处理完成后(可能包含接入认证等),将新创建的SocketChannel 注册到 IO 线程池(sub reactor 线程池)的某个 IO 线程上,由它负责SocketChannel 的读写和编解码工作。Acceptor 线程池仅仅只用于客户端的登陆、握手和安全认证,一旦链路建立成功,就将链路注册到后端 subReactor 线程池的 IO 线程上,由 IO 线程负责后续的 IO 操作。
金手指:梳理下基于Reactor主从多线程模型的事件处理过程
Reactor主线程对象通过select监听连接事件,通过Acceptor处理连接事件,当Acceptor处理连接事件后,主reactor将连接分配给从Reactor。
从Reactor将连接加入到连接队列进行监听,并创建handler进行各种事件处理,当有新事件发生时,从reactor就会对用对应的handler处理handler读取数据后,分发给后面的worker线程处理worker线程池分配独立的worker线程进行处理并返回结果,handler收到结果后再讲结果返回给客户端
所以,在主从Reactor多线程模型中,父线程与子线程之间数据交互简单、责任明确,父线程只需接收新连接,后续的处理交给子线程完成即可;主从Reactor多线程模型中,Reactor线程拆分为mainReactor和subReactor两个部分,mainReactor只处理连接事件,读写事件交给subReactor来处理。业务逻辑还是由线程池来处理,mainRactor只处理连接事件,用一个线程来处理就好。处理读写事件的subReactor个数一般和CPU数量相等,一个subReactor对应一个线程,业务逻辑由线程池处理。
六、面试金手指
Reactor模型是基于事件驱动的线程模型,可以分为Reactor单线程模型、Reactor多线程模型、主从Reactor多线程模型,通常基于在I/O多路复用实现。三个不同的角色包括:Dispatcher、Acceptor、Handler。
Reactor:把IO事件分配给对应的handler处理
Acceptor:处理客户端连接事件
Handler:处理非阻塞/非连接事件,如读写事件
Reactor单线程模型中,
Reactor:把IO事件分配给对应的handler处理;
Acceptor:处理客户端连接事件(单线程处理连接事件,Acceptor实现Runnable接口忽略);
Handler:处理非阻塞的任务(单线程处理读写事件,Handler实现Runnable接口忽略)。
Reactor多线程模型中,
Reactor:把IO事件分配给对应的handler处理;
Acceptor:处理客户端连接事件(单线程处理连接事件,Acceptor实现Runnable接口忽略);
Handler:处理非阻塞的任务(Handler类中线程池处理读写事件,Handler实现Runnable接口忽略)。
Reactor主从多线程模型中,
Reactor:把IO事件分配给对应的handler处理;
Acceptor:处理客户端连接事件(Acceptor类中线程池处理连接事件,Acceptor实现Runnable接口忽略);
Handler:处理非阻塞的任务(Handler类中线程池处理读写事件,Handler实现Runnable接口忽略)。
6.1 Reactor单线程模型
1、原理图示
在Reactor单线程模型中,操作在同一个Reactor线程中完成。根据事件的不同类型,由Dispatcher将事件转发到不同的角色中处理。连接事件转发到Acceptor处理、读写事件转发到不同的Handler处理。
2、实现图示
NIO实现中,可以将Accept事件注册到select选择器中,轮询是否有“接受就绪”事件。如果为“连接就绪”分发给Acceptor角色处理;“写就绪”事件分发给负责写的Handler角色处理;“读就绪”事件分发给负责读的Handler角色处理。这是事情都在一个线程中处理。
6.2 Reactor多线程模型
1、原理图示
在Reactor多线程模型中。根据事件的不同类型,由Dispatcher将事件转发到不同的角色中处理。连接事件转发到Acceptor单线程处理、读写事件转发到不同的Handler由线程池处理。
2、实现图示
NIO实现中,可以将Accept事件注册到select选择器中,轮询是否有“接受就绪”事件。如果为“连接就绪”分发给Acceptor角色处理,此处处理“连接就绪”为一个线程;“写就绪”事件分发给负责写的Handler角色由线程池处理;“读就绪”事件分发给负责读的Handler角色由线程池处理。
6.3 Reactor主从多线程模型
1、原理图示
Reactor多线程模型,由Acceptor接受客户端连接请求后,创建SocketChannel注册到Main-Reactor线程池中某个线程的Select中;具体处理读写事件还是使用线程池处理(Sub-Reactor线程池)。
2、实现图示
将Accept事件注册到select选择器中,轮询是否有“接受就绪”事件;“连接就绪”分发给Acceptor角色处理,创建新的SocketChannel转发给Main-Reactor线程池中的某个线程处理;在指定的Main-Reactor某个线程中,将SocketChannel注册读写事件;当“写就绪/读就绪”事件分别由线程池(Sub-Reactor线程池)处理。
七、尾声
基于NIO的Reactor三种模式,完成了。
天天打码,天天进步!!!
源工程代码:
https://download.csdn.net/download/qq_36963950/12713959