咱們知道Ip層包裹着tcp報文段把它從源Ip運送到目的Ip,若是過程當中出現差錯(16位的Ip檢驗和錯誤),Ip協議會直接丟棄該數據報而且不生成差錯報文。這種狀況tcp會發現數據丟失並進行重傳。
這篇文章想探討一下TCP協議是經過什麼方式作到這些的,曾經作過設計師的我忍不住抄起老本行畫兩張圖。前端
URG: 緊急指針
ACK: 確認序號有效
PSH: 儘量快地將數據送往接收進程
RST: 重建鏈接
SYN: 同步序號用來發起一個鏈接
FIN: 發端完成發送任務
算法
我畫了一張圖來描述TCP鏈接的過程和狀態變化: 緩存
服務器端默認處於 LISTEN
狀態監聽着某個端口。當客戶端想要鏈接這個端口的時候,客戶端首先會隨機生成一個初始序號(圖中的j),首部中的32位序號(如下簡稱序號)就變成j+1,同時首部中的SYN由0置爲1。客戶端發送完帶有這些首部的TCP段後狀態變爲 SYN_SENT
。服務器
當服務器收到客戶端第一個帶有SYN的TCP段的時候,客戶端由 LISTEN
狀態變爲 SYN_RCVD
狀態,併發送一段數據,其中首部ACK置爲1,32位確認號(如下簡稱確認號)爲客戶端發過來的序號(j)+1,含義就是客戶端的數據已經收到。同時SYN也會置爲1,序號爲k。含義是我也要與你創建鏈接。網絡
當客戶端收到服務器的ACK後,狀態變動爲 ESTABLISLISHED
,同時發送數據,首部ACK爲k+1,含義是服務器的數據已經收到。這時客戶端已經創建鏈接,而服務器端是否能創建鏈接取決於它可否收到當前客戶端發送的這段帶有ACK的數據。若是服務器收到的話,它的狀態也會變成 ESTABLISLISHED
。
到此,鏈接創建,能夠繼續交換數據。前端工程師
在不考慮斷電等特殊狀況下,主動關閉的一方(圖中爲客戶端但不老是)發送FIN+序號m,狀態變動爲 FIN_WAIT_1
。併發
被動關閉的一方(圖中爲服務器但不老是)收到後狀態變動爲 CLOSE_WAIT
。同時裏當即發送確認號m+1,頭部ACK置爲1,這時服務端狀態變動爲 LAST_ACK
,很容易理解,這是最後一次確認,客戶端收到ACK後,狀態變動爲 FIN_WAIT_2
。tcp
服務器 LAST_ACK
後還可能繼續作其它的操做,作完以後,服務器也會發送FIN+序號n。工具
客戶端收到FIN後,狀態變動爲 TIME_WAIT
,同時發送確認信息ACK n+1,服務器收到ACK後,狀態變動爲 CLOSE
。
到此,鏈接斷開。優化
揮手的時候有個問題,爲何ACK和FIN不能一塊兒發送呢?這是由TCP的半關閉(half-close)形成的。TCP鏈接是全雙工(即數據在兩個方向上能同時傳遞),所以每一個方向必須單獨地進行關閉。
收到一個FIN只意味着對方不會再向我發送數據了,可是TCP仍然支持我繼續向對方發送數據(固然,在實際的應用中,直有不多的程序這樣作),若是咱們用Wireshark等工具抓包時也常常會看到揮手只有3次的狀況。
TIME_WAIT
狀態也稱爲2倍MSL等待狀態。MSL是TCP的最大報文生存時間。當一個TCP主動關閉併發送最後一個ACK(圖中右下角最後一條紫色線)後,必須在 TIME_WAIT
狀態等待兩倍的MSL時間,防止對方沒有收到這個ACK而後重發FIN。因此一去一回最多就是兩倍時間。
操做系統默認240s(也就是2倍的兩分鐘)後會關閉處於 TIME_WAIT
的鏈接,在這以前端口一直會被佔用。
在Linux服務器上能夠經過變動/etc/sysctl.conf文件去修改該缺省值(秒):net.ipv4.tcp_fin_timeout=30。但這一般也會出現一些問題。
兩個應用程序彼此同時打開的可能性很小。這時雙方都是客戶端也都是服務器。具體流程和通常狀況同樣,只不過揮手的時候是4次而不是3次。TCP協議認爲這種狀況是創建一條鏈接而不是兩條。
爲了方便我把同時打開和同時關閉畫在了一塊兒,其實同時打開不必定同時關閉。不一樣時打開也有可能同時關閉。
咱們先調用natstat命令看一下telnet服務器。-a:顯示全部;-n:以點分十進制表示IP;-f inet:只顯示TCP和UDP。首先,沒有鏈接的時候它是這樣的:
表頭依次爲協議、接收請求數、發送請求數、本地地址、遠程地址和狀態。圖中數據顯示:當前本機的23端口正在處於LISTEN
狀態,監聽着任意IP發來的請求。下面再看一下當請求過來時的狀態:
狀態一共有3條,這三條狀態對應着三個進程,
LISTEN
狀態的進程一直存在並接受SYN報文段,一旦握手成功,系統內核中的TCP模塊就建立一個處於
ESTABLISHED
狀態的進程。
若是服務器在同一時間收到大量請求,而服務器當前沒有能力處理(或有更高優先級的進程)。TCP會先將這個請求緩存在一個固定長度的隊列中(隊列長度通常爲5,稱爲積壓值)。
應用層會不斷消費該隊列,一旦產生積壓,服務器會中止接收SYN報文,而且不返回任何消息,這時客戶端會顯示超時。
一般TCP在接收到數據時並不當即發送ACK;而是推遲發送,以便將ACK與須要沿該方向發送的數據一塊兒發送(這種現象稱爲數據捎帶ACK)。絕大多數實現採用的時延爲200ms。
200ms是最大時間,若是一直有數據等待發送,那麼就不存在延時確認時間。
Nagle算法是爲了解決小段問題。所謂「小段」,指的是小於最大MSS尺寸的數據塊。例如應用程序一次產生一字節數據(例如交互式輸入),這樣會致使網絡因爲太多的包而過載。
Nagle算法的基本定義是任意時刻,最多隻能有一個未被確認的小段。規則以下:
一、若是包長度達到MSS,則容許發送;
二、若是該包含有FIN,則容許發送;
三、設置了TCP_NODELAY選項,則容許發送;
四、未設置TCP_CORK選項時,若全部發出去的小包均被確認,則容許發送;
五、上述條件都未知足,但發生了超時(通常爲200ms),則當即發送。
Nagle算法的優勢是自適應:確認到達的越快,發送的就越快。
Nagle算法的缺點是發送存在延時。咱們能夠經過TCP_NODELAY
套接字選項來關閉Nagle算法。
對於前端工程師來說,瞭解TCP協議可以幫助咱們更好的理解並使用HTTP協議。也可以幫助咱們對網絡進行更加細緻的優化。個人理解也頗有限,但願可以和你們共同討論。