TCP通訊之資料流粘包和拆包解決方法
產生資料粘包和拆包的原因這裡不再贅述。本文簡單介紹三種解決方法。
1. 定長協議
這種方式傳送方傳送的訊息都具有固定的長度,比如每個訊息長度都是1024個位元組。
如果傳送的訊息內容沒有1024個位元組長度,那麼訊息尾部補0填充直到長度達到1024個位元組,傳送的訊息內容不能超過1024個位元組。
接收方收到訊息後,每1024個位元組擷取為一段,認為是一個完整的訊息。
缺點:傳送的訊息不能大於固定長度;訊息尾部補充的0不易清除;浪費頻寬(如果訊息只有24個位元組,那麼每次需要發1024個位元組資料,浪費1000個位元組)
2.特殊字元分隔協議
這種方法使用一個特殊的字元,比如'\r'。
傳送方傳送訊息的尾部加上這個字元用來和下一個訊息區分開。
接收方每收到訊息時,首先將這一震訊息新增到快取佇列中,然後取出快取中全部訊息,判斷訊息中是否包含特殊字元,如果沒有說明還沒有收到一個完整的訊息,繼續等待,如果訊息中包含特殊字元,則截取出特殊字元前面的所有內容作為一個完整的訊息。
缺點:訊息體中不能出現特殊字元
編碼過程:
public class DelimiterBasedFrameEncoder : IEncoder { const char DELIMITER = '\r'; public byte[] Encoder(byte[] msg) {byte[] tail = BitConverter.GetBytes(DELIMITER); List<byte> sendMsg = new List<byte>(); sendMsg.AddRange(msg); sendMsg.AddRange(tail); return sendMsg.ToArray(); } }
解碼過程:
public class DelimiterBasedFrameDecoder : IDecoder {const char DELIMITER = '\r'; public byte[] Decoder(List<byte> msg) { byte[] tail = BitConverter.GetBytes(DELIMITER); int index = FindIndexDelimiter(msg.ToArray(), tail); if (index == -1) { return null; } byte[] result = new byte[index]; Array.Copy(msg.ToArray(), result, index); msg.RemoveRange(0, index + tail.Length ); return result; } int FindIndexDelimiter(byte[] maxSource, byte[] smallSource) { int index = maxSource.ToList().IndexOf(smallSource.First()); for (int i = 1; i < smallSource.Length; i++) { int id= maxSource.ToList().IndexOf(smallSource[i]); if (id - index != i) { return -1; } } return index; } }
3.變長協議
將訊息分為兩個部分,前面一部分叫做訊息頭,長度4個位元組,後面一部分叫做訊息體,長度為傳送訊息實際長度。
傳送方傳送訊息時,首先獲得訊息的長度數值L,然後將L轉換為4個位元組的陣列,將這個陣列加到訊息體前,形成一個新的位元組陣列傳送。
接收方收到訊息首先新增到快取佇列中,然後取出快取中全部訊息,判斷訊息的位元組陣列長度是否大4,如果小於則不做處理,等待後續的訊息;如果大於4則取出前4個位元組,將其轉換為整型數字叫做L,然後再判斷整個訊息長度減去4(訊息頭)是否大於L,
如果不大於則不做處理,繼續等待後續訊息;如果大於L,則從第5個位元組開始取出L個位元組作為一個完整的訊息,然後再將快取中前4+L個位元組刪除。
傳送方構造傳送內容:
接收方解析資料:
編碼過程:
public class LengthFieldBasedFrameEncoder : IEncoder { public byte[] Encoder(byte[] msg) { byte[] header = BitConverter.GetBytes(msg.Length); List<byte> sendMsg = new List<byte>(); sendMsg.AddRange(header); sendMsg.AddRange(msg); return sendMsg.ToArray(); } }
解碼過程:
public class LengthFieldBasedFrameDecoder : IDecoder { public byte[] Decoder(List<byte> msgCache) { byte[] result = null; int cacheLength = msgCache.Count; if (cacheLength < 4) { return result; } int bodyLength = BitConverter.ToInt32(msgCache.ToArray(), 0); cacheLength = msgCache.Count; if (cacheLength - 4 < bodyLength) { return result; } result = new byte[bodyLength]; result = msgCache.Skip(4).Take(bodyLength).ToArray(); msgCache.RemoveRange(0, 4 + bodyLength); return result; } }