網絡編程之硬核程序員應該掌握的TCP理論知識

日期:2020年4月10日
作者:Commas
勵志:當你的才華撐不起你的野心的時候,你就該靜下心來好好學習!
註釋:您的每一個小小舉動,將會是技術人大家庭的福澤,非常感謝……
如果您覺得這篇博客對您有所幫助,那麼不妨給我點個贊;
如果您覺得這篇博客有哪裏寫的不好的地方,那麼不妨給我點個撥。
此篇主要講解的是TCP理論知識,後面附帶了一個Python的示例,
如果您想了解更多有關Python的知識,那麼請點《Python學習的奇妙之旅》



一、TCP的定義

TCPTransmission Contol Protocol的縮寫,即傳輸控制協議,是一種面向連接的可靠的基於字節流的傳輸層通信協議。

二、TCP的報文格式以及功能與服務特性

在這裏插入圖片描述
在這裏插入圖片描述

三、三次握手與四次揮手

(1)建立連接過程分析(三次握手)

在這裏插入圖片描述

(2)釋放連接過程分析(四次握手)

在這裏插入圖片描述

(3)發送窗口與接收窗口

在這裏插入圖片描述

四、TCP差錯控制機制

在這裏插入圖片描述
在這裏插入圖片描述
差錯控制總共有序號、確認序號、ACK和檢驗和這四個字段共同參與工作,
另外,以上ACK指Acknowledgement Number(確認序號),而非Acknowledge Character(確認字符)

(1)數據分段

TCP面向字節流的服務,也就是應用層提供給傳輸層是一連串的字節流。傳輸層在發送端發送數據之前,會對這些字節流進行分段,按規則編號,封裝成一些列的報文;而在接收端接收數據之後,會將接收到的報文,按照約定的規則,還原成之前的一連串的字節流,提交給應用層。
在這裏插入圖片描述
如上圖所示,一個3000字節長度的字節流,傳輸層接收到應用層的字節流之後,將其分成3段,每段爲1000個字節,那麼這1000個字節加上首部,即形成了TCP報文,圖中共有3個報文,分別爲①②③。在首部控制信息中有一個序號,它表示報文序列當前的第一個字節序號,如②中的1001,表示第二段報文的第一個字節,是從字節流1001位置開始的,這樣方便了接收端無論哪一個報文接收先,最終都可以準確無誤的還原發送端發送的數據字節流。

(2)確認應答

確認應答有以下四種方式:

  • 逐個確認:一發一收一確認;
  • 累計確認:多發多收一確認;
  • 時延重複確認:重複確認,直到接收到“沒有接收到的”TCP數據報爲止;
  • 丟失重複確認:重複確認,直到接收到“沒有接收到的”TCP數據報爲止;
    在這裏插入圖片描述

(3)重傳

TCP差錯控制機制的本質就是出錯重傳,檢測到錯誤的數據或者丟失的數據就需要重新傳輸,讓接收端可以收到正確且完整的數據,從而完成自己的使命。

發送端確認某個TCP報文出錯的依據有兩個,如下:

  1. 發送端設置重傳定時器,重傳定時器溢出,還沒有收到確認應該,視爲需要重傳; 在這裏插入圖片描述
  2. 連續接收到4個確認序號(ACK)相同的確認應答,視爲報文丟失,需要重傳;
    在這裏插入圖片描述

五、TCP擁塞控制機制

TCP擁塞控制機制的本質就是根據網絡的狀況決定發送窗口的發送窗口值,減少發送數據量,從而規避網絡擁塞。而發送窗口值有一個很重要的公式,如下:

  • 發送窗口值 =min(CWND,接收端公告的窗口字段值),其中,CWND爲擁塞窗口。

從上面的公式,我們不難發現,發送端調整流量就是改變擁塞窗口CWND的值,那我們該何時改變,該如何改變呢?這是我們接下來要討論並解決的問題。

(1)何時改變CWND值

何時改變?很簡單,應該是網絡發生擁塞的時候,就是需要改變的時候。但是發送端是如何知道網絡擁塞的呢?這個就需要摸着石頭過河,具體流程如下:
在這裏插入圖片描述

(2)如何改變CWND值

既然TCP報文丟失程度各有不同,那麼我們就需要有不同流量調整策略應對不同程度丟失報文的情況,具體策略有兩種,如下:

1) 慢啓動

  1. TCP連接剛建立時,發送1個(即:20)TCP報文;
  2. 收到確認應答後,發送2個(即:21)報文;
  3. 再收到確認應答後,發送4個(即:22)報文;
  4. 依次成倍增大,直至達到接收端公告的窗口值或發生報文丟失爲止;

假設擁塞點=16,慢啓動如下圖所示:
在這裏插入圖片描述

2)擁塞避免

擁塞避免是在超過慢啓動閾值時,不再採用2的指數增長方式,而是採用線性增長,逐步接近擁塞點,如下圖所示:
在這裏插入圖片描述

3)慢啓動和擁塞避免示例

在這裏插入圖片描述

  • ①之前,TCP連接剛建立,【慢啓動】探測網絡狀態;
  • ①處,到達擁塞點;
  • ①②之間,網絡狀態良好;
  • ②處,重傳定時器溢出,流量降至爲1;
  • ②③之間,採用【慢啓動】進行流量增長;
  • ③處,慢啓動閾值(即當前擁塞窗口的一半);
  • ③④,一旦超過慢啓動閾值,開始【擁塞避免】,線性增長,逐步接近擁塞點;
  • ④處,連續4次相同確認序號的確認應答,流量降至爲當前流量的一半,14÷2=7;
  • ④之後,繼續採用【擁塞避免】線性增長,逐步接近擁塞點。

六、基於TCP的socket類

socket(中文名:套接字),是進程間通信的一種方式。我們只要地址(IP+Port),就可以完成同一個終端或者不同終端兩個進程之間的通信,其中:

  • IP:是Internet Protocol(網際互連協議)的縮寫,IP地址全網統一編址,標識互聯網中不同的終端。換而言之,通過IP,我們可以精準定位對應的終端;
  • Port:端口號,是終端中統一編址,標識終端中不同應用進程。換而言之,通過Port,我們可以精準定位對應的進程;

接下來我們藉助python中的socket類,來實現兩個進程之間的通信……
首先,我們需要實現TCP服務端,文件名爲tcp_server.py,代碼如下:

import socket

# 服務端的地址(ip+port)
ip_port = ("127.0.0.1", 9000)

# 創建一個套接字
tcp_sk = socket.socket()
# 將該套接字綁定服務器的地址(IP+port)
tcp_sk.bind(ip_port)
# 監聽連接
tcp_sk.listen(1)

# 接受客戶的連接
conn, addr = tcp_sk.accept()
# 設置接收1024個字節,並接收客戶端的消息和服務端的地址(IP+port)
ret = conn.recv(1024).decode("utf-8")
print(ret)
# 服務器發送消息給客戶端(消息必須轉換成字節傳輸)
conn.send(b"Hello,I'm server,I am really glad to serve you!")
# 關閉客戶端連接(客戶端套接字)
conn.close()

# 關閉服務器套接字
tcp_sk.close()

接下來,我們需要實現TCP客戶端,文件名爲tcp_client.py,代碼如下:

import socket

# 服務端的地址(ip+port)
ip_port = ("127.0.0.1", 9000)

# 創建一個套接字
tcp_sk = socket.socket()
# 嘗試連接服務器
tcp_sk.connect(ip_port)
# 客戶端發送消息給服務端
tcp_sk.send(b"Hello,I'm client!")
# 設置接收1024個字節,並接收服務端的消息和服務端的地址(IP+port)
ret = tcp_sk.recv(1024)
print(ret.decode("utf-8"))

實現了TCP服務端和客戶端之後,我們首先要啓動服務端(即運行tcp_server.py文件),服務端在等待客戶端連接,所以控制檯並無消息輸出。
然後再運行客戶端(即運行tcp_client.py文件),控制檯順出結果如下(按先後次序):

  • 服務端控制檯:
Hello,I'm client!
  • 客戶端控制檯:
Hello,I'm server,I am really glad to serve you! 

版權聲明:本文爲博主原創文章,如需轉載,請給出:
原文鏈接:https://blog.csdn.net/qq_35844043/article/details/105385186

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章