TCP協議是網絡多層協議中運輸層的最重要的協議之一,運輸層是兩台主機的進程之間的通信。除了TCP還有一個是UDP協議(用戶數據包協議)
TCP全稱是Transmission Control Protocol,意思是傳輸控制協議
一、TCP簡介
1.TCP協議兩個對等運輸實體之間進行傳送的數據單位是:TCP報文段
2.TCP提供的是面向連接的服務,在傳送數據之前必須建立連接,數據傳送完成之后需要關閉連接,TCP只可點對點,不可廣播或多播,TCP連接是可靠的運輸服務。
3.TCP的工作方式類似於打電話,打電話之前需要先撥號(號碼就是連接的IP+端口號)連接,通話結束之后關閉連接
4.TCP提供可靠交付,即TCP傳輸的數據無差錯、不丟失、不重復且有序
5.TCP支持全雙工通信,即通信雙方可隨時發送數據,發送方發送完數據會先放到發送緩存中,發送方發送完畢就可以干別的事去了,TCP會在合適的時機將數據發送給接受方,接收方接收到數據會先把數據放到接收緩存中,應用程序會在合適的時機在緩存中獲取數據。
6.TCP是面向字節流的,流入到進程或從進程中流出的是字節序列。而發送時和接收時除了傳輸的業務數據,可能還會額外加一些字節數據,用於發送方和接收方處理。比如在微信聊天中需要提醒接收方會添加@XXX,而接收方只需要接收有用信息,@信息是和業務無關的。
7.TCP連接的雙方不是兩個主機、不是兩個IP地址、不是兩個應用程序、而是兩個套接字,每個套接字socket=(IP地址:端口號),每一個TCP連接必須有唯一的兩個套接字,即TCP連接={套接字1,套接字2}={(IP1,port1),(IP2,port2)}
二、TCP三次握手和四次揮手
TCP是面向連接的協議,所以協議的基礎就是需要有連接,而發起連接的一方可以稱為客戶端,等待連接的一方可以稱為服務端。而TCP建立連接的過程稱為握手,每次握手客戶端和服務器之前需要交換三個報文段,因此也叫做“三次握手”。
在RFC973(TCP標准文檔)中使用的名稱是three way handshake,這里的handshake沒有用復數,所以三次握手不太准確,應該叫三次報文握手,因為人與人之間握手的意思就是已經連接上了,手上搖晃了三次之后才最終握上,所以准確點說應該是握手成功了一次。但是三次握手的說法比較普遍,所以本文也叫做三次握手。在理解TCP連接之前,先來了解一些常量的含義
SYN:發起一個連接,同步位
FIN:關閉一個連接
ACK:確認連接有效
RST:重置連接
seq:序號,每次發送一次數據,seq=上一個seq+1
ack:應答序號
上圖是TCP連接三次握手示意圖
第一次握手:A向B發送連接請求報文,同步完syn=1,初始化序號seq=X,TCP協議規定syn=1的報文不允許攜帶數據,所以SYN=1即表示發起連接請求,當A發送第一次握手之后,A就進入了SYN-SENT狀態,表示同步已發送
第二次握手:B接收到A發送的請求報文,如果同意連接,則向A發送確認。發送同步報文段SYN=1,seq=y,確認報文段ACK=1,確認序號ack=x+1。此時服務端進入SYN-RCVD狀態,表示同步已接收
第三次握手:A接收到B的確認報文,再向B發起確認,設置ACK=1,確認序號seq=x+1,ack=y+1,此時TCP連接完成,雙方都進入ESTAB-LISHED狀態,表示連接以建立。
那么為什么需要三次,而不是兩次就行,還需要最后再發送一次確認呢?或者說如果沒有最后一次確認會發送什么?看看下面這種情況:
A發出連接請求,但是在網絡中長時間滯留了而沒有發送到B,此時由於A久久沒有接收到B發送過來的確認請求,所以A就將連接釋放了,在A釋放了連接之后B又接收到了A發送過來遲到的連接請求,由於B不知道此時A已經釋放了連接了,所以B認為是有效的連接,
此時B向A發送確認,同意建立連接(如果只要兩次握手,那么此時B就已經進入了ESTAB-EISHED狀態),此時A已經釋放了連接,所以接收到B的確認請求也不會理睬,但是B還是認為連接有效而一直等待着A發送數據,就會導致B持有無效的連接而浪費資源。
而如果采用三次握手,那么B在發送完確認之后,只會進入到SYN-RCVD狀態,並不會建立連接的等待數據的發送,除非A再次發送了確認請求。
TCP連接是雙向的,所以TCP連接的釋放是雙方都可以發起的,以A為例發起釋放連接,需要經過四次揮手,過程如下圖示:
在連接釋放之前,A和B都是出於ESTAB-LISHED狀態,且可以數據傳輸,此時A開始進行關閉
第一次揮手:A向B發送關閉連接請求,FIN=1,seq=u(u的值等於數據傳輸的最后一個請求的序號+1),此時A進入FIN-WAIT-1狀態,等待B確認。TCP規定FIN報文不可攜帶數據
第二次揮手:B接收到A的關閉連接請求,並發出關閉確認,ACK=1,seq=v(v的值等於B傳送的最后一個字節的序號+1),ack=u+1,然后B進入CLOSE-WAIT(等待關閉)狀態,且需要通知應用進程A需要關閉連接,不再發送數據過來了。
此時TCP連接進入半關閉狀態,也就是A-B的連接已經關閉,B-A的連接還沒有關閉,但A接收到B的關閉確認之后進入FIN-WAIT-2狀態,等待B發送關閉連接報文
第三次揮手:若B沒有數據再發送給A,則向A發送關閉連接報文,FIN=1,ACK=1,seq=w(w的值等於B傳送的最后一個字節的序號+1),ack=u+1,並且B進入LAST-ACK(最后確認)狀態,等待A的確認
第四次揮手:A收到B的關閉連接報文之后,再向B發送一次確認報文,ACK=1,seq=u+1,ack=w+1,並且進入到TIME-WAIT(時間等待)狀態,需要等待2MSL時間之后才會進入到CLOSED狀態,而B接收到關閉確認報文之后就直接進入CLOSED(關閉)狀態。
這里有兩個問題,一是為什么需要有四次握手,而不是兩次或三次?二是A在發送關閉確認請求之后,為什么還需要等待2MSL長時間才進入CLOSED狀態?(MSL一般是2分鍾,2MSL就是4分鍾)
回答一:和三次握手一樣,揮手只用兩次肯定是不夠用的,那三次為什么也不夠呢,因為第一次關閉連接是A向B發起的,表示A不會發送數據給B了,但是此時可能B還有數據需要傳送給A,如果B在數據還沒有傳輸完成就通知A,則數據就會丟失;如果B在數據傳輸完成之后再通知A,那么此時可能已經超時了,導致A以為發起的關閉連接失效了就會重新發送多次的關閉連接。而四次揮手比三次握手多的一次報文是B向A發送的關閉連接請求,表示B也已經不會發送數據給A了,這樣就確保了雙方都沒有數據發送了,且都對對方的關閉連接做了關閉確認。
回答二:設置TIME-WAIT狀態等待2MSL主要有兩個原因:
原因1:防止第四次揮手丟失,如果A在發送第四次揮手報文之后馬上進入CLOSED狀態,而B有可能沒有接收到這個確認報文,就會一直處於LAST-ACK狀態,在超時之后B就會重發關閉連接報文,而此時A已經進入CLOSED狀態了,就會導致B無法進入CLOSED狀態。
而設置了TIME-WAIT狀態之后,A會等待2MSL時間,如果B重發了FIN+ACK報文,A還是可以接收到並且發出關閉確認報文,直到B進入CLOSED狀態。
原因2:確保A最后發送的報文在網絡中消失,MSL表示報文的最長壽命,達到時間報文就會失效,所以A等到2MSL時間之后,報文無論是否被B接收到肯定是會失效,這樣就確保了此報文不會影響到下一個新的連接。而如果沒有TIME-WAIT狀態,而是A直接進入CLOSED狀態,然后A又再次和B通過三次握手建立了連接,此時A最后發送的關閉確認報文可能還在網絡中傳輸,就會出現舊的報文段出現的新的連接中。
三、TCP可靠傳輸
tcp一大特性就是可靠傳輸,而網絡傳輸過程中,很容易就會出現丟失、重復、錯誤及延遲的問題,那么TCP能保證數據的傳輸過程不會丟失、不會重復、不會亂序,是如何達到的呢?主要有以下四點:
3.1、確認和重傳機制(ack機制)
TCP連接的雙方發送報文之后,必須等待接收方的ACK,否則就認為這個報文丟失了而重傳報文,比如發送方發送了報文seq為x,則接收方會返回一個ack=x+1。另外發送方發出報文之后會啟動一個計時器(RTO),計時時間到了還沒有收到ack就會重新發送。另外接收方還會對接收到的報文進行校驗,如果校驗不通過會再ack上返回異常的報文seq,這樣發送方就會重傳這個seq。(ack可以優化成多個報文合並ack一次,只需要ack最后一條報文即可,可以減少ack次數)
3.2、數據排序機制(seq機制)
每條報文都包含一個seq序列號,這個序列號會從初始的seq值上進行遞增,如果發送方發送seq=x,則接收方的ack=x+1,然后發送方下一次的報文seq就等於x+1。而如果seq=x+n的報文丟失了,發送方就很清楚是哪一個報文丟失了,從而可以重傳。而接收方可以根據seq來對報文進行排序,並且可以丟棄掉重復的報文。
3.3、流量控制(滑動窗口機制)
發送方維持一個滑動窗口來控制發送方報文發送,防止發送方報文發送太快而導致接收方無法及時處理,所以將報文分成四類:
1.發送完成且已經確認的
2.發送完等待確認的
3.等待發送且允許發送的
4.窗口被占滿暫時不允許發送的
窗口的左邊在1-2類之間,右邊在3-4類之間,通過滑動窗口來進行流量控制有三種方法,分別如下:
1.停等協議:窗口大小為1,即每次只可發送一個報文,等確認之后才可發送下一個報文
2.后退N步協議:窗口大小大於1,每次可以發送多個報文,當接收方連續發送三次連續的同一個序號的ack,則表示這個序號的報文丟失了,則發送方從這個序號開始到后面的所有報文都重發一次。比如丟失的是7,那么7后面的8,9,10...都會再重發一次
3.選擇重傳協議:選擇重傳是后退N步都優化版,即只重發丟失的報文
3.4、擁塞控制(慢啟動、擁塞避免、快速重傳、快速恢復)
當網絡出現擁塞情況,這時發送方和接收方就會頻繁的發生丟包報文以及重發的報文,這樣就會使得本來就堵塞的網絡變得更加的堵,如果不加控制就可能導致網絡崩潰了。所以tcp添加來擁塞控制,通過控制擁塞窗口(cwnd)的大小和擁塞閾值(ssthreshold)的大小來進行擁塞控制
a、慢啟動
此時一般是(記住是一般情況)cwnd<ssthreshold,此時cwnd呈指數形式增長,1、2、4、8、16、32...這種增長趨勢
b、擁塞避免
此時一般cwnd>ssthreshold,此時cwnd呈線性增長,32、33、34、35...這種增長趨勢
c、擁塞解決
此時一般是遇到了網絡擁塞的狀況,解決方法是擁塞閾值乘性減即ssthreshold=cwnd/2,cwnd=1,或者ssthreshold=cwnd/2,cwnd=ssthreshold,這兩種情況在后面說明
d、快速恢復
一般是啟用擁塞結局策略之后,根據不同的情況,進入慢啟動或者擁塞避免階段。
3.5、Nagle算法
Nagle算法是為了提高TCP的傳輸效率的算法,比如當用戶只發送一個字節的數據包時,再加上TCP頭部20個字節,IP頭部20個字節,就需要傳輸41個字節大小的報文,很顯然這樣的傳輸效率就比較浪費網絡資源。
所以需要采用一些優化方式
1.TCP將待發送的數據進行緩存,等待緩存的數據達到最大報文長度MSS時再一次性發送
2.設置定時器,比如200毫秒發送一次
3.客戶端主動控制何時發送
所以就產生了Nagle算法:Nagle算法就是綜合了上述的第一種和第二種方式,如果數據達到一定的大小之后就立即發送;或者就等待超時時間200毫秒才會進行發送
雖然使用Nagle算法可以提供TCP的傳輸效率,但是降低了TCP傳輸的實時性,對於實時性要求較高的系統需要禁用nagle算法,否則即使網絡狀況良好也會產生延遲現象。