• Netty 4.0 中文文档


    一 处理基于流的传输(Scoket Buffer的一点附加说明)

    对于例如TCP/IP这种基于流的传输协议实现,接收到的数据会被存储在socket的接受缓冲区内。不幸的是,基于流的传输不是一个包队列而是一个字节队列。在一个开放的系统中,这意味着即使我们发送了两条消息分别包含在两个数据包里,接收方不会当作两条消息来对待,而是将其放在同一个字节独列中。因此,传输不能保证收到的消息与发送的消息一致。

    对于时间客户端的例子,一个32位的int数据量非常小,一般不会被分片(链路层限制一个package大小一般为1500字节),但是问题是它确实有可能被分成多片,分片的概率随着网络的繁忙而增加。最简单的解决办法就是增加一个内部的累加缓冲,等累计满4个字节时再向上提交数据。

    package io.netty.example.time;
    
    import java.util.Date;
    
    public class TimeClientHandler extends ChannelInboundHandlerAdapter {
        private ByteBuf buf;
    
        @Override
        public void handlerAdded(ChannelHandlerContext ctx) {
            buf = ctx.alloc().buffer(4); // (1)
        }
    
        @Override
        public void handlerRemoved(ChannelHandlerContext ctx) {
            buf.release(); // (1)
            buf = null;
        }
    
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            ByteBuf m = (ByteBuf) msg;
            buf.writeBytes(m); // (2)
            m.release();
    
            if (buf.readableBytes() >= 4) { // (3)
                long currentTimeMillis = (buf.readInt() - 2208988800L) * 1000L;
                System.out.println(new Date(currentTimeMillis));
                ctx.close();
            }
        }
    
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            cause.printStackTrace();
            ctx.close();
        }
    }

    (1)ChannelHandler有两个存活期方法:handlerAdded()和handlerRemoved(),这两个方法允许我们自己构造一个初始化任务或结束任务。

    (2)首先,所有的接受的数据先放到累计缓存里。

    (3)然后,handler必须检查是否有了足够的data,如在本例中须足够4个字节,然后执行实际的业务逻辑。若数据不足,当更多的数据到达时,netty会再次执行channelReade()方法直到累计到4个字节。

    第二个解决办法

    当字段便多时,第一种解决方案会变得非常复杂且不可维护,可以通过向ChannelPipeline中增加多个ChannelHandler的方法,将一个大的ChannelHandler分解成多个模块来降低应用的复杂性。例如,

    package io.netty.example.time;
    
    public class TimeDecoder extends ByteToMessageDecoder { // (1)
        @Override
        protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) { // (2)
            if (in.readableBytes() < 4) {
                return; // (3)
            }
    
            out.add(in.readBytes(4)); // (4)
        }
    }

    (1)ByteToMessageDecoder是一ChannelInboundHandler的实现类,可以非常容易的处理分片问题。

    (2)当新的数据到达时,ByteToMessageDecoder讲数据存储在一个内在的累积buffer中,调用decode()方法进行处理

    (3)decode()根据接收到的字节大小进行判定,若满4个字节则增加一个对象到list。

    (4)如果decode()增加了个一个out的对象,意味着decoder编码成功。ByteToMessageDecoder会丢弃累积buffer中已经读过的部分。若out.add(null),decoder即停止。

    由于ChannlePipeline中增加了一个handler,因此我们必须修改ChannelInitializer为:

    b.handler(new ChannelInitializer<SocketChannel>() {
        @Override
        public void initChannel(SocketChannel ch) throws Exception {
            ch.pipeline().addLast(new TimeDecoder(), new TimeClientHandler());
        }
    });

    如果你是个冒险者,你可能会尝试ReplayingDecoder。该handler进一步简化了decoder。

    public class TimeDecoder extends ReplayingDecoder<VoidEnum> {
        @Override
        protected void decode(
                ChannelHandlerContext ctx, ByteBuf in, List<Object> out, VoidEnum state) {
            out.add(in.readBytes(4));
        }
    }

    另外关于decode的例子可以参考一下两个包

    利用POJO代替ByteBuf

      目前我们看到的例子均是使用ByteBuf作为协议消息的数据结构。在这一节,我们将使用POJO来代替ByteBuf来改善Time协议的客户端和服务器。

      使用POJO的有点是十分明显的,通过分离出解析ByteBuf中数据的代码,handler会变得更加可维护和重用。在Time的客户端和服务器的例子中,我们只读32字节的integer且这并不是一个主要的直接应用ByteBuf的案例。然而,你会发现当你实现一个真正的协议时,做这样的分离是十分必要的。

    首先,我们先定义一个新类型,UnixTime

    package io.netty.example.time;
    
    import java.util.Date;
    
    public class UnixTime {
    
        private final int value;
    
        public UnixTime() {
            this((int) (System.currentTimeMillis() / 1000L + 2208988800L));
        }
    
        public UnixTime(int value) {
            this.value = value;
        }
    
        public int value() {
            return value;
        }
    
        @Override
        public String toString() {
            return new Date((value() - 2208988800L) * 1000L).toString();
        }
    }

    我们重新编写TimeDecoder来禅师一个UnixTIme。

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
        if (in.readableBytes() < 4) {
            return;
        }
    
        out.add(new UnixTime(in.readInt()));
    }

    然后我们更新decoder,TimeClientHandler就不再使用ByteBuf了

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        UnixTime m = (UnixTime) msg;
        System.out.println(m);
        ctx.close();
    }

    是不是非常的简洁优雅。Server端同样如此。首先更新ServerHandler

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        ChannelFuture f = ctx.writeAndFlush(new UnixTime());
        f.addListener(ChannelFutureListener.CLOSE);
    }

    现在需要编写解码部分,encoder是ChannelOutbountHandler的实现类,将UnixTIme转化成下层的ByteBuf,编写encoder要比编写decoder简单的多,因为此时不必考虑tcp包分片的问题。

    package io.netty.example.time;
    
    public class TimeEncoder extends ChannelOutboundHandlerAdapter {
        @Override
        public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
            UnixTime m = (UnixTime) msg;
            ByteBuf encoded = ctx.alloc().buffer(4);
            encoded.writeInt(m.value());
            ctx.write(encoded, promise); // (1)
        }
    }

    (1)这一行中有些十分重要的内容

      首先,我们将原始的ChannelPromis找原来的样子传输,以保证Netty在写入链路时能够正确的标记成功或失败。

      其次,并不调用ctx.flush().handler有一个默认的flush方法,若想每次写都flush则须:ctx.write(encoded,false,promise);或ctx.writeAndFlush(encode,promise);

  • 相关阅读:
    8.18 二讲背包问题之完全背包
    8.18 动态规划——背包问题之01背包
    8.17 动态规划——书的抄写
    7.25 二分查找模板
    7.19 股票问题
    7.12 枚举-Jam的计数法
    7.12 递归——双色hanoi塔问题
    7.11 NOIP2007普及组第3题 守望者的逃离
    高数之泰勒公式
    数据结构_线性表之链表(1)
  • 原文地址:https://www.cnblogs.com/legendary/p/3641282.html
Copyright © 2020-2023  润新知