Netty工作模型
最近在学习Netty,遂记录下一些学习心得。文章中的内容是在学习了李林峰老师的《netty权威指南》以及尚硅谷的Netty视频后的一些感想以及对netty内容的学习记录。
单Reactor多线程模型中,只有一个selector,负责accept,read,write事件的维护
Netty主要基于 主从Reactors多线程模型,顾名思义,使用一个主Selector,和一个从Selector
主Selector 只负责客户端的连接,即accept事件。
从Selector 负责客户端的读写。即read和write事件。
代码示例
以服务端代码为例,相关的解释都写在了注释里
//Server 代码
public class EchoServer {
public void bind(int port) throws Exception{
/**
* bossGroup 和workerGroup包含的子线程(NioEventLoop)的个数
* 默认实际 cpu核数 * 2
*/
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
try{
/**
* step1.
* 创建server端的启动对象
* 这里之所以使用无参构造方法是因为,创建该对象所需要的参数太多了,未来也有可能发生变化
* 所以引用了Builder模式
*/
ServerBootstrap b = new ServerBootstrap();
//为其配置相关参数
/**
* step2.
* 从group 方法中可以得知我们传入了bossGroup和workGroup
* 从bossGroup和workGroup对象创建时的类可以知道他们都是一个NioEventLoopGroup
* 即我们 之前说的 主从Reactor多线程模型
* group 方法就是设置 并绑定 Reactor线程池
* 每个NioEventLoopGroup 中的实例是一个 NioEventLoop对象
* NioEventLoop对象 的职责就是处理所有注册到本线程的多路复用器Selector上的Channel,
* 通俗的讲就是NioEventLoop 是 NioEventLoopGroup中的一员,NioEventLoopGroup可以有多个NioEventLoop
* 而每个NioEventLoop 都对应一个Selector,不断轮询监听绑定在Selector上面的事件(这里需要NIO的知识)
*/
b.group(bossGroup, workGroup);
/**
* step3.
* channel()方法用于设置并绑定服务端的Channel
*/
b.channel(NioServerSocketChannel.class);
b.option(ChannelOption.SO_BACKLOG, 1024);
/**
* step4.
* 设置父类的channelHandle
*/
b.handler(new LoggingHandler(LogLevel.INFO));
/**
* step4.
* 设置子类的Handler.并为Channel中的pipeline 增加handler
*/
b.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel socketChannel) throws Exception {
ByteBuf delimiter = Unpooled.copiedBuffer("$_".getBytes());
socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, delimiter));
socketChannel.pipeline().addLast(new StringDecoder());
socketChannel.pipeline().addLast(new EchoServerHandler());
}
});
/**
* 这里为什么要对父类和子类分别设置handler,以及子类的pipeline 设置handler
* 通过group()方法的源码,我得到了如下的结论
* group()的源码中 bossGroup是设置到其父类方法中,而workerGroup则被设置到了childGroup中
* handler() 的源码,其传入的handler 对象也是被赋值到了父类的handler中
* 再结合主从Reactor 多线程模型,主Reactor 只负责连接,从Reactor 只负责read/write
* 说明父类的handler 是用于处理连接的,
* childHandler 是用于处理read/write 的,所以为其对应的NioEventLoop 所在的channel 中的pipeline增加了相关业务类的handler
*
*/
ChannelFuture f = b.bind(port).sync();
f.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
protected void initChannel(SocketChannel socketChannel) throws Exception {
/**
* 为解决TCP 粘包问题
* 增加半包解码器和字符串解码器
*/
//增加半包解码器
socketChannel.pipeline().addLast(new LineBasedFrameDecoder(1024));
//增加字符串解码器
socketChannel.pipeline().addLast(new StringDecoder());
socketChannel.pipeline().addLast(new TimeServerHandler());
}
}
public static void main(String[] args) throws Exception {
int port = 8080;
new EchoServer().bind(port);
}
}
//SERVER handler代码
public class EchoServerHandler extends ChannelHandlerAdapter {
private int counter;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String body = (String)msg;
System.out.println("the counter is :"+ ++counter + "times receive client : [" + body +"]");
body += "$_";
ByteBuf echo = Unpooled.copiedBuffer(body.getBytes());
ctx.writeAndFlush(echo);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
//Client端代码
public class EchoClient {
public void connect(String host, int port) throws Exception{
EventLoopGroup group = new NioEventLoopGroup();
try{
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel socketChannel) throws Exception {
ByteBuf delimiter = Unpooled.copiedBuffer("$_".getBytes());
socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, delimiter));
socketChannel.pipeline().addLast(new StringDecoder());
socketChannel.pipeline().addLast(new EchoClientHandler());
}
});
ChannelFuture f = b.connect(host,port).sync();
f.channel().closeFuture().sync();
}finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new EchoClient().connect("127.0.0.1",8080);
}
}
//clientHandler
public class EchoClientHandler extends ChannelHandlerAdapter {
private int counter;
static final String ECHO_REQ = "hello, I am donkeyx.$_";
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//super.channelActive(ctx);
ByteBuf message = null;
for (int i=0; i< 10; i++){
message = Unpooled.copiedBuffer(ECHO_REQ.getBytes());
ctx.writeAndFlush(message);
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String body = (String)msg;
System.out.println("the counter is :"+ ++counter + "times receive client : [" + body +"]");
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception{
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
结论
通过这个echo demo可以得出
- BossGroup (NioEventLoopGroup) : 只负责客户端的连接
- WorkerGroup (NioEventLoopGroup) : 负责网络的读写
- NioEventLoopGroup : 相当于一个事件循环组,组中包含多个事件循环。每个事件循环是一个 NioEventLoop
- NioEventLoop 表示一个不断循环的执行处理任务的线程,每个NioEventLoop 有一个Selector,用于监听绑定在其上的socket 的网络通讯
- NioEventLoopGroup 可以有多个线程,即可以含有多个NioEventLoop
- 每个Boss 的NioEventLoop 执行的步骤有3布
a. 轮询accept事件,
b. 处理accept事件,与client建立连接,生成NioSocketChannel,并将其注册到某个worker de NioEventLoop 的selector
C. 处理任务队列的任务 - 每个Worker的NioEventLoop循环执行的步骤
a. 轮询read,write事件
b. 处理I/O(read,write)事件。在对应的NioSocketChannel上进行处理
C. 处理任务队列的任务 - worker NioEventLoop 在处理业务时,会使用Pipeline.pipeline中包含了channel,即通过pipeline 可与i获取到对应的通道。管道中维护了许多的handler,用于处理自定义的任务
该工作模型的图例
父类Handler和childHandler 的区别
本质区别就是:
父类中的Handler,每个新连接都会经过他,然后他为每个新连接创建新的Handler。
childHandler 就只负责read/write。
来源:oschina
链接:https://my.oschina.net/u/4353238/blog/4364737