在之前的文章中我们学习了 netty的io模型、netty的使用分析、netty线程模型、netty的ChannelPipeline类学习、netty的ChannelPromise类学习。下面我们从使用netty例子出发探究一下netty的工作流程(其中包括服务端和客户端的创建、一次完整的请求和响应)。
在探究netty一次请求的完整流程之前我们先来了解一下XXXBootstrap组件。 它是netty为我们提供的便于我们使用的工具启动类。他以builder的模式让我们可以简单设置例如channel、TCP参数、相关处理器。其中客户端使用Bootstrap类,服务端使用ServerBootstrap类两者的基类为AbstractBootstrap类。
AbstractBootstrap的
Bootstrap的
补充说明:netty服务底层是使用NIO原理实现的,所以此处分析netty相关源码我们也分析到NIO层,至于NIO的底层原理实现请期待笔者后续的博文。
netty服务端创建相关代码如下,服务端创建是使用bind()核心方法,下面来分析bind方法
//一、netty 服务端启动
//.......省略ServerBootstrap构建过程
//绑定端口 netty服务端创建
ChannelFuture sync = serverBootstrap.bind(port).sync();
//服务监听端口关闭
sync.channel().closeFuture().sync();
bind()方法最终调用io.netty.bootstrap.AbstractBootstrap#doBind()方法,下面我们来分析netty服务端启动方法。
private ChannelFuture doBind(final SocketAddress localAddress) {
//正如其方法名一样 初始化channel,并进行channel和selector(多路复用器的注册绑定)
//其返回值是netty自己封装的异步结果接口ChannelFuture
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
//如果异步结果中有异常则直接返回 此时:netty服务端初始化失败
if (regFuture.cause() != null) {
return regFuture;
}
final ChannelPromise promise;
//上述的initAndRegister的结果是异步通知的 所有此处需要进行条件分支判断
//如果上述操作已经完成(通道初始化完成)则直接进行doBind0()方法
if (regFuture.isDone()) {
promise = channel.newPromise();
//channel和端口进行绑定操作
doBind0(regFuture, channel, localAddress, promise);
} else {
//一般情况下initAndRegister会很快完成并给出结果 但是万一呢?
promise = new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE);
//此处针对如果上述未完成 添加一个事件监听,待上述操作完成后触发doBind0()
regFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
doBind0(regFuture, channel, localAddress, promise);
}
});
}
return promise;
}
netty服务端启动和绑定过程并不复杂主要分成了两部分(两个核心方法)
final ChannelFuture initAndRegister() {
Channel channel;
try {
//1、创建网络通道Channel
channel = createChannel();
} catch (Throwable t) {
return VoidChannel.INSTANCE.newFailedFuture(t);
}
try {
//2、为channel配置 线程、option、attr、事件处理器ChannelHandler
init(channel);
} catch (Throwable t) {
channel.unsafe().closeForcibly();
return channel.newFailedFuture(t);
}
ChannelPromise regFuture = channel.newPromise();
//channel注册多路复用器selector
channel.unsafe().register(regFuture);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
return regFuture;
}
netty服务端初始化也可以分成三部分
//调用channel(NioServerSocketChannel.class)方法 会创建ServerBootstrapChannelFactory
return channelFactory(new ServerBootstrapChannelFactory<ServerChannel>(channelClass));
createChannel方法调用ServerBootstrapChannelFactory的newChannel(),通过反射调用NioServerSocketChannel的构造函数
//构建函数
public NioServerSocketChannel(EventLoop eventLoop, EventLoopGroup childGroup) {
//父构造函数 newSocket()创建一个NIO的ServerSocketChannel 和eventLoop线程以及工作线程childGroup
//包装成NioServerSocketChannel
super(null, eventLoop, childGroup, newSocket(), SelectionKey.OP_ACCEPT);
config = new DefaultServerSocketChannelConfig(this, javaChannel().socket());
}
在上述方法的第二部分init()方法初始化channel配置最终实现类为io.netty.bootstrap.ServerBootstrap#init。
void init(Channel channel) throws Exception {
//获取AbstractBootstrap的options(TCP配置信息) 设置到ChannelConfig中
final Map<ChannelOption<?>, Object> options = options();
synchronized (options) {
channel.config().setOptions(options);
}
//获取AbstractBootstrap的attr属性 设置到Attribute中
final Map<AttributeKey<?>, Object> attrs = attrs();
synchronized (attrs) {
for (Map.Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
@SuppressWarnings("unchecked")
AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
channel.attr(key).set(e.getValue());
}
}
//获取AbstractBootstrap的ChannelHandler 添加到ChannelHandlerPipline中
ChannelPipeline p = channel.pipeline();
if (handler() != null) {
p.addLast(handler());
}
//子类型的Handler、options、attrs (用户一般使用会创建ChannelInitializer 并通过initChannel
// 添加自定义的ChannelHandler)
final ChannelHandler currentChildHandler = childHandler;
final Map.Entry<ChannelOption<?>, Object>[] currentChildOptions;
final Map.Entry<AttributeKey<?>, Object>[] currentChildAttrs;
synchronized (childOptions) {
currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size()));
}
synchronized (childAttrs) {
currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
}
//将在ServerBootstrap通过childHandler()、childOptions()、childAttrs()等方法
// childHandler、currentChildOptions、currentChildAttrs 包装成ServerBootstrapAcceptor使用
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(Channel ch) throws Exception {
//ServerBootstrapAcceptor
ch.pipeline().addLast(new ServerBootstrapAcceptor(currentChildHandler, currentChildOptions,
currentChildAttrs));
}
});
}
init()方法的分析可以分成如下部分,
options:通过ServerBootstrap设置的options添加到channel的ChannelConfig实例中(DefaultServerSocketChannelConfig实现类在createChannel方法中实现的)
attrs:通过ServerBootstrap设置的attrs添加到channel的Attribute中。
handler:过ServerBootstrap设置的handler添加到ChannelHandlerPipline中。
为通道添加一个ChannelInitializer实现类,该类是一个继承ChannelHandlerAdapter的抽象类,其只重写了channelRegistered事件,当channel通过后面的方法 channel.unsafe().register(regFuture) 注册后会触发其中的channelRegistered方法。
当前的channel进行注册成功后会将childHandler、currentChildOptions、currentChildAttrs 包装成ServerBootstrapAcceptor使用,ServerBootstrapAcceptor是一个特殊的ChannelHandler,该类也是继承了ChannelHandlerAdapter,其重写了channelRead事件,每当有一个客户端连接进来后,会先进入到ServerBootstrapAcceptor的channelRead()。
补充: channelRegistered事件:channel和selector注册后会触发该方法
channelRead事件:每当有一个客户端连接进来后,会先进入到ServerBootstrapAcceptor的channelRead()
上述的4和5步骤我们需要了解ChannelInitializer实现,同时对ServerBootstrapAcceptor类作用进行介绍。
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(Channel ch) throws Exception {
//Channel添加ServerBootstrapAcceptor
ch.pipeline().addLast(new ServerBootstrapAcceptor(currentChildHandler, currentChildOptions,
currentChildAttrs));
}
});
在上述init()方法中最终在ChannelPipline通过addLast方法 添加了一个匿名实现ChannelInitializer
public final void channelRegistered(ChannelHandlerContext ctx) throws Exception {
ChannelPipeline pipeline = ctx.pipeline();
boolean success = false;
try {
//提供的抽象方法 用于自定义实现
initChannel((C) ctx.channel());
//将其从ChannelPipline中移除
pipeline.remove(this);
//将channelRegistered事件透传到别的ChannelHandler
ctx.fireChannelRegistered();
success = true;
} catch (Throwable t) {
//异常处理
} finally {
}
}
ChannelInitializer类的作用是处理channelRegistered事件,主要是执行initChannel,然后移除本匿名ChannelInitializer实现类。结合上述netty源码,这里就是往pipline中添加ServerBootstrapAcceptor对象同时移除这里的ChannelInitializer实现类。
在之前的博文中netty的 线程模型 Reactor多线程模型中 netty会通过注册一个Acceptor事件处理器到mainReactor中,Acceptor事件处理器所关注的事件是ACCEPT事件,这样mainReactor会监听客户端向服务器端发起的连接请求事件(ACCEPT事件),Acceptor会将客户端的I/O事件分发到sub Reactor线程池。 ServerBootstrapAcceptor对象的作用是那个所谓的Acceptor事件处理器,其重写了channelRead方法。
//该方法触发条件是 每一个客户端与服务端建立连接后都会调用该方法
public void channelRead(ChannelHandlerContext ctx, Object msg) {
//msg是客户端连接服务端创建的Channel(NioSocketChannel) 强转为Channel
Channel child = (Channel) msg;
//为ChannelPipeline添加childHandler(通过ServerBootstrap的childHandler()方法设置的)
child.pipeline().addLast(childHandler);
//childOptions配置到客户端Channel的ChannelConfig中
for (Entry<ChannelOption<?>, Object> e: childOptions) {
try {
if (!child.config().setOption((ChannelOption<Object>) e.getKey(), e.getValue())) {
logger.warn("Unknown channel option: " + e);
}
} catch (Throwable t) {
logger.warn("Failed to set a channel option: " + child, t);
}
}
//childOptions配置到客户端Channel的attr中
for (Entry<AttributeKey<?>, Object> e: childAttrs) {
child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
}
//客户端Channel进行注册
child.unsafe().register(child.newPromise());
}
这个方法和我们上面的init方法的Channel的注册逻辑相似。这里需要补充说明一下:Channel注册分为ServerSocketChannel和SockerChannel注册,服务端启动的时候会先进行ServerSocketChannel的注册,启动完成后,每当有一个客户端连接过来,会进行一次SocketChannel注册。所以之前的init方法注册的是netty服务端的channel而这里的注册是netty客户端的channel。
下面我们继续来分析initAndRegister方法的第三部分注册,其调用不是由channel直接调用而是使用channel的工具类Unsafe类的register()方法去执行真正的注册逻辑。
public final void register(final ChannelPromise promise) {
//netty使用这种机制保证线程安全
//如果调用和eventloop是同一个线程 则直接调用
if (eventLoop.inEventLoop()) {
register0(promise);
} else {
try {
//否则将调用包装成任务交由eventloop
eventLoop.execute(new Runnable() {
@Override
public void run() {
register0(promise);
}
});
} catch (Throwable t) {
//异常处理
}
}
}
1.一个EventLoopGroup当中包含一个或多个EventLoop
2.一个EventLoop在其生命周期内只和唯一的一个Thread线程绑定(即一个io线程)
3.所有由EventLoop处理的各种io事件都将在其所关联的io线程上执行,因为是单线程保证了线程安全
4.一个Channel在其生命周期内只会注册在一个EventLoop(selector)上
5.运行期间,一个EventLoop会被分配给一个或多个channel
private void register0(ChannelPromise promise) {
try {
//注册之前确保channel通道是打开状态
if (!ensureOpen(promise)) {
return;
}
//调用底层NIO的将Channel注册到selector(多路复用器)
doRegister();
registered = true;
promise.setSuccess();
//将channelRegistered事件透传(事件往后传递让别的对该事件感兴趣的
// ChannelHandler处理器进行处理) (这里我们所有实现了channelRegistered都有可能接受到该事件 调用自己initChannel方法)
pipeline.fireChannelRegistered();
//javaChannel().socket().isBound()
//isBound() 方法判断 ServerSocket 是否已经与一个端口绑定 此时应该没有绑定
if (isActive()) {
//产生一个channelActive事件 放入pipeline中
pipeline.fireChannelActive();
}
} catch (Throwable t) {
//异常处理
}
}
该方法主要分成三个部分
调用java底层的NIO的api将channel注册到eventLoop的selector(一个selector可以轮询处理多个线程,所以一个eventLoop可以和多个channel进行绑定),eventLoop的selector是创建EventLoopGroup的子EventLoop初始化的。 ops为0表明此处只简单的注册不对任何事件感兴趣。
fireChannelRegistered :将channelRegistered事件透传(事件往后传递让别的对该事件感兴趣的ChannelHandler处理器进行处理)。
ServerSocket 如果和一个端口绑定了就产生一个channelActive事件 放入pipeline中。
private static void doBind0(
final ChannelFuture regFuture, final Channel channel,
final SocketAddress localAddress, final ChannelPromise promise) {
//在触发channelRegistered()之前调用此方法。给用户处理程序一个设置的机会
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
//上述的initAndRegister()方法处理完成且成功
if (regFuture.isSuccess()) {
//服务端Channel进行端口绑定,并添加异步结果监听(绑定结果失败的话关闭channel)
channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
} else {
//失败设置失败信息
promise.setFailure(regFuture.cause());
}
}
});
}
不解释,直接看channel的bind方法
//调用pipeline的bind方法
public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
return pipeline.bind(localAddress, promise);
}
//调用pipeline管理的chanelHandler列表
public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
return tail.bind(localAddress, promise);
}
//从tail往前查找对bind事件刚兴趣的ChannelHandler
public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) {
DefaultChannelHandlerContext next = findContextOutbound(MASK_BIND);
next.invoker.invokeBind(next, localAddress, promise);
return promise;
}
//通过调用其运行环境invoker中的invokeBindNow方法
ctx.handler().bind(ctx, localAddress, promise);
//最终查找到header
public void bind(
ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise)
throws Exception {
unsafe.bind(localAddress, promise);
}
调用链如下:
channel.bind()
-->pipeline.bind()
-->next.invoker.invokeBind()
-->header.bind()
public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
//确保
if (!ensureOpen(promise)) {
return;
}
//端口绑定之前 wasActive 为false
//调用方法isBound() 方法判断 ServerSocket 是否已经与一个端口绑定
boolean wasActive = isActive();
try {
//最终调用java原生NIO api进行端口绑定
doBind(localAddress);
} catch (Throwable t) {
}
//端口绑定后isActive() 为true 则此时产生一个channelActive事件
if (!wasActive && isActive()) {
invokeLater(new Runnable() {
@Override
public void run() {
//产生channelActive事件放入管道 同时selector监听ON_ACCEPT事件
pipeline.fireChannelActive();
}
});
}
//成功异步操作标识
promise.setSuccess();
}
两部分
上述服务端创建过程以经接近尾声,channel完成了初始化、同时注册了多路复用器(监听的事件是0)不对任何事件感兴趣,这里是有点疑问,后续客户端连接和读写事件怎么处理?别着急这里为多路复用器添加感兴趣的事件在fireChannelActive方法中 下面我们来看看该方法逻辑
public ChannelPipeline fireChannelActive() {
//触发pipeline中ChannelHandler的channelActive事件
head.fireChannelActive();
//调用chanel的read(autoRead属性默认为true)
if (channel.config().isAutoRead()) {
//调用链如下:
// channel.read()
// -----> pipeline.read
// ------>tail.read() 从tail往上查找符合MASK_READ的ChannelHandler 这里找到的是headHandler
// ------>unsafe.beginRead(); 调用channel的unsafe
//。 ------> unsafe.doBeginRead();找到最终调用,我们分析一下该方法
channel.read();
}
return this;
}
protected void doBeginRead() throws Exception {
if (inputShutdown) {
return;
}
//获取当前channel感兴趣的SelectionKey(java的NIO四种事件集合)
final SelectionKey selectionKey = this.selectionKey;
if (!selectionKey.isValid()) {
return;
}
final int interestOps = selectionKey.interestOps();
//readInterestOp是该channel在初始化阶段传入的ON_ACCEPT事件
//所以此处将channel的interestOps为0 添加监听ON_ACCEPT事件
if ((interestOps & readInterestOp) == 0) {
selectionKey.interestOps(interestOps | readInterestOp);
}
netty服务端经历了Channel的初始化、注册多路复用器、绑定端口、添加对客户端连接事件的监听分析到这里有关netty服务端创建过程完成。贴一个完成的流程图
netty客户端创建相关代码如下,服务端创建是使用bind()核心方法,下面来分析bind方法
//一、netty 客户端启动
//.......省略Bootstrap构建过程
//发起异步连接请求
ChannelFuture sync = bootstrap.connect(host,port).sync();
//客户端监听端口关闭
sync.channel().closeFuture().sync();
connect()方法最终调用io.netty.bootstrap.AbstractBootstrap#doConnect()方法,下面我们来分析netty客户端连接方法。其实netty客户端建立连接的过程和netty服务端启动过程类似,这里我们也只对区别说明。
private ChannelFuture doConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
//netty客户端初始化类似netty服务端类似
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) {
return regFuture;
}
final ChannelPromise promise = channel.newPromise();
//此处不再是doBind0方法而是doConnect0方法
if (regFuture.isDone()) {
doConnect0(regFuture, channel, remoteAddress, localAddress, promise);
} else {
regFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
doConnect0(regFuture, channel, remoteAddress, localAddress, promise);
}
});
}
return promise;
}
netty服务端启动和绑定过程并不复杂主要分成了两部分(两个核心方法)
初始化: initAndRegister方法,该方法主要功能是创建Channel对象(网络请求的数据流通道),
createChannel方法netty客户端是通过BootstrapChannelFactory对象创建的是NioSocketChannel实例,netty服务端是通过ServerBootstrapChannelFactory对象创建NioServerSocketChannel。
init(Bootstrap实现)方法只是添加handler、options、attr,netty服务端init(ServerBootstrap) 还会将child相关配置包装成ServerBootstrapAcceptor。
register0实现方式两者一致
连接建立:doConnect0方法,该方法绑定客户端channel并和netty服务端建立连接。着重描述。
private static void doConnect0(
final ChannelFuture regFuture, final Channel channel,
final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
if (regFuture.isSuccess()) {
//客户端连接建立
if (localAddress == null) {
channel.connect(remoteAddress, promise);
} else {
channel.connect(remoteAddress, localAddress, promise);
}
//连接建立失败监听 关键channel
promise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
} else {
promise.setFailure(regFuture.cause());
}
}
});
}
调用链如下:
pipeline.connect(remoteAddress, localAddress, promise)
--------->tail.connect(remoteAddress, localAddress, promise)
---------->findContextOutbound //从tail往前查找对connect感兴趣的HeadHandler
---------->unsafe.connect(remoteAddress, localAddress, promise) //最终调用unsafe.connect
connect实际调用的是AbstractNioUnsafe的connect方法
public void connect(
final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
if (!ensureOpen(promise)) {
return;
}
try {
if (connectPromise != null) {
throw new IllegalStateException("connection attempt already made");
}
boolean wasActive = isActive();
//调用Java原生API 与服务端建立连接。
//连接立即建立的话直接调用fulfillConnectPromise(作用是连接建立完成触发channelActive和channel的read方法,
//表示通道中已经有了可读的数据,可以执行读操作了)
if (doConnect(remoteAddress, localAddress)) {
fulfillConnectPromise(promise, wasActive);
} else {
//否则异步处理连接请求
connectPromise = promise;
requestedRemoteAddress = remoteAddress;
//如果设置了超时事件则创建一个延时任务(延时事件即为超时时间,里面用来处理超时连接的问题)
int connectTimeoutMillis = config().getConnectTimeoutMillis();
if (connectTimeoutMillis > 0) {
connectTimeoutFuture = eventLoop().schedule(new Runnable() {
@Override
public void run() {
ChannelPromise connectPromise = AbstractNioChannel.this.connectPromise;
ConnectTimeoutException cause =
new ConnectTimeoutException("connection timed out: " + remoteAddress);
if (connectPromise != null && connectPromise.tryFailure(cause)) {
close(voidPromise());
}
}
}, connectTimeoutMillis, TimeUnit.MILLISECONDS);
}
//注册一个连接完成的监听 如果完成说明异步连接成功 则取消之前超时处理延迟任务
promise.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isCancelled()) {
if (connectTimeoutFuture != null) {
connectTimeoutFuture.cancel(false);
}
connectPromise = null;
close(voidPromise());
}
}
});
}
} catch (Throwable t) {
//省略异常处理
}
}
方法逻辑如下:
1. doConnect 调用原生java API 进行和服务端建立连接
2. 连接立即建立:接着调用fulfillConnectPromise 触发客户端的读请求 发送数据到channel
3. 连接异步建立:设置超时任务处理连接(连接成功后该Channel注册在NioEventLoop上selector会轮循所有的事件进行处理)可以进行后续的读请求处理。
@Override
protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
if (localAddress != null) {
//调用原生java NIO 客户端socket绑定端口
javaChannel().socket().bind(localAddress);
}
boolean success = false;
try {
//调用原生java NIO 客户端socket发起服务端建立连接
//此时客户端会在服务端channel ON_ACCEPT事件 此时服务端添加的ServerBootstrapAcceptor对象的
//channelRead方法为服务端处理请求添加业务自定义的ChannelHandler。
boolean connected = javaChannel().connect(remoteAddress);
if (!connected) {
//连接建立是异步的
//所以此处该channel还需要监听 服务端建立完连接的ON_CONNECT事件
selectionKey().interestOps(SelectionKey.OP_CONNECT);
}
success = true;
return connected;
} finally {
if (!success) {
doClose();
}
}
}