2019独角兽企业重金招聘Python工程师标准>>>
这里我们就要分析下netty4 中 hanlder从注册到执行用户消息事件的流程。
handler的注册
在server端或是client端,都需要注册handler,才能使用。通过如下方式为channel设置相应的handler。
channel.pipeline().addLast(new RpcDecoder(MomResponse.class));
或者
ChannelPipeline cp = socketChannel.pipeline();
cp.addLast(new RpcEncoder(RpcRequest.class));等
一般是要通过channel获取pipeline,因为Channel的构造函数中会 new DefaultChannelPipeline(this);
而这个pipeline内部又维护了一个 双向链表,
public DefaultChannelPipeline(AbstractChannel channel) {
if (channel == null) {
throw new NullPointerException("channel");
}
this.channel = channel;
tail = new TailContext(this);
head = new HeadContext(this);
head.next = tail;
tail.prev = head;
}
而在addLast的过程中,如下 ,会将handler 加到内部链表的尾部。但是在add之前 ,会将其封装到一个DefaultChannelHandlerContext中,而这个context就 做为链表中的一个节点。通过链表表实现每个handler的顺序执行。
//DefaultChannelPipeline
@Override
public ChannelPipeline addLast(String name, ChannelHandler handler) {
return addLast(null, name, handler);
}
@Override
public ChannelPipeline addLast(EventExecutorGroup group, final String name, ChannelHandler handler) {
synchronized (this) {
checkDuplicateName(name);
AbstractChannelHandlerContext newCtx = new DefaultChannelHandlerContext(this, group, name, handler);
addLast0(name, newCtx);
}
return this;
}
private void addLast0(final String name, AbstractChannelHandlerContext newCtx) {
checkMultiplicity(newCtx);
AbstractChannelHandlerContext prev = tail.prev;
newCtx.prev = prev;
newCtx.next = tail;
prev.next = newCtx;
tail.prev = newCtx;
name2ctx.put(name, newCtx);
callHandlerAdded(newCtx);
}
handler 的执行
netty中使用nio 中 selector也就是linux的epoll系统调用,来实现IO的多路复用。
channel监听 SelectionKey.OP_READ | SelectionKey.OP_ACCEPT 事件,而后会调用channel中构造的内部类 nioUafe 的read方法 。unsafe.read(); 而后通过channel中pipeline串联整个msg的消息处理。核心是Context中的 fireChannelRead,因为每个handler都封装到一个Context中,通过如下的方法
//DefaultChannelHandlerContext
@Override
public ChannelHandlerContext fireChannelRead(final Object msg) {
if (msg == null) {
throw new NullPointerException("msg");
}
final AbstractChannelHandlerContext next = findContextInbound();//找到链表中下一个处理读的handler
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeChannelRead(msg);
} else {
executor.execute(new OneTimeTask() {
@Override
public void run() {
next.invokeChannelRead(msg);
}
});
}
return this;
}
private void invokeChannelRead(Object msg) {
try {
((ChannelInboundHandler) handler()).channelRead(this, msg); //执行handler的逻辑。
} catch (Throwable t) {
notifyHandlerException(t);
}
}
可以完成整个handler链的执行。
而在handler的执行流中,一般在中间的handler中,在执行channelRead的业务逻辑中,会将 context自身传到方法里,并会通过调用该context的 fireChannelRead 将处理后的msg 通过 查找下一个handler(因为context是一个双向链表),找到相应的handler(In or Out)中的业务逻辑,直到最后一个hanlder不在调用 ctx.fireChannelRead 而整个handler链可以分为几类,对于In 也就是收到消息的处理handlers来说,主要是分隔handler,Decoderhandler,业务逻辑handler。这三大类,是将收到的字节符按照设定的协议,执行完结束。
常用handle 分析
除了用户定义的业务逻辑的handler之外,netty也为我们提供了很多十分有用的handler。我们下面是以in类型的为主进行介绍,out逻辑。常用的有ByteToMessageDecoder 、SimpleChannelInboundHandler、ChannelInboundHandlerAdapter、SslHandler、DelimiterBasedFrameDecoder、FixedLengthFrameDecoder等,这些handler之间有继承的关系,在使用中我们可以直接用,有些也可以通过 继承 来扩展达到我们的业务功能。从基类开始介绍
1. ChannelInboundHandlerAdapter
相对来说比较底层的handler,可以直接继承,通常用在处理handler的register,unregister等事件,最为核心的就是继承 channelRead,通过之前的handler对msg的处理,直接可以转换为java类,执行业务。
2. SimpleChannelInboundHandler
继承自 ChannelInboundHandlerAdapter ,为我们做了一次转换,将msg先转换为java类,而我们可以通过继承,直接调用channelRead0,参数就是转换好的java类。使用十分简单。前提是前面的handler要先解码。通常放在最后一个handler。
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
boolean release = true;
try {
if (acceptInboundMessage(msg)) {
@SuppressWarnings("unchecked")
I imsg = (I) msg;
channelRead0(ctx, imsg); // 实现业务逻辑
} else {
release = false;
ctx.fireChannelRead(msg);
}
} finally {
if (autoRelease && release) {
ReferenceCountUtil.release(msg);
}
}
}
3. ByteToMessageDecoder
这个也是比较重要的handler,用户解码的基类handler,从名字也可猜出,其核心将接收的byte转换为用户定义的mssage, 用户需要 实现 decode方法,完成具体的转换。
如下是其channelRead源码
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof ByteBuf) {
RecyclableArrayList out = RecyclableArrayList.newInstance();
try {
ByteBuf data = (ByteBuf) msg;
first = cumulation == null; //上一次是否有累积的字节
if (first) {
cumulation = data;
} else {
if (cumulation.writerIndex() > cumulation.maxCapacity() - data.readableBytes()
|| cumulation.refCnt() > 1) {
// Expand cumulation (by replace it) when either there is not more room in the buffer
// or if the refCnt is greater then 1 which may happen when the user use slice().retain() or
// duplicate().retain().
//
// See:
// - https://github.com/netty/netty/issues/2327
// - https://github.com/netty/netty/issues/1764
expandCumulation(ctx, data.readableBytes());//剩余的空间不足写下本次的数据,扩充累积区,
}
cumulation.writeBytes(data);//将本次读取的data附加到上一次剩余的数据中。
data.release();
}
callDecode(ctx, cumulation, out); //解码过程
} catch (DecoderException e) {
throw e;
} catch (Throwable t) {
throw new DecoderException(t);
} finally {
if (cumulation != null && !cumulation.isReadable()) {
cumulation.release(); //当累积区已经不可读了,释放。
cumulation = null;
}
int size = out.size();
decodeWasNull = size == 0;
for (int i = 0; i < size; i ++) {
ctx.fireChannelRead(out.get(i)); //执行下一个handler。
}
out.recycle();
}
} else {
ctx.fireChannelRead(msg);
}
}
主要完成两个事件
1)解码(byte 转成object)
过程相对简单,通过定义了一个 cumulation 的累积缓存区,用以保存本次没有处理完的buf,并等待下一个tcp包的到来。一起传递到decode方法解决执行,如此反复。解决了粘包的问题。不过注意一点,这个handler是非线程安全的,一个channle对应一个该handler。所以通常我们在加入到piepelie中都是重新new的。
而对于转换的逻辑来说,就需要根据逻辑,转换成相应的对象了。通过callDecode,将重新组合后的cumulation,进行解码。将解码后的信息加到out中,该方法会通过循环,每次解码后的out大小与解码前大小是否一致,以此来决定是否结束本次解码过程。因为一次callDecode可能会 携带多个msg。
2) 下一个handler
将转换后的信息传递到下一个handler, 通过ctx.fireChannelRead。上面已经分析handler执行链的过程。
4. DelimiterBasedFrameDecoder
继承自ByteToMessageDecoder,只需完成解码的工作,同样从名字看出,起到分隔的作用,就是将收到的字节以特殊的字符分隔。一般要指定最大长度,以及分隔符。超过最大长度抛异常。
一般用在如下,以换行符结尾,转化为string。
5. FixedLengthFrameDecoder
同上,解码成固定长度的字节。
protected Object decode(
@SuppressWarnings("UnusedParameters") ChannelHandlerContext ctx, ByteBuf in) throws Exception {
if (in.readableBytes() < frameLength) {
return null;
} else {
return in.readSlice(frameLength).retain();
}
}
handler组合
接下来我们给出几组常用的handler组合
1) 定义协议,decode为java对象,
channel.pipeline().addLast(new RpcDecoder(Response.class));
channel.pipeline().addLast(new RpcEncoder(Request.class));
channel.pipeline().addLast(handle);
这种在Decoder中需要用户去实现协议,最简单的过程如下,信息头部指定有效字节数,先读取头部长度。而后在读取相应长度的字节,反序列化。而复杂的可能设定magic、type、length等。
@Override
public void decode(ChannelHandlerContext ctx, ByteBuf in, List
2) 以换行符结尾分隔,转化为string。
// 以("\n")为结尾分割的 解码器
pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
// 字符串解码 和 编码
pipeline.addLast("decoder", new StringDecoder());
pipeline.addLast("encoder", new StringEncoder());
// 自己的逻辑Handler
pipeline.addLast("handler", new SelfHandler());
http://blog.csdn.net/langzi7758521/article/details/52712159