1. 程式人生 > >【網路協議-3】TCP/UDP協議

【網路協議-3】TCP/UDP協議

TCP報頭格式:

一共20個位元組;

  • 源、目標埠號欄位:各佔2個位元組,18位元。TCP協議通過使用”埠”來標識源端和目標端的應用程序。埠號可以使用0到65535之間的任何數字。在收到服務請求時,作業系統動態地為客戶端的應用程式分配埠號。在伺服器端,每種服務在"眾所周知"的埠”(Well-Know Port)為使用者提供服務。
  • Sequence number(順序號碼) Acknowledge number(確認號碼) 各佔4個位元組,32位元。
  • 標誌位:佔6位元 SYN(synchronous建立聯機) ACK(acknowledgement 確認) PSH(push傳送) FIN(finish結束) RST(reset重置) URG(urgent緊急)。
  • 資料偏移:佔4位元,該資料偏移不是ip分片中的資料偏移,是指資料部分的開頭到TCP開頭的部分距離,就是整個TCP包頭的長度,單位是4位元組
  • 保留(3位元長)—須置0
  • 標誌符(9位元長)
    • NS—ECN-nonce。 
    • CWR—Congestion Window Reduced。
    • ECE—ECN-Echo有兩種意思,取決於SYN標誌的值。
    • URG—為1表示高優先順序資料包,緊急指標欄位有效。
    • ACK—為1表示確認號欄位有效
    • PSH—為1表示是帶有PUSH標誌的資料,指示接收方應該儘快將這個報文段交給應用層而不用等待緩衝區裝滿。
    • RST—為1表示出現嚴重差錯。可能需要重現建立TCP連線。還可以用於拒絕非法的報文段和拒絕連線請求。
    • SYN—為1表示這是連線請求或是連線接受請求,用於建立連線和使順序號同步
    • FIN—為1表示傳送方沒有資料要傳輸了,要求釋放連線
  • 視窗大小(WIN,2位元組16位元長)表示從確認號開始,本報文的接受方可以接收的位元組數,因為快取區的有限,防止對方傳送的資料過快,導致資料丟失。
  • 校驗和(Checksum,2位元組16位元長)對整個的TCP報文段,包括TCP頭部和TCP資料,以16位字進行計算所得。這是一個強制性的欄位。
  • 緊急指標(2位元組,16位元長)—本報文段中的緊急資料的最後一個位元組的序號。緊急資料放在資料的開頭。
  • 選項欄位—最多40位元組。每個選項的開始是1位元組的kind欄位,說明選項的型別。
    • 0:選項表結束(1位元組)
    • 1:無操作(1位元組)用於選項欄位之間的字邊界對齊。
    • 2:最大報文段長度(4位元組,Maximum Segment Size,MSS)通常在建立連線而設定SYN標誌的資料包中指明這個選項,指明本端所能接收的最大長度的報文段。通常將MSS設定為(MTU-40)位元組,攜帶TCP報文段的IP資料報的長度就不會超過MTU,從而避免本機發生IP分片。只能出現在同步報文段中,否則將被忽略。
    • 3:視窗擴大因子(4位元組,wscale),取值0-14。用來把TCP的視窗的值左移的位數,使視窗值乘倍。只能出現在同步報文段中,否則將被忽略。這是因為現在的TCP接收資料緩衝區(接收視窗)的長度通常大於65535位元組。
    • 4:sackOK—傳送端支援並同意使用SACK選項。
    • 5:SACK實際工作的選項。
    • 8:時間戳(10位元組,TCP Timestamps Option,TSopt)
      • 傳送端的時間戳(Timestamp Value field,TSval,4位元組)
      • 時間戳回顯應答(Timestamp Echo Reply field,TSecr,4位元組)
  • 填充欄位:讓整個報文長度是4位元組的倍數。

 

TCP三次,四次握手:

TCP協議提供可靠的連線服務,採用三次握手建立一個連線: 

  • 第一次握手:建立連線時,客戶端把資料包標誌符中的SYN(建立聯機)=1,標誌符變成00000010,隨機產生一個序列號碼number_client(假設number_client = 10000)的資料包SYN發給伺服器, 並進入SYN_SENT狀態,伺服器由客戶端發來的標誌符中SYN=1知道,客戶端要求建立聯機; 
  • 第二次握手:伺服器收到客戶端發來的資料包後,給客戶端發資料包,資料包中的SYN(建立聯機)=1,ACK(確認欄位)=1,標誌符變成00010010。隨機產生一個序列號碼number_server(假設number_server = 20000),確認序列號碼為(number_client+1),此時伺服器進入SYN_RECV狀態;
  • 第三次握手:客戶端收到伺服器的資料包後,檢查伺服器返回的確認號碼是否正確(number_client + 1 = 10001),如果為10001說明正確, 向伺服器傳送資料包,資料包中標誌符ACK = 1,確認序列號碼為(number_server + 1 = 20001) 此包傳送完畢,客戶端和伺服器進入ESTAB_LISHED狀態,完成三次握手,客戶端與伺服器開始傳送資料.

 

TCP通過序列號與確認應答提高可靠性

 

TCP傳送資料,是以位元組流的形式傳送。它不關心資料是什麼型別,但是為了確保資料正確性,重發控制和重複控制等。

這些功能都以序列號來實現。序列號初始值並非為0,而是由客戶端建立連線時候,隨機產生的

(TCP的資料長度並未寫入TCP首部。實際通訊中求得TCP包的長度的計算公式是:IP首部中的資料包長度-IP首部長度TCP首部長度。)

 

上圖每次傳送資料長度1000是在三次握手的時候,在兩端主機之間被計算得出。兩端的主機在發出建立連線的請求時,會在TCP首部中寫入MSS選項,告訴對方自己的介面能夠適應的MSS的大小(為附加MSS選項,TCP首部將不再是20位元組,而是4位元組的整數倍)。然後會在兩者之間選擇一個較小的值投入使用(在建立連線時,如果某一方的MSS選項被省略,可以選為IP包的長度不超過576位元組的值(IP首部20位元組,TCP首部20位元組,MSS 536位元組))
 

選中三次握手中MSS選項中值較小的1460,作為一個傳輸資料的一個段單位。

 

滑動視窗方式並行處理

每次傳輸資料都需要接收資料的主機確認的話,效率會很慢。因此可以設定視窗大小,就是指無需等待確認應答而可以繼續傳送資料的最大值。這種機制稱為滑動視窗方式並行處理:

滑動視窗方式處理重傳:

 

1-1000位元組資料已傳送到主機B,但是1-1000的資料確認應答丟失。在收到確認序號ACK = 2001時,就可以把客戶端1-1000的資料從緩衝中刪除了,因為主機B已傳送確認序號ACK = 2001,說明1-1000資料已經發送成功了。

 

滑動視窗方式-高速重發控制

當某段保溫丟失後,接收端會給傳送端不停的發報文確認應答,如上圖所示,接收端在收到3次重複應答後,

知道此段報文丟失,重發。

流控制:

當接收端在高負載下無法接收發送端的資料包,把傳送端傳送的資料丟棄時候,傳送並不知道,會一直髮送資料。

造成網路流量的浪費。TCP提供一種機制可以讓傳送端根據接收端的實際接收能力控制傳送的資料量。這就是所謂的流控制。

具體操作就是改變有接收端改變傳送端的資料包的視窗大小。

 

未連線佇列

在三次握手協議中,伺服器維護一個未連線佇列,該佇列為每個客戶端的SYN包(syn=j)開設一個條目,該條目表明伺服器已收到SYN包,並向客戶發出確認,正在等待客戶的確認包時,刪除該條目,伺服器進入ESTAB_LISHED狀態

1.為什麼需要三次握手,兩次不可以嗎?或者四次、五次可以嗎? 

  1. 如果client傳送的連線請求由於網路延時到客戶端連線釋放後才到達server,這是早已失效的報文,如果只進行二次握手,伺服器就認為有新連線請求,但是client並沒有建立連線,不會給server傳送資料,但是server為了這個連線,會一直有資源消耗。
  2. 如果是兩次握手,假設伺服器給客戶端在第二次握手時傳送資料,資料從伺服器發出,伺服器認為連線已經建立,但在傳送資料的過程中資料丟失,客戶端認為連線沒有建立,會進行重傳。假設每次傳送的資料一直在丟失,客戶端一直SYN,伺服器就會產生多個無效連線,佔用資源,這個時候伺服器可能會掛掉。這個現象就是我們聽過的“SYN的洪水攻擊”。 

四次揮手過程(關閉客戶端到伺服器的連線)

  1. 首先,客戶端傳送一個FIN,用來關閉客戶端到伺服器的資料傳送,然後等待伺服器的確認。其中終止標誌位FIN=1,序列號seq=u。
  2. 伺服器收到這個FIN,它傳送一個ACK,確認ack為收到的序號加一。
  3. 關閉伺服器到客戶端的連線,傳送一個FIN給客戶端。
  4. 客戶端收到FIN後,併發回一個ACK報文確認,並將確認序號seq設定為收到序號加一。首先進行關閉的一方將執行主動關閉,而另一方執行被動關閉。

客戶端傳送FIN後,進入終止等待狀態,伺服器收到客戶端連線釋放報文段後,就立即給客戶端傳送確認,伺服器就進入CLOSE_WAIT狀態,此時TCP伺服器程序就通知高層應用程序,因而從客戶端到伺服器的連線就釋放了。此時是“半關閉狀態”,即客戶端不可以傳送給伺服器,伺服器可以傳送給客戶端。 
此時,如果伺服器沒有資料報傳送給客戶端,其應用程式就通知TCP釋放連線,然後傳送給客戶端連線釋放資料報,並等待確認。客戶端傳送確認後,進入TIME_WAIT狀態,但是此時TCP連線還沒有釋放,然後經過等待計時器設定的2MSL後,才進入到CLOSE狀態。

2.為什麼需要2MSL時間? 
首先,MSL即Maximum Segment Lifetime,就是最大報文生存時間,是任何報文在網路上的存在的最長時間,超過這個時間報文將被丟棄。《TCP/IP詳解》中是這樣描述的:MSL是任何報文段被丟棄前在網路內的最長時間。RFC 793中規定MSL為2分鐘,實際應用中常用的是30秒、1分鐘、2分鐘等。

TCP的TIME_WAIT需要等待2MSL,當TCP的一端發起主動關閉,三次揮手完成後傳送第四次揮手的ACK包後就進入這個狀態,等待2MSL時間主要目的是:防止最後一個ACK包對方沒有收到,那麼對方在超時後將重發第三次握手的FIN包,主動關閉端接到重發的FIN包後可以再發一個ACK應答包。在TIME_WAIT狀態時兩端的埠不能使用,要等到2MSL時間結束才可以繼續使用。當連線處於2MSL等待階段時任何遲到的報文段都將被丟棄。

3.為什麼是四次揮手,而不是三次或是五次、六次? 
雙方關閉連線要經過雙方都同意。所以,首先是客服端給伺服器傳送FIN,要求關閉連線,伺服器收到後會傳送一個ACK進行確認。伺服器然後再發送一個FIN,客戶端傳送ACK確認,並進入TIME_WAIT狀態。等待2MSL後自動關閉。

總結: 
(1)為了保證客戶端傳送的最後一個ACK報文段能夠到達伺服器。即最後一個確認報文可能丟失,伺服器會超時重傳,然後伺服器傳送FIN請求關閉連線,客戶端傳送ACK確認。一個來回是兩個報文生命週期。

如果沒有等待時間,傳送完確認報文段就立即釋放連線的話,伺服器就無法重傳,因此也就收不到確認,就無法按步驟進入CLOSE狀態,即必須收到確認才能close。 
(2)防止已經失效的連線請求報文出現在連線中。經過2MSL,在這個連續持續的時間內,產生的所有報文段就可以都從網路消失。



UDP報頭格式:

  • 來原連線埠:傳送端埠(IPv4可選)
  • 目的連線埠:接收端埠
  • 報文長度:UDP首部的長度跟資料的長度之和,單位位元組
  • 校驗和:用於校驗UDP資料報的數字段和包含UDP資料報首部的“偽首部”。
  • 偽首部,又稱為偽包頭(Pseudo Header):是指在TCP的分段或UDP的資料報格式中,在資料報首部前面增加源IP地址、目的IP地址、IP分組的協議欄位、TCP或UDP資料報的總長度等,共12位元組,所構成的擴充套件首部結構,如下圖。此偽首部是一個臨時的結構,它既不向上也不向下傳遞,僅僅是為了保證可以校驗套接字的正確性。

 

UDP工作流程:

UDP套介面是無連線的、不可靠的資料報協議;既然他不可靠為什麼還要用呢?其一:當應用程式使用廣播或多播時只能使用UDP協議;其二:由於他是無連線的,所以速度快。因為UDP套介面是無連線的,如果一方的資料報丟失,那另一方將無限等待,解決辦法是設定一個超時



TCP和UDP的區別:

  1.基於連線與無連線
  2.TCP要求系統資源較多,UDP較少; 
  3.UDP程式結構較簡單 
  4.流模式(TCP)與資料報模式(UDP); 
  5.TCP保證資料正確性,UDP可能丟包 
  6.TCP保證資料順序,UDP不保證 

    PS:

  1. 呼叫了一次write,傳送了100個位元組,但是對方可以分10次收完,每次10個位元組;你也可以呼叫10次write,每次10個位元組,但是對方可以一次就收完。
  2. UDP資料報模式,發幾次,伺服器讀幾次。


具體方法:

TCP: 
TCP程式設計的伺服器端一般步驟是: 
  1、建立一個socket,用函式socket(); 
  2、設定socket屬性,用函式setsockopt(); * 可選 
  3、繫結IP地址、埠等資訊到socket上,用函式bind(); 
  4、開啟監聽,用函式listen(); 
  5、接收客戶端上來的連線,用函式accept(); 
  6、收發資料,用函式send()和recv(),或者read()和write(); 
  7、關閉網路連線; 
  8、關閉監聽; 

TCP程式設計的客戶端一般步驟是: 
  1、建立一個socket,用函式socket(); 
  2、設定socket屬性,用函式setsockopt();* 可選 
  3、繫結IP地址、埠等資訊到socket上,用函式bind();* 可選 
  4、設定要連線的對方的IP地址和埠等屬性; 
  5、連線伺服器,用函式connect(); 
  6、收發資料,用函式send()和recv(),或者read()和write(); 
  7、關閉網路連線;

UDP:
與之對應的UDP程式設計步驟要簡單許多,分別如下: 
  UDP程式設計的伺服器端一般步驟是: 
  1、建立一個socket,用函式socket(); 
  2、設定socket屬性,用函式setsockopt();* 可選 
  3、繫結IP地址、埠等資訊到socket上,用函式bind(); 
  4、迴圈接收資料,用函式recvfrom(); 
  5、關閉網路連線; 

UDP程式設計的客戶端一般步驟是: 
  1、建立一個socket,用函式socket(); 
  2、設定socket屬性,用函式setsockopt();* 可選 
  3、繫結IP地址、埠等資訊到socket上,用函式bind();* 可選 
  4、設定對方的IP地址和埠等屬性; 
  5、傳送資料,用函式sendto(); 
  6、關閉網路連線;

參考博文:

https://blog.csdn.net/Pk_zsq/article/details/6087367 https://blog.csdn.net/sinat_37138973/article/details/72822229 https://zh.wikipedia.org/wiki/%E4%BC%A0%E8%BE%93%E6%8E%A7%E5%88%B6%E5%8D%8F%E8%AE%AE https://blog.csdn.net/ZWE7616175/article/details/80432486 https://www.cnblogs.com/rootq/articles/1377355.html https://blog.csdn.net/deramer1/article/details/73527336  https://www.cnblogs.com/HPAHPA/p/7737531.html https://blog.csdn.net/Li_Ning_/article/details/52117463 https://blog.csdn.net/hanchaoman/article/details/6409106