我是靠谱客的博主 怕孤独海燕,最近开发中收集的这篇文章主要介绍NIO基于长度域的报文在Netty下的解码,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

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, 基于长度域的报文在Netty下的解码

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

假设原报文内容是

x=1111,y=2222,z=3333 

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

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

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

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

out.write("00000020".getBytes());//字符串00000020转化为字节
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);
    }
}

转载于:https://my.oschina.net/wangxu3655/blog/3066594

最后

以上就是怕孤独海燕为你收集整理的NIO基于长度域的报文在Netty下的解码的全部内容,希望文章能够帮你解决NIO基于长度域的报文在Netty下的解码所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(45)

评论列表共有 0 条评论

立即
投稿
返回
顶部