技术标签: java # io-nio 运维 服务器 程序那些事 响应式系统 netty
我们知道ChannelHandler有两个非常重要的子接口,分别是ChannelOutboundHandler和ChannelInboundHandler,基本上这两个handler接口定义了所有channel inbound和outbound的处理逻辑。
不管是ChannelHandler还是ChannelOutboundHandler和ChannelInboundHandler,几乎他们中所有的方法都带有一个ChannelHandlerContext参数,那么这个ChannelHandlerContext到底是做什么用的呢?它和handler、channel有什么关系呢?
熟悉netty的朋友应该都接触过ChannelHandlerContext,如果没有的话,这里有一个简单的handler的例子:
public class ChatServerHandler extends SimpleChannelInboundHandler<String> {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.info("accepted channel: {}", ctx.channel());
log.info("accepted channel parent: {}", ctx.channel().parent());
// channel活跃
ctx.write("Channel Active状态!\r\n");
ctx.flush();
}
}
这里的handler继承了SimpleChannelInboundHandler,只需要实现对应的方法即可。这里实现的是channelActive方法,在channelActive方法中,传入了一个ChannelHandlerContext参数,我们可以通过使用ChannelHandlerContext来调用它的一些方法。
先来看一下ChannelHandlerContext的定义:
public interface ChannelHandlerContext extends AttributeMap, ChannelInboundInvoker, ChannelOutboundInvoker {
首先ChannelHandlerContext是一个AttributeMap,可以用来存储多个数据。
然后ChannelHandlerContext继承了ChannelInboundInvoker和ChannelOutboundInvoker,可以触发inbound和outbound的一些方法。
除了继承来的一些方法之外,ChannelHandlerContext还可以作为channel,handler和pipline的沟通桥梁,因为可以从ChannelHandlerContext中获取到对应的channel,handler和pipline:
Channel channel();
ChannelHandler handler();
ChannelPipeline pipeline();
还要注意的是ChannelHandlerContext还返回一个EventExecutor,用来执行特定的任务:
EventExecutor executor();
接下来,我们具体看一下ChannelHandlerContext的实现。
AbstractChannelHandlerContext是ChannelHandlerContext的一个非常重要的实现,虽然AbstractChannelHandlerContext是一个抽象类,但是它基本上实现了ChannelHandlerContext的所有功能。
首先看一下AbstractChannelHandlerContext的定义:
abstract class AbstractChannelHandlerContext implements ChannelHandlerContext, ResourceLeakHint
AbstractChannelHandlerContext是ChannelHandlerContext的一个具体实现。
通常来说一个handler对应一个ChannelHandlerContext,但是在一个程序中可能会有多于一个handler,那么如何在一个handler中获取其他的handler呢?
在AbstractChannelHandlerContext中有两个同样是AbstractChannelHandlerContext类型的next和prev,从而使得多个AbstractChannelHandlerContext可以构建一个双向链表。从而可以在一个ChannelHandlerContext中,获取其他的ChannelHandlerContext,从而获得handler处理链。
volatile AbstractChannelHandlerContext next;
volatile AbstractChannelHandlerContext prev;
AbstractChannelHandlerContext中的pipeline和executor都是通过构造函数传入的:
AbstractChannelHandlerContext(DefaultChannelPipeline pipeline, EventExecutor executor,
String name, Class<? extends ChannelHandler> handlerClass) {
this.name = ObjectUtil.checkNotNull(name, "name");
this.pipeline = pipeline;
this.executor = executor;
this.executionMask = mask(handlerClass);
// Its ordered if its driven by the EventLoop or the given Executor is an instanceof OrderedEventExecutor.
ordered = executor == null || executor instanceof OrderedEventExecutor;
}
可能有朋友会有疑问了,ChannelHandlerContext中的channel和handler是如何得到的呢?
对于channel来说,是通过pipeline来获取的:
public Channel channel() {
return pipeline.channel();
}
对于handler来说,在AbstractChannelHandlerContext中并没有对其进行实现,需要在继承AbstractChannelHandlerContext的类中进行实现。
对于EventExecutor来说,可以通过构造函数向AbstractChannelHandlerContext传入一个新的EventExecutor,如果没有传入或者传入为空的话,则会使用channel中自带的EventLoop:
public EventExecutor executor() {
if (executor == null) {
return channel().eventLoop();
} else {
return executor;
}
}
因为EventLoop继承自OrderedEventExecutor,所以它也是一个EventExecutor。
EventExecutor主要用来异步提交任务来执行,事实上ChannelHandlerContext中几乎所有来自于ChannelInboundInvoker和ChannelOutboundInvoker的方法都是通过EventExecutor来执行的。
对于ChannelInboundInvoker来说,我们以方法fireChannelRegistered为例:
public ChannelHandlerContext fireChannelRegistered() {
invokeChannelRegistered(findContextInbound(MASK_CHANNEL_REGISTERED));
return this;
}
static void invokeChannelRegistered(final AbstractChannelHandlerContext next) {
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeChannelRegistered();
} else {
executor.execute(new Runnable() {
@Override
public void run() {
next.invokeChannelRegistered();
}
});
}
}
fireChannelRegistered调用了invokeChannelRegistered方法,invokeChannelRegistered则调用EventExecutor的execute方法,将真实的调用逻辑封装在一个runnable类中执行。
注意,在调用executor.execute方法之前有一个executor是否在eventLoop中的判断。如果executor已经在eventLoop中了,那么直接执行任务即可,不需要启用新的线程。
对于ChannelOutboundInvoker来说,我们以bind方法为例,看一下EventExecutor是怎么使用的:
public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) {
ObjectUtil.checkNotNull(localAddress, "localAddress");
if (isNotValidPromise(promise, false)) {
// cancelled
return promise;
}
final AbstractChannelHandlerContext next = findContextOutbound(MASK_BIND);
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeBind(localAddress, promise);
} else {
safeExecute(executor, new Runnable() {
@Override
public void run() {
next.invokeBind(localAddress, promise);
}
}, promise, null, false);
}
return promise;
}
可以看到执行的逻辑和invokeChannelRegistered方法很类似,也是先判断executor在不在eventLoop中,如果在的话直接执行,如果不在则放在executor中执行。
上面的两个例子中都调用了next的相应方法,分别是next.invokeChannelRegistered和next.invokeBind。
我们知道ChannelHandlerContext只是一个封装,它本身并没有太多的业务逻辑,所以next调用的相应方法,实际上是Context中封装的ChannelInboundHandler和ChannelOutboundHandler中的业务逻辑,如下所示:
private void invokeUserEventTriggered(Object event) {
if (invokeHandler()) {
try {
((ChannelInboundHandler) handler()).userEventTriggered(this, event);
} catch (Throwable t) {
invokeExceptionCaught(t);
}
} else {
fireUserEventTriggered(event);
}
}
private void invokeBind(SocketAddress localAddress, ChannelPromise promise) {
if (invokeHandler()) {
try {
((ChannelOutboundHandler) handler()).bind(this, localAddress, promise);
} catch (Throwable t) {
notifyOutboundHandlerException(t, promise);
}
} else {
bind(localAddress, promise);
}
}
所以,从AbstractChannelHandlerContext可以得知,ChannelHandlerContext接口中定义的方法都是调用的handler中具体的实现,Context只是对handler的封装。
DefaultChannelHandlerContext是AbstractChannelHandlerContext的一个具体实现。
我们在讲解AbstractChannelHandlerContext的时候提到过,AbstractChannelHandlerContext中并没有定义具体的handler的实现,而这个实现是在DefaultChannelHandlerContext中进行的。
DefaultChannelHandlerContext很简单,我们看一下它的具体实现:
final class DefaultChannelHandlerContext extends AbstractChannelHandlerContext {
private final ChannelHandler handler;
DefaultChannelHandlerContext(
DefaultChannelPipeline pipeline, EventExecutor executor, String name, ChannelHandler handler) {
super(pipeline, executor, name, handler.getClass());
this.handler = handler;
}
@Override
public ChannelHandler handler() {
return handler;
}
}
DefaultChannelHandlerContext中额外提供了一个ChannelHandler属性,用来存储传入的ChannelHandler。
到此DefaultChannelHandlerContext可以传入ChannelHandlerContext中一切必须的handler,channel,pipeline和EventExecutor。
本节我们介绍了ChannelHandlerContext和它的几个基本实现,了解到了ChannelHandlerContext是对handler,channel和pipline的封装,ChannelHandlerContext中的业务逻辑,实际上是调用的是底层的handler的对应方法。这也是我们在自定义handler中需要实现的方法。
本文已收录于 http://www.flydean.com/04-4-netty-channelhandlercontext/
最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!
欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!
文章浏览阅读2.9k次,点赞2次,收藏8次。一、搭建http服务器'use strict'var http =require('http');var app=http.createServer(function(req,res){ res.writeHead(200,{'Content-Type':'text/plain'}); res.end('Http:Hello World\n');}).listen(8081..._webrtc nodejs
文章浏览阅读6.8k次,点赞7次,收藏20次。简单实现队列先进先出:package com;import java.util.LinkedList;public class MyQueue{ private LinkedList list = new LinkedList(); public void put(Object t){ //加入数据 list.addFirst(t); }..._java请编写类fifoqueue实现接口collection,fifoqueue类实现先进先出队列的数据操
文章浏览阅读1.3k次,点赞9次,收藏13次。Univer 是一套企业文档与数据协同解决方案,包括电子表格、文档和幻灯片三大文档类型,高可扩展性设计使得开发者可以在 Univer 的基础上定制个性化功能。它的前身是Luckysheet ,Luckysheet 是一款纯前端类似excel的在线表格,功能强大、配置简单、完全开源。一款纯前端类似excel的在线表格,功能强大、配置简单、完全开源。_univer在线表格
文章浏览阅读586次。分布式缓存出于如下考虑,首先是缓存本身的水平线性扩展问题,其次是缓存大并发下的本身的性能问题,再次避免缓存的单点故障问题(多副本和副本一致性)。分布式缓存的核心技术包括首先是内存本身的管理问题,包括了内存的分配,管理和回收机制。其次是分布式管理和分布式算法,其次是缓存键值管理和路由。
文章浏览阅读1.5k次,点赞2次,收藏16次。【实验目的】 掌握I2C协议的内容,了解I2C接口的特点。 了解陀螺仪MMA8451的用途及数据采集过程。 熟悉s5p6818处理器的I2C配置,完成通过I2C读取MMA8451三轴加速度传感器芯片和加速度的改变实现计步功能数据的实验。实验平台:FS_6818开发平台,Ubuntu,secureCRT。【实验原理】I2C (Inter-Integrated Circuit)协议是由Phiilps公司开发的,由于它具引脚少,硬件实现简单,可扩展性强,不需要如USART..._6818 加速度传感器
文章浏览阅读191次,点赞7次,收藏3次。城市供热需求和规模不断增长扩大,且随着自动化技术和物联网技术得发展,通过中控室集中监控分散的热换站,实现换热站无人值守,就能极大提高供热效率、降低运营成本和提升服务水平,实现智慧供热。_换热站无人值守
文章浏览阅读2.5k次。//人脸检测和人脸比对百度ai人脸检测//1.获取access_token//每次更新access_token//获取client_id和client_secret使用百度ai的下面这个已经失效//client_id=YXtYiFxEUU7OBFF4sG6K1v88&client_secret=j1a5FdWp4jvGzwS0n37hzy1kKh9rIQog//uni.request(..._uni-app 人脸对比虹软
文章浏览阅读2.4k次。需求静默安装软件,动态配置参数解决第一步:按引导创建脚本,这部分就不描述了; Script generated by the Inno Setup Script Wizard.; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!#define MyAppName &quot;My Test&quot;#defi..._inno setup如何修改用户信息框说明
文章浏览阅读2.1w次,点赞14次,收藏140次。关于OpenMVG与OpenMVS之间的关系可见下图。关于目前常用的三维重建系统的对比见网址:http://leohope.com/%E8%A7%A3%E9%97%AE%E9%A2%98/2018/03/06/compare-re3d-system/可见OpenMVG与OpenMVS搭配使用,可以实现一个完美的三维重建流程。下面开始讲解两者的配置与简单使用: 1. 编译 o..._openmvg+openmvs
文章浏览阅读551次。demo在线下载地址(完整代码包含插件地址)http://www.shagua.wiki/project/3?p=125_select多选下拉框 源码
文章浏览阅读498次,点赞9次,收藏11次。显示所有适配器的 IPv6 地址、 IPv4 地址、子网掩码、默认网关ipconfig显示本机 TCP/IP 配置的详细信息DHCP 客户端手工释放 IP 地址DHCP 客户端手工向服务器刷新请求显示本地 DNS 内容清除本地 DNS 缓存内容。
文章浏览阅读524次,点赞9次,收藏7次。android13 获取双卡信号强度_android 监听双卡信号强度