Netty完整源码+代码示例地址:http://docs.52im.net/extend/docs/src/netty4/
Netty API地址:https://netty.io/4.1/api/index.html
一.Reactor模式
1.概念:Reactor模式又称为反应器模式,是一种处理同步IO通信的设计模式,应用于同步IO场景,基于事件驱动。传统的IO操作会为每一个连接或请求建立一个单独的线程然后执行IO操作阻塞在那里,这样非常的浪费资源和效率。 Reactor设计模式能够避免为每个消息,请求,连接创建一个线程的问题,它采用事件驱动,将业务处理器感兴趣的事件注册到分发器上,只有当相应事件发生时,分发器才会通知相应的处理器处理发生的事务--“We wlill call you”
2.模式组成部分
(1)Handle:即操作系统中的句柄,是对资源在操作系统层面上的一种抽象,它可以是打开的文件、一个连接(Socket)、Timer等。由于Reactor模式一般使用在网络编程中,因而这里一般指Socket 连接,即一个网络连接(Connection,在Java NIO中的Channel)。这个Channel注册到Synchronous Event Demultiplexer中,以监听Handle中发生的事件,对ServerSocketChannnel可以是CONNECT事件,对SocketChannel可以是READ、WRITE、CLOSE事件等。
(2)Synchronous Event Demultiplexer:阻塞等待一系列的Handle中的事件到来,如果阻塞等待返回,即表示在返回的Handle中可以不阻塞的执行返回的事件类型。这个模块一般使用操作系统的select来实现。在Java NIO中用Selector来封装,当Selector.select()返回时,可以调用Selector的selectedKeys()方法获取发生的事件
(3)Initiation Dispatcher:用于管理Event Handler,即EventHandler的容器,用以注册、移除EventHandler等;另外,它还作为Reactor模式的入口调用Synchronous Event Demultiplexer的select方法以阻塞等待事件返回,当阻塞等待返回时,根据事件发生的Handle将其分发给对应的Event Handler处理,即回调EventHandler中的handle_event()方法。
(4)Event Handler:定义事件处理方法:handle_event(),以供InitiationDispatcher回调使用。
(5)Concrete Event Handler:事件EventHandler接口,实现特定事件处理逻辑。比如有专门处理读的ReadHandler,专门处理写的WriteHandler
二.同步异步阻塞非阻塞
(1)同步和异步:同步A调用B,B处理直到获得结果,才返回给A。需要调用者一直等待和确认调用结果是否返回,然后继续往下执行。异步A调用B,无需等待结果,B通过状态通知A或回调函数来处理。调用结果返回时,会以消息或回调的方式通知调用者。同步=主动询问,异步=他发生时会主动告诉你
(2)阻塞非阻塞:阻塞A调用B,A被挂起直到B返回结果给A,才能继续执行。调用结果返回前,当前线程挂起不能够处理其他任务,一直等待调用结果返回。非阻塞A调用B,A不会被挂起,A可以执行其他操作。调用结果返回前,当前线程不挂起,可以处理其他任务。
(3)同步异步是个操作方式(获得响应的一种方式),阻塞非阻塞是线程的一种状态。同步异步指的是被调用者结果返回时通知线程的一种机制,阻塞非阻塞指的是调用结果返回进程前的状态,是挂起还是继续处理其他任务。
(4)例子(来源于网络,侵删):
故事:老王烧开水。
出场人物:老张,水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶)。
老王想了想,有好几种等待方式
1.老王用水壶煮水,并且站在那里,不管水开没开,每隔一定时间看看水开了没。-同步阻塞
老王想了想,这种方法不够聪明。
2.老王还是用水壶煮水,不再傻傻的站在那里看水开,跑去寝室上网,但是还是会每隔一段时间过来看看水开了没有,水没有开就走人。-同步非阻塞
老王想了想,现在的方法聪明了些,但是还是不够好。
3.老王这次使用高大上的响水壶来煮水,站在那里,但是不会再每隔一段时间去看水开,而是等水开了,水壶会自动的通知他。-异步阻塞
老王想了想,不会呀,既然水壶可以通知我,那我为什么还要傻傻的站在那里等呢,嗯,得换个方法。
4.老王还是使用响水壶煮水,跑到客厅上网去,等着响水壶自己把水煮熟了以后通知他。-异步非阻塞
老王豁然,这下感觉轻松了很多。
三.Java NIO与Reactor模式
1.Java NIO对Reactor的支持
Java NIO是一种同步非阻塞的IO,同步是因为NIO需要主动去轮询事件的发生,非阻塞是因为IO操作是非阻塞的。
NIO对Reactor模式有无缝的支持,即使用Selector类封装了操作系统提供的Synchronous Event Demultiplexer功能。NIO中Reactor的核心是Selector,
Selector内部原理实际是在做一个对所注册的channel的轮询访问,不断的轮询(目前就这一个算法),一旦轮询到一个channel有所注册的事情发生,比如数据来了,他就会通知我们我们感兴趣的事件发生了,交出一把钥匙,让我们通过这把钥匙来读取这个channel的内容,而不需要我们主动去轮询事件了。一个传统的NIO代码其实就是一个简单的Reactor模式,通过select()获取发生事件,然后进行相应的事件处理。
2.Java NIO Reactor模式的封装实现
Synchronous Event Demultiplexer:由Selector封装,提供select()方法获取事件
Initiation Dispatcher:封装为一个Reactor类,其内部有一个Selector进行轮询,Reactor根据事件分发Handler
Event Handler:使用SelectionKey中的Attachment来存储事件对应的EventHandler对象,因而不需要注册EventHandler这个步骤,或者设置Attachment就是这里的注册。
(1)单线程Reactor模式
最简单的单Reactor单线程模型。Reactor线程是个多面手,负责多路分离套接字,Accept新连接,并分派请求到Handler处理器中。Acceptor是个专门处理连接事件的Handler,缺点是当其中某个 handler 阻塞时, 会导致其他所有的 client 的 handler 都得不到执行, 并且更严重的是, handler 的阻塞也会导致整个服务不能接收新的 client 请求(因为 acceptor 也被阻塞了)。代码如下:
class Reactor implements Runnable { //封装Reactor负责处理连接,分配handler
final Selector selector;//封装Synchronous Event Demultiplexer,实现轮询事件通知
final ServerSocketChannel serverSocket;
Reactor(int port) throws IOException { //Reactor初始化
selector = Selector.open();
serverSocket = ServerSocketChannel.open();
serverSocket.socket().bind(new InetSocketAddress(port));
serverSocket.configureBlocking(false); //非阻塞
SelectionKey sk = serverSocket.register(selector, SelectionKey.OP_ACCEPT); //分步处理,第一步,注册accept事件
sk.attach(new Acceptor()); //通过attach方法将处理accept的handler对象附加给相应的selectionkey,相当于注册handler
}
public void run() {
try {
while (!Thread.interrupted()) {
selector.select();
Set selected = selector.selectedKeys();
Iterator it = selected.iterator();
while (it.hasNext())//事件通知
dispatch((SelectionKey)(it.next()); //Reactor负责dispatch收到的事件
selected.clear();
}
} catch (IOException ex) { /* ... */ }
}
void dispatch(SelectionKey k) {
Runnable r = (Runnable)(k.attachment()); //调用之前注册的callback对象
if (r != null)
r.run();//处理事务
}
class Acceptor implements Runnable { //内部类,连接处理handler
public void run() {
try {
SocketChannel c = serverSocket.accept();
if (c != null)
new Handler(selector, c);//分配新的handler
}
catch(IOException ex) { /* ... */ }
}
}
}
//一个handler根据判断同时处理read和write
final class Handler implements Runnable {
final SocketChannel socket;
final SelectionKey sk;
ByteBuffer input = ByteBuffer.allocate(MAXIN);
ByteBuffer output = ByteBuffer.allocate(MAXOUT);
static final int READING = 0, SENDING = 1;
int state = READING;
Handler(Selector sel, SocketChannel c) throws IOException {
socket = c; c.configureBlocking(false);
// Optionally try first read now
sk = socket.register(sel, 0);
sk.attach(this); //将Handler作为callback(回调)对象
sk.interestOps(SelectionKey.OP_READ); //第二步,注册Read事件
sel.wakeup();//唤醒select()
}
boolean inputIsComplete() { /* ... */ }
boolean outputIsComplete() { /* ... */ }
void process() { /* ... */ }
public void run() {//处理事务
try {
if (state == READING) read();
else if (state == SENDING) send();
} catch (IOException ex) { /* ... */ }
}
void read() throws IOException {
socket.read(input);
if (inputIsComplete()) {
process();
state = SENDING;
// Normally also do first write now
sk.interestOps(SelectionKey.OP_WRITE); //第三步,接收write事件
}
}
void send() throws IOException {
socket.write(output);
if (outputIsComplete()) sk.cancel(); //write完就结束了, 关闭select key
}
}
//可以用State-Object pattern来更优雅的实现,read和write处理分离
class Handler { // ...
public void run() { // initial state is reader
socket.read(input);
if (inputIsComplete()) {
process();
sk.attach(new Sender()); //状态迁移, Read后变成write, 用Sender作为新的callback对象
sk.interest(SelectionKey.OP_WRITE);
sk.selector().wakeup();
}
}
class Sender implements Runnable {
public void run(){ // ...
socket.write(output);
if (outputIsComplete()) sk.cancel();
}
}
}
(2)单Reactor多线程模式
在线程Reactor模式基础上,做如下改进:将Handler处理器的执行放入线程池,多线程进行业务处理。防止handler阻塞,将请求接收和事务处理分离开来。代码如下:
class Handler implements Runnable {
// uses util.concurrent thread pool
static PooledExecutor pool = new PooledExecutor(...);//线程池分配线程
static final int PROCESSING = 3;
// ...
synchronized void read() { // ...
socket.read(input);
if (inputIsComplete()) {
state = PROCESSING;
pool.execute(new Processer()); //使用线程pool异步执行
}
}
synchronized void processAndHandOff() {
process();
state = SENDING; // or rebind attachment
sk.interest(SelectionKey.OP_WRITE); //process完,开始等待write事件
}
/*
*内部类
*内部类可以使用外部类的任何属性和方法,可使用Out.this声明
*内部类的创建必须拥有外部类的实例: new Out().new Inner()
*内部类持有外部类的引用,因此可以将两个线程联系起来
*/
/*
*线程
*没有父子线程之说,线程都是相互独立的
*线程之间互不影响包括生命周期
*/
class Processer implements Runnable {
public void run() { processAndHandOff(); }
}
}
(3)多Reactor多线程模式
对于多核CPU,为了充分利用CPU的性能,引入了多Reactor,也即一个主Reactor负责监控所有的连接请求,多个子Reactor负责监控并处理读/写请求,减轻了主Reactor的压力,降低了主Reactor压力太大而造成的延迟。并且每个子Reactor分别属于一个独立的线程,每个成功连接后的Channel的所有操作由同一个线程处理。这样保证了同一请求的所有状态和上下文在同一个线程中,避免了不必要的上下文切换,同时也方便了监控请求响应状态。Netty就是利用了该Reactor模式去搭建的,代码如下:
Selector[] selectors; //subReactors集合, 一个selector代表一个subReactor
int next = 0;
class Acceptor { // ...
public synchronized void run() { ...
Socket connection = serverSocket.accept(); //主selector负责accept
if (connection != null)
new Handler(selectors[next], connection); //选个subReactor去负责接收到的connection
if (++next == selectors.length) next = 0;
}
}