02 | Bootstrap源码分析(二)
一、Nio Client 刨析
先看下Netty4 客户端启动的demo代码:
// Configure the client.
EventLoopGroup group = new NioEventLoopGroup(1);
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group) //【线程模型】
.channel(NioSocketChannel.class) //【IO模型】
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)//连接超时时间
.option(ChannelOption.SO_KEEPALIVE, true)//保活探测,若正常会回复ACK响应
.option(ChannelOption.TCP_NODELAY, true) //开启Nagle算法【Nagle算法通过减少需要传输的数据包,来优化网络;提高实时性:true 提高网络利用率:false】
.attr(AttributeKey.newInstance("clientName"), "X的netty客户端") // 用于给客户端的IO模型设置属性,即给NioSocketChannel设置属性
.handler(new ChannelInitializer<SocketChannel>() {//【读写处理器】
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
//p.addLast(new LoggingHandler(LogLevel.INFO));
p.addLast(new EchoClientHandler());
}
});
// connect(bootstrap, HOST, PORT, 5);
// Start the client.
ChannelFuture f = bootstrap.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();
}
01 NioEventLoopGroup创建
同Boss模式,唯一的区别是NioEventLoop的数量不一样,一般会是默认的CPU核数2
// Group线程初始化为1,不传参时默认为CPU核数2
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
也需要经历以下步骤:
1)NioEventLoopGroup实例创建
2)NioEventLoop实例创建
3)NioEventLoop#Selector创建
4)NioEventLoop#Chooser创建
02 NioSocketChannel 创建
客户端的解析并连接,对应于服务端的注册并绑定。
private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
if (regFuture.isDone()) {
return doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise());
...
} else {
regFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
doResolveAndConnect0(channel, remoteAddress, localAddress, promise);
...
}
});
return promise;
}
}
1)NioSocketChannel 实例创建
逻辑同Boss模式,这里会创建一个 NioSocketChannel 实例,其构造方法中newSocket创建了jdk的ServerSocketChannel,并预设置OP_READ变量。
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
// 实例创建
channel = channelFactory.newChannel();
// Bootstrap 初始化channel配置
init(channel);
} catch (Throwable t) {
...
}
// channel注册到Group线程池,由Group线程池来处理read事件
ChannelFuture regFuture = config().group().register(channel);
...
}
实例创建的具体逻辑,最终使用jdk对象创建Channel
public NioSocketChannel() {
this(DEFAULT_SELECTOR_PROVIDER);
}
public NioSocketChannel(SelectorProvider provider) {
this(newSocket(provider));
}
private static SocketChannel newSocket(SelectorProvider provider) {
try {
return provider.openSocketChannel();
} catch (IOException e) {
throw new ChannelException("Failed to open a socket.", e);
}
}
参数绑定,这些参数作用于NioSocketChannel,因为客户端只有这么一个Channel。
void init(Channel channel) {
ChannelPipeline p = channel.pipeline();
// 添加启动时放进去的自定义handler
p.addLast(config.handler());
// 设置Options属性
setChannelOptions(channel, newOptionsArray(), logger);
setAttributes(channel, newAttributesArray());
}
2)SocketChannel#Selector关联
逻辑同Boss模式,这里的注册是指将新建出来的NioSocketChannel(JDK创建出来的Channel),与NioEventLoopGroup中NioEventLoop的unwrappedSelector相绑定。
NioEventLoop初始化时生成了Jdk的Selector,一个Selector在jdk中用来管理多个Channel,他是jdk开发出来对多个Channel操作与管理的一个功能。
final ChannelFuture initAndRegister() {
ChannelFuture regFuture = config().group().register(channel);
}
调用AbstractChannel中内部类AbstractUnsafe的register方法,当前线程NioEventLoop作为参数,传递过去了
public ChannelFuture register(final ChannelPromise promise) {
promise.channel().unsafe().register(this, promise);
}
绑定时关心的监听事件设置为0,表示对应Channel的FD相关事件不做监听,具体由register0中doRegister()方法实现,其调用AbstractNioChannel#doRegister方法,do相关方法就是真正干活的。
protected void doRegister() throws Exception {
// channel注册到selector,但是监听事件为0。
// 对client来说,监听事件应该是READ
// 创建channel时,ACCEPT/READ事件以参数形式传递给父类AbstractNioChannel的readInterestOp成员变量,什么时候把ACCEPT/READ事件绑定到Selector上呢?最终会在AbstractNioChannel的doBeginRead方法中绑定到Selector上
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
...
}
3)NioSockerChannel#Handler关联
关联自定义handler到pipeline中,等待其nio-thread执行register0内的pipeline.invokeHandlerAddedIfNeeded()来触发。
void init(Channel channel) {
ChannelPipeline p = channel.pipeline();
// 添加启动时放进去的自定义handler
p.addLast(config.handler());
...
}
03 NioSocketChannel 解析连接
1)NioSockerChannel#解析
NioSocketChannel注册流程结束的时候,会执行safeSetSuccess(promise)给promise对象设置返回值,触发Bootstrap给promise对象设置返回值,触发AbstractBootstrap.doResolveAndConnect中regFuture.addListener#doResolveAndConnect0方法。
private void register0(ChannelPromise promise) {
try {
// 触发ServerBootstrap.init中的ChannelInitializer.initChannel方法
pipeline.invokeHandlerAddedIfNeeded();
// Bootstrap给promise对象设置返回值,触发AbstractBootstrap.doResolveAndConnect中regFuture.addListener#doResolveAndConnect0方法,进行地址解析和连接
safeSetSuccess(promise);
...
}
doResolveAndConnect0进行地址解析和连接。
private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
if (regFuture.isDone()) {
return doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise());
} else {
regFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
doResolveAndConnect0(channel, remoteAddress, localAddress, promise);
}
});
}
}
2)NioSockerChannel#连接
在nio-thread中进行连接操作,最终通过pipeline调用AbstractNioChannel.AbstractNioUnsafe.connect方法,
private static void doConnect(
final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise connectPromise) {
final Channel channel = connectPromise.channel();
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
channel.connect(remoteAddress, connectPromise);
}
});
}
连接服务器,数据报文经过本机网络栈->网络->服务端网络栈->服务端的OP_Accept。
但此时只是往服务器发送连接请求了,还未真正完成连接。(tcp三次握手)
关注事件为OP_CONNECT客户端连接事件,操作系统会监听tcp第二次握手的ack报文。
protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
try {
// 连接服务器
boolean connected = SocketUtils.connect(javaChannel(), remoteAddress);
if (!connected) {
selectionKey().interestOps(SelectionKey.OP_CONNECT);
}
...
}
3)NioSockerChannel#事件重置
NioEventLoop拿到ack报文后,先将关注事件设置为0,并标识当前Channel为已连接状态。
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch){
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
// remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking
// See https://github.com/netty/netty/issues/924
int ops = k.interestOps();
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops);
unsafe.finishConnect();
}
}
同时发送第三次握手报文,通过finishConnect通知Linux底层客户端已经完成连接建立了,在这之后如果获取SocketChannel是否打开、是否连接,都会返回true。
protected void doFinishConnect() throws Exception {
if (!javaChannel().finishConnect()) {
throw new Error();
}
}
public boolean isActive() {
SocketChannel ch = javaChannel();
return ch.isOpen() && ch.isConnected();
}
4)NioSockerChannel#关注read
通过pipeline触发流水线上Handle的ChannelActive()方法,业务Handler可以埋入业务所需的逻辑,比如用户登录成功、连接建立成功等等。
private void fulfillConnectPromise(ChannelPromise promise, boolean wasActive) {
...
if (!wasActive && active) {
pipeline().fireChannelActive();
}
...
}
ChannelActive会继续触发readIfIsAutoRead相关方法,进行read事件的关注。
private void readIfIsAutoRead() {
if (channel.config().isAutoRead()) {
channel.read();
}
}
protected void doBeginRead() throws Exception {
// readInterestOp为accept、read事件等,java.nio.channels.SelectionKey // boss:OP_ACCEPT // workor:OP_READ
final int interestOps = selectionKey.interestOps();
if ((interestOps & readInterestOp) == 0) {
selectionKey.interestOps(interestOps | readInterestOp);
}
}
二、总结
客户端的启动和服务端启动复用性比较高,特别是NioEventLoopGroup|NioEventLoop等逻辑思想都是一致的,唯一的区别就在于服务端是DoBind、客户端是doResolveAndConnect,客户端这里有一个三次握手的实现,可以结合TCP的三次握手协议对照看看。
如果有条件,后期还会补充对应jdk nio、linux epoll等处理逻辑,待续。