Netty源码分析之ChannelPipeline(二)—ChannelHandler的添加与删除
上篇文章中,我们对netty中channelpipeline的构造与初始化进行了分析与总结,本篇文章我们将对channelhandler的添加与删除操作进行具体的的代码分析;
一、channelhandler的添加
下面是netty官方的一段demo源码,可以看到在服务端初始化时执行了向channelpipeline中添加自定义channelhandler的操作。
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)); // 向channelpipeline中添加自定义channelhandler p.addlast(serverhandler); } });
我们可以看到上面的代码中调用channelpipeline的addlast方法实现了channelhandler的添加,下面我们就分析下addlast方法的具体源码实现
首先看下addlast方方法的具体源码实现
public final channelpipeline addlast(eventexecutorgroup group, string name, channelhandler handler) { final abstractchannelhandlercontext newctx; synchronized (this) { //判断handler是否被重复添加 checkmultiplicity(handler); //创建channelhandlercontext节点 filtername检查名称是否重复 newctx = newcontext(group, filtername(name, handler), handler); //双向链表中增加channelhandlercontext 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()) {//判断是否在同一线程中 callhandleraddedineventloop(newctx, executor); return this; } } callhandleradded0(newctx); return this; }
分析addlast方法代码可以看到,channelhandler的添加基本可以分为四步
1、验证channelhandler是否重复添加
我们看下checkmultiplicity方法的具体实现
private static void checkmultiplicity(channelhandler handler) { if (handler instanceof channelhandleradapter) { channelhandleradapter h = (channelhandleradapter) handler; if (!h.issharable() && h.added) {//如果该handler非共享且已经被添加 throw new channelpipelineexception( h.getclass().getname() + " is not a @sharable handler, so can't be added or removed multiple times."); } h.added = true;//添加过之后,修改标识 } }
2、创建一个handlercontext对象
我们之前说过netty会把一个channelhandler封装成一个channelhandlercontext对象,如下面代码所示
newctx = newcontext(group, filtername(name, handler), handler);
封装对象时,我们可以给要添加的channelhandler起一个名字,filtername方法可以判断该handler的命名是否重复
private string filtername(string name, channelhandler handler) { if (name == null) { return generatename(handler);//返回一个默认名称 } checkduplicatename(name); return name; }
checkduplicatename方法会遍历链表中节点如果查询到有重复的name则会抛出异常
private void checkduplicatename(string name) { if (context0(name) != null) { //遍历节点,查找是否有重复name throw new illegalargumentexception("duplicate handler name: " + name); } }
private abstractchannelhandlercontext context0(string name) { abstractchannelhandlercontext context = head.next; while (context != tail) { if (context.name().equals(name)) { return context; } context = context.next; } return null; }
3、向链表中添加添加context
前面进行了一系列判断后,通过addlast0方法我们把channelhandlercontext添加到pipeline中的双向链表中
//相当于在tail节点前面插入一个节点,也就是addlast private void addlast0(abstractchannelhandlercontext newctx) { abstractchannelhandlercontext prev = tail.prev;//拿到tail节点的前置节点 newctx.prev = prev;//把当前context的前置节点置为 prev newctx.next = tail;//把当前context的后置节点置为tail prev.next = newctx;//把prev节点的后置节点置为context tail.prev = newctx;//把tail节点的前置节点置为context }
addlast0内部实现很简单,就是在tail节点前面插入一个节点,也就是把该channelhandlercontext放在链表的最后。
4、调用回调方法,通知添加成功
这一步就是当channelhandler添加到pipeline中时调用,通过callhandleradded()回调方法通知channelhandler添加成功,执行handleradded()方法;
首先判断当前线程与eventloop线程是否一致,不一致的话封装成task提交给eventloop线程,是同一线程直接执行callhandleradded0方法,我们看下方法具体实现
private void callhandleradded0(final abstractchannelhandlercontext ctx) { try { ctx.callhandleradded();//调用callhandleradded回调方法 } catch (throwable t) { boolean removed = false; try { remove0(ctx);//如果出现异常的话,把该ctx删除 ctx.callhandlerremoved();//调用callhandlerremoved回调方法 removed = true; } catch (throwable t2) { if (logger.iswarnenabled()) { logger.warn("failed to remove a handler: " + ctx.name(), t2); } } if (removed) { fireexceptioncaught(new channelpipelineexception( ctx.handler().getclass().getname() + ".handleradded() has thrown an exception; removed.", t)); } else { fireexceptioncaught(new channelpipelineexception( ctx.handler().getclass().getname() + ".handleradded() has thrown an exception; also failed to remove.", t)); } } }
final void callhandleradded() throws exception { // we must call setaddcomplete before calling handleradded. otherwise if the handleradded method generates // any pipeline events ctx.handler() will miss them because the state will not allow it. if (setaddcomplete()) {//在添加handler之前,保证状态为可添加状态 handler().handleradded(this); } }
通过上面代码实现,我们就可以通过重写channelhandler的handleradded方法,执行一些当channelhandler添加到pipeline中时需要触发的功能逻辑。
二、channelhandler的删除
channelhandler的删除主要是通过channelpipeline的remove方法来实现的
我们先看下remove方法源码具体实现
@override public final channelpipeline remove(channelhandler handler) { remove(getcontextordie(handler));//删除handler return this; }
private abstractchannelhandlercontext remove(final abstractchannelhandlercontext ctx) { //不能删除头节点和尾节点 assert ctx != head && ctx != tail; //加锁,保证线程安全 synchronized (this) { remove0(ctx);//在链表中删除context对象 // if the registered is false it means that the channel was not registered on an eventloop yet. // in this case we remove the context from the pipeline and add a task that will call // channelhandler.handlerremoved(...) once the channel is registered. //这里主要判断下该pipline对应channel是否已经注册到eventloop上 if (!registered) { callhandlercallbacklater(ctx, false); return ctx; } eventexecutor executor = ctx.executor(); if (!executor.ineventloop()) {//判断是否在同一线程中 executor.execute(new runnable() { @override public void run() { callhandlerremoved0(ctx); } }); return ctx; } } //调用回调方法,通知handler已删除 callhandlerremoved0(ctx); return ctx; }
删除操作整个流程与添加类似
1、获取channelhandlercontext对象
//根据传入的handler拿到其包装的channelhandlercontext对象 private abstractchannelhandlercontext getcontextordie(channelhandler handler) { //根据context方法从链表中获取该handler的channelhandlercontext对象 abstractchannelhandlercontext ctx = (abstractchannelhandlercontext) context(handler); if (ctx == null) { throw new nosuchelementexception(handler.getclass().getname()); } else { return ctx; } }
@override public final channelhandlercontext context(channelhandler handler) { if (handler == null) { throw new nullpointerexception("handler"); } abstractchannelhandlercontext ctx = head.next; //遍历链表拿到该handler封装的channelhandlercontext对象 for (;;) { if (ctx == null) { return null; } if (ctx.handler() == handler) { return ctx; } ctx = ctx.next; } }
2、判断是否是首尾节点
首先判断删除的节点既不是头节点也不是尾节点
//不能删除头节点和尾节点 assert ctx != head && ctx != tail;
3、执行删除操作
然后通过remove0方法删除指定context节点
private static void remove0(abstractchannelhandlercontext ctx) { abstractchannelhandlercontext prev = ctx.prev;//获取当前节点的前置节点 abstractchannelhandlercontext next = ctx.next;//获取当前节点的后置节点 prev.next = next;//把prev后置节点设置为next next.prev = prev;//把next前置节点设置为prev }
4、调用回调方法,通知删除成功
同样会判断当前线程与eventloop线程是否一致,不一致的话封装成task提交给eventloop线程
eventexecutor executor = ctx.executor(); if (!executor.ineventloop()) {//判断是否在同一线程中 executor.execute(new runnable() { @override public void run() { callhandlerremoved0(ctx); } }); return ctx; }
//调用回调方法,通知handler已删除 callhandlerremoved0(ctx);
调用callhandlerremoved()回调方法,通知触发handlerremoved删除成功。
private void callhandlerremoved0(final abstractchannelhandlercontext ctx) { // notify the complete removal. try { ctx.callhandlerremoved();//调用ctx中的回调方法 } catch (throwable t) { fireexceptioncaught(new channelpipelineexception( ctx.handler().getclass().getname() + ".handlerremoved() has thrown an exception.", t)); } }
final void callhandlerremoved() throws exception { try { // only call handlerremoved(...) if we called handleradded(...) before. if (handlerstate == add_complete) { handler().handlerremoved(this);//通知handler删除成功事件 } } finally { // mark the handler as removed in any case. setremoved(); } }
三、总结
通过上面的内容,我们梳理了channelhandler被封装成channelhandlercontext对象后,基于channelpipeline的双向链表的添加和删除操作,整个流程还是比较简单清晰的。其中如有不足与不正确的地方还望指出与海涵
关注微信公众号,查看更多技术文章。
上一篇: Java8 日期和时间API
下一篇: php二维数组排序详解