• 35648

    文章

  • 23

    评论

  • 20

    友链

  • 最近新加了很多技术文章,大家多来逛逛吧~~~~
  • 喜欢这个网站的朋友可以加一下QQ群,我们一起交流技术。

NIO基于长度域的报文在Netty下的解码 原

欢迎来到阿八个人博客网站。本 阿八个人博客 网站提供最新的站长新闻,各种互联网资讯。 喜欢本站的朋友可以收藏本站,或者加QQ:我们大家一起来交流技术! URL链接:https://www.abboke.com/jsh/2019/0627/4089.html

>>>

1, 先复习一下粘包/拆包

1.1, 粘包/拆包的含义

TCP是个“流”协议, 并不了解上层业务数据的具体含义, 它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题。

假设客户端分别发送了两个数据包D1和D2给服务端,可能存在以下4种情况:

(a)服务端分两次读取到了两个独立的数据包,分别是D1和D2,没有粘包和拆包;

(b)服务端一次接收到了两个数据包,D1和D2粘合在一起,被称为TCP粘包;

(c)服务端分两次读取到了两个数据包,第一次读取到了完整的D1包和D2包的部分内容,第二次读取到了D2包的剩余内容,这被称为TCP拆包;

(d)服务端分两次读取到了两个数据包,第一次读取到了D1包的部分内容D1_1,第二次读取到了D1包的剩余内容D1_2和D2包的整包。

1.2, 粘包/拆包的处理

粘包/拆包的解决方法都是在报文结构上做处理,一般有3种方式: 定长报文、报文分隔符 、报文长度域

在NIO下, 框架Netty对于以上3种方式分别有自己的实现: FixedLengthFrameDecoder、DelimiterBasedFrameDecoder 、LengthFieldBasedFrameDecoder

 

2, NIO基于长度域的报文在Netty下的解码

对于“长度域”的值, 虽然底层都是以字节的形式发送, 但是在上层数据类型上, 长度域有‘字符串’和‘数字’类型两种.

假设原报文内容是

x=1111,y=2222,z=3333

原报文20个字节, 在对其添加长度域时, Java开发者可能见过下面这种结构, 尤其是在金融/银行开发中

00000020x=1111,y=2222,z=3333

上面报文的长度域就是‘字符串’类型, 对应的整型值为原报文的字节长度, 不足8位左边补0, 这个逻辑很通俗. 

对于‘字符串’类型的长度域, 发送方输出流的形式如下:

out.write("00000020".getBytes());//字符串转化为字节
out.write("x=1111,y=2222,z=3333".getBytes());

//后面再说NIO下我们该如何去解码字符串长度域的报文. 

接着说NIO下Netty自带的长度域解码器LengthFieldBasedFrameDecoder, 它支持的长度域就是‘数字’类型. 

对于‘数字’类型的长度域, 如果约定长度为4位, 则其报文结构大抵如下, 有点不太好刻画, 前面4个字节是数字20转化的字节数据.

[0,0,0,20]x=1111,y=2222,z=3333

发送方输出流的形式如下:

out.write(new byte[]{0,0,0,20});//数字20转换为4位字节数组
out.write("x=1111,y=2222,z=3333".getBytes());

然后Netty接收端直接使用LengthFieldBasedFrameDecoder就可以很方便解开

new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4)

如果客户端和服务端都是Netty开发, 大家默认的就是‘数字’类型的长度域, 发送端直接使用Netty自带的LengthFieldPrepender编码器就行了.

但是如果你作为数据接收方的NIO开发者, 而发送方是权威方, 它给的报文的长度域是‘字符串’类型时, 你该怎么处理?

这个时候, 我们可以基于Netty自定义一个解码器, 专门处理字符串类型的长度域, 实现如下:

package com.test;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.util.CharsetUtil;

import java.util.List;

/**
 * 字符串类型的长度域报文解码器
 *
 * @Author:tt
 * @Description:
 * @CreateTime:2019/6/26 下午11:30
 */
public class StringLengthFieldDecoder extends ByteToMessageDecoder {

    //长度域的字符串长度,比如:长度字段的长度为8,则报文有100个字节时,长度字段值为:00000100
    private int lengthFieldSize;

    public StringLengthFieldDecoder(int lengthFieldSize) {
        this.lengthFieldSize = lengthFieldSize;
    }

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        Object decoded = this.decode(ctx, in);
        if (decoded != null) {
            out.add(decoded);
        }
    }

    protected Object decode(ChannelHandlerContext ctx, ByteBuf in) {

        if (in.readableBytes() < lengthFieldSize) {
            return null;
        }

        //长度域的字符串值
        String lengthFieldValStr = in.readBytes(lengthFieldSize).toString(CharsetUtil.UTF_8);

        //原报文长度
        int frameLength = Integer.parseInt(lengthFieldValStr);

        if (in.readableBytes() < frameLength) {
            //这一步很重要,回退读索引
            in.readerIndex(in.readerIndex() - lengthFieldSize);
            return null;
        }

        return in.readBytes(frameLength);
    }
}

相关文章

暂住......别动,不想说点什么吗?
  • 全部评论(0
    还没有评论,快来抢沙发吧!