文档章节

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

老菜鸟0217
 老菜鸟0217
发布于 06/27 00:29
字数 1169
阅读 17
收藏 0

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);
    }
}

© 著作权归作者所有

老菜鸟0217
粉丝 47
博文 85
码字总数 43453
作品 0
海淀
程序员
私信 提问
rocketmq之源码分析netty流程及细节(七)

netty的标准设计流程为:编码,解码,检测,链接,其他handler,业务。按照这个流程将rocketmq的netty的实现流程进行细化。 编码 NettyEncoder 继承MessageToByteEncoder,netty的编码规范要...

wangshuaixin
05/30
0
0
【Netty】Netty实例开源项目

版权声明:本文为谙忆原创文章,转载请附上本文链接,谢谢。 https://blog.csdn.net/qq_26525215/article/details/81989644 Netty netty-not-sticky-pack-demo 项目地址 Netty 本篇博客讲解:...

谙忆
2018/08/23
0
0
Netty构建游戏服务器(一)--基本概念与原理

一,Netty是什么 1,Netty是由JBOSS提供的一个java开源框架。 2,Netty是JAR包,一般使用ALL-IN-ONE的JAR包就可以开发了。 3,Netty不需要运行在Tomcat这类服务器中,他是单独构建一个服务器...

安世博
2016/09/10
1K
0
Netty高性能架构的理解之道

Netty的简单介绍 Netty 是一个 NIO client-server(客户端服务器)框架,使用 Netty 可以快速开发网络应用,例如服务器和客户 端协议。 Netty 提供了一种新的方式来使开发网络应用程序,这种新...

烂猪皮
2018/05/04
0
0
Netty源码阅读入门实战(八)-解码

就像很多标准的架构模式都被各种专用框架所支持一样,常见的数据处理模式往往也是目标实现的很好的候选对象,它可以节省开发人员大量的时间和精力。 当然这也适应于本文的主题:编码和解码,或...

芥末无疆sss
2018/10/16
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Linux的基本命令

目录的操作命令(增删改查) 增: mkdir 目录名称; 查: ls 可以看到该目录下的所有的目录和文件 ls -a,可以看到该目录下的所有文件和目录,包括隐藏的 ls -l,可以看到该目录下的所有目录和...

凹凸凸
今天
2
0
在古老unix中增加新用户

Installing 4.3 BSD Quasijarus on SIMH 目标:要在4.3BSD中新增加用户dmr,指定目录/home/dmr,uid为10 gid=31(guest组,系统已建立) 4.3BSD还没有adduser或useradd 直接修改/etc/passwd...

wangxuwei
今天
2
0
Bootstrap(六)表单样式

基本样式 所有设置了 .form-control 类的 <input>、<textarea> 和 <select> 元素都将被默认设置宽度属性为 width: 100%;。 将 label 元素和前面提到的控件包裹在 .form-group 中可以获得最好...

ZeroBit
昨天
3
0
SSL 证书格式转换

SSL 证书格式转换 不同服务器情况下,需要不同的证书格式。 比如 pem 转 pfx。 pem在window 平台下可以导入,但是无法正常使用。 需要转换成pfx。 推荐在线转换工具,由中国数字证书网站提供...

DrChenXX
昨天
2
0
HAProxy

xx

Canaan_
昨天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部