日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Socket通用TCP通信协议设计及实现(防止粘包,可移植,可靠)

發布時間:2024/7/23 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Socket通用TCP通信协议设计及实现(防止粘包,可移植,可靠) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Socket通用TCP通信協議設計及實現(防止粘包,可移植,可靠

?

引文

我們接收Socket字節流數據一般都會定義一個數據包協議。我們每次開發一個軟件的通信模塊時,盡管具體的數據內容是不盡相同的,但是大體上的框架,以及常用的一些函數比如轉碼,校驗等等都是相似甚至一樣的。所以我感覺設計一個通用的通信協議,可以在之后的開發中進行移植實現高效率的開發是很有必要的。另外,本協議結合我自己所了解的通信知識盡可能的提升了可靠性和移植性,可處理類似粘包這樣的問題。對于本文中可能存在的問題,歡迎各位大神多多指點。

?

報文設計

???????? 本報文的字段結構分為Hex編碼和BCD(8421)編碼兩種。由于BCD編碼的取值范圍其實是Hex編碼的真子集,也就是所謂的16進制編碼中的“ABCDEF”這六個字母對應的數值是在BCD編碼中無法取值的。所以我利用這個特點,將報文中的用于標識的不含實際數據的抽象字段用Hex編碼,且取值范圍在A~F之間。將反應實際數據的字段用BCD編碼。這樣,具有標識作用的字段與實際數據字段的取值是互不交叉的。這無形中就避免了很多出現的問題,增強了報文的可靠性。例如:我使用”0xFFFF”代表報文起始符,這個取值是不會在任何一個數據字段中出現的,應為它們是BCD編碼。也就是是說,字節流緩沖區中只要出現”0xFFFF”我們就可以判斷這個是一個數據包的開頭(我在實現在緩沖區中找尋數據包算法時還做了另外的控制,進行雙重保障)。

???????? 對于正文部分,我設計成了“標識符|數據”成對出現的形式。每個標識符用來指示后面出現的數據的含義,數據字段用于傳輸真實的數據。這種對的形式,增強了報文的移植性,在新的一次開發到來時,我們只要按需求定義好正文部分的“標識符|數據”對即可。另外,這種設計還增強了發送報文方的靈活性。標識符的存在使得各項數據可以按照任意的順序發送,沒有的數據也可以不發。

???????? 基于以上的這些考慮,我把報文設計成了如下形式:

?

通用報文協議

序號

名稱

編碼說明

?1

報文起始符

2字節Hex編碼? ??0xFFFF

?2

功能碼(報文類型)

2字節Hex編碼??? 0xD1D1

?3

密碼

4字節BCD編碼??? 00 00 00 01

?4

長度

2字節BCD編碼??? 正文實際長度

?5

標識符1

2字節Hex編碼?? 自定義數據標識符? 0xA001

?6

數據1

N字節BCD編碼? N根據實際情況自定義

?7

標識符2

2字節Hex編碼?? 自定義數據標識符? 0xA002

?8

數據2

N字節BCD編碼? N根據實際情況自定義

?...

?

?

報文終止符

2字節Hex編碼?? 0xEEEE

?

校驗碼

校驗碼前所有字節的CRC校驗,生成多項式:X16+X15+X2+1,高位字節在前,低位字節在后。

?

報文示例:

示例背景:發送報文通知遠程服務器第1號設備開關的當前狀態為開啟

需自定義正文部分,含兩個字段,設備編號和開關狀態

發送的字節數組:255 255 | 209209 | 0 0 0 1 | 0 6 | 160 1 | 1 | 160 2| 0 | 238 238 | 245 40 |

對應含義解釋:?? 起始符FFFF | 功能碼D1D1 | 密碼00 00 00 01 | 長度(正文)00 06|??? 標識符A001 | 數據 1 | 標識符A002 | 數據 0 | 報文終止符 EEEE | 校驗結果 |

?

粘包問題的解決

針對我的協議,我設計了一個緩沖區中找尋數據包算法,這兩者的配合完美的實現了防止粘包,過濾噪聲數據等類似的各種令人頭疼的問題。此算法思路來自博文點擊打開鏈接?

算法流程圖如下:


算法C#代碼具體實現:

/// <summary>/// 數據緩沖區/// </summary>public class DataBuffer{//字節緩沖區private List<byte> m_buffer = new List<byte>();#region 私有方法/// <summary>/// 尋找第一個報頭 (0xFFFF)/// </summary>/// <returns>返回報文起始符索引,沒找到返回-1</returns>private int findFirstDataHead(){int tempIndex=m_buffer.FindIndex(o => o == 0xFF);if (tempIndex == -1)return -1;if ((tempIndex + 1) < m_buffer.Count) //防止越界if (m_buffer[tempIndex + 1] != 0xFF)return -1;return tempIndex;}/// <summary>/// 尋找第一個報尾 (0xEEEE)/// </summary>/// <returns></returns>private int findFirstDataEnd(){int tempIndex = m_buffer.FindIndex(o => o == 0xEE);if (tempIndex == -1)return -1;if((tempIndex+1)<m_buffer.Count) //防止越界if (m_buffer[tempIndex + 1] != 0xEE)return -1;return tempIndex;}#endregion/// <summary>/// 在緩沖區中尋找完整合法的數據包/// </summary>/// <returns>找到返回數據包長度len,數據包范圍即為0~(len-1);未找到返回0</returns>public int Find(){if (m_buffer.Count == 0)return 0;int HeadIndex = findFirstDataHead();//查找報頭的位置if (HeadIndex == -1){//沒找到報頭m_buffer.Clear();return 0; }if (HeadIndex >= 1)//不為開頭移掉之前的字節m_buffer.RemoveRange(0, HeadIndex);int length = GetLength();if (length==0){//報文還未全部接收return 0;}int TailIndex = findFirstDataEnd(); //查找報尾的位置if (TailIndex == -1){return 0;}else if (TailIndex + 4 != length) //包尾與包長度不匹配{//退出前移除當前報頭m_buffer.RemoveRange(0, 2);return 0;}return length;}/// <summary>/// 包長度/// </summary>/// <returns></returns>public int GetLength(){//報文起始符 功能碼 密碼 正文長度 報文終止符 CRC校驗碼 這六個基礎結構占14字節//因此報文長度至少為14if (m_buffer.Count >= 14){int length = m_buffer[8] * 256 + m_buffer[9];//正文長度return length + 14;}return 0;}/// <summary>/// 提取數據/// </summary>public void Dequeue(byte[] buffer, int offset, int size){m_buffer.CopyTo(0, buffer, offset, size);m_buffer.RemoveRange(offset, size);}/// <summary>/// 隊列數據/// </summary>/// <param name="buffer"></param>public void Enqueue(byte[] buffer){m_buffer.AddRange(buffer);}}


調用示例:

private void receive(){while (true)//循環直至用戶主動終止線程{int len = Server.Available;if (len > 0){byte[] temp = new byte[len];Server.Receive(temp,len,SocketFlags.None);buffer.Enqueue(temp);while (buffer.Find()!=0) //while可處理同時接收到多個包的情況 {int length = buffer.GetLength();byte[] readBuffer = new byte[len];buffer.Dequeue(readBuffer, 0, length);//OnReceiveDataEx(readBuffer); //這里自己寫一個委托或方法就OK了,封裝收到一個完整數據包后的工作 //示例,這里簡單實用靜態屬性處理:DataPacketEx da = Statute.UnPackMessage(readBuffer);ComFun.receiveList.Add(da);}}Thread.Sleep(100);//這里需要根據實際的數據吞吐量合理選定線程掛起時間}} 其中DataPacketEx是封裝數據包正文部分的類,其中的屬性記錄了要發送的數據
使用時只需開啟一個線程,不斷的將收到的字節流數據加入緩沖區中。調用Find()方法找尋下一個數據包,如果該方法返回0,說明當前緩沖區中不存在數據包(數據尚未完整接收/存在錯誤數據,該方法可自行進行處理),如果返回一個正數n,則當前緩沖區中索引0-(n-1)的數據即為一個收到的完整的數據包。對其進行處理即可。


協議的實現

在實現協議前,首先我在自定義的TransCoding類中實現了幾個靜態方法用于Hex、BCD、string等之間的轉換。

/// <summary>/// 將十進制形式字符串轉換為BCD碼的形式/// </summary>/// <param name="str">十進制形式的待轉碼字符串,每個字符需為0~9的十進制數字</param>/// <returns></returns>public static byte[] BCDStrToByte(string str){#region 原方法//長度為奇數,隊首補0if (str.Length % 2 != 0){str = '0' + str;}byte[] bcd = new byte[str.Length / 2];for (int i = 0; i < str.Length / 2; i++){int index = i * 2;//計算BCD[index]處的字節byte high = (byte)(str[index] - 48); //高四位high = (byte)(high << 4);byte low = (byte)(str[index + 1] - 48); //低四位bcd[i] = (byte)(high | low);}return bcd;#endregion}/// <summary>/// 將字節數據轉化為16進制的字符串(注意:同樣適用與轉8421格式的BCD碼!!!!)/// </summary>/// <param name="hex"></param>/// <param name="index"></param>/// <returns></returns>public static string ByteToHexStr(byte[] hex, int index){string hexStr = "";if (index >= hex.Length || index < 0)throw new Exception("索引超出界限");for (int i = index; i < hex.Length; i++){if (Convert.ToInt16(hex[i]) >= 16){hexStr += Convert.ToString(hex[i], 16).ToUpper();}else{hexStr += "0" + Convert.ToString(hex[i], 16).ToUpper();}}return hexStr;}/// <summary>/// 將16進制字符串轉化為字節數據/// </summary>/// <param name="hexStr"></param>/// <returns></returns>public static byte[] HexStrToByte(string hexStr){if (hexStr.Trim().Length % 2 != 0){hexStr = "0" + hexStr;}byte[] hexByte = new byte[hexStr.Length / 2];for (int i = 0; i < hexByte.Length; i++){string hex = hexStr[i * 2].ToString(CultureInfo.InvariantCulture) + hexStr[i * 2 + 1].ToString(CultureInfo.InvariantCulture);hexByte[i] = byte.Parse(hex, NumberStyles.AllowHexSpecifier);}return hexByte;#region 使用Convert.ToByte轉換//長度為奇數,隊首補0,確保整數//if (str.Length % 2 != 0)//{// str = '0' + str;//}//string temp = "";//byte[] BCD = new byte[str.Length / 2];//for (int index = 0; index < str.Length; index += 2)//{// temp = str.Substring(index, 2);// BCD[index / 2] = Convert.ToByte(temp, 16);//}//return BCD;#endregion}
以下是協議的實現的兩個核心方法,裝包和解包

裝包方法將已有的具體的不同數據類型的數據轉換成byte字節流,以便進行socket通信

解包方法將socket接收到的完整數據包字節流解析成封裝數據包的類DataPacketEx

/// <summary>/// 構造向終端發送的消息(示例)/// </summary>/// <param name="data">記錄發送消息內容的數據包</param>/// <returns>發送的消息</returns>public byte[] BuildMessage(DataPacketEx data){List<byte> msg = new List<byte>(); //先用消息鏈表,提高效率//幀起始符byte[] tempS = TransCoding.HexStrToByte("FFFF");ComFun.bytePaste(msg, tempS);//功能碼tempS = TransCoding.HexStrToByte("D1D1");ComFun.bytePaste(msg, tempS);//密碼tempS = TransCoding.BCDStrToByte("00000001");ComFun.bytePaste(msg, tempS);//長度tempS = TransCoding.BCDStrToByte("0006");ComFun.bytePaste(msg, tempS);//開關設備編號標識符tempS = TransCoding.HexStrToByte("A001");ComFun.bytePaste(msg, tempS);//開關設備編號tempS = TransCoding.BCDStrToByte(data.ObjectID);ComFun.bytePaste(msg, tempS);//開/關標識符tempS = TransCoding.HexStrToByte("A002");ComFun.bytePaste(msg, tempS);//開/關tempS = TransCoding.BCDStrToByte(data.IsOpen);ComFun.bytePaste(msg, tempS);//報文終止符tempS = TransCoding.HexStrToByte("EEEE");ComFun.bytePaste(msg, tempS);//CRC校驗byte[] message = new byte[msg.Count];for (int i = 0; i < msg.Count; i++){message[i] = msg[i];}byte[] crc = new byte[2];Checksum.CalculateCrc16(message, out crc[0], out crc[1]);message = new byte[msg.Count + 2];for (int i = 0; i < msg.Count; i++){message[i] = msg[i];}message[message.Length - 2] = crc[0];message[message.Length - 1] = crc[1];return message;}/// <summary>/// 解包數據/// </summary>/// <param name="message">需要解包的數據</param>/// <returns>成功解析返回true,否則返回false </returns>public DataPacketEx UnPackMessage(byte[] message){//先校驗信息是否傳輸正確if (!CheckRespose(message))return null;//檢查密碼是否正確.(假設當前密碼為00 00 00 01,需在應用時根據實際情況解決)byte[] temp = new byte[4];temp[0] = message[4];temp[1] = message[5];temp[2] = message[6];temp[3] = message[7];if (TransCoding.ByteToHexStr(temp, 0) != "00000001")return null;DataPacketEx DataPacket = new DataPacketEx("", "", "");//獲取功能碼byte[] funType = new byte[2] { message[2], message[3] };string functionStr = TransCoding.ByteToHexStr(funType, 0);#region 具體解包過程,需根據實際情況修改int index = 10; //(當前索引指向第一個標識符)string tempStr="";switch (functionStr){case "D1D1":temp = new byte[2] { message[index], message[index + 1] };index = index + 2;tempStr = TransCoding.ByteToHexStr(temp, 0);while (tempStr != "EEEE"){switch (tempStr){//注意:每種標識符對應的數據長度是協議中自定義的case "A001"://開關設備編號temp = new byte[1] { message[index] };index = index + 1;tempStr = TransCoding.ByteToHexStr(temp, 0);DataPacket.ObjectID = tempStr;break;case "A002"://開or關(開:00 關:11)temp = new byte[1] { message[index] };index = index + 1;tempStr = TransCoding.ByteToHexStr(temp, 0);DataPacket.IsOpen = tempStr;break;//case "其他標識符":// //對應信息// break;}temp = new byte[2] { message[index], message[index + 1] };index = index + 2;tempStr = TransCoding.ByteToHexStr(temp, 0);}break;//case "其他功能碼":// //對應功能// break;}#endregionreturn DataPacket;}


對于通信可靠性的驗證

對此,我制作了兩個簡單的demo,一個服務器端,一個客戶端。

客戶端可想服務器端循環發送數據,其中以0.5的概率夾雜著隨機長度隨機取值的干擾數據,以此來判斷本協議在實際應用中的可行性。

服務器端負責循環接收并處理顯示收到的數據

最終的運行結果如下圖:



由運行結果可以看出,服務器端完美屏蔽掉了客戶端發出的錯誤數據,全部解析出了客戶端發送的實際數據。證明本協議可以解決類似粘包,傳錯等等類似的通訊中的棘手問題。當然,協議中如果有不完美的地方,希望各位大神指教。另外,上面的demo只是為了驗證協議所做,還存在一些零零碎碎的小bug。


以上就是通信協議的全部核心內容。

具體實現的代碼中可能包含一些并未給出的不太重要的類,并不影響理解。

具體的demo我上傳到了http://download.csdn.net/detail/u011583927/8653701?

畢竟認真總結了好久,所以設置了積分大家不要介意哈


總結

以上是生活随笔為你收集整理的Socket通用TCP通信协议设计及实现(防止粘包,可移植,可靠)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 亚洲一区二区三区免费 | 黄色理论片| 漂亮人妻洗澡被公强 日日躁 | 91制服诱惑 | 丁香婷婷综合激情五月色 | 天天噜夜夜噜 | 中文字幕人妻熟女人妻a片 麻豆91视频 | 中文字幕国产在线观看 | 国产女无套免费视频 | 丰满少妇一区二区三区 | 亚洲综合久久av | 亚洲一二三视频 | 午夜精品福利电影 | 丰满熟女人妻一区二区三 | 波波野结衣 | 在线免费观看黄网站 | 国产极品久久久 | 波多野吉衣一区 | 污污污www精品国产网站 | 久久久精品中文字幕麻豆发布 | 一炮成瘾1v1高h| 久久久久无码精品 | 黄色麻豆网站 | 国产伦精品一区二区三区 | 日本一区二区三区精品 | 黄色在线观看网站 | av不卡在线免费观看 | 亚洲裸体网站 | 欧洲女性下面有没有毛发 | 污视频网站免费观看 | 制服丝袜先锋影音 | 欧美成人精品一区二区 | 国产一区二区高清视频 | 丁香花国语版普通话 | 动漫玉足吸乳羞免费网站玉足 | 精品人人 | 在线久| 一本视频在线 | 69xx欧美| 久久精品丝袜高跟鞋 | 国内精品视频一区 | 一级性生活大片 | 亚洲 欧美 自拍偷拍 | 欧美xxxx性 | 亚洲天堂免费视频 | 2024国产精品| 男女涩涩网站 | 四虎在线观看 | 国产专区视频 | 草草影院网址 | 日韩国产毛片 | 国产精品无码中文字幕 | 麻豆久久久9性大片 | 久久久久亚洲 | 日本午夜精品 | 国产精品日日摸天天碰 | 99热1 | 欧美日韩在线免费播放 | 国产欧美一区二区三区视频在线观看 | 国产日韩一区二区三区 | 亚洲精品少妇 | 亚洲国产精一区二区三区性色 | 羞羞动漫免费观看 | 免费在线观看一区二区 | 91久久国产视频 | 有码在线视频 | 亚洲人午夜射精精品日韩 | 天堂综合网久久 | 一区二区三区精品在线 | 亚洲视频自拍偷拍 | 99re8在线精品视频免费播放 | 成人高潮片| 成人无码久久久久毛片 | 极品美女销魂一区二区三区 | 中出一区二区 | 射婷婷| 中文字幕在线免费视频 | 在线xxxxx| 性色av无码久久一区二区三区 | 花房姑娘免费观看全集 | 人人妻人人澡人人爽精品欧美一区 | 少妇特黄一区二区 | 国产自偷 | 亚洲天堂无吗 | 日本妈妈3| 亚洲AV无码精品色毛片浪潮 | 黄色小视频在线观看 | 我看黄色一级片 | 欧美亚洲一级片 | 黄色一级大片在线免费看国产一 | 无码 人妻 在线 视频 | 久草视频在线免费看 | 日韩激情在线播放 | 在线亚洲免费 | 波多野结衣电车痴汉 | 在线看片 | 人人插人人看 | 欧美性猛交一区二区三区精品 | 性开放耄耋老妇hd |