目前所在的项目中需要与电力设备的采集终端进行通讯.
相关的协议为376.1,根据规划及相关的参考数据,最终决定使用netty作为scoket框架.
解决相关的高并发问题.
在实现的过程中,由于硬件端不可控,可能是很多的家的设备,且实际的网络情况不是特别稳定。所以兼容性尤其显得重要。
这里就需要解决netty的自定义协议的粘包拆包。
TCP粘包/拆包解决办法
1-定长消息,例如每个报文长度固定,不够补空格
2-使用回车换行符分割,在包尾加上分割符,例如Ftp协议
3-消息分割,头为长度(消息总长度或消息体长度),通常头用一个int32表示
4-复杂的应用层协议
最终的解决代码附上:
1. 解码器继承 ByteToMessageDecoder
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception { // 可读长度必须大于基本长度 if (byteBuf.readableBytes() > HEADER_SIZE) { //超过最大长度 if (byteBuf.readableBytes() > 16391) { byteBuf.skipBytes(byteBuf.readableBytes()); } //寻找包头 while (true){ //记录数据包开始的指针位置 byteBuf.markReaderIndex(); int head=byteBuf.readByte()& 0xFF; //判断是否是属于包头 if(head == HEAD){ break; } //不是包头 //指针复位 byteBuf.resetReaderIndex(); // 缓存指针向后移动一个字节 byteBuf.readByte(); //判断当前缓存是否依然满足一条指令的最小长度 if(byteBuf.readableBytes() < HEADER_SIZE){ return; } } //成功找到包头 byte lengthA = byteBuf.readByte(); byte lengthB = byteBuf.readByte(); byte lengthC = byteBuf.readByte(); byte lengthD = byteBuf.readByte(); //比较是否相等 if(lengthC==lengthA&&lengthD==lengthB){ int lenB= lengthB & 0xFF; int lenA= lengthA & 0xFF; //颠倒 String lentCode=Integer.toHexString(lenB)+Integer.toHexString(lenA); //转换为二进制 String binLen=Integer.toBinaryString(Integer.valueOf(lentCode,16)); //转换为十进制 int msgLength=Integer.valueOf(binLen.substring(0,binLen.length()-2),2)+8; //复位到最后一次标记的地方 byteBuf.resetReaderIndex(); //判断当前数据是否已经到齐 if(byteBuf.readableBytes() < msgLength){ //重新读取 //等待消息完成 System.out.println(new Date()+" "+Thread.currentThread().getName()+" 长度不对 当前长度为: " + byteBuf.readableBytes()+" 需求长度为:"+msgLength); return; } //读取消息内容 byte[] bytes = new byte[msgLength]; byteBuf.readBytes(bytes); //构造对象 list.add( new Message(bytes)); } }
//不是包头
//指针复位
byteBuf.resetReaderIndex();
// 缓存指针向后移动一个字节
byteBuf.readByte();
缓存指针向后移动一个字节这一步是否有必要?请指点下
这里其实需要解决的问题是,当读取到的数据不是头HEAD 之后,这个时候恢复到之前的开始读的地方,byteBuf.markReaderIndex();
通过byteBuf.resetReaderIndex(); 这里来恢复到位置,然后通过byteBuf.readByte();进行废弃操作,也就是这一位是无效位。
下一句的 byteBuf.readableBytes() 获取长度 是为了确认数据长度是否达到可解析的状态,如果小于最低长度。
当然如果不进行 复位 读取一个byte ,这里就需要保证你数据无法达到解析长度时(这个时候,读取的不会再恢复了。等同丢弃),你之前的读取的数据是能被丢弃的。
这句是做什么用的
//超过最大长度
if (byteBuf.readableBytes() > 16391) {
byteBuf.skipBytes(byteBuf.readableBytes());
}
如果你待解析的报文长度大于你最大报文的长度,你是很难知道哪一段是你需要的内容,常规都是选择放弃,通过补偿(重发等)方式解决。