1. 搞清楚 netty 服务端的 启动原理.
核心的就是 做了两件事情 initAndRegister 和 dobind
final ChannelFuture initAndRegister() {
Channel channel = null;
// ...
channel = channelFactory.newChannel();
//...
init(channel);
//...
ChannelFuture regFuture = config().group().register(channel);
//...
return regFuture;
}
initAndRegister做的事情 就是 将 channel 注册到 reactor 线上上, 在创建 channel的过程中一个
非常重要的参数 是backlog, 指的是 三次握手 机制的 连接中的队列 和 已完成队列的 数目之和,当延迟
增大的时候,需要适当调大该值 来达到更大的吞吐量
p.addLast()向serverChannel的流水线处理器中加入了一个 ServerBootstrapAcceptor
在注册的过程中,其实就是 jdk 底层 将 socket 注册到 reactor 线程对应的 selector, 注册的时候
其实 没绑定监听的事件的,通过 autoRead=true来进行 重新修正的.
io.netty.channel.nio.AbstractNioChannel#doBeginRead 重新修正的
接下来介绍下 dobind的过程
HeadContext.java
@Override
public void bind(
ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise)
throws Exception {
unsafe.bind(localAddress, promise);
}
@Override
public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
// ...
boolean wasActive = isActive();
// ...
doBind(localAddress);
if (!wasActive && isActive()) {
invokeLater(new Runnable() {
@Override
public void run() {
pipeline.fireChannelActive();
}
});
}
safeSetSuccess(promise);
}
protected void doBind(SocketAddress localAddress) throws Exception {
if (PlatformDependent.javaVersion() >= 7) {
//noinspection Since15
javaChannel().bind(localAddress, config.getBacklog());
} else {
javaChannel().socket().bind(localAddress, config.getBacklog());
}
}
调用到jdk底层做端口绑定,并触发active事件,active触发的时候,真正做服务端口绑定
bind的过程也是发生 在 reactor线程。
不论是 bind 和 register 都是 unsafe AbstractNioUnsafe来实现的。
最后,我们来做下总结,netty启动一个服务所经过的流程
1.设置启动类参数,最重要的就是设置channel
2.创建server对应的channel,创建各大组件,包括ChannelConfig,ChannelId,ChannelPipeline,ChannelHandler,Unsafe等
3.初始化server对应的channel,设置一些attr,option,以及设置子channel的attr,option,给server的channel添加新channel接入器,并出发addHandler,register等事件
4.调用到jdk底层做端口绑定,并触发active事件,active触发的时候,真正做服务端口绑定
2.搞清楚 netty 的客户端是怎么 连接上的
1. 同服务端 initAndRegister() 创建channel 并做selector的注册
2. connect的时候第一时间是连接不成功的,那么 就监听op_connect事件,连接完成后 channelActive的事件触发。 有定时任务 会清理 超时还连接不成功的 句柄。
粘包 和 拆包是对应的
io.netty.handler.codec.ByteToMessageDecoder#channelRead
拆包的关健点:容器、协议(比如基于 lenth定义的,换行符定义的), 丢弃模式(报文过大的情况)
还有一个重要的就是 reactor线程
pipeline 的 双链表模式
inbound事件就是 从 头到尾
outbound 就是从尾到头,headContext里面 调用unsafe的,进行真正的读写操作
tail 处理 异常。
异常的传播 都是从头到尾的, handle->next的这种模式,所以 异常处理器往往要定义为最后一个handle.
pipeline.write 会 生成task 在 eventLoop里面运行。
思考一个问题?
netty 是怎么实现高性能的。
线程模型:
Netty 的 NioEventLoop 读取到消息之后,直接调用 ChannelPipeline 的fireChannelRead (Object msg)。 只要用户不主动切换线程, 一直都是由NioEventLoop 调用用户的 ChannelHandler,期间不进行线程切换。这种串行化处理方式避免了多线程操作导致的锁的竞争,从性能角度看是最优的。
Netty拥有两个NIO线程池,分别是bossGroup和workerGroup,前者处理新建连接请求,然后将新建立的连接轮询交给workerGroup中的其中一个NioEventLoop来处理,后续该连接上的读写操作都是由同一个NioEventLoop来处理。注意,虽然bossGroup也能指定多个NioEventLoop(一个NioEventLoop对应一个线程),但是默认情况下只会有一个线程,因为一般情况下应用程序只会使用一个对外监听端口。