把Netty4关于源码解读的文章看完以后还是很多疑惑,例如:Netty是基于一个怎样的点来设计框架的(解决了一个什么样的痛点),把这些文章归纳总结很有必要。
如果把解析Netty4原理作为一个项目来管理,可以尝试分析这个流程:
- 范围管理:规划过程(代码模块很多,需要有主次区别对待阅读理解;基础知识储备),监控过程(检查是否过多陷入不必要的细节)
- 时间管理:对流程有完整的理解,需要对代码进行拆解,进行优先级排序(Netty4主要模块Bootstrap、Channel、Handle、Eventloop、Bytebuf),
- 通过类图关系找出依赖关系,通过启动入口找出对象的调用关系。
项目落地步骤
- Java NIO的网络编程基本原理
- 如何拆解串行流程
- 整理Netty的数据流程
Java NIO 网络编程基本代码分析
// 创建一个selector
Selector selector = Selector.open();
// 创建一个ServerSocketChannel
ServerSocketChannel servChannel = ServerSocketChannel.open();
servChannel.configureBlocking(false);
// 绑定端口号
servChannel.socket().bind(new InetSocketAddress(8080), 1024);
// 注册感兴趣事件
servChannel.register(selector, SelectionKey.OP_ACCEPT);
// select系统调用
selector.select(1000);
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectedKeys.iterator();
SelectionKey key = null;
while (it.hasNext()) {
key = it.next();
it.remove();
if (key.isValid()) {
// 处理新接入的请求消息
if (key.isAcceptable()) {
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
// 接收客户端的连接,并创建一个SocketChannel
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
// 将SocketChannel和感兴趣事件注册到selector
sc.register(selector, SelectionKey.OP_READ);
}
if (key.isReadable()) {
// 读数据的处理
SocketChannel clientChannel = (SocketChannel) key.channel();
ByteBuffer buf = (ByteBuffer) key.attachment();
long bytesRead = clientChannel.read(buf);
if (bytesRead == -1) {
clientChannel.close();
} else if (bytesRead > 0) {
key.interestOps(OP_READ | SelectionKey.OP_WRITE);
System.out.println("Get data length: " + bytesRead);
}
}
if (key.isValid() && key.isWritable()) {
ByteBuffer buf = (ByteBuffer) key.attachment();
buf.flip();
SocketChannel clientChannel = (SocketChannel) key.channel();
clientChannel.write(buf);
if (!buf.hasRemaining()) {
key.interestOps(OP_READ);
}
buf.compact();
}
}
}
selector、SelectionKey、pollArray等几个数据结构以及相互持有关系
为了使用 Selector,首先需要将Channel注册到Selector中,随后调用Selector的select()方法,这个方法会阻塞,直到注册在Selector中的Channel发送可读写事件。当这个方法返回后,当前的这个线程就可以处理Channel的事件。
从上面的代码可以拆解出不同事件:
- Channel与Selector 注册(
多线程中可以初始化多个selector与channel绑定,boosgroup关键所在)(绑定多端口时有多个selector) - Selector Accept事件(处理新接入的请求消息(完成TCP握手,建立物理链接))
- Selector 读、写事件(此方法非阻塞)
拆解思路
最初很疑惑的地方,俗称伪异步I/O设计,网络接收+队列,服务端收到数据后直接放到队列中,模式可能没问题。但是对网络接收处理有问题,如果要响应上千万的网络请求连接同时需要快速的把数据从网卡(TCP缓冲区)中拿走,如果拿走不及时,TCP window size 会越来越小,客户端最终会停止发送数据,新的请求也无法被响应。
Selector的出现使网络接收处理的流程彻底改变,Selector Accept 事件与读写事件可以由独立线程处理(TCP),线程处理完Accept事件后,丢给后面的读写线程处理读写事件。而Netty所做的事情就是让这些事件可以在它所设计的多线程体系中不断被编排处理。
Netty 数据流程
客户端代码:
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 ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(new EchoClientHandler());
}
});
// Start the client.
ChannelFuture f = b.connect(HOST, PORT).sync();
// Wait until the connection is closed.
f.channel().closeFuture().sync();
} finally {
// Shut down the event loop to terminate all threads.
group.shutdownGracefully();
}
客户端Channel初始化流程:
Bootstrap.connect -> Bootstrap.doConnect -> AbstractBootstrap.initAndRegister
pipeline初始化流程:
DefaultChannelPipeline 初始化,包含TailContext,HeadContext
EventloopGroup初始化流程:
MultithreadEventExecutorGroup 实现初始化
channel 的注册过程:
AbstractBootstrap.initAndRegister -> MultithreadEventLoopGroup.register -> SingleThreadEventLoop.register -> AbstractChannel.AbstractUnsafe.register
Handler添加过程:
ChannelInitializer 抽象类
客户端连接过程:
Bootstrap.doConnect ->Channel.connect->DefaultChannelPipeline.connect->AbstractChannelHandlerContext.connect->HeadContext.connect -> AbstractNioUnsafe.connect(抽象方法):
服务端代码:
public static void main(String[] args) throws Exception {
final SslContext sslCtx;
if (SSL) {
SelfSignedCertificate ssc = new SelfSignedCertificate();
sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
} else {
sslCtx = null;
}
// Configure the server.
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
final EchoServerHandler serverHandler = new EchoServerHandler();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 100).handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
if (sslCtx != null) {
p.addLast(sslCtx.newHandler(ch.alloc()));
}
// p.addLast(new LoggingHandler(LogLevel.INFO));
p.addLast(serverHandler);
}
});
// Start the server.
ChannelFuture f = b.bind(PORT).sync();
// Wait until the server socket is closed.
f.channel().closeFuture().sync();
} finally {
// Shut down all event loops to terminate all threads.
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
- NioServerSocketChannel 初始化
- ChannelPipeline 初始化
- Channel 的注册
- 绑定:AbstractBootstrap.bind -> AbstractBootstrap.doBind -> AbstractBootstrap.initAndRegister->ServerBootstrap.init (ServerBootstrapAcceptor )
- handler 的添加过程
- 在服务器 NioServerSocketChannel 的 pipeline 中添加的是 handler 与 ServerBootstrapAcceptor.
- 当有新的客户端连接请求时, ServerBootstrapAcceptor.channelRead 中负责新建此连接的 NioSocketChannel 并添加 childHandler 到 NioSocketChannel 对应的 pipeline 中, 并将此 channel 绑定到 workerGroup 中的某个 eventLoop 中.
- handler 是在 accept 阶段起作用, 它处理客户端的连接请求.
- childHandler 是在客户端连接建立以后起作用, 它负责客户端连接的 IO 交互.
基本流程认识后需要对后续的每个主要类进行详细分析。
@Override
void init(Channel channel) {
setChannelOptions(channel, newOptionsArray(), logger);
setAttributes(channel, newAttributesArray());
ChannelPipeline p = channel.pipeline();
final EventLoopGroup currentChildGroup = childGroup;
final ChannelHandler currentChildHandler = childHandler;
final Entry<ChannelOption<?>, Object>[] currentChildOptions = newOptionsArray(childOptions);
final Entry<AttributeKey<?>, Object>[] currentChildAttrs = newAttributesArray(childAttrs);
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(final Channel ch) {
final ChannelPipeline pipeline = ch.pipeline();
ChannelHandler handler = config.handler();
if (handler != null) {
pipeline.addLast(handler);
}
//boosgroup register到channel(注册在AbstractBootstrap中),bossGroup线程执行该代码
// ServerBootstrapAcceptor 添加到尾端,最后转发,把连接注册到workGroup
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
logger.info("===========转发客户端连接");
pipeline.addLast(new ServerBootstrapAcceptor(ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
}
private void doStartThread() {
//workgroup bossgroup
executor.execute(new Runnable() {
@Override
public void run() {
try {
logger.info("========================doStartThread run()");
SingleThreadEventExecutor.this.run();
success = true;
} catch (Throwable t) {
logger.warn("Unexpected exception from an event executor: ", t);
} finally {
}
}
});
}
#SingleThreadEventExecutor
private void execute(Runnable task, boolean immediate) {
boolean inEventLoop = inEventLoop();
addTask(task);
if (!inEventLoop) {
startThread();
if (isShutdown()) {
boolean reject = false;
try {
if (removeTask(task)) {
reject = true;
}
} catch (UnsupportedOperationException e) {
}
if (reject) {
reject();
}
}
}
}
读代码注意:
- 注册监听处理器,是由bossGroup线程执行(bossGroup与channel绑定)
- SingleThreadEventExecutor execute方法执行线程, executor属性可以是bossGroup或workGroup,读代码过程要注意EventLoop是哪个
- inEventLoop方法也需要注意,可能会执行startThread()方法(startThread()会获取IO事件)
- NioEventLoop中run方法循环获取事件
- handle链如何执行,pipeline绑定在channel上,NioEventLoop.run()执行绑定handle
- handler与childHandler把这个handler理解成一个,其实是独立的,分别对应boss和work线程中的channel
日志
13:37:46.293 [main] INFO i.n.c.MultithreadEventLoopGroup - ============multithread register
main============single thread
13:37:46.324 [main] INFO io.netty.channel.AbstractChannel - ==============register
13:37:46.339 [main] INFO i.n.u.c.SingleThreadEventExecutor - false=======================io.netty.util.internal.ThreadExecutorMap$1@2344fc66
13:37:46.339 [main] INFO i.n.u.c.SingleThreadEventExecutor - ========================doStartThread
#boss线程启动
13:37:46.339 [nioEventLoopGroup-2-1] INFO i.n.u.c.SingleThreadEventExecutor - ========================doStartThread run()
13:37:46.339 [nioEventLoopGroup-2-1] INFO io.netty.channel.nio.NioEventLoop - =================NioEvent start
13:37:46.339 [nioEventLoopGroup-2-1] INFO i.n.channel.nio.AbstractNioChannel - =========selectionKey:::sun.nio.ch.SelectionKeyImpl@5b9db458
13:37:46.354 [nioEventLoopGroup-2-1] INFO i.n.channel.DefaultChannelPipeline - ========================invokeHandlerAddedIfNeeded[id: 0x5965918e]
13:37:46.354 [nioEventLoopGroup-2-1] INFO i.n.u.c.SingleThreadEventExecutor - true=======================io.netty.util.internal.ThreadExecutorMap$1@2344fc66
13:37:46.354 [nioEventLoopGroup-2-1] INFO io.netty.channel.AbstractChannel - ===================register0##########DefaultChannelPipeline{(LoggingHandler#0 = io.netty.handler.logging.LoggingHandler)}
13:37:46.354 [nioEventLoopGroup-2-1] INFO i.n.u.c.SingleThreadEventExecutor - true=======================io.netty.util.internal.ThreadExecutorMap$1@2344fc66
13:37:46.354 [nioEventLoopGroup-2-1] INFO i.n.channel.DefaultChannelPipeline - ========================invokeHandlerAddedIfNeeded[id: 0x5965918e]
13:37:46.354 [nioEventLoopGroup-2-1] INFO i.n.c.AbstractChannelHandlerContext - =======================fireChannelRegistered
13:37:46.354 [nioEventLoopGroup-2-1] INFO i.n.handler.logging.LoggingHandler - [id: 0x5965918e] REGISTERED
13:37:46.354 [nioEventLoopGroup-2-1] INFO i.n.c.AbstractChannelHandlerContext - =======================fireChannelRegistered
13:37:46.354 [nioEventLoopGroup-2-1] INFO io.netty.bootstrap.ServerBootstrap - ===========转发客户端连接
13:37:46.370 [nioEventLoopGroup-2-1] INFO i.n.handler.logging.LoggingHandler - [id: 0x5965918e] BIND: 0.0.0.0/0.0.0.0:8007
13:37:46.370 [nioEventLoopGroup-2-1] INFO i.n.u.c.SingleThreadEventExecutor - true=======================io.netty.util.internal.ThreadExecutorMap$1@2344fc66
13:37:46.386 [nioEventLoopGroup-2-1] INFO i.n.handler.logging.LoggingHandler - [id: 0x5965918e, L:/0:0:0:0:0:0:0:0:8007] ACTIVE
###########boss启动完成
###########boss线程监听到read事件
nioEventLoopGroup-2-1 AbstractNioMessageChannel.read()=============DefaultChannelPipeline{(LoggingHandler#0 = io.netty.handler.logging.LoggingHandler), (ServerBootstrap$ServerBootstrapAcceptor#0 = io.netty.bootstrap.ServerBootstrap$ServerBootstrapAcceptor)}
13:38:29.549 [nioEventLoopGroup-2-1] INFO i.n.handler.logging.LoggingHandler - [id: 0x5965918e, L:/0:0:0:0:0:0:0:0:8007] READ: [id: 0xd43b4a62, L:/127.0.0.1:8007 - R:/127.0.0.1:64813]
=================log read
13:38:29.549 [nioEventLoopGroup-2-1] INFO io.netty.bootstrap.ServerBootstrap - =======================ServerBootstrapAcceptor
13:38:29.549 [nioEventLoopGroup-2-1] INFO io.netty.bootstrap.ServerBootstrap - =======================ServerBootstrapAcceptor########### DefaultChannelPipeline{(EchoServer$1#0 = io.netty.example.echo.EchoServer$1)}
13:38:29.549 [nioEventLoopGroup-2-1] INFO io.netty.bootstrap.ServerBootstrap - =======================ServerBootstrapAcceptor register
13:38:29.549 [nioEventLoopGroup-2-1] INFO i.n.c.MultithreadEventLoopGroup - ============multithread register
nioEventLoopGroup-2-1============single thread
13:38:29.549 [nioEventLoopGroup-2-1] INFO io.netty.channel.AbstractChannel - ==============register
13:38:29.549 [nioEventLoopGroup-2-1] INFO i.n.u.c.SingleThreadEventExecutor - false=======================io.netty.util.internal.ThreadExecutorMap$1@f8071a0
13:38:29.549 [nioEventLoopGroup-2-1] INFO i.n.u.c.SingleThreadEventExecutor - ========================doStartThread
################work线程启动
13:38:29.549 [nioEventLoopGroup-3-1] INFO i.n.u.c.SingleThreadEventExecutor - ========================doStartThread run()
13:38:29.549 [nioEventLoopGroup-3-1] INFO io.netty.channel.nio.NioEventLoop - =================NioEvent start
13:38:29.549 [nioEventLoopGroup-3-1] INFO i.n.channel.nio.AbstractNioChannel - =========selectionKey:::sun.nio.ch.SelectionKeyImpl@3be818a7
13:38:29.549 [nioEventLoopGroup-3-1] INFO i.n.channel.DefaultChannelPipeline - ========================invokeHandlerAddedIfNeeded[id: 0xd43b4a62, L:/127.0.0.1:8007 - R:/127.0.0.1:64813]
13:38:29.549 [nioEventLoopGroup-2-1] INFO i.n.handler.logging.LoggingHandler - [id: 0x5965918e, L:/0:0:0:0:0:0:0:0:8007] READ COMPLETE
13:38:29.549 [nioEventLoopGroup-3-1] INFO io.netty.channel.AbstractChannel - ===================register0##########DefaultChannelPipeline{(EchoServerHandler#0 = io.netty.example.echo.EchoServerHandler)}
13:38:29.549 [nioEventLoopGroup-3-1] INFO i.n.channel.DefaultChannelPipeline - ========================invokeHandlerAddedIfNeeded[id: 0xd43b4a62, L:/127.0.0.1:8007 - R:/127.0.0.1:64813]
13:38:29.549 [nioEventLoopGroup-3-1] INFO i.n.c.AbstractChannelHandlerContext - =======================fireChannelRegistered
参考:
Java NIO 的前生今世 之四 NIO Selector 详解
《TCP-IP详解:卷2》