TCP 協議下 socket 有可能丟包嗎?

問題是這樣的,客戶端Send的數據很大(假設9000KB,但只發一次數據),故會被分包/片傳輸至伺服器,那麼想問當數據的第一包被服務端接收後,服務端由於一直在sockServ.Receive(buffer),那有可能buffer里只收到第一包的數據嗎(buffer很大,比9000KB大),還是說會等所有包都到了才能Receive?還有就是假設成功發送第一包後網路突然不穩定,第二包發送不成功,那服務端有相應的處理措施嗎(還是說Receive收到第一包數據,第二次Receive時出異常?)?(假設服務端收到包的順序是正確的,即收到的第一個包就是客戶端發送數據被分包後的第一個包,第二個、第三個。。。都是正確的順序)


1876年法國贈送美國政府自由女神鵰塑,女神體積很龐大,法國政府委託快遞公司運輸,先拆分成300片打包,編號為1-300,需要三艘船運輸,第一艘運1-100片,以此類推。

場景一:三艘船按序到達

美國政府每天派人去快遞公司查件(Receive函數),第一艘船到達,快遞公司將1-100片交付美國政府,以此類推,收完300片,可以完整重構自由女神,傳輸任務完成。

場景二:三艘船亂序到達

由於第一艘船動力有點問題,結果最後達到,船到達順序為2、3、1,假設委託人也是天天去查件,在第一艘船沒有到達之前,快遞公司會假裝什麼都沒有發生,直到第一艘船到達,然後按照船編號順序將貨物提交給委託人,與場景一相比,會有大一些的延遲。

場景三:有一艘船掉大西洋里了

浩瀚的大西洋,波瀾壯闊,掉一艘船到海里,天知,海知,你我他不知,第一艘、第三船都到達了,可是第二艘船卻遲遲未到,一周過去了,一個月過去了,還是杳無音訊,快遞公司美國分公司意識到出事了,他們做了兩方面工作:

1)將第一艘船的貨物提交給美國政府(在剛接收到就可以提交給美國政府)

2)派人通知法國總公司將第二艘船的貨物再建造一份,雕塑編號為101-200,重新運輸

半個月之後,第二艘運輸船到達了,然後提交給美國政府,最後是第三艘船的貨物。

與場景一、二相比,傳輸的延遲最大。

上文的故事,法國政府代表客戶端,負責發送大塊數據給伺服器端;美國政府是伺服器端,負責接收大塊數據;快遞公司代表TCP,負責貨物的按序提交給客戶(應用程序),負責亂序的重新排序、以及丟包的超時重傳。

簡單回答一下問題:

TCP接收方知道數據(位元組流)的起始序列號,怎麼知道的?同步握手裡有對方的ISN(Initial Sequence Number),所以會將接收到的數據按位元組排好序(1,2,3…N)放入buffer,應用程序會通過receive函數來取走,至於取走多少,取決於buffer有多少數據,以及receive函數的入口長度欄位。然後應用程序對拿到的位元組流進行解釋、分段。

續:http://www.zhihu.com/question/34003599/answer/139286798


首先需要擺正一點觀點, tcp是面向流的

1. 伺服器一次調用recv, 只收到了一個segment的數據, 這個是有可能的, 因為tcp是面向流的,沒有一次兩次的概念, 所以協議棧的層面不知道需要給你緩存多少數據才算"收齊"了

2.發了一個segment, 網路就gg了, 還是上面的說法, 協議棧不知道還有數據, 如果你再次調用recv, 如果是block模式, 那麼就是block住, 不會給你拋什麼異常

以上, 所以基於tcp的應用層協議需要做協議切割, 應用層需要想辦法從流裡面切出一個"服務單元", 常見做法, 1.固定格式, 固定長度的頭,分隔字元 2.指定長度 3.傳輸層指定, 比如一次連接.

傳輸層指定可以簡單說下, 一種做法就是, 一次服務一個連接, 客戶端發完數據主動斷開, 伺服器就一直收數據到連接斷開, 當然實際使用還有很多corner case需要處理


理論上,如果你發了100個包,第一個沒到,後面的先到了,那就都緩存著,API表現為你什麼都沒收到。要麼第一個包重發後儘快到達,要麼buffer滿了傻逼,要麼伺服器自己覺得太久了決定掉線。


這種把許多問題夾雜在一起的問法真的很難回答

簡單回答的話,在TCP的實現方面,超時重傳、快速重傳和SACK機制都能在這種情況下實現讓客戶端重新傳輸丟掉的第2個包,分別講講三種不同的機制

1. 超時重傳

客戶端通過tcp一次性把數據都發送了出去,在未收到這些發出報文的ack確認之間,這些報文仍然會緩存在發送隊列里,如果在限定的時間內(RTO 重傳過期時間)沒有收到對應報文的ack,那麼這些報文需要被重傳。

RTO不是靜態配置的,在這個問題里系統會根據前面三次握手和第一個報文的ack的RTT計算出來一個RTO,這個時間可能是200ms到120s之間的一個值,所有到了RTO時間沒有收到ack的報文都需要被重新傳送到網路上

2. 快速重傳

等待一個RTO的時間太久了,尤其是在RTT比較大的網路環境里,如果丟包率再比較高的情況,那麼TCP的效率就會非常低,所以需要通過快速重傳機制來實現優化。

簡單來說,客戶端發送的第2個報文丟了,沒有收到ack,但伺服器在收到第3、4、5個包時會檢查他們的順序編號sequence number,並察覺到與第1個包之間並不連續,於是會發送3個連續的dup ack 給客戶端,要求客戶端重新發送第1個包後面具有正確sequence number的報文。客戶端在收到三個連續的dup ack之後就會確認第2個報文丟掉了,於是實現立即重傳而不用再等到RTO的過期時間

3. SACK selective acknowledgment

如果只是丟了第2個包其實快速重傳機制就已經可以搞定了,但如果在中間丟了多個報文的時候,比如2-9之間可能丟了第3,第5和第7個報文,其他報文都已經順利到達伺服器,如果只有快速重傳,那麼客戶端需要重傳從3到第7個之間的所有報文,而如果客戶端和伺服器都支持sack的話,伺服器端就可以通過sack選項來告知發送方到底丟了哪些報文,這樣客戶端就只需要把對應丟失的報文重傳就好了

順便再說一下其他幾個問題:

1. 一次性發送9000KB,需要雙方的windows size設置都超過這個值,一般的初始window size也就2-4個mss大小,新的實現已經增加到10個mss,但設置超過9000KB的initial window size 實在太激進了,各種buffer size,timeout都可能會造成這麼大的數據沒法發送成功,如果題主把9000KB修改為9KB就靠譜多了

2. 在使用tcp socket編程時,實際上有兩個buffer的區別,一個是socket的buffer,一個是 tcp的buffer,對於應用程序而言,數據send出去只是從socket buffer拷貝到tcp buffer里,剩下的事情由系統層面的tcp協議棧來處理


TCP是一個流式設備,是一個虛擬的連接。從TCP建立連接開始,到連接正常結束,所有的數據應看成一個整體,它保證的是發送端發送的這個整體和接收端接收的整體是一致的。

例如,你整個TCP連接存在期間,發送端全部數據是 12345,不論你是怎麼發送的,123,45也好,1,234,5也好,對於tcp而言,它保證接收端接收的是12345,不論是1234,5返回也好,12,3,4,5返回也好。

而直至TCP斷開前,接收端是不知道發送端發送的是多大的數據,怎麼發送的,還有多少數據。

所以,在應用中,不應該對tcp是如何交付數據存在假設,tcp是一個協議,一個規範,而如何交付是一個庫的實現。

或許,libVczhNetwork里是每1ms交付一次,或者收滿1KB交付一次。

或許,libBillNetwork里是每640ms交付一次,或者收滿640KB交付一次。


簡短版:站在TCP協議用戶的立場上看,TCP是一種「可靠的、基於連接的流協議」。

回答完畢。

————————————————————————————

啰里啰唆版:簡短版提到的、關於TCP的定義,是幾乎任何一本不太離譜的網路書都會提到的老生常談。但是,太多人壓根就沒讀懂它。

其中,「可靠」的意思是:(物理網路正常的前提下)TCP保證用戶數據的完整性。完整性本身就包含了順序等方面的要求。

而「流」的意思則是:數據是以位元組為單位的,不存在分包概念。

打個比方的話,TCP不是快遞公司,不會一包一包的把你的包裹傳給你;它實際上有點像自來水管,發送方灌水進去,接收方一點一滴的收到它(不太一樣的地方是它保證順序),此所謂「流」。

合起來就是:不管你是一次提交100個位元組還是100000個位元組,TCP保證按發送方的原始順序把數據傳輸給接收方;但並不保證按你提交時對數據的分割方式傳輸——你可能先收到第1個位元組,然後再收到接下來的99999位元組;也可能會先收到前99999位元組,然後再讀到最後的1個位元組。

如果你看過使用TCP的成熟代碼,就會發現,作者總是以自己準備好的緩衝區大小為參數、向socket請求讀取(recv)緩衝區大小個位元組的數據;而recv的返回值如果大於0,就表示真正讀取了多少個位元組的數據——如果這個數字小於預期,你就需要等待socket有數據可讀時,再繼續讀取數據,直到補全一整個包。

當然,實踐上,尤其在用戶自定義包大小適中時,經常可以「一次讀全整個包」。具體機制很複雜,但關鍵是:如果你要基於TCP做點什麼,就只能按「可靠的、有連接的流」的方式看待它——至於TCP/IP協議棧背後搞的什麼MTU、滑動窗口什麼的,與你無關。


簡單看了下題主的問題,其實就是在問傳輸層協議如何在不穩定傳輸上實現穩定傳輸。這個在計算機網路里有很標準的演算法。

我一個學生就班門弄斧,簡單說說。

我這裡簡單的把sender等同於客戶端,reciever等同於服務端。

第一種叫stay and wait,客戶端一個packet發過去,伺服器收到並校正確的話返回一個acknowledgement(如果不嫌煩的話可以有負信號NAK,但其實不用),然後客戶端收到發出去的包的ack就接著發下一個。然後客戶端有一個timer,超時沒收到packet就會重發。

缺點很明顯,這樣速度很慢,帶寬利用率會很低。當然,樓下大神指出這個不是tcp用的,當然不是了,弱爆了,對吧。

第二種是go back n,這種是客戶端維護一個window,然後window會標記已經發出並收到ack的數據的sequence number, base;發出了但是沒有收到ack數據的sequence number, nextseqnum;還有那些落在窗外,還沒發的包。同樣客戶端有個timer,發包或者收到ack的時候做檢查,如果base等於nextsequence(即沒有發出去還沒有沒收到ack的包)則啟動(重置)timer; 如果收到了一個seq的ack,這個ack對應的seq的值大於base,那麼就更新base的值為seq+1。如果這個值等於nextseqnum,則停止timer(表示上一組已經傳完)。

伺服器這邊,只有收到一個seq number正確的(意味著順序是對的)且校驗完好的包才會回復這個包的ack,否則會把包直接扔掉,發上一個正確收到的包的ack。

關於超時的話,如果客戶端timer超時會重新發所有從base以來到nextseqnum-1的包。

這樣相比第一種提高了利用率然而實際上會造成大量重傳然後會把管道填得滿滿的,效率還是不高,如果你的base包一開始就掛了,後面基本上就是全部的包都傳了一遍。

第三種是selective repeat, 這裡伺服器本身也會有一個窗,和客戶端類似,也就是說會有個buffer去緩存收到的數據,不至於順序亂了就要從頭開始傳。

至於丟包的話,這三種是可以解決的因為你的包頭裡有seq。當然有種情況是客戶端發太快以至於接收端的buffer被淹沒了,這種丟包可能是不可恢復的。但是,現在tcp協議包括底層的協議有很多控制流的措施以避免這種情況。

分段的話tcp應該是可以指定segment大小的吧我記得。

瞎寫的,不對請多多指教。


強答

乙太網好像mtu是1500,所以9000byte會被切成幾塊。但是具體每塊切多少要根據整個網路最小mtu可能會被多次切。每個都會附上ip協議頭。

在網路層。

被切的報文附上ip協議頭之後,經過路由等等,發送到目的ip。然後根據ip頭恢復被切報文。然後將(ip頭+tcp頭+報文)發送到網路層。tcp協議則將上傳到網路層的報文根據(ip頭和tcp 頭 也就是source ip +destination ip + source port + destination port + protocol version ) 來轉發到相應的file 下的緩衝區。

tcp協議 好像有16位是可以計算報文序號的。

超時重傳,和窗口什麼的都是靠tcp頭的序號來處理的。

具體看《tcp ip 協議詳解卷2》卷1也行


謝謝大家的回答,我基本已經有了我要的答案,我問這個問題是因為我的服務端每次只Receive(buffer)一次客戶端的數據就斷開它的連接(每次收的只有41位元組),但是發現收到的數據里有時會有10位元組、39位元組等數據。故來問下只一次Receive(buffer)有沒有可能只收到了客戶端發送的41個位元組的一部分(即被分包成10、31兩個包等情況但是我Receive(buffer)只收到了前面的也就是10位元組的包s)。對大家答案的總結是:只一次Receive(buffer)哪怕buffer大於對方Send的數據大小也是有可能只收到客戶端發送的數據的前面一部分(若先收到的是後面的包會等待而不能Receive(buffer)成功[或說不被Recv發現「收到了」數據],只有收到的包s[複數]是連續的且它們前面的包都已收到[後面的包可以不管,因為TCP是Stream方式]就能被Receive(buffer)發現並接收),故服務端不能只Receive(buffer)一次(除非協定的數據是1個位元組。。。。)而應循環讀取,然後自己判斷是否已經接收完整。故:TCP只能保證收到的數據是連續的且連續部分的正確性但不能保證它對於人而言的完整性。


更新下:Wireshark抓的包 寫成fiddler了

------------

tcp是stream有排隊的概念。這個不贅述了。

其實我也有疑問,是關於MSS和tcp傳輸大塊數據時的。

問題1:用Wireshark抓過,比如:tcp建立時,MSS是1000(假設),實際傳輸中一次發送的數據長度是超過這個值的,不過是MSS的整數倍(這個應該不是巧合)。

問題2:Tcp傳輸大塊數據時,發送和應答不是一一對應的,但傳輸完成後seq和ack還是統一了。

具體表現為:假如發送1W位元組,建立Tcp時確認MSS=1000。實際上總共發了4條報文,第一條1000位元組,第二條2000位元組,第三條3000,第四條4000。而應答可能有6次,第一次應答ack=1001,第二次2501,第三次4001,第四次6001,第五次8001,第六次10001。大致如上,表述不是很清楚。來張圖吧。


tcp對上層提供了一種可靠的,面向流的傳輸。沒有一包,二包,幾包的概念。客戶端和服務端要進行通信,三次握手建立起連接後,就是流式的傳輸數據,不管發大的數據還是小的數據,都是流式的傳輸的,你可以想像成水管,水管的一頭是客戶端,另一頭是服務端,服務端是個水池,代表了操作系統內核的buffer,服務端的應用程序recv就從池子里取數據。假設一次取了100位元組的數據,就return100。沒有數據就等,有了就取,直到連接斷開,也就是管子撤了為止。


TCP:

Flow control: 接受端通過報文的欄位告訴發送端我還有多大的buffer(你可以傳多少)。如果整個線路的BW*delay遠超接受端的buffer大小的話,有些網路支持在握手的時候交換一個倍數的控制字。

Congestion control: 常見的兩種應該是Tahoe和Reno。Tahoe只有兩個階段,slow start和Congestion avoidance,ss超過設置的水線了就進去ca階段,ca發現timeout或者三個duplicated ACK就重回ss。Reno和Tahoe的區別是,它能區分timeout的情況和三個duplicated ACK的情況,如果是前者,則跳回ss階段,如果是後者則進入Fast Recovery。各個階段的窗口變化和水線變化都不相同,建議去網上找狀態機的變化圖來看。

發送端的發送窗口取Flow Control和Congestion Control得到的兩個窗口的最小值。單位是MTU。


會,自己用FTP下載幾g的文件,就會發現了


會重發的。


超時丟包後重傳和鏈接斷了丟包,上面有個回答很好,保證了雙端一致,但是數據正確與否你要自己檢驗


可以看一下 滑動窗口協議 以及 tcp的重傳機制。

個人學藝不精,就不深入討論了。

願對你有益。


tcp只保證雙端數據順序一致,不保證雙端交付一致。frame問題是應用層協議考慮的問題。


是會造成丟包的。影響其丟包的原因最主要是網速過慢。就像其他回答所說的,tcp協議的sender會將data按照package的形式發送。並且每個package有sequence number。這就讓receiver可以按照sequence number來ack(所謂的ack就是發送收到某個包的信息)。這裡就要提到window size。window size是指sender單次發送的包的數量。通常window size會不斷提高,直至出現time out的情況,然後減至原先window size的一半繼續增加。這也就是為什麼我們bandwidth的實際使用率會比正常使用率低的原因。言歸正傳。假如sender發送1 2 3 4 5 這幾個包。因為tcp/ip協議以及各種監聽的邏輯,receivor可能會按照4 2 1 3 5的順序收到這些包。從而返回ack5(返回連續sequence number最大的。但是因為window size會不斷提升至timeout的情況。那麼如果五個包就time out且假如receiver沒有收到第三個包和第五個包。那麼它會返回ack2。(因為ack3並沒有收到)。然後sender知曉,重新發送3 4 5。 然後receiver接受後繼續ack。

謝謝


這也叫個問題,tcp下socket會丟包么。。。。上下層都搞反了,這種問題自己刪掉比較好


推薦閱讀:

誰能幫我系統的講解下TCP/IP,HTTP,Socket,Servlet,他們之間的邏輯關係,系統點?
多線程網路程序有什麼好的調試方法?
如何思考並解決網路編程中的問題?
windows 利用socket實現從http下載?
想問下php的socket的工作流程是什麼?

TAG:網路傳輸 | Socket | TCPIP | TCP | socketio |