前言
公司项目是一款路灯控制系统,系统分为2大部分。上层服务端接受和下发指令,底层集中器接受和上报指令。服务端是java语言开发,底层集中器是c语言开发。
由于二者语言的数据类型存在差异,导致了一个隐藏较深的bug。
问题
近期项目要上线了,领导让增加一个单灯告警功能。该功能负责监听路灯是否故障。
在开发中,出现一些奇怪的现象。例如添加单灯档案时,如果少量添加,则可以顺利进行。如果批量添加,则集中器掉线断网重启。
排查
-
由于单灯告警功能在原有的.net服务端可以正常运行,我们选择对比.net服务端和java服务端的差异,在反复重复操作,对比指令的区别,结果是无功而返。二者产生的指令和下发的指令是相同的。
-
指令不存在问题,那么现在可能存在的问题就是java服务在运行时可能哪里产生了异常。这里不断增加可能出现故障的地方,输出日志。
最终定位到故障的地方
image.png
-
再次增加更多日志输出。检查发送该异常之前的信息。
image.png
-
其中-47引起我的注意,是不应该出现的。因为这里输出的是底层发送的二进制数据。不会出现负值。
-
继续查看这里输出的代码
if (logger.isDebugEnabled()){
byte[] dst = new byte[in.readableBytes()];
in.readBytes(dst);
logger.debug("cached="+Arrays.toString(dst));
in.readerIndex(beginIndex);
}
由于java的Byte 是有符号类型的。所以如果byte值超过127 则会变成负数。
- 这个 -47 的位置,根据集中器通信协议,是属于长度范围。于是查看长度解析的代码
byte length1 = in.readByte() ;
byte length2 = in.readByte();
long length = length2 << 8 | length1;
这里的含义是 长度用2个byte表示,集中器发送长度的时候,会先发低位。再发高位。
因为解析的时候, 需要进行 位操作。
- 根据异常的超标提示,应该是这里长度出现问题。输出一下解析的长度。是 -47
那么问题找到了,怎么会解析出 -47呢。
原因
这是因为集中器c语言和java语言的基本数据类型差异导致的。
- 在计算机中,可以区分正负的类型,称为有符类型(signed),无正负的类型(只有正值),称为无符类型。 (unsigned)
- 使用二制数中的最高位表示正负。
- 有符号和无符号存在范围不同。
这里以8位的byte为例。
在c语言中,byte可以是有符号,也可以无符号。集中器这里选择的是无符号的byte.
它可表示的数值范围 是 0-256
但是在java语言中,不存在无符号类型。因此 byte的范围是 -127 到 127
因此如果一个数字,在0-127之间。java和c语言的结果是相同的。不会出错。
如果这个数值超过127,例如集中器发送了一个 128 则java这里就会异常。
因为这个数值超出了byte的范围。
解决
既然是数据类型的范围不满足需要。这里选择比byte 大的范围来盛放数据。
- 修改代码如下:
//低位
int length1 = in.readByte() ;
//高位
int length2 = in.readByte() ;
long length = length2 << 8 | length1;
依旧不行。
- 继续修改
//低位
int length1 =in.readByte() & 0x000000FF;
//高位
int length2 =in.readByte() & 0x000000FF;
int length = length2 << 8 | length1;
依旧不行。
- 最终修改
//低位
int length1 = (int)dst[2] & 0x000000FF;
//高位
int length2 = (int)dst[3] & 0x000000FF;
int length = length2 << 8 | length1;
解释: 把有符号的 byte 提升成 int 类型,然后对这个 int 进行按位与操作,仅保留最后 8 个比特位。因为 Java 中的 byte 是有符号的,所以当一个 byte 的无符号值大于 127 的时候,表示符号的二进制位将被设置为 1(严格来说,这个不能算是符号位,因为在计算机中数字是按照补码方式编码的),对于 Java 来说,这个就是负数。当将负数数值对应的 byte 提升为 int 类型的时候,0 到 7 比特位将会被保留,8 到 31 比特位会被设置为 1。然后将其与 0x000000FF 进行按位与操作来擦除 8 到 31 比特位的 1。
- 最终可以简化
//低位
int length1 = (int)dst[2] & 0xFF;
//高位
int length2 = (int)dst[3] & 0xFF;
int length = length2 << 8 | length1;
网友评论