串口裝置可能單次發送完整封包,也可能在極短時間內分多次發送(如斷續傳輸)。需區分「單次完整封包」與「多次碎片封包」並分別處理。
單純依靠「時間間隔」判斷極不穩定(作業系統排程、緩衝區、傳輸延遲皆會干擾)。標準做法是在應用層協議中定義明確的幀結構:
[帧头 2 bytes] [长度 2 bytes] [命令 1 byte] [数据 N bytes] [校验 1 byte] [帧尾 2 bytes]
例:0xAA 0x55 0x00 0x0A 0x01 [data...] 0xXX 0x0D 0x0A
private readonly List<byte> _buffer = new List<byte>();
private readonly object _lock = new object();
private void Port_DataReceived(object sender, SerialDataReceivedEventArgs e) {
int count = serialPort.BytesToRead;
byte[] tmp = new byte[count];
serialPort.Read(tmp, 0, count);
lock (_lock) {
_buffer.AddRange(tmp);
}
// 通知處理執行緒
_dataEvent.Set();
}
private void ProcessLoop() {
while (_running) {
_dataEvent.WaitOne(50);
List<byte> frame;
lock (_lock) {
frame = TryExtractFrame(_buffer); // 從緩衝區提取一個完整幀
if (frame == null) continue;
_buffer.RemoveRange(0, frame.Count);
}
HandleFrame(frame); // 交給業務邏輯
}
}
private List<byte> TryExtractFrame(List<byte> buf) {
// 1. 尋找帧头
int start = buf.FindIndex(b => b == 0xAA);
if (start < 0) return null;
if (start > 0) { buf.RemoveRange(0, start); } // 丟棄雜訊
// 2. 長度檢查
if (buf.Count < 4) return null; // 至少 帧头+长度
int len = (buf[2] << 8) | buf[3]; // 假設大端序
int totalLen = 2 + 2 + 1 + len + 1 + 2; // 完整幀長度
if (buf.Count < totalLen) return null; // 資料未完整
// 3. 校驗帧尾、校驗和
if (buf[totalLen-2] != 0x0D || buf[totalLen-1] != 0x0A) {
buf.RemoveAt(0); // 帧头錯誤,移除一個字節重試
return null;
}
// TODO: 校驗和驗證
return buf.GetRange(0, totalLen);
}
若必須靠時間判斷,可設定「閒置超時」:收到資料後啟動 Timer (如 50ms),超時未再收到資料視為一個完整邏輯封包。但此法不可靠,僅作臨時方案。
「在協議中加入封包頭、長度、校驗」是處理串口資料分包/粘包的工業標準做法,請優先推動硬體端協議升級。