在上一篇章讲了这么多关于recator模型,那么在netty里面到底是怎么使用这个模型的呢?
1、netty的服务器端网络模型
个人阅读netty源代码的时候,认为netty的服务器端用的是最简单的recator网络模型,也就是单线程的recator模型。本文涉及到的netty源码版本是3.6.5,主要分析socket协议中的nio。
1.1、netty服务器端真实的模型设计
Netty服务器代码中还有个叫NioServerBossPool的类,乍看起来Netty的服务器端网络架构也应该是MainReactor和SubReactor模式才对,但是仔细发现,只有当服务器端绑定多个端口的时候NioServerBossPool类才会发挥真正的作用,下面跟踪下代码来:
ServerBootstrap的bindAsync方法里的getFactory().newChannel->
NioServerSocketChannelFactory的newChannel方法里面可以看到如下代码:return new NioServerSocketChannel(this,pipeline,sink,bossPool.nextBoss(),workerPool)
可以看到一个NioServerSocketChannel只对应一个NioServerBoss类,所以我才下结论说服务器端的网络模型是最简单的Reactor模型。其实也不难理解,我们常说subReactor是用来负责建立连接和分配连接,但是在服务器端,mainReactor不可能把建立连接的工作分配到subReactor上去,因为服务器只有建立了连接才知道有新连接,因为服务器是被动的,只有客户端才能很好的执行mainReactor和subReactor模型。废话讲完,继续进入正题。
1.2、netty服务器端代码类说明
netty3.6.5版本对代码进行了大面积的重构,类文件的命名上也清晰了很多。
netty的服务器端的nio的recator是:org.jboss.netty.channel.socket.nio.NioServerBoss.java
netty的服务器端的nio的工作线程是:org.jboss.netty.channel.socket.nio.NioWorker.java
其中netty的工作线程是服务器端和客户端公用的。
1.2.1、NioServerBoss功能方法
- bind:主要是服务器端启动的时候绑定端口
- run:NioServerBoss首先是一个线程类,会不停得调用process方法
- process:主要是遍历Selector查看是否有新的请求链接进来,如果有,建立链接,并且通过registerAcceptedChannel将链接注册到工作线程(NioWorker)中去。
- registerAcceptedChannel:负责将链接分配到其中一个NioWorker中去。
1.2.2、NioWorker工作线程的功能简介
NioWorker的核心处理流程其实是在它的父类AbstractNioWorker中,主要通过process方法来处理其管理的链接,其中read是需要子类去具体实现的,netty用了一些巧妙的方法优化了读取过程也在一定程度上提升了性能,本文暂时不分析这个读取过程,先从大的框架上去描绘。
当然无论是NioServerBoss还是NioWorker,都是通过一个单独的线程来添加新进来的链接的。NioServerBoss中的bind方法其实就是通过NioServerBoss的RegisterTask来完成的,我想netty使用这种机制是为了提升CPU的使用率?
1.3、模拟netty的服务器端网络模型
为了更加深刻的理解netty的服务器端网络模型,最好的方式就是用代码来表述,而且是尽可能简单的代码,因此我参考了netty的代码后,提炼出我认为比较简单的模型代码如下:
在展示代码前,先大致描述下这些类:
1、NioService:服务器端启动类,startUp做一些初始化工作并且绑定一个reactor也就是Boss线程
2、Boss:NioService的内部类,一个线程,不停的轮询Selector来处理请求的连接,连接建立完毕后分配到指定的work线程去处理
3、Work:完成读,处理业务逻辑,写功能。
4、WorkPool:work线程的池,Boss线程就是从这个池里面获取到其中的一个work线程
5、SimpleHandler:模拟netty的handler,用于处理业务逻辑,里面暂时就两个方法,一个是messageReceived,在work线程数据读取完成后触发,一个是 channelConnected,在work线程注册连接的时候触发(注册连接的时候就是连接建立完毕的时候).
6、DefaultSimpleHandler:随便实现了一个业务处理逻辑类,实现SimpleHandler.
recator类和服务器端启动类,写在一起了。
package org.ben.nio.multithread;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/**
* @Auth ben.s
* @Email [email protected]
* @Date 2013-2-22
*
* 服务器启动类
*/
public class NioService {
// 假设是四核CPU,就开四个Boss类
private static WorkerPool workerPool = new WorkerPool();
// 启动服务
public void startUp() {
try {
// 创建一个通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel
.open();
// 绑定地址,类似sokcet的绑定
serverSocketChannel.socket().bind(
new InetSocketAddress(InetAddress.getLocalHost(), 4444));
List handlers = new ArrayList();
handlers.add(new DefaultSimpleHandler());
workerPool.init(handlers);
Boss boss = new Boss(serverSocketChannel);
boss.start();
System.out.println("服务器启动成功");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* Recator类,默认只允许开一个端口,所以不像netty有bind方法可以被多次调用,这里直接用构造函数直接注册了,简化了注册过程代码。
*
* run过程中等同于把netty的NioServerBoss类的process方法直接写进去。
* registerAcceptedChannel方法就是将简历好的链接分配到指定的worker中去。
* @author shao
*
*/
private final class Boss extends Thread {
private final Selector selector;
private final ServerSocketChannel serverSocketChannel;
Boss(ServerSocketChannel serverSocketChannel) throws IOException {
this.selector = Selector.open();
this.serverSocketChannel = serverSocketChannel;
boolean registered = false;
try {
this.serverSocketChannel.configureBlocking(false);
this.serverSocketChannel.register(this.selector,
SelectionKey.OP_ACCEPT);
registered = true;
} finally {
if (!registered) {
closeSelector();
}
}
}
private void closeSelector() {
try {
selector.close();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void run() {
// TODO Auto-generated method stub
for (;;) {
try {
this.selector.select(1000);
Set selectedKeys = this.selector.selectedKeys();
if (selectedKeys.isEmpty()) {
continue;
}
for (Iterator i = selectedKeys.iterator(); i
.hasNext();) {
SelectionKey k = i.next();
i.remove();
for (;;) {
SocketChannel acceptedSocket = this.serverSocketChannel
.accept();
if (acceptedSocket == null) {
break;
}
registerAcceptedChannel(acceptedSocket);
}
}
} catch (Throwable e) {
e.printStackTrace();
}
}
}
private void registerAcceptedChannel(SocketChannel acceptedSocket) {
try {
workerPool.getReader().register((SocketChannel) acceptedSocket);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static void main(String[] ben) {
NioService ns = new NioService();
ns.startUp();
}
}
worker类
package org.ben.nio.multithread;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
public class Worker extends Thread {
private List handlers;
// 自己管理selector
private Selector selector;
private int id;
private final AtomicBoolean wakenUp = new AtomicBoolean();
public Worker(List handlers, int id) throws IOException {
this.handlers = handlers;
this.id = id;
this.selector = (this.selector == null) ? Selector.open()
: this.selector;
}
public void register(SocketChannel socketChannel) throws IOException {
if (socketChannel == null || socketChannel.isOpen()) {
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
for (SimpleHandler handler : handlers) {
handler.channelConnected(id, socketChannel, this);
}
// 使尚未返回的第一个选择操作立即返回。
// 如果另一个线程目前正阻塞在 select() 或 select(long) 方法的调用中,则该调用将立即返回。
// 如果当前未进行选择操作,那么在没有同时调用 selectNow() 方法的情况下,对上述方法的下一次调用将立即返回。
// 在任一情况下,该调用返回的值可能是非零的。如果未同时再次调用此方法,则照常阻塞 select() 或 select(long)
// 方法的后续调用。
// 在两个连续的选择操作之间多次调用此方法与只调用一次的效果相同。
// 在一次阻塞过程中避免太多的wakeup,一次阻塞过程中,再多的注册也只会wakeup一次,由于在run方法中存在
// if(wakenup.get())this.selector.wakeup()方法,会让一次阻塞过程中注册的多个channel在当次循环中未被执行的,在下一次会立即执行.
// 比如A,B,C,D顺序快速注册
// A注册触发this.selector.wakeup()
// B注册时候未触发this.selector.wakeup(),但是也正好A的触发wakeup之前进行注册
// C和D都在wakeup之后注册。
// 那么A和B会在当次循环执行掉,C和D理论上要等this.selector.select(500)中设置的500ms才会执行,
// 但是由于if(wakenup.get())this.selector.wakeup()的存在(当前没有selector阻塞,会作用到下一次),
// 那么C和D就不需要等500ms了,而是立即执行。等C和D执行完,由于wakeup又是false,所以不会继续唤醒下一次的selector操作
if (wakenUp.compareAndSet(false, true)) {
this.selector.wakeup();
}
}
}
public void run() {
while (true) {
wakenUp.set(false);
try {
int selectNum = this.selector.select(500);
if (selectNum > 0) {
Set keys = this.selector.selectedKeys();
// 使下一次直接调用,可以在这个执行过程中注册的通道能在下一次调用的时候被执行.
if (wakenUp.get()) {
this.selector.wakeup();
}
for (Iterator i = keys.iterator(); i
.hasNext();) {
SelectionKey k = i.next();
i.remove();
try {
int readyOps = k.readyOps();
if ((readyOps & SelectionKey.OP_READ) != 0
|| readyOps == 0) {
if (!read(k)) {
// Connection already closed - no need to
// handle write.
continue;
}
}
/**
* 其实OP_WRITER我这里没有使用到,一直没有去触发,在netty我也暂时还没了解它是怎么触发的
* , 将来有时间再去细细了解,反正即使不使用OP_WRITE也不妨碍代码正常运行。
*/
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
writeFromSelectorLoop(k);
}
} catch (CancelledKeyException e) {
k.cancel();
} catch(IOException e){
System.out.println("客户端连接被强制关闭!");
k.cancel();
}
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/**
* 这里只是简单的模拟获取数据,固定分配1024个字节,在实际需求中,要考虑数据的大小,解码,编码,性能等.
*
* @param key
* @return
* @throws IOException
*/
private boolean read(SelectionKey key) throws IOException {
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer data = ByteBuffer.allocate(1024);
sc.read(data);
/**
* 根据注册的handlers执行messageReceived
* 这里其实就是处理业务逻辑的地方,netty中有handler链完成,此部分会在后面章节描述,这里只需要了解是处理业务逻辑即可。
*/
for (SimpleHandler handler : handlers) {
handler.messageReceived(id, data, sc, this);
}
return true;
}
// 无用
private void writeFromSelectorLoop(SelectionKey k) {
// TODO Auto-generated method stub
}
// 写数据
private void write0(SocketChannel sc, ByteBuffer msg) throws IOException {
sc.write(msg);
}
// 用户业务逻辑触发写数据
public void writeFormUserCode(SocketChannel sc, ByteBuffer msg) {
try {
write0(sc, msg);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
work池类
package org.ben.nio.multithread;
import java.util.ArrayList;
import java.util.List;
public class WorkerPool {
private static List workList = null;
private int workNum = 5;
private int nextId = 0;
public void init(List handlers) {
workList = new ArrayList();
for (int i = 0; i < workNum; i++) {
try {
Worker worker = new Worker(handlers, i);
workList.add(worker);
worker.start();
} catch (Throwable e) {
e.printStackTrace();
}
}
}
public Worker getReader() {
return workList.get(nextId++ % 5);
}
}
handler接口以及简单的默认实现类
package org.ben.nio.multithread;
import java.nio.channels.SocketChannel;
/**
* 业务处理类
*
* @Auth ben.s
* @Email [email protected]
* @Date 2013-2-23
*/
public interface SimpleHandler {
public void messageReceived(int id,Object param,SocketChannel sc,Worker worker);
public void channelConnected(int id,SocketChannel sc,Worker worker);
}
package org.ben.nio.multithread;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class DefaultSimpleHandler implements SimpleHandler {
@Override
//netty的SocketChannel里包含了执行的worker,传递的数据,已经封装了writer方法,所以在
//netty的操作是socketChannel.write(msg);这种方式
public void messageReceived(int id,Object param, SocketChannel sc,Worker worker) {
System.out.println("["+System.currentTimeMillis()+"]客户端获取("+id+"):"+new String(((ByteBuffer)param).array()));
worker.writeFormUserCode(sc, ByteBuffer.wrap(("服务器端work线程"+id+"thanks to access!").getBytes()));
}
@Override
public void channelConnected(int id, SocketChannel sc,
Worker worker) {
// TODO Auto-generated method stub
}
}
2、netty的客户端网络模型
2.1、netty的客户端模型类说明
netty的客户端网络模型使用的是mainRecator和subRecator模式,mainReactor负责建立连接和分配连接,subRecator负责读写数据和业务逻辑处理。
- mainReactor:org.jboss.netty.channel.socket.nio.NioClientSocketPipelineSink.connect方法
- subReactor:org.jboss.netty.channel.socket.nio.NioClientBoss.java
- subRecatorPool:org.jboss.netty.channel.socket.nio.NioClientBossPool.java
- worker线程:org.jboss.netty.channel.socket.nio.NioWorker.java
NioClientSocketPipelineSink是建立连接最后处理的终结类,下一篇文章分析netty整体架构的时候会讲到,NioclientSocketPipelineSink创建的时候会要求NioClientBossPool,默认情况只有一个NioClientBoss线程,当调用NioClientSocketPipelineSink的connect方法的时候会通过nextBoss来获取一个NioClientBoss来具体处理创建连接和分配连接,这就是NioClientBoss主要去完成的工作。
NioClientBoss的主要逻辑还是有其父类(AbstractNioSelector)来完成,而NioClientBoss的方法调用逻辑如下:
process->processSelectedKeys->connect,connect方法的ch.worker.register是讲建立的连接分配到指定的worker线程中去执行。
2.2、netty的客户端模型的简化代码示例
在接下来的简化模型中,NioClient类做mainReacator,NioClient的内部类Boss做subReacator,worker线程仍旧是工作线程.另外由于客户端建立连接是个异步模式,所以在创建完连接前是不能写数据的,所以这里根据netty的方式建立了一个ChannelFuture来做处理,并且还使用FutureListiner作为ChannelFuture连接建立完毕后的回调类。
- NioClient:客户端类,也充当mainReacatory模式.其中有两个connect方法,没有参数的connect方法就是返回一个默认的ChannelFuture,有参数的connect允许为ChannelFuture添加一个监听器,用于在ChannelFuture完成后去触发某些操作,比如写数据
- ChannelFuture:由于在客户端使用了mainReactor和subReactor模型,所以创建连接是一个异步过程,所以创建一个ChannelFuture可以添加监听器在连接穿件完毕触发事件,也可以调用ChannelFuture的waitUntilSuccess来阻塞直到连接创建完毕.
- FutureListiner:ChannelFuture的监听器,里面只有一个简单的callBack方法,这个是最简单的监听器了.
- Boss:SubReactor类,用于建立连接并且分配到其中的work线程去处理.
NioClient类:
package org.ben.nio.multithread;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
public class NioClient {
private static int bossCount = 2;
private static AtomicLong count = new AtomicLong(0);
private static Boss[] bossPool = null;
private static WorkerPool workerPool = null;
static {
List handlers = new ArrayList();
handlers.add(new DefaultClientSimpleHandler("hello world!"));
if (workerPool == null) {
workerPool = new WorkerPool();
workerPool.init(handlers);
}
if (bossPool == null) {
bossPool = new Boss[bossCount];
for (int i = 0; i < 2; i++) {
Boss boss = new Boss(i);
boss.start();
bossPool[i] = boss;
}
}
}
private Boss nextBoss() {
Boss boss = bossPool[(int) count.longValue() % 2];
count.incrementAndGet();
return boss;
}
public ChannelFuture connect() {
ChannelFuture future = new ChannelFuture();
return this.connect(future);
}
public ChannelFuture connect(FutureListiner listiner) {
ChannelFuture future = new ChannelFuture();
future.addListiner(listiner);
return this.connect(future);
}
private ChannelFuture connect(ChannelFuture future) {
SocketChannel socketChannel;
try {
socketChannel = SocketChannel.open();
future.setSocketChannel(socketChannel);
nextBoss().registor(future);
return future;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
static class Boss extends Thread {
private SocketChannel socketChannel;
private ChannelFuture future;
private Selector selector = null;
private int id;
public Boss(int id) {
try {
this.selector = (this.selector == null) ? Selector.open()
: this.selector;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.id = id;
}
public void registor(ChannelFuture future) {
this.socketChannel = future.getSocketChannel();
this.future = future;
try {
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_CONNECT);
socketChannel.connect(new InetSocketAddress(InetAddress
.getLocalHost(), 4444));
this.selector.wakeup();
System.out.println("BOSS[" + this.id + "]注册一个connect操作");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void run() {
for (;;) {
process();
}
}
private void process() {
int selectNum;
try {
selectNum = this.selector.select(500);
if (selectNum > 0) {
System.out.println("BOSS[" + this.id + "]开始connect操作");
Set keys = selector.selectedKeys();
for (Iterator it = keys.iterator(); it
.hasNext();) {
SelectionKey key = it.next();
it.remove();
if (key.isConnectable()) {
SocketChannel channel = ((SocketChannel) key
.channel());
connect(channel);
key.cancel();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void connect(SocketChannel channel) throws IOException {
// 判断此通道上是否正在进行连接操作。
// 完成套接字通道的连接过程。
if (channel.isConnectionPending()) {
channel.finishConnect();
this.future.setSuccess();
workerPool.getReader().register(channel);
}
}
}
public static void main(String[] ben) throws IOException,
InterruptedException {
// 调用方式一
/**
* 4个线程全部新建一个SocketChannel,性能很差
*/
// NioClient nioClient = new NioClient();
// for(int i=0;i<4;i++){
// nioClient.connect();
// }
// 调用方式二
/**
* 只建立一个通道,连续发送数据 下面是发送四次数据,但是后面发现,服务器端可能接收到少于四次的数据,因为有可能数据被合并了,
* 第一次发送的是正常的:how are you! 第二次到第四次可能被合并发送成:how are you!how are you!how are you!
*
* 记得netty中对于此类事件是怎么处理的么?用户手册有提到这种数据包合并的问题,当然这模型代码里面,我们暂时忽略这个问题。
*/
NioClient nioClient = new NioClient();
ChannelFuture future = nioClient.connect();
// 休眠一分钟等待连接创建完毕,在netty是通过ChannelFuture来判断连接是否建立成功,这里偷懒直接这样做了。
System.out.println("before waitUntilSuccess,is connected:"
+ future.getSocketChannel().isConnected());
future.waitUntilSuccess();
System.out.println("after waitUntilSuccess,is connected:"
+ future.getSocketChannel().isConnected());
for (int i = 0; i < 4; i++) {
future.getSocketChannel().write(
ByteBuffer.wrap("how are you!".getBytes()));
}
// 调用方式三
/**
* 简单的添加listiner方式.
*/
/*
* NioClient nioClient = new NioClient(); ChannelFuture future =
* nioClient.connect(new FutureListiner() { public void
* callBack(ChannelFuture future) { try {
* future.getSocketChannel().write(
* ByteBuffer.wrap("how are you!".getBytes())); } catch (IOException e)
* { // TODO Auto-generated catch block e.printStackTrace(); } } });
*/
}
}
ChannelFuture类
package org.ben.nio.multithread;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* 简单的模拟下netty的ChannelFuture
* 新增了一个asyncWriter的方法,是异步写,当连接建立完毕后就发送数据,采用了一个线程从queue里面去获取数据写,这个可以优化下。
* @author shao
*
*/
public class ChannelFuture {
public SocketChannel socketChannel;
public boolean isSuccess = false;
private BlockingQueue queue = new LinkedBlockingQueue();
private List futureLintiners = new ArrayList();
public void addListiner(FutureListiner listiner){
this.futureLintiners.add(listiner);
}
public SocketChannel getSocketChannel() {
return this.socketChannel;
}
public void setSocketChannel(SocketChannel socketChannel) {
this.socketChannel = socketChannel;
}
public boolean isSuccess() {
return isSuccess;
}
public void setSuccess() {
synchronized (this) {
System.out.println("连接成功!");
if(this.socketChannel.isConnected()) {
this.isSuccess = true;
notifyListiner();
notifyAll();
}
}
}
private void notifyListiner(){
for(FutureListiner listiner : futureLintiners){
listiner.callBack(this);
}
}
public void asyncWrite(ByteBuffer msg) throws InterruptedException {
queue.put(msg);
}
public void waitUntilSuccess() {
synchronized (this) {
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
FutureListiner类
package org.ben.nio.multithread;
public interface FutureListiner{
public void callBack(ChannelFuture future);
}
DefaultClientSimpleHandler类:
package org.ben.nio.multithread;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class DefaultClientSimpleHandler implements SimpleHandler{
private String firstData;
public DefaultClientSimpleHandler(String data){
this.firstData = data;
}
@Override
public void messageReceived(int id, Object param, SocketChannel sc,
Worker worker) {
ByteBuffer data = (ByteBuffer) param;
System.out.println("客户端work线程【"+id+"】接受到数据:"+new String(data.array()));
}
@Override
public void channelConnected(int id, SocketChannel sc, Worker worker) {
// TODO Auto-generated method stub
ByteBuffer bf = ByteBuffer.wrap(firstData.getBytes());
System.out.println("开始向服务器端发送数据!");
worker.writeFormUserCode(sc, bf);
}
}
附件是所有的源文件。