最近由于一個項目需要和單片機(jī)通信,和硬件工程師溝通好之后石蔗,大致確定協(xié)議為 :
消息頭部 + 消息長度 + 設(shè)備號 + 命令 + data + crc16
由于netty自帶的decoder有些不滿足這個格式畅形,所以自定義了一個decoder。
代碼如下
/**
* 消息格式為 消息頭部(1字節(jié)) + 消息長度(2字節(jié)) + 設(shè)備號(12字節(jié)) + 命令(2字節(jié)) + data(n字節(jié)) + crc16(2字節(jié))
*
* @author watermelon
* @time 2020/5/21
*/
public class SmartHomeDecoder extends ByteToMessageDecoder implements SmartHomeCodeC {
private final Logger LOG = LoggerFactory.getLogger(SmartHomeDecoder.class);
/**
* ByteBuf 超過這個值之后棍厌,會清除已讀區(qū)域
* 默認(rèn)不清除
*/
private int clearReadMaxLength;
/**
* 默認(rèn)構(gòu)造器,ByteBuf 可能會無限擴(kuò)容
* ByteBuf 超過1024之后敬肚,會清除已讀區(qū)域
*/
public SmartHomeDecoder() {
this(0);
}
/**
*
* ByteBuf 超過 clearReadMaxLength 之后束析,會清除已讀區(qū)域
*
* @param clearReadMaxLength
*/
public SmartHomeDecoder(int clearReadMaxLength) {
this(clearReadMaxLength);
}
private SmartHomeDecoder(int clearReadMaxLength) {
this.clearReadMaxLength = clearReadMaxLength;
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
Object decoded = this.decode(ctx, in);
if (decoded != null) {
out.add(decoded);
}
}
/**
* 解碼消息
*
* @param ctx
* @param in
* @throws Exception
*/
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
clearRead(in);
//消息小于接收的最小長度
if (in.readableBytes() < MSG_MIN_LENGTH) {
return null;
}
//記錄消息頭部位置
int beginIndex;
while (true) {
//獲取消息頭部位置
beginIndex = in.readerIndex();
//讀到消息頭部的時候,跳出循環(huán)
byte b = in.readByte();
if (b == HEADER) {
break;
}
//如果讀完了所有的數(shù)據(jù) 都沒有獲取到 消息頭部 則判定所有消息為無效消息弄慰,直接放棄掉
if (in.readableBytes() == 0) {
return null;
}
}
//消息長度
if (in.readableBytes() < MSG_LENGTH_LENGTH) {
//消息長度不夠,還原readerIndex到消息頭部的位置
in.readerIndex(beginIndex);
return null;
}
//獲取 消息長度
//長度 共兩個字節(jié) 所以將第一個左移8位
int length1 = in.readByte();
length1 = length1 << 8;
int length2 = in.readByte();
//最終的長度
length1 = length1 + length2;
//判斷數(shù)據(jù)包是否完整
if (in.readableBytes() < length1) {
//還原readerIndex到消息頭部的位置
in.readerIndex(beginIndex);
return null;
}
//讀取數(shù)據(jù)
byte[] data = new byte[length1];
in.readBytes(data);
//所有的數(shù)據(jù)
byte[] data1 = ConvertUtil.byteSplit(data, 0, data.length - CSC2_LENGTH);
//獲取數(shù)據(jù)對應(yīng)的 crc2 校驗碼
byte[] crc= ConvertUtil.crc(data1);
//獲取傳過來來的校驗碼
byte[] crc2 = ConvertUtil.byteSplit(data, data.length - CSC2_LENGTH, CSC2_LENGTH);
//比較,如果校驗不通過陆爽,就忽略這次消息
if (!Arrays.equals(crc, crc2)) {
LOG.debug("crc2校驗不通過");
return null;
}
//將得到的data 根據(jù)約定 轉(zhuǎn)換為實體
return new MessagePush().setReceiveEntity(new MessageDistributor(data1).distribute());
}
/**
* 清除 0 - readIndex 的數(shù)據(jù)扳缕,以免 ByteBuf 過大
* 如果每一次消息最后,都帶有一段解析不了的臟消息驴剔,或者有一段小于{@link #MSG_MIN_LENGTH} 的消息,這樣每次都會有未讀完的消息粥庄, 就可能導(dǎo)致 ByteBuf 無限擴(kuò)容
*
* @param in
*/
private void clearRead(ByteBuf in) {
if (clearReadMaxLength > 0 && in.writerIndex() > clearReadMaxLength) {
LOG.debug("byteBuf中留存的數(shù)據(jù)太大,自動清除已讀數(shù)據(jù)");
in.discardReadBytes();
}
}
}
整個解碼主要是檢索頭部飒赃,然后根據(jù)頭部之后的長度去讀取消息。
這里主要是處理了一下當(dāng)出現(xiàn)了粘包問題炒事,消息不完整的時候蔫慧,指針要回到頭部,等待下一次讀取完整消息姑躲。
以及合理的設(shè)置一個 clearReadMaxLength ,當(dāng)緩沖區(qū)過大時卖怜,清除掉已經(jīng)讀取的數(shù)據(jù)阐枣。