如果客戶端連續(xù)不斷的向服務端發(fā)送數(shù)據(jù)包時,服務端接收的數(shù)據(jù)會出現(xiàn)兩個數(shù)據(jù)包粘在一起的情況,這就是TCP協(xié)議中經(jīng)常會遇到的粘包以及拆包的問題。
成都創(chuàng)新互聯(lián)公司專業(yè)提供德陽服務器托管服務,為用戶提供五星數(shù)據(jù)中心、電信、雙線接入解決方案,用戶可自行在線購買德陽服務器托管服務,并享受7*24小時金牌售后服務。
傳輸層的UDP協(xié)議是否會發(fā)生粘包或者拆包問題?
不會。UDP是基于報文發(fā)送的,在UDP首部采用了16bit來指示UDP數(shù)據(jù)報文的長度,因此在應用層能很好的將不同的數(shù)據(jù)報文區(qū)分開,從而避免粘包和拆包的問題。
傳輸層的TCP協(xié)議是否會發(fā)生粘包或者拆包問題?
會。原因有以下兩點:
1、TCP是基于字節(jié)流的,雖然應用層和傳輸層之間的數(shù)據(jù)交互是大小不等的數(shù)據(jù)塊,但是TCP把這些數(shù)據(jù)塊僅僅看成一連串無結構的字節(jié)流,沒有邊界;
2、在TCP的首部沒有表示數(shù)據(jù)長度的字段,基于上面兩點,在使用TCP傳輸數(shù)據(jù)時,才有粘包或者拆包現(xiàn)象發(fā)生的可能。
現(xiàn)在假設客戶端向服務端連續(xù)發(fā)送了兩個數(shù)據(jù)包,用packet1和packet2來表示,那么服務端收到的數(shù)據(jù)可以分為三種,現(xiàn)列舉如下:
第一種情況,接收端正常收到兩個數(shù)據(jù)包,即沒有發(fā)生拆包和粘包的現(xiàn)象,此種情況不在本文的討論范圍內。
第二種情況,接收端只收到一個數(shù)據(jù)包,由于TCP是不會出現(xiàn)丟包的,所以這一個數(shù)據(jù)包中包含了發(fā)送端發(fā)送的兩個數(shù)據(jù)包的信息,這種現(xiàn)象即為粘包。這種情況由于接收端不知道這兩個數(shù)據(jù)包的界限,所以對于接收端來說很難處理。
第三種情況,這種情況有兩種表現(xiàn)形式,如下圖。接收端收到了兩個數(shù)據(jù)包,但是這兩個數(shù)據(jù)包要么是不完整的,要么就是多出來一塊,這種情況即發(fā)生了拆包和粘包。這兩種情況如果不加特殊處理,對于接收端同樣是不好處理的。
發(fā)生TCP粘包或拆包有很多原因,現(xiàn)列出常見的幾點:
1、要發(fā)送的數(shù)據(jù)大于TCP發(fā)送緩沖區(qū)剩余空間大小,將會發(fā)生拆包。
2、待發(fā)送數(shù)據(jù)大于MSS(最大報文長度),TCP在傳輸前將進行拆包。
3、要發(fā)送的數(shù)據(jù)小于TCP發(fā)送緩沖區(qū)的大小,TCP將多次寫入緩沖區(qū)的數(shù)據(jù)一次發(fā)送出去,將會發(fā)生粘包。
4、接收數(shù)據(jù)端的應用層沒有及時讀取接收緩沖區(qū)中的數(shù)據(jù),將發(fā)生粘包。
通過以上分析,我們清楚了粘包或拆包發(fā)生的原因,那么如何解決這個問題呢?解決問題的關鍵在于如何給每個數(shù)據(jù)包添加邊界信息,常用的方法有如下幾個:
1、發(fā)送端給每個數(shù)據(jù)包添加包首部,首部中應該至少包含數(shù)據(jù)包的長度,這樣接收端在接收到數(shù)據(jù)后,通過讀取包首部的長度字段,便知道每一個數(shù)據(jù)包的實際長度了。
2、發(fā)送端將每個數(shù)據(jù)包封裝為固定長度(不夠的可以通過補0填充),這樣接收端每次從接收緩沖區(qū)中讀取固定長度的數(shù)據(jù)就自然而然的把每個數(shù)據(jù)包拆分開來。
3、可以在數(shù)據(jù)包之間設置邊界,如添加特殊符號,這樣,接收端通過這個邊界就可以將不同的數(shù)據(jù)包拆分開。
Tomcat是通過http的協(xié)議來進行傳輸數(shù)據(jù)的,http協(xié)議在請求頭內有一個Content-Length字段來告訴服務端請求體的長度,并且請求頭和請求體通過空一行來分開的
Netty支持多種處理粘包和拆包的方式,可以根據(jù)具體的場景進行選擇。
參考:
TCP協(xié)議下的粘包與拆包,如何解決一、粘包、拆包1.1 粘包原因1.1.1 滑動窗口1.1.2 Nagle算法1.1.3 應用層原因1.2 拆包原因1.2.1 滑動窗口1.2.2 MSS限制1.2.3 應用層原因1.2 Netty提供的解決方案二、自定義協(xié)議解決粘包、拆包2.1 自定義協(xié)議要素
因為TCP/IP在起初,所有的請求是串行化的,之后做成了滑動窗口的概念。那么在接收方,如果接收不及時且窗口大小足夠大,就可能出現(xiàn)粘包的情況。
因為每次數(shù)據(jù)發(fā)送的時候,都需要加上消息頭等特殊數(shù)據(jù),TCP與IP協(xié)議分別會加20Byte數(shù)據(jù),因此哪怕只是發(fā)送1Byte數(shù)據(jù),最終接收方還是會接收到41Byte;在這樣的背景下,Nagle算法可能會將多個數(shù)據(jù)包合并在一起發(fā)送。
接收方ByteBuf設置太大(Netty默認為1024Byte),因此如果客戶端發(fā)送的報文都非常小,拋開上述 1.1.1.1 中的原因不談,光在Netty這一處也非常容易出現(xiàn)粘包現(xiàn)象
假設接收方的窗口只剩128Bytes,發(fā)送方的報文大小是256Bytes,這時放不下了,只能先發(fā)送前128Bytes,等待ack后,窗口有剩余空間了才會發(fā)送剩余部分,這就導致了拆包
當發(fā)送數(shù)據(jù)超過MSS限制后,會將數(shù)據(jù)切分發(fā)送,而這個MSS是根據(jù)不同類型網(wǎng)卡的限制來看,譬如某筆記本網(wǎng)卡可以發(fā)送1500Byte,除去一次數(shù)據(jù)包中的TCP/IP固定40Byte外,真正的數(shù)據(jù)也只能發(fā)送1460Byte。
Netty中ByteBuf設置的大小小于數(shù)據(jù)包大小。
未完待續(xù)... ...
粘包和分包是利用Socket在TCP協(xié)議下內部的優(yōu)化機制。
1、什么是粘包
只有TCP有粘包現(xiàn)象,UDP永遠不會粘包,為何,且聽我娓娓道來。發(fā)送數(shù)據(jù)時間間隔很短,數(shù)據(jù)了很小,也就是發(fā)送數(shù)據(jù)比較頻繁,會合到一起,產(chǎn)生粘包;
2、什么是分包
當我們發(fā)送的數(shù)據(jù)量很大的時候,可能是幾千字節(jié),TCP就會自動分開發(fā)送,其實說通俗點,就是你去拿快遞,一看20個,一次拿不完,分幾次拿!
3、總結
指發(fā)送方發(fā)送的若干包數(shù)據(jù)到接收方接收時粘成一包,從接收緩沖區(qū)看,后一包數(shù)據(jù)的頭緊接著前一包數(shù)據(jù)的尾。出現(xiàn)粘包現(xiàn)象的原因是多方面的,它既可能由發(fā)送方造成,也可能由接收方造成。
發(fā)送方引起的粘包是由TCP協(xié)議本身造成的,TCP為提高傳輸效率,發(fā)送方往往要收集到足夠多的數(shù)據(jù)后才發(fā)送一包數(shù)據(jù)。
若連續(xù)幾次發(fā)送的數(shù)據(jù)都很少,通常TCP會根據(jù)優(yōu)化算法把這些數(shù)據(jù)合成一包后一次發(fā)送出去,這樣接收方就收到了粘包數(shù)據(jù)。接收方引起的粘包是由于接收方用戶進程不及時接收數(shù)據(jù),從而導致粘包現(xiàn)象。
這是因為接收方先把收到的數(shù)據(jù)放在系統(tǒng)接收緩沖區(qū),用戶進程從該緩沖區(qū)取數(shù)據(jù),若下一包數(shù)據(jù)到達時前一包數(shù)據(jù)尚未被用戶進程取走,則下一包數(shù)據(jù)放到系統(tǒng)接收緩沖區(qū)時就接到前一包數(shù)據(jù)之后,而用戶進程根據(jù)預先設定的緩沖區(qū)大小從系統(tǒng)接收緩沖區(qū)取數(shù)據(jù),這樣就一次取到了多包數(shù)據(jù)。分包是指在出現(xiàn)粘包的時候我們的接收方要進行分包處理。
在前面的測試程序中,是沒有粘包問題的,這時候你可能有疑惑,我為啥數(shù)據(jù)會發(fā)送的特別快,我們以游戲服務器舉例,比如游戲有聯(lián)機對戰(zhàn)功能,這時候肯定是需要同步位置信息的,這個頻率是很快的,大約每秒就要40~80次,這個時候就會出現(xiàn)粘包問題。
其實很簡單只要簡單修改一下客戶端即可。
1、程序測試 — 粘包問題
客戶端:
服務器查看調試信息:
服務器已啟動......
有一個客戶端進行連接成功......
從客戶端接收到的數(shù)據(jù):0
從客戶端接收到的數(shù)據(jù):123
從客戶端接收到的數(shù)據(jù):4567
從客戶端接收到的數(shù)據(jù):8910
從客戶端接收到的數(shù)據(jù):1112131415
從客戶端接收到的數(shù)據(jù):161718
從客戶端接收到的數(shù)據(jù):192021222324
從客戶端接收到的數(shù)據(jù):25262728
從客戶端接收到的數(shù)據(jù):2930313233
從客戶端接收到的數(shù)據(jù):34353637
從客戶端接收到的數(shù)據(jù):38394041
從客戶端接收到的數(shù)據(jù):42434445
從客戶端接收到的數(shù)據(jù):46474849
從客戶端接收到的數(shù)據(jù):50515253
從客戶端接收到的數(shù)據(jù):5455565758
從客戶端接收到的數(shù)據(jù):59606162636465666768
從客戶端接收到的數(shù)據(jù):6970717273
從客戶端接收到的數(shù)據(jù):74757677
從客戶端接收到的數(shù)據(jù):78798081
從客戶端接收到的數(shù)據(jù):82838485
從客戶端接收到的數(shù)據(jù):86878889
從客戶端接收到的數(shù)據(jù):90919293
從客戶端接收到的數(shù)據(jù):9495969798
從客戶端接收到的數(shù)據(jù):99
(很明顯數(shù)據(jù)沒有發(fā)送100次)
1、程序測試 — 粘包問題
客戶端:
服務器查看調試信息:
服務器已啟動......
有一個客戶端進行連接成功......
從客戶端接收到的數(shù)據(jù):
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000撒大聲地所多所多所多所多所多所多所多所多所多所000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000撒大聲地所多所多所多所多所多所多所多所多所多所多所多所多所多所多撒大聲地所 多所多所多所多所多所多所多所多所多所多所多所多所多所多撒大聲地所多所多所多所多所多所多所多所多所多所多所多所多所多所多000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000----------------------------------------------------------------撒大聲地所多所多所多所多所多所多所多所多所?
從客戶端接收到的數(shù)據(jù):
77777777777777777777777777777777777777777777777777777777790909090909090909090909090909090909090000000000000000000000000099
(可以看出服務器是分兩次接收的,但其實只要static byte[] dataBuffer = new byte[1024];給的空間足夠大,分包問題就可解決)
其實也很好解決,我們在發(fā)送數(shù)據(jù)的時候事先存儲數(shù)據(jù)的長度,不過用來存儲數(shù)據(jù)長度的內存大小需要指定好,否則就沒法判斷了。
假設我們現(xiàn)在的數(shù)據(jù)出現(xiàn)了粘包,如下圖所示:
這里只是演示一下,如果只有連續(xù)發(fā)送4次數(shù)據(jù),一般是不會出現(xiàn)粘包的,看上圖橙色部分表示我們用一個int32類型儲存數(shù)據(jù)的長度,藍色部分為我們實際要發(fā)送的數(shù)據(jù),現(xiàn)在發(fā)生了粘包,也就是這四條數(shù)據(jù)合在一起發(fā)送給了服務器,
此時這條數(shù)據(jù)的總大小為 4字節(jié) * 4 + 5 + 7 + 10 + 4 = 42字節(jié)
我們通過讀取4字節(jié)數(shù)據(jù)可以知道數(shù)據(jù)的實際長度,以第一個數(shù)據(jù)為例,我們讀取4字節(jié)數(shù)據(jù),知道了這個數(shù)據(jù)有5個字節(jié),程序如下:
int data_length = BitConverter.ToInt32(_data, 0);
此時的data_length = 5;此時我們就讀取這5個字節(jié)的數(shù)據(jù)即可!
string s = Encoding.UTF8.GetString(_data, 4, 5);
然后我們截取數(shù)據(jù),從源數(shù)據(jù)的第4 + 5的位置開始截取到一個新數(shù)組,新字節(jié)數(shù)組索引從零開始,此時新字節(jié)數(shù)據(jù)的長度為42 - (5 + 4);(下圖為新字節(jié)數(shù)組)
Array.Copy(_data, 5 + 4, _data, 0, 42 - (5 + 4));
依次循環(huán)下去,粘包就被成功的分包了。當然這個不要忘記每次更新一下當前數(shù)據(jù)長度。
_curLength = _curLength - (data_length + 4); // _curLength = 42 - (5 + 4)
1、客戶端
創(chuàng)建Message類,用于發(fā)送數(shù)據(jù)前做處理,使得首4字節(jié)儲存數(shù)據(jù)長度。
Message:
Main:
2、服務端
創(chuàng)建Message類,解決粘包問題!
Main:
服務器查看調試信息:
服務器已啟動......
有一個客戶端進行連接成功......
解析到一條數(shù)據(jù):0米
4
8
解析到一條數(shù)據(jù):1米
4
630
解析到一條數(shù)據(jù):2米
4
622
解析到一條數(shù)據(jù):3米
4
614
解析到一條數(shù)據(jù):4米
4
606
解析到一條數(shù)據(jù):5米
4
598
解析到一條數(shù)據(jù):6米
4
590
解析到一條數(shù)據(jù):7米
4
582
解析到一條數(shù)據(jù):8米
4
574
解析到一條數(shù)據(jù):9米
4
566
解析到一條數(shù)據(jù):10米
5
558
..............................................................................
解析到一條數(shù)據(jù):98米
5
18
解析到一條數(shù)據(jù):99米
5
9
有客戶端退出.....
你解決個屁,異步接收的情況下,把_data數(shù)組調大點就完了,傻逼,咱們是做游戲!一般不會有分包問題!!
用戶數(shù)據(jù)報協(xié)議(User Datagram Protocol,縮寫為UDP),又稱用戶數(shù)據(jù)報文協(xié)議,是一個簡單的面向數(shù)據(jù)報(package-oriented)的傳輸層協(xié)議,正式規(guī)范為RFC 768。
UDP只提供數(shù)據(jù)的不可靠傳遞,它一旦把應用程序發(fā)給網(wǎng)絡層的數(shù)據(jù)發(fā)送出去,就不保留數(shù)據(jù)備份(所以UDP有時候也被認為是不可靠的數(shù)據(jù)報協(xié)議)。
UDP在IP數(shù)據(jù)報的頭部僅僅加入了復用和數(shù)據(jù)校驗。
由于缺乏可靠性且屬于非連接導向協(xié)議,UDP應用一般必須允許一定量的丟包、出錯和復制粘貼。
1 在接收udp包時,如果接收包時給定的buffer太小的話,就要自己解決粘包問題。
2 udp包的發(fā)送和接收不保證一定成功,不保證按正確順序抵達。
3 如果不允許丟包的情況出現(xiàn)的話,要有重發(fā)機制來保證,如:反饋機制確認。
服務端
客戶端
在開發(fā)程序的時候?使用易語言的?服務端?與?客戶端?控件時?,一般不直接使用發(fā)送數(shù)據(jù)功能。
因為網(wǎng)絡存在丟包的可能,所以易語言的服務端會自動的重發(fā)剛剛丟失的包,直到完全結束。
有很多新手朋友在使用易語言傳送文件的老出現(xiàn)這個問題。
估計你的這個問題也是一樣的原因照成的,丟包!
最簡單有效的解決辦法:
封裝一個發(fā)包的方法,我這里已服務端給客戶端發(fā)送消息舉例,到時候你還需要同樣在客戶端上寫相同的代碼。
-------------------------------------------
.版本?2
.程序集?窗口程序集1
.程序集變量?temp數(shù)據(jù)包尾部,?文本型
.子程序?__啟動窗口_創(chuàng)建完畢
temp數(shù)據(jù)包尾部?=?“{【結尾】[over]}”
.子程序?封裝的發(fā)送方法
.參數(shù)?客戶IP,?文本型
.參數(shù)?data,?字節(jié)集
服務器1.發(fā)送數(shù)據(jù)?(客戶IP,?data?+?到字節(jié)集?(temp數(shù)據(jù)包尾部),?)
.子程序?_服務器1_數(shù)據(jù)到達
接收到的數(shù)據(jù)進行處理?(服務器1.取回數(shù)據(jù)?())
.子程序?接收到的數(shù)據(jù)進行處理
.參數(shù)?data,?字節(jié)集
.局部變量?oldData,?字節(jié)集,?靜態(tài),?,?注意,這是一個靜態(tài)的變量,如果理解不到靜態(tài)的意思就請使用全局變量
oldData?=?oldData?+?oldData
'?判斷如果?該數(shù)據(jù)包的尾部不等于
.如果?(取字節(jié)集右邊?(oldData,?取字節(jié)集長度?(oldData)?-?取文本長度?(temp數(shù)據(jù)包尾部))?≠?到字節(jié)集?(temp數(shù)據(jù)包尾部))
'?如果說不相同就說明?數(shù)據(jù)還沒有完全的過來,所以這里不做任何處理
.否則
處理完整傳遞的數(shù)據(jù)?(oldData)??'?如果相等了,就說明數(shù)據(jù)已經(jīng)完全的過來了,我們就調用數(shù)據(jù)傳遞后的方法
oldData?=?{??}??'?這里記住,調用完畢后就必須置空字節(jié)集
.如果結束
.子程序?處理完整傳遞的數(shù)據(jù)
.參數(shù)?data,?字節(jié)集
'?在這里就可以寫你的處理方法了,
'?by?:?炫e小鋒?QQ:251708339
分享標題:go語言粘包怎么解決,go語言 閉包
瀏覽地址:http://www.2m8n56k.cn/article4/hcecie.html
成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站收錄、網(wǎng)站營銷、ChatGPT、商城網(wǎng)站、域名注冊、網(wǎng)站策劃
聲明:本網(wǎng)站發(fā)布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:[email protected]。內容未經(jīng)允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)