1 糟糕设计之一:消息格式“包头+数据+包尾”安全
与UDP不一样,TCP通讯属于流式通讯,没有消息边界,因此须要应用层自行对报文进行界定分离。实际项目1中,包头为{{两个字节,包尾为}}两个字节,例如{{t=123}}。其格式为:网络
开始边界+消息1+结束边界+开始边界+消息2+结束边界+开始边界+消息3+结束边界+....布局
因为TCP是安全的传输层协议,除非特别须要,应用层无需再作校验。消息边界只须要一个标识便可,基本格式为:编码
消息1+边界+消息2+边界+消息3+边界+...spa
不管从节约网络带宽,仍是从简化报文解析代码,第一种设计都是很是的愚蠢!操作系统
无独有偶,项目2中基于串口的通讯应用层协议也采用了这种设计格式。设计
当问其设计人员为什么如此设计时,说一直就是这么设计的,本身也不知道这么设计的缘由,还美滋滋地说一直没有什么问题,真想揍他一拳。开发
2 糟糕设计之二:用结构体代码而不是文本描述消息结构字符串
项目2中,根本无协议的描述文本,只有一个包含结构体定义的头文件供协议的使用者参考。编译器
通讯就会涉及到多个机器,因此通讯协议必需要能跨平台。而咱们知道
struct A
{
char x;
int y;
};
在不一样编译器,不一样平台,不一样编译选项下会有不一样的二进制布局。何况协议使用者也可能看不懂C系语言代码。更搞笑的是,头文件中居然没有强制结构体单字节对齐。
问到协议的设计者设计思路时,说咱们公司一直这样啊,一直没问题啊。之因此没有问题,是由于使用这个协议的全部机器都是同一CPU型号,同一开发环境,同一操做系统。
3 糟糕设计之三:传送二进制浮点数
浮点数的二进制格式并非只有一种,不一样平台采用不一样的方式存放。这要比大端小端的整数差异更加严重。因此跨平台传送二进制浮点数是很是不安全的。而在项目2中,消息中大量使用了二进制浮点数。
要传送浮点数,一般有两种解决方式:
文本化。也就是传送描述浮点数的字符串,咱们知道字符串是彻底跨平台的,尤为是在UTF-8这样全球统一字符编码的状况下。
转换为整数。例如1.2,能够用整数12代替,只是要规定单位为0.1便可。
4 糟糕设计之三:大量备用字段
项目二的消息结构体相似以下:
struct A
{
char name[16];
int age;
int spare1;
short spare2;
short spare3;
int spare4;
};
大量的备用字段充斥在结构体中。少许的备用字段能够理解,如此大量的后备力量,真是深远谋虑啊。真不知道协议使用者在看到spare时会不会吐。若是真的须要这么多备用字段,彻底能够从新定义一个消息结构了。
5 糟糕设计之四:照猫画虎的握手和校验
握手和校验是保证安全完整通讯的基本手段,可是其实现却很是不简单,看看TCP的实现代码就知道了,须要考虑各类异常状况。项目二中串口设备和主机之间照猫画虎地定义了一个握手协议。开机后 设备向主机一直发送AA,主机收到AA后向设备发送AA,设备收到AA后向主机发送55,主机收到55后向设备发送55。这个简单的握手存在不少问题,随便说几个:
彻底没有必要握手。通常的串口设备无需知道主机的工做状态,主机若是想了解设备状态,发个询问报文便可。
若是主机发送AA后程序退出,那么串口设备永远也等不到来自主机的55。
若是主机中途关掉,在运行时可能收到来自串口设备的AA,而此时的AA其实只是消息报文的一个字节,而不是握手信号。
只要仔细想一想,还有不少相似的状况须要处理。并且实际使用过程当中,确实发生了上面的状况,导致必须重启串口设备或主机。
仍是项目2中,基于UDP的应用层协议自行设计了校验。其实这也无可厚非,好比著名的tftp就是这样的协议。只是设计者考虑不周,各类问题频出,最终的结果是这些校验字段根本就没有实际使用,白白浪费了网络带宽。须要说明的是,这个协议的设计者仍是国内很大的一家公司,固然是国企,你懂的。