介于網(wǎng)絡(luò)上充斥著大量的含糊其辭的Socket初級教程锌订,擾亂著新手的學(xué)習(xí)方向竹握,我來扼要的教一下新手應(yīng)該怎么合理的處理Socket這個(gè)玩意兒。
一般來說瀑志,教你C#下Socket編程的老師涩搓,很少會教你如何解決Socket粘包、半包問題劈猪。
更甚至昧甘,某些師德有問題的老師,根本就沒跟你說過Socket的粘包战得、半包問題是什么玩意兒充边。
直到有一天,你的Socket程序在傳輸信息時(shí)出現(xiàn)了你預(yù)期之外的結(jié)果(多于的信息常侦、不完整的信息浇冰、亂碼、Bug等等)聋亡。
任你喊了一萬遍“我擦”肘习,依舊是不知道問題出在哪兒!
好了坡倔,不說廢話了漂佩,進(jìn)入正題,包教包會罪塔,學(xué)不會投蝉,送路費(fèi)。
如果你讀到這篇文章了征堪,想必你已經(jīng)遇到了以上問題瘩缆,情況再理想一點(diǎn)兒,其實(shí)你在原理上已經(jīng)知道怎么解決這個(gè)問題了佃蚜,只是不知道怎么動手庸娱。
那么着绊,首先,你需要新建一個(gè)【消息協(xié)議類】涌韩,這個(gè)類我們暫定由5大屬性組成畔柔,分別是:
【(①=1個(gè)byte)(②=1個(gè)byte])(③=1個(gè)int)(④=1個(gè)byte[])(⑤=1個(gè)byte[])】
解釋一下這個(gè)【消息協(xié)議類】:
(①=1個(gè)byte):這個(gè)屬性占1個(gè)字節(jié),可以放一個(gè)0到254的整數(shù)臣樱,我稱作1號標(biāo)志靶擦;
(②=1個(gè)byte):這個(gè)屬性占1個(gè)字節(jié),可以放一個(gè)0到254的整數(shù)雇毫,我稱作2號標(biāo)志玄捕。那么1號標(biāo)志和2號標(biāo)志就有多達(dá)255×255個(gè)組合,我們用它來自定義這個(gè)消息的標(biāo)志棚放,比如枚粘,0-0表示登錄請求消息,1-1表示物理攻擊飘蚯,1-2表示魔法攻擊馍迄,3-3表示坐標(biāo)移動;
(③=1個(gè)int):這個(gè)屬性占4個(gè)字節(jié)局骤,可以放一個(gè)0到2147483647的整數(shù)攀圈,它表示(④=1個(gè)byte[])的長度;
(④=1個(gè)byte[]):這個(gè)屬性存著你要發(fā)送的全部消息體字節(jié)峦甩,所以赘来,你的消息體需要被轉(zhuǎn)化為字節(jié)數(shù)組才可存放進(jìn)去;
(⑤=1個(gè)byte[]):這個(gè)屬性存著【多余的消息體字節(jié)】凯傲。那么問題來了犬辰,什么是【多余的消息體字節(jié)】?休息一下繼續(xù)往下看冰单。
再解釋一下這個(gè)【消息協(xié)議類】具體怎么用幌缝。
1,【消息發(fā)送方】先定義【消息協(xié)議類】①②屬性诫欠,也就是隨便寫2個(gè)數(shù)狮腿;
2,【消息發(fā)送方】再將消息“裝入”【消息協(xié)議類】的④屬性呕诉,那么③屬性就有了;
3吃度,【消息發(fā)送方】將封裝好的【消息協(xié)議類】轉(zhuǎn)為byte[]甩挫,發(fā)送給【消息接收方】,我們把這道工序稱作【封包】椿每;
4伊者,【消息接收方】接收到【消息發(fā)送方】發(fā)來的byte[]時(shí)英遭,先判斷這個(gè)byte[]長度是否大于6双肤,即是否大于①屬性+②屬性+③屬性的長度和妄田,如果byte[]長度小于6婆硬,【消息接收方】就循環(huán)繼續(xù)接收屋讶;
5耙箍,【消息接收方】接收到【消息發(fā)送方】發(fā)來的byte[]長度大于等于6了V嗝宙地!則將byte[]還原為【消息協(xié)議類】送漠,為了區(qū)別搂蜓,我們暫時(shí)把它為【新消息協(xié)議類】狼荞;
6,循環(huán)判斷【消息發(fā)送方】發(fā)來的byte[]長度減去6之后的值是否大于等于【新消息協(xié)議類】的③的值帮碰。這個(gè)可以理解為byte[]是否為一個(gè)完整的【消息協(xié)議類】相味,如果是就把【新消息協(xié)議類】的④屬性拆出來,就得到了一個(gè)剛剛好完整的消息殉挽,不多也不少丰涉。那么,我們就把這道工序稱作【拆包】斯碌;
7一死,相信你已經(jīng)反應(yīng)過來⑤這個(gè)【多余的消息體字節(jié)】是干嘛用的了。上一步當(dāng)中输拇,如果byte[]信息剛好是一個(gè)完整長度自然用不到⑤了摘符,但是在網(wǎng)絡(luò)傳輸中byte[]自然不會永遠(yuǎn)那么剛好了,所以當(dāng)byte[]長度大于一個(gè)完整消息時(shí)策吠,就把多于的byte放入⑤當(dāng)中逛裤,和下次新接收的byte[]組合在一起,再次進(jìn)行這樣的循環(huán)猴抹,保證數(shù)據(jù)的完整性和獨(dú)立性带族。
8,好了蟀给,以上過程就是利用封包蝙砌、拆包原理解決Socket粘包、半包問題跋理,接下來你可以在發(fā)送方以非常頻繁的發(fā)送頻率來發(fā)送择克,接收方依然會規(guī)規(guī)矩矩完完整整的以正確的姿勢來接收消息了。最下面是要點(diǎn)代碼前普,相信聰明的你一定學(xué)會了肚邢,如果還沒學(xué)會,可以加我QQ:119945778,包教包會骡湖,不然我還得送路費(fèi)不是...
下面是源碼時(shí)間:
? 1///<summary>? 2/// 【消息協(xié)議】=【協(xié)議一級標(biāo)志】+【協(xié)議二級標(biāo)志】+【實(shí)際消息長度】+【實(shí)際消息內(nèi)容】+【多于消息內(nèi)容】? 3///</summary>? 4publicclass MessageXieYi? 5? ? {? 6#region自定義? 7#region協(xié)議一級標(biāo)志贱纠,值 = (0 至 254 )? 8privatebyte xieYiFirstFlag;? 9///<summary> 10/// 協(xié)議類別,值 = ( 0 直 254 ) 11///</summary> 12publicbyte XieYiFirstFlag 13? ? ? ? { 14get{return xieYiFirstFlag; } 15set{ xieYiFirstFlag = value; } 16? ? ? ? } 17#endregion 18 19#region協(xié)議二級標(biāo)志响蕴,值 = (0 至 254 ) 20privatebyte xieYiSecondFlag; 21///<summary> 22/// 協(xié)議二級標(biāo)志谆焊,值 = (0 至 254 ) 23///</summary> 24publicbyte XieYiSecondFlag 25? ? ? ? { 26get{return xieYiSecondFlag; } 27set{ xieYiSecondFlag = value; } 28? ? ? ? } 29#endregion 30 31#region實(shí)際消息長度 32privateint messageContentLength; 33///<summary> 34/// 實(shí)際消息長度 35///</summary> 36publicint MessageContentLength 37? ? ? ? { 38get{return messageContentLength; } 39set{ messageContentLength = value; } 40? ? ? ? } 41#endregion 42 43#region實(shí)際消息內(nèi)容 44privatebyte[] messageContent =newbyte[] { }; 45///<summary> 46/// 實(shí)際消息內(nèi)容 47///</summary> 48publicbyte[] MessageContent 49? ? ? ? { 50get{return messageContent; } 51set{ messageContent = value; } 52? ? ? ? } 53#endregion 54 55#region多余的Bytes 56privatebyte[] duoYvBytes; 57///<summary> 58/// 多余的Bytes 59///</summary> 60publicbyte[] DuoYvBytes 61? ? ? ? { 62get{return duoYvBytes; } 63set{ duoYvBytes = value; } 64? ? ? ? } 65 66#endregion 67#endregion 68 69#region構(gòu)造函數(shù)兩個(gè) 70public MessageXieYi() 71? ? ? ? { 72// 73? ? ? ? } 74 75publicMessageXieYi(byte_xieYiFirstFlage,byte_xieYiSecondFlage,byte[] _messageContent) 76? ? ? ? { 77xieYiFirstFlag = _xieYiFirstFlage; 78xieYiFirstFlag = _xieYiSecondFlage; 79messageContentLength = _messageContent.Length; 80messageContent = _messageContent; 81? ? ? ? } 82#endregion 83 84#regionMessageXieYi 轉(zhuǎn)換為 byte[] 85///<summary> 86/// MessageXieYi 轉(zhuǎn)換為 byte[] 87///</summary> 88///<returns></returns> 89publicbyte[] ToBytes() 90? ? ? ? { 91byte[] _bytes;//自定義字節(jié)數(shù)組,用以裝載消息協(xié)議 92 93using(MemoryStream memoryStream =newMemoryStream())//創(chuàng)建內(nèi)存流 94? ? ? ? ? ? { 95BinaryWriter binaryWriter =newBinaryWriter(memoryStream);//以二進(jìn)制寫入器往這個(gè)流里寫內(nèi)容 96 97binaryWriter.Write(xieYiFirstFlag);//寫入?yún)f(xié)議一級標(biāo)志浦夷,占1個(gè)字節(jié) 98binaryWriter.Write(xieYiSecondFlag);//寫入?yún)f(xié)議二級標(biāo)志辖试,占1個(gè)字節(jié) 99binaryWriter.Write(messageContentLength);//寫入實(shí)際消息長度,占4個(gè)字節(jié)100101if(messageContentLength >0)102? ? ? ? ? ? ? ? {103binaryWriter.Write(messageContent);//寫入實(shí)際消息內(nèi)容104? ? ? ? ? ? ? ? }105106_bytes = memoryStream.ToArray();//將流內(nèi)容寫入自定義字節(jié)數(shù)組107108binaryWriter.Close();//關(guān)閉寫入器釋放資源109? ? ? ? ? ? }110111return_bytes;//返回填充好消息協(xié)議對象的自定義字節(jié)數(shù)組112? ? ? ? }113#endregion114115#regionbyte[] 轉(zhuǎn)換為 MessageXieYi116///<summary>117/// byte[] 轉(zhuǎn)換為 MessageXieYi118///</summary>119///<param name="buffer">字節(jié)數(shù)組緩沖器军拟。</param>120///<returns></returns>121publicstaticMessageXieYi FromBytes(byte[] buffer)122? ? ? ? {123intbufferLength = buffer.Length;124125MessageXieYi messageXieYi =new MessageXieYi();126127using(MemoryStream memoryStream =newMemoryStream(buffer))//將字節(jié)數(shù)組填充至內(nèi)存流128? ? ? ? ? ? {129BinaryReader binaryReader =newBinaryReader(memoryStream);//以二進(jìn)制讀取器讀取該流內(nèi)容130131messageXieYi.xieYiFirstFlag = binaryReader.ReadByte();//讀取協(xié)議一級標(biāo)志剃执,讀1個(gè)字節(jié)132messageXieYi.xieYiSecondFlag = binaryReader.ReadByte();//讀取協(xié)議二級標(biāo)志,讀1個(gè)字節(jié)133messageXieYi.messageContentLength = binaryReader.ReadInt32();//讀取實(shí)際消息長度懈息,讀4個(gè)字節(jié)? ? ? ? ? ? ? ? 134135//如果【進(jìn)來的Bytes長度】大于【一個(gè)完整的MessageXieYi長度】136if((bufferLength - 6) >messageXieYi.messageContentLength)137? ? ? ? ? ? ? ? {138messageXieYi.messageContent = binaryReader.ReadBytes(messageXieYi.messageContentLength);//讀取實(shí)際消息內(nèi)容肾档,從第7個(gè)字節(jié)開始讀139messageXieYi.duoYvBytes = binaryReader.ReadBytes(bufferLength -6- messageXieYi.messageContentLength);140? ? ? ? ? ? ? ? }141142//如果【進(jìn)來的Bytes長度】等于【一個(gè)完整的MessageXieYi長度】143if((bufferLength - 6) ==messageXieYi.messageContentLength)144? ? ? ? ? ? ? ? {145messageXieYi.messageContent = binaryReader.ReadBytes(messageXieYi.messageContentLength);//讀取實(shí)際消息內(nèi)容怒见,從第7個(gè)字節(jié)開始讀146? ? ? ? ? ? ? ? }147148binaryReader.Close();//關(guān)閉二進(jìn)制讀取器,是否資源149? ? ? ? ? ? }150151returnmessageXieYi;//返回消息協(xié)議對象152? ? ? ? }153#endregion154}
1///<summary> 2/// 按照先后順序合并字節(jié)數(shù)組類 3///</summary> 4publicclass CombineBytes 5? ? { 6///<summary> 7/// 按照先后順序合并字節(jié)數(shù)組纪隙,并返回合并后的字節(jié)數(shù)組。 8///</summary> 9///<param name="firstBytes">第一個(gè)字節(jié)數(shù)組</param>10///<param name="firstIndex">第一個(gè)字節(jié)數(shù)組的開始截取索引</param>11///<param name="firstLength">第一個(gè)字節(jié)數(shù)組的截取長度</param>12///<param name="secondBytes">第二個(gè)字節(jié)數(shù)組</param>13///<param name="secondIndex">第二個(gè)字節(jié)數(shù)組的開始截取索引</param>14///<param name="secondLength">第二個(gè)字節(jié)數(shù)組的截取長度</param>15///<returns></returns>16publicstaticbyte[] ToArray(byte[] firstBytes,intfirstIndex,intfirstLength,byte[] secondBytes,intsecondIndex,int secondLength)17? ? ? ? {18using(MemoryStream ms =new MemoryStream())19? ? ? ? ? ? {20BinaryWriter bw =new BinaryWriter(ms);21? ? ? ? ? ? ? ? bw.Write(firstBytes, firstIndex, firstLength);22? ? ? ? ? ? ? ? bw.Write(secondBytes, secondIndex, secondLength);2324? ? ? ? ? ? ? ? bw.Close();25? ? ? ? ? ? ? ? bw.Dispose();2627return ms.ToArray();28? ? ? ? ? ? }29? ? ? ? }30}
1byte[] msgBytes = Encoding.Unicode.GetBytes("要發(fā)送的消息");2MessageXieYi msgXY =newMessageXieYi(0,0, msgBytes);3networkStream.Write(msgXY.ToBytes(),0, msgXY.ToBytes().Length);
1#region線程執(zhí)行體,接收消息 2///<summary> 3/// 線程執(zhí)行體,接收消息 4///</summary> 5///<param name="obj">傳遞給線程執(zhí)行體的用戶名麸锉,用以與用戶通信</param> 6privatevoidThreadReceive(object obj) 7? ? ? ? { 8//通過用戶名找出已經(jīng)保存在哈希表里的Socket 9Socket savedSocket = hashtable_UserNameToSocket[obj]as Socket;1011MessageXieYi msgXY =new MessageXieYi();1213byte[] buffer =newbyte[64];//定義一個(gè)大小為64的緩沖區(qū)14//byte[] receivedBytes = new byte[] { };15byte[] newBuffer =newbyte[] { };//大小可變的緩存器1617int receivedLength;18intavailableLength;//沒什么實(shí)際意義,就是為了方便理解Socket傳輸機(jī)制1920while(true)21? ? ? ? ? ? {22try23? ? ? ? ? ? ? ? {24buffer =newbyte[64];2526for(inti =0; i <10; i++)27? ? ? ? ? ? ? ? ? ? {28availableLength = savedSocket.Available;2930Console.WriteLine("【循環(huán)判斷有多少可讀Bytes】savedSocket.Available["+ i +"]="+ availableLength);//沒實(shí)際意義毙芜,就是來個(gè)直觀感受Socket的原理31? ? ? ? ? ? ? ? ? ? }3233Console.WriteLine("【可變緩存器大小】newBuffer.Length="+ newBuffer.Length);3435receivedLength = savedSocket.Receive(buffer);3637Console.WriteLine("【接收到數(shù)據(jù)】buffer.Length="+ receivedLength);3839newBuffer = CombineBytes.ToArray(newBuffer,0, newBuffer.Length, buffer,0, receivedLength);4041Console.WriteLine("【將接收到的數(shù)據(jù)追加在newBuffer后】newBuffer.Length="+ newBuffer.Length);4243if(newBuffer.Length <6)44? ? ? ? ? ? ? ? ? ? {45Console.WriteLine("newBuffer.Length="+ newBuffer.Length +"< 6 \t -> \t continue");46continue;47? ? ? ? ? ? ? ? ? ? }48else//newBuffer.Length >= 649? ? ? ? ? ? ? ? ? ? {50//取msgXY包頭部分51msgXY = MessageXieYi.FromBytes(newBuffer);52intfirstFlag = msgXY.XieYiFirstFlag;53intsecondFlag = msgXY.XieYiSecondFlag;54intmsgContentLength = msgXY.MessageContentLength;555657//判斷去掉msgXY包頭剩下的長度是否達(dá)到可以取包實(shí)質(zhì)內(nèi)容58while((newBuffer.Length -6) >= msgContentLength)59? ? ? ? ? ? ? ? ? ? ? ? {60Console.WriteLine("【newBuffer去掉包頭的長度="+ (newBuffer.Length -6) +"】>=【"+"包實(shí)質(zhì)內(nèi)容長度="+ msgContentLength +"】");61msgXY =null;62msgXY = MessageXieYi.FromBytes(newBuffer);63Console.WriteLine("\n【拆包】="+ Encoding.Unicode.GetString(msgXY.MessageContent) +"\n");6465newBuffer = msgXY.DuoYvBytes;66Console.WriteLine("【剩余的newBuffer】newBuffer.Length="+ newBuffer.Length);6768if(newBuffer.Length >=6)69? ? ? ? ? ? ? ? ? ? ? ? ? ? {70msgXY = MessageXieYi.FromBytes(newBuffer);71firstFlag = msgXY.XieYiFirstFlag;72secondFlag = msgXY.XieYiSecondFlag;73msgContentLength = msgXY.MessageContentLength;74continue;75? ? ? ? ? ? ? ? ? ? ? ? ? ? }76else77? ? ? ? ? ? ? ? ? ? ? ? ? ? {78break;79? ? ? ? ? ? ? ? ? ? ? ? ? ? }80? ? ? ? ? ? ? ? ? ? ? ? }81? ? ? ? ? ? ? ? ? ? }8283availableLength = savedSocket.Available;84Console.WriteLine("savedSocket.Available="+ availableLength +"\n\n\n\n");8586continue;87? ? ? ? ? ? ? ? }88catch89? ? ? ? ? ? ? ? {90//異常處理91? ? ? ? ? ? ? ? }92? ? ? ? ? ? }93? ? ? ? }94#endregion