TCP協議詳解

2021-09-26 10:29:55 字數 3436 閱讀 6024

參考部落格

在可靠的tcp網路通訊中,客戶端和伺服器端通訊建立連線的過程可簡單表述為三次握手(建立連線的階段)和四次揮手(釋放連線階段),下圖是這兩個階段的乙個完整的表述:

其狀態圖可以表示為,

在tcp連線建立的時候,存在乙個如下的有限狀態機:

在狀態轉化圖中,其中客戶端的狀態轉移用帶箭頭的粗實線表示,伺服器端的狀態轉換用帶箭頭的粗虛線表示。帶箭頭的細線表示一些不常見的事件,如復位、同時開啟、同時關閉等。關於有限狀態圖可以參考部落格裡面的細節都將的很清楚;如果要深入理解tcp連線建立和釋放的過程就需要結合socket程式設計裡的connect(),socket(),bind(),listen(),send(),close()等函式。

從圖中看到,三次握手對應的berkeley socket api:connect, listen, accept 3個,connect用在客戶端,另外2個用在服務端。對於tcp/ip protocol stack來說,tcp層的tcp_in&tcp_out也參與這個過程。我們這裡只討論這3個應用層的api幹了什麼事情。

(1) connect()

傳送了乙個syn,收到server的syn+ack後,代表連線完成。傳送最後乙個ack是protocol stack,tcp_out完成的。

(2)listen()

在server這端,準備了乙個未完成的連線佇列,儲存只收到syn_c的socket結構;還準備了已完成的連線佇列,即儲存了收到了最後乙個ack的socket結構。

(3)accept()

應用程序呼叫accept的時候,就是去檢查上面說的已完成的連線佇列,如果佇列裡有連線,就返回這個連線;如果沒有,即空的,blocking方試呼叫,就睡眠等待;客戶端呼叫connect函式之後就發起完成tcp的三次握手,客戶端呼叫connect後,由核心中的tcp協議完成tcp的三次握手,close操作會完成四次揮手。

其中accept發生在三次握手之後。

第一次握手:客戶端傳送syn包(syn=j)到伺服器。

第二次握手:伺服器收到syn包,必須確認客戶的syn(ack=j+1),同時自己也傳送乙個ask包(ask=k)。

第三次握手:客戶端收到伺服器的syn+ack包,向伺服器傳送確認包ack(ack=k+1)。

三次握手完成後,客戶端和伺服器就建立了tcp連線。這時可以呼叫accept函式獲得此連線。

close_wait

close_wait 是被動關閉 tcp 連線時產生的,如果收到另一端關閉連線的請求後,本地(server端)不關閉相應套接字就會導致本地套接字進入這一狀態。

(如果對方關閉了,沒有收到關閉鏈結請求,就是下面的不正常情況)按tcp狀態機,我方收到fin,則由tcp實現傳送ack,因此進入close_wait狀態。但如果我方不執行close(),就不能由close_wait遷移到last_ack,則系統中會存在很多close_wait狀態的連線。如果存在大量的 close_wait,則說明客戶端併發量大,且伺服器未能正常感知客戶端的退出,也並未及時 close 這些套接字。(如果不及時處理,將會出現沒有可用的socket描述符的問題,原因是sockfd耗盡)。

正常情況下:一方關閉sockfd,另外一方將會有讀事件產生, 當recv資料時,如果返回值為0,表示對端已經關閉。此時我們應該呼叫close,將對應的sockfd也關閉掉。

不正常情況下:一方關閉sockfd,另外一方並不知道,(比如在close時,自己斷網了,對方就收不到傳送的資料報)。此時,如果另外一方在對應的sockfd上寫send或讀recv資料。

recv時,將會返回0,表示鏈結已經斷開。

send時, 將會產生錯誤,errno為econnreset。

3.close()函式和shutdown()函式的區別:

首先我們來看看close()函式的原型:

標頭檔案:#include 定義函式:int close(int fd);
close 乙個套接字的預設行為是把套接字標記為已關閉,然後立即返回到呼叫程序,該套接字描述符不能再由呼叫程序使用,也就是說它不能再作為read或write的第乙個引數,然而tcp將嘗試傳送已排隊等待傳送到對端的任何資料,傳送完畢後發生的是正常的tcp連線終止序列。在多程序併發伺服器中,父子程序共享著套接字,套接字描述符引用計數記錄著共享著的程序個數,當父程序或某一子程序close掉套接字時,描述符引用計數會相應的減一,當引用計數仍大於零時,這個close呼叫就不會引發tcp的四路握手斷連過程。如果是主動呼叫close()則會發起核心tcp協議的四次揮手,斷開連線.

再來看看shutdown()函式的原型:

int shutdown(int sockfd,int howto); //返回成功為0,出錯為-1.

該函式的行為依賴於howto的值

1.shut_rd:值為0,關閉連線的讀這一半。

2.shut_wr:值為1,關閉連線的寫這一半。

3.shut_rdwr:值為2,連線的讀和寫都關閉。

終止網路連線的通用方法是呼叫close函式。但使用shutdown能更好的控制斷連過程(使用第二個引數)。

當呼叫shut_rd的時候,套接字sockfd的讀端將會關閉,不能呼叫接受資料的函式,這對於協議層沒有影響。然和當前在sockfd讀端的資料緩衝區的資料都會被捨棄掉,程序將不能對該套接字發起讀操作,對tcp套接字呼叫shut_rd將會導致協議層將接收到的資料無聲的丟掉!!!如果想要繼續接受資料都要重置鏈結;

當呼叫shut_wr的時候,對於tcp套接字來說,這意味著會在所有資料傳送出並得到接受端確認後產生乙個fin包。而此時套接字的狀態會由established變成fin_wait_1,然後對方傳送乙個 ack包作為回應,套接字又變成fin_wait_2。如果對方也關閉了連線則對方會發出fin,我方會回應乙個ack並將套接字置為 time_wait。

4.如何判斷socket連線斷開:

非阻塞模式,如果暫時沒有資料,返回的值也會是<=0的,如果用阻塞模式的話,返回<=0的值是可以認為socket已經無效了。當使用 select()函式測試乙個socket是否可讀時,如果select()函式返回值為1,且使用recv()函式讀取的資料長度為0 時,就說明該socket已經斷開。經過**試驗,如果程序受到一些訊號時,例如:eintr,recv()返回值小於等於0時,這是就需要判斷 errno是否等於 eintr , 如果errno == eintr 則說明recv函式是由於程式接收到訊號後返回的,socket連線還是正常的,不應close掉socket連線。如果write,我覺得還有一些情況需要考慮,那就是寫的太快的時候,有可能buffer寫滿了,errno是eagain,可以根據實際需要,如果errno是eagain的話,再寫幾次。

詳解TCP協議

16位的源埠號 傳送源的埠號 16位的目標埠號 目標的埠號 32位的序號 互動的初始資料段,序號值由系統生成的隨機值 isn。後續的報文段的序號為isn 所攜帶資料在整個位元組流中的偏移量。特點 1 所有的報文段序號不重複。2 後續的報文段序號值比前面的大。32位的確認號 由接收段填充,其值為序列號...

TCP協議詳解

首先tcp是一種可靠的面向位元組流的協議,流指的是流入到程序或從程序流出的位元組序列。tcp的可靠性主要是由其首部的複雜結構結合可靠性傳輸原理 比如停止等待協議 arq 協議實現,可以說,tcp的首部是tcp能可靠傳輸的必要保障,了解tcp的首部很有必要 1.tcp首部 2.可靠傳輸基本原理 停止等...

TCP協議詳解

傳輸控制協議 tcp,transmission control protocol 是一種面向連線的 可靠的 基於位元組流的傳輸層通訊協議。tcp旨在適應支援多網路應用的分層協議層次結構。連線到不同但互連的計算機通訊網路的主計算機中的成對程序之間依靠tcp提供可靠的通訊服務。tcp假設它可以從較低階別...