在前面两篇文章中我们介绍了EventLoopGroup和ServerBootstrap的初始化和启动的源码解析,在启动过程中,我们可以在Channel中自定义handler来处理业务,但是在netty中我们必须通过pipeline的方式进行添加,就如下面的代码实现
public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
NettyServerHandlerTest1 handlerTest1 = new NettyServerHandlerTest1();
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG,1024)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(handlerTest1);
}
});
ChannelFuture channelFuture = serverBootstrap.bind(8010).sync();
channelFuture.channel().closeFuture().sync();
}catch (Exception e) {
e.printStackTrace();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
我们启动服务时看下pipeline的初始添加值过程,通过源码跟踪我们可以得到以下的大概流程图
结合上面的代码pipeline.addLast()我们直接进入到DefaultChannelPipeline.addLast(ChannelHandler... handlers)方法,然后在通过源码跟踪进入到如下的代码中
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
// 同步块操作
synchronized (this) {
// 检查定义的handler是否使用了@ChannelHandler.Sharable注解并且是否已经添加过
checkMultiplicity(handler);
// 定义DefaultChannelHandlerContext类
newCtx = newContext(group, filterName(name, handler), handler);
// 对newCtx值的前后链表进行赋值操作
addLast0(newCtx);
// If the registered is false it means that the channel was not registered on an eventLoop yet.
// In this case we add the context to the pipeline and add a task that will call
// ChannelHandler.handlerAdded(...) once the channel is registered.
if (!registered) {
newCtx.setAddPending();
// 进行回调函数的定义操作
callHandlerCallbackLater(newCtx, true);
return this;
}
EventExecutor executor = newCtx.executor();
if (!executor.inEventLoop()) {
// 异步初始化handler在EventLoop调用
callHandlerAddedInEventLoop(newCtx, executor);
return this;
}
}
// handler在EventLoop调用
callHandlerAdded0(newCtx);
return this;
}
通过上面的一些步骤就完成了对pipeline中的大体维护过程,当然pipeline还有addFirst,addBefore等方法,需要特别说明的是checkMultiplicity()方法
private static void checkMultiplicity(ChannelHandler handler) {
if (handler instanceof ChannelHandlerAdapter) {
ChannelHandlerAdapter h = (ChannelHandlerAdapter) handler;
// 判断是否实现了@ChannelHandler.Sharable注解并且有没有添加到缓存
if (!h.isSharable() && h.added) {
throw new ChannelPipelineException(
h.getClass().getName() +
" is not a @Sharable handler, so can't be added or removed multiple times.");
}
h.added = true;
}
}
public boolean isSharable() {
/**
* Cache the result of {@link Sharable} annotation detection to workaround a condition. We use a
* {@link ThreadLocal} and {@link WeakHashMap} to eliminate the volatile write/reads. Using different
* {@link WeakHashMap} instances per {@link Thread} is good enough for us and the number of
* {@link Thread}s are quite limited anyway.
*
* See <a href="https://github.com/netty/netty/issues/2289">#2289</a>.
*/
Class<?> clazz = getClass();
Map<Class<?>, Boolean> cache = InternalThreadLocalMap.get().handlerSharableCache();
Boolean sharable = cache.get(clazz);
// 判断是否添加了注解,并且添加到缓存中没有
if (sharable == null) {
sharable = clazz.isAnnotationPresent(Sharable.class);
cache.put(clazz, sharable);
}
return sharable;
}
分析完pipeline的handler维护过程,我们在来了解下pipeline数据处理的过程,跟踪源码大概的流程图如下
我们先启动客户端,然后跟踪服务端的pipeline处理流程,服务端的pipeline入口是DefaultChannelPipeline.fireChannelRead(Object msg)方法
public ChannelHandlerContext fireChannelRead(final Object msg) {
invokeChannelRead(findContextInbound(), msg);
return this;
}
static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
// 判断是否是ReferenceCounted类型的数据,如果是则进行具体实现处理,否则直接返回msg
final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
EventExecutor executor = next.executor();
// 判断当前线程是否是已经在执行的线程中,是则直接进行读取操作,否则异步执行读取操作
if (executor.inEventLoop()) {
next.invokeChannelRead(m);
} else {
executor.execute(new Runnable() {
@Override
public void run() {
next.invokeChannelRead(m);
}
});
}
}
private void invokeChannelRead(Object msg) {
// 判断是否已经初始化完成,没有完成则重复调用
if (invokeHandler()) {
try {
// 调用接口实现类去完成自己的逻辑业务操作
((ChannelInboundHandler) handler()).channelRead(this, msg);
} catch (Throwable t) {
notifyHandlerException(t);
}
} else {
fireChannelRead(msg);
}
}
通过上面的代码分析,其实职责链的代码还是相对容易的,最后都是调用自己的实现类来完成最终的业务操作,这里需要注意的是如果自己的handler实现类没有@ChannelHandler.Sharable注解,那么多个客户端来连接的时候会报xxx is not a @Sharable handler, so can't be added or removed multiple times的错误。