zoukankan      html  css  js  c++  java
  • Netty 对通讯协议结构设计的启发和总结

    Netty 通讯协议结构设计的总结

    key words: 通信,协议,结构设计,netty,解码器,LengthFieldBasedFrameDecoder

    原创

    包含与机器/设备的通讯协议结构的设计,安全性,数据有效性的设计思路记录

    通讯协议结构选择

    按照解决TCP粘包的解决方案的协议设计思路,大部分情况也就是:

    1. 定长消息,每个报文固定长度,不够补0或其他
    2. 用特殊字符/字节做分割符,遇到分隔符拆包
    3. 不定长报文,包头带长度,以长度字节为准进行消息分割

    每种处理方式都有不同的适用场景(例如 方法2适合文本传输过程中的拆包,却不适合byte[]数据的拆包),方法1,2,3在netty里面得到了很好的支持,具体可以见详见netty 在TCP粘包问题处理这篇文章

    对于物联通讯来说,传输是最佳数据类型,所以方法3是比较合适的,这就要求通讯协议在设计时,需要把报文长度放在最前面,下面看看netty自带的基于包头不定长的解码器,能省去自己解决粘包的时间,把关注点放到业务数据的处理上

    基于包头不固定长度的解码器:LengthFieldBasedFrameDecoder

    LengthFieldBasedFrameDecoder参数说明

    • maxFrameLength:解码的帧的最大长度
    • lengthFieldOffset:长度属性的起始位(偏移位),包中存放有整个大数据包长度的字节,这段字节的起始位置
    • lengthFieldLength:长度属性的长度,即存放整个大数据包长度的字节所占的长度
    • lengthAdjustmen:长度调节值,在总长被定义为包含包头长度时,修正信息长度。
    • initialBytesToStrip:跳过的字节数,根据需要我们跳过lengthFieldLength个字节,以便接收端直接接受到不含“长度属性”的内容
    • failFast :为true,当frame长度超过maxFrameLength时立即报TooLongFrameException异常,为false,读取完整个帧再报异常

    有了这个解码器,就很轻松完成拆包工作,拆出来的业务数据,再交由下一个decoder handler处理

    那么封包呢?封包也不用自己加长度,直接在ChannelPipeline的最后加上LengthFieldPrepender 编码器

    LengthFieldPrepender 编码器

    参数说明:

    • lengthFieldLength:长度属性的字节长度
    • lengthIncludesLengthFieldLength:false,长度字节不算在总长度中,true,算到总长度中

    配合使用LengthFieldPrepender,很容易就完成了,这样在flush前,netty自动会为报文加上一个length。

    需要注意的是,在业务处理器里面要响应write时,请用pipeline.write,如果直接用ctx.write,最后报文就不会加长度,因为不会进入到LengthFieldPrepender编码器中去

    示例代码:

    @Component("MyChannelInit")
    public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {
    
    	@Override
    	protected void initChannel(SocketChannel channel) throws Exception {
    		ChannelPipeline pipeline = channel.pipeline();
    		//pipeline.addLast(new LoggingHandler(LogLevel.INFO));
    		pipeline.addLast("frameDecode",new LengthFieldBasedFrameDecoder(1024,0,2,-2,2));
    		pipeline.addLast("decoder", new MyDataDecoder());//in 1
    		pipeline.addLast("handler", new MyInboundHandler());//in 2
    
    		pipeline.addLast(new IdleStateHandler(40, 0, 0));//out3
    		//pipeline.addLast("encoder", new DataEncoder());//out2
    		pipeline.addLast("frameEncode",new LengthFieldPrepender(2,true));//out1
    	}
    }
    

    收到设备发过来的数据,new LengthFieldBasedFrameDecoder(1024,0,2,-2,2):
    解码最大长度1024,起始偏移0,长度参数占字节数2,总长包含长度字节数,修正长度-2,传输到下一个Decoder时数据,跳过字节数2(也就是不带长度)

    发送到设备的数据,new LengthFieldPrepender(2,true),自动加上两个字节的长度

    通讯数据的保密性

    如果不想让人拦截有效数据和入侵破坏,通讯数据最好还是带上加密,最好还是动态的,不要说什么MD5 RSA DES之类的,终端硬件那边处理器性能没那么强悍,服务端这边也影响性能

    所以,哪怕就是个简单的加解密,也足够让90%的人知难而退(大部分人没事突突你干嘛(-__-)b)

    我们当时设计了token机制,token有时效性,每隔一段时间就需要从服务端获取新的token值,而数据解密的参数就跟token有关,这样就算拿着数据去分析规律,由于token值时不时变换,导致解密方法和解密参数都不一样,这样也许能起到部分作用

    因为token,一段时间就会失效,所以我们就有一条专门获取token的指令,为了保证协议的一致性(凡是数据传递都需要token),所以订了一个特殊的token和特殊的加解密,这样可以保证获取token获取能够通过程序的辨识

    所以数据结构的头 为: LENGTH + TOKEN

    通讯数据的有效性验证

    对于高要求的的数据传输,是否有必要进行校验,CRC16 CRC32校验应该就够了
    数据结构的头 :LENGTH + TOKEN + CRC + DATA

    后面的就是具体传输的数据的处理

  • 相关阅读:
    stmt.executeQuery不执行解决办法
    可变参数
    深度理解JVM
    JDBC 基础入门
    Flask中Mysql数据库的常见操作
    Flask与mysql数据库字段类型的区别以及基本用法
    Flask里面session的基本操作
    Flask里面的cookie的基本操作
    Flask表单(form)的应用
    Flask网页模板的入门
  • 原文地址:https://www.cnblogs.com/sloong/p/5050732.html
Copyright © 2011-2022 走看看