1.基础逻辑
一句话说明Netty是个什么东西:Netty就是把java 的NIO重构包装了。
因为javaNIO的种种bug以及不易用,其实Netty把一些常用的的东西包装简化了,实际内部也是调用的JavaNio的东西,比如用的也是java的selector去对接epoll,来轮训就绪的socket队列。本质上是没有区别的,看完主源码其实就是线程模型和一些设计模式封装的组件。

上面的图可能没有《Netty权威指南》那么精准,但是描述了netty整个逻辑:
eventloop内嵌了javanio的selector,在进行bind的时候,调用了selector.open()开启了端口监听,并且创建了一个NioServerChannel,注册到了该eventloop的selector上。这样就创建了一个监听的server端,然后在接收到请求之后,首先NioServerChannel接受连接请求,并且读取请求内容创建NioSocketChannel,这个channel用来监听读取数据的请求,然后通过将channel发送到pipeline(类似于spring的过滤器责任链),最后在第二个Eventloopgroup中创建一个EventLoop,将NioSocketChannel注册到这个新创建的eventloop上。通过这种方法将接受请求和读取请求数据分离到了不同的线程,用这种方法,可以承载更多的socket连接,应对更多的并发,实际上nio的目的就是这个:监听更多请求。
2.demo代码
下面通过源码解读netty的运行原理,先贴出来demo代码。 服务端
public class TimeServerNetty {
AtomicInteger count = new AtomicInteger();
public static void main(String[] args) throws InterruptedException {
new TimeServerNetty().bind(9888);
}
public void bind(int port) throws InterruptedException {
//创建主线程组
EventLoopGroup bossGroup = new NioEventLoopGroup();
//创建工作线程组
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//线程模型通过serverBootstrap来进行组织
ServerBootstrap serverBootstrap = new ServerBootstrap();
//通过builder模式设置参数以及handler
serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 1024).childHandler(new ChildChannelHadler());
//启动请求监听线程并且创建NioServerChannel进行监听。
ChannelFuture future = serverBootstrap.bind(port).sync();
future.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
class ChildChannelHadler extends ChannelInitializer<SocketChannel> {
//初始化channel,这里通过linebasedframdecoder来处理粘包拆包(涉及到TCP协议) stringDecoder将消息变成字符串编码
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new TimeServerHandler());
}
}
class TimeServerHandler extends ChannelHandlerAdapter{
//通过适配器模式来将处理器加入到pipeline中处理数据
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String body = (String) msg;
System.out.println(body+" "+count.incrementAndGet());
ctx.writeAndFlush(Unpooled.copiedBuffer(("accepted!!"+System.getProperty("line.separator")).getBytes()));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
}
客户端
public class TimeClientNetty {
public void connect(int port, String host) throws InterruptedException {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true).handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new TimeClientHandler());
}
});
ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
channelFuture.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
class TimeClientHandler extends ChannelHandlerAdapter {
private byte[] req;
public TimeClientHandler() {
req = ("client test"+System.getProperty("line.separator")).getBytes();
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ByteBuf msg = null;
for (int i = 0; i < 1; i++) {
msg = Unpooled.buffer(req.length);
msg.writeBytes(req);
ctx.writeAndFlush(msg);
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String body = (String) msg;
System.out.println(body);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
public static void main(String[] args) throws InterruptedException {
new TimeClientNetty().connect(9888,"127.0.0.1");
}
3.源码分析
demo逻辑比较简单,就是一个应答模式。只分析服务端源码逻辑,品一品。
3.1 Eventloop
整个线程模型以及参数设置责任链设置,都是通过Serverbootstrap启动器类来组织的,先来看一下EventLoopGroup,可以叫事件线程组,默认一个EventLoopGroup中会有8个EventLoop实例,但是bossEventLoop中只会有一个被线程运行,每一个EventLoop都是单线程的。先来看一下EventLoopGroup的创建。
跟踪new EventLoop()构造方法,找到他的父类MultithreadEventExecutorGroup,这里核心的代码就是两个
- 创建一个excutor,实际这个类就是一个线程池,里面核心用到的就是任务的阻塞队列,还有创建线程的方法。
- 就是创建了EventLoop实例,通过eventloop实例来挂在socketchannel,轮训底层epoll返回的就绪队列。

先来看一下第一个executor的创建,这里创建了一个线程工厂类来表示怎么创建线程。其实就是根据EventLoopGroup的线程池序号和一个自增的序号来命名线程。

ThreadPerTaskExecutor就是根据命令来创建线程执行任务。

再来看第二点newChild是怎么创建eventloop的,跟踪newChile的会发现他在NioEventLoopGroup中创建了新的eventloop。

这里可以看到又一个openSelector()的方法,调用了javanio的selector.open()


到这里其实就创建完了一个eventloop,最终一个evetloopgroupmoren会创建8个evetloop。
3.2 ServerBootstrap
在初始化Serverbootstrap的时候,通过builder模式设置了group,这里会讲bossgroup设置到group属性中,workergroup设置到childgroup中,这里两个比较重要后续会通过这两个属性来分配线程,指定channel为NioServerSocketChannel,这里后续会通过反射来创建channel的实例,然后是设置option参数,最后设置childhandler这个是worker线程组的处理器,是用户自定义实现的会放在挂载在wokergroup的NioSocketchannel的pipeline中;其实这里也可以设置handler,这个handler就是给bosseventloop的NioServerSocketChannel使用的,就是每一次接收到请求都会运行这个handller。

在设置完serverbootstrap后,需要绑定监听端口,一路跟进bind()方法来,会到AbstractBootstrap.doBind()方法中,这个方法里面会调用initAndRegister()来进行创建和初始化channel,并且吧channel绑定到eventloop中。


先来看一下创建channel的过程,创建channel的具体实现是在ServerBootstrap中。其实这里就是利用工厂模式,通过构造方法,创建channel实例并且向channel的属性中设置了一个bossEventLoop,还有childGroup。


group()方法,获取的就是之前设置的bossGroup的eventloop线程组,next()方法会在EventLoopGroup所创建持有的eventloop数组中获取一个eventloop实例。

在通过builder模式设置ServerBootstrap的时候其实就已经指定了clazz的类型是NioServerSocket,所以这里创建的就是这个类的实例。


创建完了之后就是初始化,其实初始化没什么好说的,就是设置一些参数和pipeline。
再来看register方法,这里的unsafe其实就是封装的一个java逻辑,每一个channel的实现类都单独封装了自己的逻辑,然后通过这个unsafe去调用channel的一些操作,但是这个unsafe是protected类型的,不会提供给用户调用。看下NioServerSocketChannel的register的逻辑。
首先会判断当前的线程是不是channel所对应的之前设置的eventloop的线程,因为这里是主线程调用的首次的NioServerSocketChannel,所以当前线程肯定不是eventloop的线程,所以这里就会执行到下面eventLoop.execute()方法中。

这里要在说一下,之前创建的eventloop的时候,新建了一个executor的实例,设置到了eventloop中,其实这里executor的作用只是为了有一个新建线程的方法,也只是为了指定线程的名字加以区分,试试上eventloop自己有一个任务队列,也只会持有一个线程,所以他才叫做loop。。。


Eventloop继承了SingleThreadEventExecutor(看名字就知道是只有一个现成的执行器),在SingleThreadEventExecutor的函数中,创建了一个LinkedBlockingQueue的阻塞队列用来存放任务。

ok,那现在再来明确一下eventloop是个什么玩意儿,他就是个自带阻塞队列的单线程运行的实例,里面封装了一个JavaNio提供的Selector,可以筛选出来就绪队列,并且不断循环执行就绪队列中的和其他线程或者自己线程添加到任务队列中的任务,看清楚红字,就干这两件事儿。现在,也能通过这个分析出来他为什么要判断当前线程是不是eventloop自己的线程,因为execute是需要新建线程的,如果别的线程执行,那么说明当前调用的eventloop实例还没有线程,需要新建,如果当前线程就是eventloop实例的线程,那么直接执行就行了,不需要新建线程。
上面说了一段说清楚了注册这个任务应该放到哪个线程或者队列中执行,来看下怎么执行的。
NioEventLoop继承了SingleThreadEventExecutor,所以会调用到SingleThreadEventExecutor.execute方法,又去判断了当前线程是不是eventloop的线程,明显不是,所以开启了一个新的线程,并且吧当前的注册任务添加到了eventloop的任务队列中,到这里就要去看NioEventLoop异步执行的run()方法了。

可以看到run()方法里面包含两个主要逻辑,一个是获取epoll就绪队列中的socket句柄,并执行发送到pipeline中,另一个就是执行任务队列中的任务。因为这个时候刚启动没有socket请求进来,所以执行下面的runAllTasks()。(ps:这里的select()方法中设置了超时时间,epoll一定时间内没有就绪的socket就会返回)

这里就会执行之前提交到eventloop.execute()中的register0()方法。

regeister0方法就调用doRegister(),实际里面就是调用JavaNIO的register方法去吧channel挂载到了epoll上,并且吧这个注册事件和channel激活事件发送到了pipeline中,让感兴趣的hannler继续处理。

到此为止,netty服务端就正式开启socket的请求监听了。
3.3 请求接受流程
客户端的代码就不说了,没啥好说的,就是个socketchannel建立连接的过程,服务端其实是会走这个线程模型的。
上面说到主线程组的bossgroup里面会创建一个eventloop,用来接受线程,eventloop继承了SingleThreadEventExecutor,里面有run方法,会调用select(),去接受请求。
我们运行客户端代码,这个时候epoll会返回就绪列表,这时候会调用到processSelectedKeysOptimized()方法中。

这里会调进processSelectedKey()方法中,这个方法里面就会根据连接的类型进行区分进行不同的逻辑,因为我们这里是一个新的socket的连接事件,会发送accept操作位的请求,同样的eventloop通过select()方法调用出来会进入accept的逻辑,通过AbstractNioChannel中的unsafe的实现类进行数据的读取。

看下这个read()方法中实现了消息从bytebuf中的读取并且发送消息到pipeline的责任链中,先看下消息读取doReadMessage()方法。
其实就是JavaNio那一套,新创建一个socketchannel,并且从channel里面读取数据,但是这里设置了线程模型,把新的channel的读的监听绑定到了工作线程组上,这样就实现了bosseventloop只负责连接,workereventloop处理读写事件,但是这里只是把线程组设置到了channel的属性里,并没有真正设置注册监听。

我们再回到read方法中看下他的第二个功能,就是把消息发送到pipeline中,这里去吧消息循环读取循环发送到了pipeline中。

pipeline其实在初始化的时候就已经装载好了,我们一路跟进去发现他最终调用到了ServerBootstrap中的channelRead方法中,这里根据channel的类型的unsafe方法,进行了注册,将新创建的channel注册到了工作线程组的eventloop上进行监听,也就是我们上面说的注册监听,实际上是在pipeline中才真正实现了。

再往后的请求读取的逻辑和请求接受差不多,只不过是在工作线程组,监听的是read操作位,最后会发送到用户自己指定的handler中,进行处理,这里比较重要的就是这个线程模型是怎么实现的,因为我们初始化的时候默认最大的线程也就是eventloop的个数就是8个,所以最后只有一个主线程eventloop在bossgroup中,负责请求的接受,其余8个线程eventloop在workergroup中,负责实际消息内容的读取写入。

通过这种方式可以提升可接入的socket的数量,也可以快速响应。