Don't cry over spilt milk.算法
"覆水難收"服務器
參考資料:TCP/IP入門經典 (第五版)網絡
TCP/IP詳解 卷一:協議socket
TCP是協議棧中很是重要的一個部分,它和IP組成了整個協議棧的核心(從協議族的名字就能夠看出來)。因爲TCP內容較多,並且不少細節筆者也不是瞭解的很深刻,因此本文只對最基本的概念和最簡單的狀況作一個介紹,更詳細的內容請參考相關文檔或書籍搜索引擎
1、簡介spa
TCP(Transmission Control Protocol,傳輸控制協議),是協議棧的核心協議之一,提供一種面向鏈接的,可靠的字節流服務,位於協議棧的傳輸層線程
回顧UDP,UDP是把數據一整塊的發送給網絡層,由IP來管理數據的分片和傳輸,UDP並不確保傳輸的可靠性。然而TCP與UDP則徹底不一樣,TCP在數據傳輸以前先在通訊兩端創建鏈接,而後傳輸數據,最後斷開鏈接,並在數據傳輸過程當中控制流量和傳輸的正確性。下面來看看TCP如何提供可靠性:設計
● 應用數據被分割成TCP認爲最合適發送的數據塊,避免IP分片(UDP則交給IP管理)3d
● 當TCP發出一個段後,它將啓動一個定時器,等待目的端確認收到這個報文段,若是沒有及時收到確認,它將從新發送這個報文段,這保證了數據完整正確的發送指針
● 當TCP收到另外一端的數據時,它將發送一個確認
● TCP將保持它首部和數據的檢驗和,這樣能夠保證數據的完整性和正確性
● 有必要的話,TCP將會對收到的數據進行排序,將數據以正確的順序發送給應用層
● 在IP數據報發生重複的狀況下,TCP的接收端必須丟棄重複的數據
● TCP每一方都有固定大小的緩衝空間,接收端只容許另外一端發送接收端緩衝區所能容納的數據
2、TCP首部
TCP首部的數據格式以下
各字段含義以下:
● 端口號:標識應用程序,IP地址加上端口號組成了插口(socket,也稱做套接字)
● 32位序號:用來標識從TCP發端向TCP收端的發送字節流,表示在這個報文段中的第一個數據字節。序號是一個32 bit的無符號數,序號到達232 - 1後又從0開始
● 32位確認號:確認序號包含發送確認的一端所指望收到的下一個序號,所以,確認序號應該是上一次成功接收到的數據字節序號加1,。只有ACK標誌爲1時確認序號纔有效
● 4位首部長度:表示的含義跟IP首部中的首部長度字段同樣
● 保留(6位):筆者目前還未了解
● 6個標誌比特:
URG 緊急指針
ACK 確認序號有效
PSH 接收方應儘快將這個報文段交給應用層
RST 重建鏈接
SYN 同步序列號,用於創建一個鏈接
FIN 發送端完成發送任務
● 16位窗口大小:TCP經過在每一端聲明窗口大小來進行流量控制,在後面會詳細介紹
● 16位檢驗和:檢驗和覆蓋了TCP首部和TCP數據,這是一個強制性的字段,其計算方法和UDP的計算方法類似
● 16位緊急指針:緊急指針是一個正的偏移量,和序號值相加獲得緊急數據最後一個字節的序號,只有URG被置1時纔有效
● 選項:最多見的可選字段是最長報文大小,又稱爲MSS(Maxinum Segment Size)。每一個鏈接方一般在通訊的第一個報文段中指明這個選項,指明本端所能接收的最大長度的報文段
數據部分是可選的,在許多狀況下,TCP會發送一個不帶數據的報文段,好比創建鏈接和斷開鏈接時
3、TCP鏈接的創建與終止
因爲TCP提供的是可靠的、面向鏈接的服務,因此在數據傳輸以前,通訊雙方須要創建一條鏈接。數據傳輸完成後,須要斷開鏈接,下面來一一介紹
創建鏈接協議(三次握手)
爲了創建一條TCP鏈接:
①主動打開:請求端(一般爲客戶端)發送一個帶SYN標誌以及初始序號(ISN)的報文段給服務器請求創建一個鏈接。這個SYN段爲報文段1
②被動打開及確認:服務器發回包含服務器初始序號(ISN)的SYN報文段(報文段2)做爲應答。同時,將確認序號設置爲報文段1的ISN加1並置ACK位爲1來確認已經收到客戶的SYN段。一個SYN將佔用一個序號
③最後確認:客戶發送一個確認報文段(報文段3)來確認已經收到報文段2,該報文段將ACK位置1並將確認序號設置爲報文段2的ISN加1
這三個報文段完成鏈接的創建,這個過程也稱爲三次握手
鏈接終止協議(四次揮手)
當數據傳輸結束後:
①主動關閉:請求端(一般爲客戶端)發送一個帶FIN標誌的報文段給服務器請求斷開客戶端方向的鏈接(表示我已經沒有數據要發送給你了)
②被動關閉及確認:服務器發回一個帶ACK標誌的確認報文段,確認序號爲收到的序號加1(好吧,你先關閉,但我還要向你發送數據)
③服務器端關閉:服務器發送一個帶FIN標誌的報文段給客戶端請求斷開服務器方向的鏈接(個人數據也發送完了,斷開鏈接吧)
④終止鏈接:客戶端發送一個帶ACK標誌的確認報文段代表已經收到服務器的FIN報文(收到請求)
第④步以後,因爲最後一個帶ACK標誌的報文段可能丟失(前面的報文段也可能丟失,可是前面的報文段由其後一個報文段來確認,所以不須要特殊處理。最後一個報文段沒有確認),因此客戶端在發送完該報文段以後設置一個2MSL的定時器。若是在這個2MSL以內收到了服務器重發的FIN,則代表最後的ACK報文段丟失了,如今要從新發送。若是2MSL結束後沒有收到,說明服務器已經收到確認,正常斷開鏈接。而且,在2MSL內,服務器和客戶端的正在使用的端口號和IP地址不能再被使用。
第①②步以後,請求主動關閉的一方就進入半關閉狀態,在該狀態下,請求端還能夠接收來自另外一方向上的數據。與其對應的是半打開狀態:當已經創建鏈接的雙方當中某一方斷開鏈接而另外一方還不知道的狀況下,好比客戶端忽然斷電,這時服務器就處於半打開狀態,只要不打算髮送數據,服務器就不會檢測到客戶端已經斷開。
正常狀況下,創建鏈接和斷開鏈接時對應的狀態以下:
特殊狀況
固然,並非全部的鏈接都能這樣順利進行,還有可能出現一些特殊的狀況
● 同時打開:這個時候通訊雙方都主動請求鏈接,因此兩端都執行了主動打開的兩個步驟,一共交換了4個報文段。其狀態變遷過程以下:
● 同時關閉:通訊雙方同時請求斷開鏈接,因爲單方向上的關閉須要交換2個報文段,因此一共須要交換4個報文段:
4、數據傳輸
創建好鏈接之後,通訊雙方就要進行數據傳輸了。對於不一樣類型的數據,應當使用不一樣的算法來傳輸數據。好比,咱們登錄QQ時客戶端須要與服務器驗證帳號密碼,這時的數據只有不多的字節數;而當咱們從網頁上點擊下載一部電影時,可能須要大塊的數據傳輸不少次才能下載完成。TCP須要同時處理這兩類數據,前者被稱爲交互數據,後者被稱爲成塊數據。接下來咱們就來了解一下這兩種數據傳輸方式
TCP的交互數據流
當咱們使用某些搜索引擎時,常常出現這樣的狀況,咱們尚未完整的輸入一個單詞,只鍵入了一部分字符的時候,它就會自動彈出一些匹配信息。這是由於咱們輸入的每個交互按鍵都會產生一個數據分組,也就是說事實上每次傳送的是一個字節。搜索引擎在每次輸入完一個字符後,就將該字符傳送到服務器,服務器將已經輸入的全部字符做爲一個字符串進行模糊匹配,這樣的輸入方式稱爲交互式輸入。
在前面將TCP如何提供可靠性的時候講到:當TCP收到另外一端發來的數據時,將會發送一個確認報文段來確認已經收到數據。然而若是每次剛收到數據就當即發送確認的話,可能會下降傳輸的效率,由於可能緊接着確認端又要發送一份數據,若是把確認信息包含在要發送的數據報文段內,那麼不就能夠提升一些效率了嗎?可是確認和發送數據之間可能存在必定的時間差,也就是說「確認」須要等一下「數據」,絕大多數的實現採用200ms作爲最大的時延,這樣的確認方式稱爲經受時延的確認。
Nagle算法
前面講到,交互式輸入時TCP每次只傳送一個字節的數據,然而當這樣的小分組太多的時候,可能會形成網絡擁堵。針對這個問題,TCP採用Nagle算法來解決:在一個TCP鏈接上,最多隻能有一個未被確認的未完成的小分組,在該分組的確認到達以前不能發送其餘的分組。當已經產生不少個分組,但尚未收到前面某個分組的確認時,TCP將這些待發送的小分組合併爲一個分組,等到收到確認後,將這個合併後的分組發送出去。這樣作的優勢就在於:只要收到一個確認,就能將全部待發送的數據所有發送出去(前提是不超過MSS),而且不用產生不少小分組來佔用網絡。
然而並非全部狀況都須要Nagle算法,好比咱們在使用QQ的遠程控制功能時,一端能夠控制對方的電腦,這時控制方的每個動做(包括鼠標移動)都要當即反饋給對方,這時就必須關閉Nagle算法。
TCP的成塊數據流
滑動窗口協議
當咱們從服務器上下載一個很大的文件時,可能須要發送不少的分組和不少的確認來傳輸文件。這時的分組通常都是「滿長度」的,也就是要把分組「塞滿」,這樣才能夠傳的更快嘛。 正常狀況下,發送端發送一個分組,接收方確認,發送端再發送下一個,。。。,然而,若是接收方的速度跟不上發送方的話,效率就可能很是低。那麼是否是能夠這樣:發送方不須要等到接收方的確認,直接不停的發送,直到發送完成,而後等待全部確認。這樣看起來可行,可是若是接收方「接收不下」這麼多數據怎麼辦?
TCP提供了一種流量控制方法:接收方通告一個大小爲S的窗口,並設置一個大小爲S的緩衝區,用來保存已經收到可是還來不及處理的數據。當接收方處理完一塊數據後,就發送該塊數據的確認給發送方,當緩衝區沒有滿的時候,發送方能夠繼續發送數據;當緩衝區滿了之後,發送方就要等待接收方處理數據,把緩衝區「謄出」空間來接收新數據,這種方法稱做滑動窗口協議。
咱們發現一個問題,發送方怎麼知道接收方的緩衝區滿沒滿呢?這就要用到序號和確認號了。假設窗口大小爲2048,發送方發送的分組大小爲500。某一時刻,發送方收到的確認號爲1001,發送方將要發送的分組序號爲2501,此時緩衝區的剩餘空間爲2500-1000=1500, 1500+500=2000<2048,即便再發送一個分組緩衝區也能「裝下」,那麼發送方將會繼續發送分組;假設發送玩該分組後,仍是沒有收到確認,那麼此時下一個分組的序號爲3001,緩衝區內的數據大小爲3001-1001=2000, 2000+500=2500>2048,這時若是再發送一個分組,緩衝區就「裝不下了」,發送方就會中止發送,直到下一個確認到來。
滑動窗口協議的可視化表示以下:
緊急方式
考慮這樣一種狀況:我正在下載一個很大的文件,可是此時服務器由於某種緣由須要重啓,不能繼續發送剩下的數據,那麼服務器應該要給我發送一個通知告訴我不用等待了,而且緩衝區的數據也不用處理了,由於得不到完整的文件,那麼這個通知應該在緩衝區的數據以前被接收方處理。TCP提供了「緊急方式」,經過將TCP首部中的URG比特置1,而且設置16 bit的緊急指針,來通知接收方「這是緊急數據,須要優先處理」,緊急指針加上序號字段獲得緊急數據的最後一個字節的序號。
5、TCP的超時與重傳
這篇文章只是簡單介紹一下TCP,所以不會更深的介紹更詳細的內容,好比:RTT的計算、快速重傳算法等等(好吧,我認可我比較水)
TCP提供了可靠的傳輸,它使用的方法是對接收到的數據進行確認。然而數據和確認均可能在傳輸過程當中丟失,TCP經過在發送時設置一個定時器來解決這種問題。當定時器超時時尚未收到確認,就重傳該數據。
對於每一個鏈接,TCP管理4個不一樣的定時器:
①重傳定時器:當超時之後尚未收到另外一端的確認時,就重傳該數據
②堅持定時器:發送方使用一個堅持定時器來週期性地向接收方查詢,以便觀察接收方的窗口更新狀況(由於接收方的確承認能丟失,而發送方須要知道接收方緩衝區的實時狀況)
③保活定時器:當創建鏈接的雙方都沒有數據須要傳送的時候,有可能出現半打開狀態(前面講過),此時就須要使用保活定時器來檢測這種半打開狀態。當一端的保活定時器超時後,就會向另外一端發送一個探查報文段,若是另外一端沒有響應,那麼發送端將會終止鏈接
④2MSL定時器:用於重傳斷開鏈接的確認,前面已經介紹過
6、TCP服務器的設計
TCP服務器的端口號
對於處於LISTEN狀態的服務器進程(主進程/線程),將本地套接字設置爲「*.port」的格式,表示能夠經過服務器的任意接口來創建鏈接;將遠端套接字設置爲「*.*」格式,表示能夠接收來自任何IP地址和任意端口的鏈接請求。
限定的本地IP地址
將服務器進程綁定到指定的接口,客戶端只能經過指定的IP地址訪問服務器
限定的遠端IP地址
服務器只能跟指定的客戶端創建鏈接並傳輸數據
呼入的鏈接請求隊列
設置等待隊列來處理已經完成三次握手但尚未被應用層接受的客戶鏈接,通常將隊列長度設置爲5
總結:TCP是一個可靠的傳輸層協議,但也由於提供可靠性,TCP要比UDP複雜不少,之後慢慢的補充吧