Netty 5.X 官方指南翻译版7

本文深入探讨了流数据处理中Socket缓冲区的使用与优化,通过Netty库实现了对流协议下数据包的正确重组。重点介绍了两个解决方案:增加内部缓冲区以确保完整接收数据,以及利用ByteToMessageDecoder简化分片数据的处理。通过实践案例,展示了如何在复杂协议中灵活应对数据分片问题。

处理流数据
关于socket缓冲区的小说明

一个基于流的传输,比如TCP/IP, 接收的数据存储在一个socket接收缓冲区里,
不幸的是,这个缓冲区不是基于报文而是基于字节。
这就意味着,即使你发送了2个消息作为2个独立的报文,操作系统不会认为它们是2个报文
而是一堆字节。
因此,没有机制保证你读的内容是远程程序所写的。
例如,让我们假设TCP/IP栈接受了3个数据包。

三个数据报文正如它们被发送的,因为流协议很有可能从程序来看,以片段的方式读取。
三个报文分离进入4个缓冲区。

因此,一个接收的部分,不考虑服务端还是客户端,应该重新组织数据被程序理解。

例如,下面的数据
四个缓冲区的数据成为3个报文。

第一个解决方案

让我们回到TIME客户端的例子,我们有同样的问题,

一个32位的整数非常小,很可能不会被分开。
尽管如此,问题是它可以被分片,分片会导致流量增加。
简单的解决方案就是:增加一个内部的渐增的缓冲区,等待知道4个字节都接收到了内部的缓冲区。

下面的是修改过的客户端的例子来解决问题。

 

package io.netty.example.time;

import java.util.Date;

public class TimeClientHandler extends ChannelHandlerAdapter {
    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.readUnsignedInt() - 2208988800L) * 1000L;
            System.out.println(new Date(currentTimeMillis));
            ctx.close();
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}
一个ChannelHandler 有两个生命周期监听方法:handlerAdded() and handlerRemoved().
你可以建立一个强制性的初始化任务只要它不耗费很长时间。
第一:所有接收的数据应该都进入缓冲区。
那个时候,handler必须检查是否buf有足够的数据,4个字节这里,处理真实的业务逻辑。
否则,netty将调用channelRead()方法当更多的数据到来,最终所有的4个数据都有了。

第2种解决方案
尽管第一种解决方案已经解决了问题,代码不怎么好看。

想象一下,一个更复杂的协议包含多个字段,比如各种长度字段。
你的ChannelHandler实现将会很复杂。
你可以增加多个ChannelHandler to a ChannelPipeline, 因此,你可以分裂一个整体ChannelHandler为多个部分。
来降低复杂度。例如,你可以分成两部分。

TimeDecoder处理分片,并且最初的简化版

幸运的,netty提供了一个可扩展得类帮助你读第一个数据。

 

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)
    }
}
ByteToMessageDecoder是一个ChannelHandler的实现,让你更简单的处理分片数据。

ByteToMessageDecoder 调用decode() 方法通过内部的缓冲区当新的数据来到时。


decode() 决定是否什么都不做当没有足够的数据
ByteToMessageDecoder将在有更多的数据来到时调用decode().
如果decode()增加一个对象到out,这就意味着,decoder 解码了一个消息,
ByteToMessageDecoder将忽略累积缓冲区的读部分,
记住:你没有必要decode多个消息,
 ByteToMessageDecoder将保持调用decode()方法直到它添加nothing到out.
 既然我们有另外的handler来加入到ChannelPipeline, 我们应该修改ChannelInitializer的实现。
b.handler(new ChannelInitializer<SocketChannel>() {
    @Override
    public void initChannel(SocketChannel ch) throws Exception {
        ch.pipeline().addLast(new TimeDecoder(), new TimeClientHandler());
    }
});
如果你是一个冒险的人,你也许想试试ReplayingDecoder,它简化了decoder.
你将需要查看API文档获取更多消息。

public class TimeDecoder extends ReplayingDecoder<Void> {
    @Override
    protected void decode(
            ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
        out.add(in.readBytes(4));
    }
}
另外,NETTY提供out-of-the-box decoders(可以让你来实现大多数协议,帮助你避免一些意外)
下面有更多信息
io.netty.example.factorial for a binary protocol, and
io.netty.example.telnet for a text line-based protocol.

翻译

okokok
理解okokok

 

最后推荐一篇好文章 http://www.dozer.cc/2014/12/netty-long-connection/

转载于:https://my.oschina.net/qiangzigege/blog/389087

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值