信息系统实践手记8-两模块通讯的一些事
说明:
正文:
- 信息系统实践手记系列是系笔者在平时研发中先后遇到的大小的问题,其中比较典型的内容加以收集和分享。
- 信息系统实践手记目录:博客园(或查看源码的README.MD文件)
摘要:
- 此文描述了两个模块通信方面的内容。
正文
在信息系统开发和对接的过程中,免不了要遇到两个模块之间的消息互通,即便都是自己开发的2个模块互通,或者自己模块和第三方某个模块通讯。
这里尽量不涉及具体技术(这个大家自己找资料看书),主要是掰扯一下会涉及到的一些思考和情况。
1. 对接协议的2个层次:
- 两个代码实体(简称模块)互相需要通讯,往往在不同的主机(PC/IP)上,也偶有在同一个主机上的,TCP/IP协议已经屏蔽了物理机的差异,通过IP/PORT来区分资源,区分模块或功能对等实体。回过来,2个模块要通讯,2端代码要交互信息(数据/消息/信令/码流/结构/随便怎么称呼),参照目前互联网的TCP/IP协议或其他奇怪协议,基本分为两种层次:
- A:物理实现层(对于TCP/IP中的物理层,链路层等)的比特流,二进制,高低电平,网卡,网络设备等支持这个协议。比如3伏特/12伏特表示0或1等等,这是电气特性;可靠性依赖于硬件的工作特性,电气特性,以及纠错手段(一些数学和工程方法)。
- B:高层(统一叫高层,就是这个意思),基于二进制bit层次上的有含义的层次,字节流层次,byte(一般是8bit=1byte),而byte就对应了byte编码规范,比如ASCII或UTF-8,这样一串bytes就有逻辑含义了(26个英文字母及大小写,自然数10个数字,特殊符号,回车换行,加减乘除符号,不可见符号等等);其实网络上传递的都是字节流,而TCP/IP协议主要也是处理这个字节流从端到端的传送;可靠性依赖于下层A的bit流,并也可以增加适当的校验CRC等各种手段(也是一些数学方法)
- 所以虽然ISO/OSI的7层网络模型设计的很好,其实落实到实际实现的TCP/IP协议,也就主要分为以上A/B两个层次,A中还细分几个,B中也有细分但用的不多;主要理解这2个层次,从物理bit的可靠传输,架构其有逻辑含义的高层字节byte传输,这么个概念。后续主要就谈B层了,毕竟A层次目前已经稳定,除非量子力学颠覆当前的计算机原型(节点PC),然后自然而然的可能颠覆网络基础物理层A的电气特性,搞些先进的,这个我们就不谈了,还有5到10年就扑面而来了。
这里只谈B层;
2. TCP/IP协议主要分为TCP/UDP2种:
- TCP/IP协议分为主要的2种,UDP/TCP:
- TCP:面向连接(在网络基础层A上维护的逻辑上的连接通道) 的有状态的字节流通讯,保证数据包的按序及正确传送,有状态,所以能错误重传,并维护连接通道;但数据报的缓存及发送依赖于“逻辑连接通道”两端模块程序(往往是OS的网络驱动,网卡的驱动程序等)自主决定。所以会产生粘包及粘包如何分割的问题,这是TCP经典问题,请另查资料(或看netty官网的userguide入门章节,有比较简单经典的描述);
- 场景:需要可靠连接,直接拿来用的场景,牺牲一定的效率;用的较广泛;
- UDP:无连接,不保证数据包按照发送顺序到达目的地,甚至不保证能到达目的地;它其实允许2端模块自己通过高层协议来组织,数据包的顺序检查,丢包重传,及状态流转等;等于自己实现一个小小的简单TCP;
- 场景: 网络环境表稳定可靠,对数据正确性及顺序无特别严格要求,效率较高;比如局域网播放视频等;
- 说明:随着网络硬件性能,模块所在节点PC或服务器的性能提升,也许大家觉得即使看视频也用TCP来的更方便和效果好,那也是可以的,这无一个定论,看具体情况和项目方案的需求和实施情况而定
- TCP:面向连接(在网络基础层A上维护的逻辑上的连接通道) 的有状态的字节流通讯,保证数据包的按序及正确传送,有状态,所以能错误重传,并维护连接通道;但数据报的缓存及发送依赖于“逻辑连接通道”两端模块程序(往往是OS的网络驱动,网卡的驱动程序等)自主决定。所以会产生粘包及粘包如何分割的问题,这是TCP经典问题,请另查资料(或看netty官网的userguide入门章节,有比较简单经典的描述);
4. 针对TCP的几种使用方法
- TCP比较好用,但是有个特点就是粘包。虽然它是面向连接,有状态维护管理,丢包重传,且保证次序。但是两端节点上的庄模块的网络驱动底层库,各自有算法,缓存cache和策略,效率,切分包的大小(MTU)等都不同,导致接收方得到的包只在整体字节流bytes上是一致的,但无法知道发送方每次send给底层的byte字节是如何划分的。这就是经典的粘包及切分问题;一般有如下几个方法来处理;
-
方法1:定长字段法;
- 这样就不怕粘包了,发送端只管发,接收端从接受缓存不断轮询,如果满了一定长度length后,就读入;这里当然假设TCP是有序,丢包会重传的。
- 优点:实现简单;逻辑也简单;
- 缺点:一旦TCP传输有错(网络不好,概率偶发,协议栈缺陷等,总会错误),则整体就会偏移,数据错位就会错误;
- 改进:根据其缺点,可在定长中增加特殊头标记,比如定长length=64bytes,其中开始标记flag=\x0C等,当满足两者,就解码为逻辑内容,内容正确就用,内容错误就丢弃,并找到下一个有效的定长数据片(且以flag开头);这其实已经有一点TLV的雏形了。
-
方法2:分隔符方式(变长字段法);
- 和定长字段法相对的,就是通过一个分隔符来区分前后两端内容,则没断长度可变化,分隔符不可重复,同样能解决粘包问题;
- 优点:实现简单,逻辑较简单,比较可靠;
- 缺点:要保证分隔符是payload(有效载荷,即实际负载和传输的数据)中不能包含的!而且算法效率低下,需要每个byte都检查是否为分隔符;
- 改进:如果能配合定长的数据片,那其实等价于加一个flag头。
-
方法3:TLV方式(经典高效):
- TLV是比较经典的方式,用的相对较多。TLV(一般指Type-Length-Value)。它的协议一般会约定一个head头结构,包含flag和length,flag用来找到TLV的头(是个标记字节,不必唯一,但不能很多),找到flag后,就根据协议约定解析head头结构,解析失败则继续查找flag;解析成功后,就能获得head+body的整体长度length,从而解析得到body的数据;在body中通过一组组不定长的(length-value)结构,一个长度一个value值来承载payload,但length字段的自身长度是固定的,一般为1都4个字节之间,看TLV整体的协议定义;body的length和head的中length的自身长度也不必要一定一致。另外,这个type是类型的含义,是TLV协议手册的约定,一般会说明有K组<length,value>,每组是int,long,double,bytes[],等等;
- 优点:高效的通过length字段获取value,不用每个字节都检查;且支持不同的数据结构;
- 缺点:实现比较复杂,两边TLV协议手册显然比前2种方法的描述要详细;
- 改进:可以通过诸如JAVA的反射或其他方法,来将TLV协议通过“配置文件”的方式,让程序自动根据清晰明了容易维护的配置文件来生成编解码程序(CODEC)。
-
4. 针对TCP心跳的一些说明
TCP协议内容较多,有好几本大书,但核心内容,网上也转载很多,这里只提一些内容。
TCP如果不在初始化socket的时候给予超时timeout设定(看tcp类库支持哪些超时,一般如java io/NIO支持连接超时,传输超时等),那么默认就是2小时(120分钟),在120分钟内TCP协议内部自己有心跳维持socket连接通道,
超过120分钟,则按照状态机变化,两端都开始拆除TCP连接。当然,两端庄模块开发的如果不好,这就会导致对端有大量端口TIME_WAIT等等TCP状态机不能顺利,快速,高效的拆除并被复用,导致资源消耗,甚至耗光65535个port口。
除了TCP自身的超时,或者网络lib库能力范围内给予的连接超时,传输超时(闲置idle超时)外,桩模块可以人为的在逻辑层添加自己的超时和心跳机制。举例如下:
(1)收到PING,就发PONG,收到PONG,不响应;这是对等心跳探测包,有发起者探测对方是否活着alive;
(2)两边约定心跳间隔时间,比如每120秒发送心跳包(比如“HB”2个字符,或某种字节等),或者约定只有server发给client,不用反向;
特别说明:这是高层逻辑的心跳,应用层自己使用的,而且其实现根据前述3种使用方法而不同,比如你用TLV,那么也需要额外定义一个“心跳包”的协议;
5. 用哪些类库?
一般开发桩模块的时候,两端约定好传输协议,比如TLV,那么就各自开发。你用熟悉的语言比如java,而且会进一步使用现成的框架,比如netty(异步io框架),
这样你就不用赤裸的使用java的nio自己一步步处理,不用重复发明轮子。netty是一个非常好用的库,开发者同时也开发了mina,两个库有点类似,各有千秋和侧重。
详见我整理的[ITIS-资料集合贴]中的介绍和电子书;
6. 对接协议的的安全性问题
TCP是有连接,有状态,有序,丢包重传的,所以他有基础的正确性保证。但TCP完全会受到重传攻击,或侦听窃听,或篡改数据包等破坏方式。而且TCP保证的是网络层的字节流的按序正确到达,不保证上层逻辑(应用层)认为的数据是百分百正确的。所以,有如下建议:
(1)可以根据需求,对自己的数据进行编码,压缩,加密;
(2)可以参考一些开源的密钥,加密算法,认证,证书等协议的使用;
(3)曾经这样弄过,给payload转为base64,并且做了MD5摘要,传到对端MD5用来验证数据对不对,也可作为异步反馈的key值。
总结,总是TCP协议丰富多彩,如果熟悉一些框架和常用手段,再加上一些诸如MD5,加密压缩算法等,完全可以自己搭建和实现不同的传输协议,满足不同情况的要求,在两个对端桩模块之间实现数据的传递。