Netty 4 常用 handler 分析

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

这里我们就要分析下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 out) throws Exception {
		int HEAD_LENGTH=4;
        if (in.readableBytes() < HEAD_LENGTH) {
            return;
        }
        in.markReaderIndex();               
        int dataLength = in.readInt();       
        if (dataLength < 0) { 				 
            ctx.close();
        }
        if (in.readableBytes() < dataLength) { 
            in.resetReaderIndex();
            return;
        }
        byte[] body = new byte[dataLength]; 
        in.readBytes(body);  				      
        Object obj=SerializeUtils.Deserialize(body);//通过定义序列化工具将字节数组转换为指定类的实例
        out.add(obj);
	}
 
   

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

转载于:https://my.oschina.net/ovirtKg/blog/796219

你可能感兴趣的:(Netty 4 常用 handler 分析)