1. TCP簡介
傳輸控制協議(TCP,Transmission Control Protocol)是一種面向連接的、可靠的、基于字節流的傳輸層通信協議。
1.1 TCP報頭
1、源/目的端口號: 表示數據是從哪個進程來, 到哪個進程去
2、6位標志位:
- URG: 緊急指針是否有效
- ACK: 確認號是否有效
- PSH: 提示接收端應用程序立刻從TCP緩沖區把數據讀走
- RST: 對方要求重新建立連接; 我們把攜帶RST標識的稱為復位報文段
- SYN: 請求建立連接; 我們把攜帶SYN標識的稱為同步報文段
- FIN: 通知對方, 本端要關閉了, 我們稱攜帶FIN標識的為結束報文段
3、16位校驗和: 發送端填充, CRC校驗. 接收端校驗不通過, 則認為數據有問題. 此處的檢驗和不光包含TCP首部, 也包含TCP數據部分.
4、16位緊急指針: 標識哪部分數據是緊急數據
對于TCP報頭主要講解幾個重要部分:
4位首部長度,首部,和選項之間的關系:
- 4位TCP報頭長度: 表示該TCP頭部有多少個32位bit(有多少個4字節); 所以TCP頭部最大長度是15 * 4 = 60(15即1111)。但是四位首部長度不可以是0,即最小首部長度是20,如果長度比20大,則將多出來的長度填到選項中,以保證報頭和有效數據的分離。所以他們之間的關系就是:選項是對超過首部長度的補充,而根據四位首部長度可以計算出報頭總長度大小。(0<=選項<=40)。
32位序號和32位確認序號分別是干嘛的?
- 首先TCP是基于確認應答機制的,即主機A給主機B發送了一則消息,當收到主機B的確認信息以后,才保證主機A發的信息主機B收到了,這里就可以提出可靠性的概念,可靠性又可以分為相對可靠性和絕對可靠性,網絡傳輸數據是無法保證絕對可靠的,因為總有一條最新的消息沒有被確認。
- 32位序號即主機A對主機B發送消息時由于是面向字節流的,會將原始報文進行分割,則分割的時候就需要對報文進行編上序號,以保證在網絡中傳輸時將順序打亂時主機B可以按照序號讀到正確的報文順序,(按序到達)。32位確認序號則是主機B收到來自主機A的100號消息時,則要給主機A返回應答消息101,代表前100已經收到,下次發送從101號開始發。即確認應答機制。保證雙方有序正常通信。
2. 確認應答機制
TCP通過肯定的確認應答(ACK)實現可靠的數據傳輸。當發送端將數據發出之后會等待對短的確認應答。如果有確認應答,說明數據已經成功到達對端,反之,則數據丟失的可能性很大
TCP將每個字節的數據都進行了編號. 即為序列號。
每一個ACK都帶有對應的確認序列號, 意思是告訴發送者, 我已經收到了哪些數據; 下一次你從哪里開始發。
3. 超時重傳機制
重發超時是指在重發數據之前,等待確認應答到來的那個特定時間間隔。如果超過了這個時間仍未收到確認應答,發送端將進行數據重發。
情況一:丟包
- 主機A發送數據給B之后, 可能因為網絡擁堵等原因, 數據無法到達主機B;
- 如果主機A在一個特定時間間隔內沒有收到B發來的確認應答, 就會進行重發。
情況二:ACK丟失
- 主機B會收到很多重復數據. 那么TCP協議需要能夠識別出那些包是重復的包, 并且把重復的丟棄掉.這時候我們可以利用前面提到的序列號, 就可以很容易做到去重的效果.
4. 連接管理機制
連接管理機制主要是講三次握手和四次揮手,在之前的博文中已經有詳細的講解。
這里主要講面試高頻問題:
三次握手:
一次,兩次,四次或者更多次握手行不行?
- 一次兩次不行,比如給服務器發送大量請求,會被攻擊(洪水攻擊)。三次握手,是對通信信道的驗證,讓客戶端和服務器都驗證了接受和發送能力正常,用最小成本驗證全雙工。第一次和第二次握手丟失,不用擔心,因為雙方都不會確立連接,最擔心第三次握手丟失,客戶端認為握手成功。四次握手,如果最后一次握手失敗,最擔心,服務器認為握手成功, 不管幾次握手,都擔心最后一次。最擔心最后一次握手丟失,應該讓客戶端背鍋,免得服務器是一對多的,有大量的無用連接,而浪費資源。讓服務器不要出現連接建立誤判的情況,減少服務器的資源浪費??隙ú粫门紨荡危嗥鏀荡问强梢缘?,但是要最小成本。
TCP的三次握手是否都可以攜帶數據?
- 帶SYN的是不可以攜帶數據的第一次和第二次是不可以攜帶數據的,但是第三次是可以攜帶數據的。
- 假如第一次握手可以攜帶數據的話,那對于服務器太危險,如果惡意攻擊服務器,每次都在第一次握手中的SYN報文中放入大量數據。而且頻繁重復發SYN報文,服務器會花費很多的時間和內存空間去接收這些報文。
- 第三次握手,此時客戶端已經處于ESTABLISHED狀態。對于客戶端來說,他已經建立起連接了,并且已經知道服務器的接收和發送能力是正常的。所以也就可以攜帶數據了。
TCP三次握手失敗,服務端會如何處理?
握手失敗的原因有兩種:
- 第一種是服務端沒有收到SYN,則什么都不做。
- 第二種是服務端回復了SYN+ACK后,長時間沒有收到ACK響應。server端發送了SYN+ACK報文后就會啟動一個定時器,等待client返回的ACK報文。如果第三次握手失敗的話client給server返回了ACK報文,server并不能收到這個ACK報文。那么server端就會啟動超時重傳機制,超過規定時間后重新發送SYN+ACK,重傳次數根據/proc/sys/net/ipv4/tcp_synack_retries來指定,默認是5次。如果重傳指定次數到了后,仍然未收到ACK應答,那么一段時間后,server自動關閉這個連接。但是client認為這個連接已經建立,如果client端向server寫數據,server端將以RST包響應。
什么是半連接隊列?
- 服務器第一次收到客戶端的SYN之后,就會處于SYN_RECD狀態,此時雙方還沒有完全建立連接。服務器會把這種狀態下的請求連接放在一個隊列里,我們把這種隊列稱之為半連接隊列。當然還有一個全連接隊列,就是已經完成三次握手,建立起來連接的就會放在全連接隊列中,如果隊列滿了就有可能出現丟包現象。
四次揮手:
當客戶端想要斷開連接時,并不是徹底和服務器斷開了,是指應用層不會發數據了。客戶端有四次狀態,服務器有三次狀態。
為什么握手是三次,而揮手時需要四次呢?
- 其實在TCP握手的時候,接收端將SYN包和ACK確認包合并到一個包中發送的,所以減少了一次包的發送。對于四次揮手,由于TCP是全雙工通信,主動關閉方發送FIN請求不代表完全斷開連接,只能表示主動關閉方不再發送數據了。而接收方可能還要發送數據,就不能立即關閉服務器端到客戶端的數據通道,所以就不能將服務端的FIN包和對客戶端的ACK包合并發送,只能先確認ACK,等服務器無需發送數據時在發送FIN包,所以四次揮手時需要四次數據包的交互。
CLOSE_WAIT狀態詳談:
- 出現CLOSE_WAIT,說明Server端沒有發起close()操作,這基本上是用戶server端程序的問題了;通常情況下,Server都是等待Client訪問,如果Client退出請求關閉連接,server端自覺close()對應的連接,當服務器接受到來自客戶端FIN的斷開請求時,服務器進入CLOSE_WAIT狀態,并發送ACK,直到服務器想要斷開連接時,發送FIN和ACK斷開請求,并轉變為LAST_ACK狀態,如果用netstat查看有大量的CLOSE_WAIT狀態說明服務器代碼有BUG。
- 對于服務器上出現大量的 CLOSE_WAIT 狀態, 原因就是服務器沒有正確的關閉 socket, 導致四次揮手沒有正確完成. 這是一個 BUG. 只需要加上對應的 close 即可解決問題
TIME_WAIT狀態詳談:
- 首先調用close()發起主動關閉的一方,在發送最后一個ACK之后會進入time_wait的狀態,也就說該發送方會保持2MSL時間之后才會回到初始狀態。MSL指的是是數據包在網絡中的最大生存時間。
- 確保最后一個確認報文能夠到達。進而盡快釋放服務器的資源。如果沒能到達,服務端就會會重發FIN請求釋放連接。等待一段時間沒有收到重發就說明服務的已經CLOSE了。如果有重發,則客戶端再發送一次ACK信號。
- 等待一段時間是為了讓本連接持續時間內所產生的所有報文都從網絡中消失,使得下一個新的連接不會出現舊的連接請求報文。
- 如果沒有TIME_WAIT的話,假設連接1已經斷開,然而其被動方最后重發的那個FIN(或者FIN之前發送的任何TCP分段)還在網絡上,然而連接2重用了連接1的所有的5元素(源IP,目的IP,TCP,源端口,目的端口),剛剛將建立好連接,連接1遲到的FIN到達了,這個FIN將以比較低但是確實可能的概率終止掉連接2
解決TIME_WAIT狀態引起的bind失敗的方法:
在server的TCP連接沒有完全斷開之前不允許重新監聽, 某些情況下可能是不合理的
- 服務器需要處理非常大量的客戶端的連接(每個連接的生存時間可能很短, 但是每秒都有很大數量的客戶端來請求).
- 這個時候如果由服務器端主動關閉連接(比如某些客戶端不活躍, 就需要被服務器端主動清理掉), 就會產生大量TIME_WAIT連接.
- 由于我們的請求量很大, 就可能導致TIME_WAIT的連接數很多, 每個連接都會占用一個通信五元組(源ip,源端口, 目的ip, 目的端口, 協議). 其中服務器的ip和端口和協議是固定的. 如果新來的客戶端連接的ip和端口號和TIME_WAIT占用的鏈接重復了, 就會出現問題.
使用setsockopt()設置socket描述符的選項SO_REUSEADDR為1, 表示允許創建端口號相同但IP地址不同的多個socket描述符:
5. 滑動窗口
如果出現了丟包, 那么該如何進行重傳呢?
分兩種情況討論:
1、數據包已經收到, 確認應答ACK丟了:
2、數據包丟失:
6. 流量控制
那么接收端如何把窗口大小告訴發送端呢?
- TCP首部中, 有一個16位窗口大小字段, 就存放了窗口大小的信息;16位數字最大表示65536, 那么TCP窗口最大就是65536字節么?
- 實際上, TCP首部40字節選項中還包含了一個窗口擴大因子M, 實際窗口大小是窗口字段的值左移 M 位(左移一位相當于乘以2).
7. 擁塞控制
如果網絡上的延時突然增加,那么TCP對這個事作出的應對只有重傳數據,但是重傳會導致網絡的負擔更重,于是會導致更大的延遲以及更多的丟包,于是這個情況就會進入惡性循環被不斷地放大。試想一下,如果一個網絡內有成千上萬的TCP連接都這么行事,那么馬上就會形成“網絡風暴”,TCP這個協議就會拖垮整個網絡。所以TCP不能忽略網絡上發生的事情,而無腦地一個勁地重發數據,對網絡造成更大的傷害。對此TCP的設計理念是:TCP不是一個自私的協議,當擁塞發生的時候,要做自我犧牲。就像交通阻塞一樣,每個車都應該把路讓出來,而不要再去搶路了。
少量丟包, 僅僅是觸發超時重傳; 大量丟包, 認為網絡擁塞;
當TCP通信開始后, 網絡吞吐量會逐漸上升; 隨著網絡發生擁堵, 吞吐量會立刻下降;
擁塞控制, 歸根結底是TCP協議想盡可能快的把數據傳輸給對方, 但是又要避免給網絡造成太大壓力的折中方案。
8. 延遲應答
我們知道TCP中,有確認應答機制以保證數據的可靠傳輸。但是是不是接受方接受到數據就立即返回ACK應答呢?如果是這樣,這時候的緩沖區中接收區的數據還沒能夠處理,緩存區的剩余大小就是窗口大小。但是如果我們延遲一會,等待緩存區中數據被處理,那么剩余的緩存區就會大些——這就是延時應答。
ps:假設接收端緩存區大小為1M,一次接收到了500K的數據,現在緩存區中剩余大小為500。但如果我們延時一段時間,等待接受方處理了該緩存區中的數據,處理以后接受窗口變大,那么我們的剩余大小就為1M了(即:窗口大?。?/p>
窗口越大, 網絡吞吐量就越大, 傳輸效率就越高. 我們的目標是在保證網絡不擁塞的情況下盡量提高傳輸效率。
等待的時間
- 每個操作系統中設置的等待時間是不一樣的。(200ms)
是不是所有的包都可以延時應答?
- 數量限制:每隔兩個包就應答一次
- 時間限制:超過最大延時時間就應答一次(200ms)
具體的數量和超時時間, 依操作系統不同也有差異; 一般N取2, 超時時間取200ms;
9. 捎帶應答
捎帶應答是指在同一個TCP包中即發送數據又發送確認應答的一種機制。由此,網絡的利用率會提高,計算機的負荷也會減輕。不過,確認應答必須等到應用處理完數據并將作為回執的數據返回為止,才能進行捎帶應答。
10. 面向字節流
11.粘包問題
- 首先要明確, 粘包問題中的 “包” , 是指的應用層的數據包.
- 在TCP的協議頭中, 沒有如同UDP一樣的 “報文長度” 這樣的字段, 但是有一個序號這樣的字段.
- 站在傳輸層的角度, TCP是一個一個報文過來的. 按照序號排好序放在緩沖區中.
- 站在應用層的角度, 看到的只是一串連續的字節數據.
- 那么應用程序看到了這么一連串的字節數據, 就不知道從哪個部分開始到哪個部分, 是一個完整的應用層數據包.
那么如何避免粘包問題呢? 歸根結底就是一句話, 明確兩個包之間的邊界.
- 對于定長的包, 保證每次都按固定大小讀取即可; 例如上面的Request結構, 是固定大小的, 那么就從緩沖區從頭開始按sizeof(Request)依次讀取即可;
- 對于變長的包, 可以在包頭的位置, 約定一個包總長度的字段, 從而就知道了包的結束位置;
- 對于變長的包, 還可以在包和包之間使用明確的分隔符(應用層協議, 是程序猿自己來定的, 只要保證分隔符不和正文沖突即可);
思考: 對于UDP協議來說, 是否也存在 “粘包問題” 呢?
- 對于UDP, 如果還沒有上層交付數據, UDP的報文長度仍然在. 同時, UDP是一個一個把數據交付給應用層. 就有很明確的數據邊界.
- 站在應用層的站在應用層的角度, 使用UDP的時候, 要么收到完整的UDP報文, 要么不收. 不會出現"半個"的情況
- TCP異常情況
- 進程終止: 進程終止會釋放文件描述符, 仍然可以發送FIN. 和正常關閉沒有什么區別.
- 機器重啟: 和進程終止的情況相同.
- 機器掉電/網線斷開: 接收端認為連接還在, 一旦接收端有寫入操作, 接收端發現連接已經不在了, 就會進行reset. 即使沒有寫入操作, TCP自己也內置了一個?;疃〞r器, 會定期詢問對方是否還在. 如果對方不在, 也會把連接釋放.
另外, 應用層的某些協議, 也有一些這樣的檢測機制. 例如HTTP長連接中, 也會定期檢測對方的狀態. 例如QQ, 在QQ斷線之后, 也會定期嘗試重新連接。
13. 基于TCP應用層協議
- HTTP
- HTTPS
- SSH
- Telnet
- FTP
- SMTP
也包括自己寫TCP程序時自定義的應用層協議。
14. TCP小結
為什么TCP這么復雜? 因為要保證可靠性, 同時又盡可能的提高性能.
可靠性:
- 校驗和
- 序列號(按序到達)
- 確認應答
- 超時重發
- 連接管理
- 流量控制
- 擁塞控制
提高性能:
- 滑動窗口
- 快速重傳
- 延遲應答
- 捎帶應答
其他:
- 定時器(超時重傳定時器, ?;疃〞r器, TIME_WAIT定時器等。
-
數據
+關注
關注
8文章
7033瀏覽量
89040 -
TCP
+關注
關注
8文章
1353瀏覽量
79078 -
應用程序
+關注
關注
37文章
3268瀏覽量
57710 -
應用層協議
+關注
關注
0文章
5瀏覽量
6350
發布評論請先 登錄
相關推薦
評論