傳輸層的七七八八

佟暉發表於2023-11-09

TCP

TCP提供一種面向連線、可靠的位元組流服務。
面向連線:兩端各自維護一份資料結構,傳輸資料之前,先進行資料結構部分資訊的狀態同步,就是去建立連線,建立好之後才能傳輸資料,不需要的時候斷開連線,然後釋放相關資料結構
可靠性

  • 由TCP將報文段分段為合適的大小後交給IP層
  • TCP發出段後啟動定時器,目的端在定時器到期之前沒有確認應答,TCP會重發該段
  • 接收端收到資料段後需要確認應答,告知傳送端資料已經接收到哪裡了(透過Sequence Number和acknowledgement Number記錄)
  • TCP的首部校驗和由傳送端計算和儲存,接收端如果校驗出錯,將包丟棄不傳送確認應答,等待重發
  • TCP分段後委託下層傳送資料段,到達目的端如果出現亂序,TCP會重排序後交給應用層
  • 如果資料重複,則丟棄
  • 接收端會告知傳送端能接受的最大資料段,實現流量控制

首部格式

各欄位含義

  • Source Port: 傳送端埠號,例如http:80
  • Destionation Port:接收端埠號
  • Sequence Number(Seq):初始值由主機隨機生成,TCP流服務會對每個位元組進行編號,對傳輸的Data部分進行計數(不包含資料鏈路層、IP首部、TCP首部)。目的端的ACK會將Seq+Tcp Segment Data的值返回到傳送端,這個值就是傳送端下次傳送時的seq值。
    • 注:雖然SYN、FIN在首部,但傳輸他們時仍然會計數,單位為1byte,所以三次握手和四次揮手時,雖然Tcp Segment Data的長度為0,回覆的ACK值仍然要加1。
  • Acknowledgement Number:Seq + Tcp Segment Data計算值後返回給傳送端,表明該值和上次Seq值之間的資料我已經收到了,下次傳送時以這個值作為Seq值傳送
  • Data offset:TCP首部的長度,如果沒有選項內容,首部長度為固定20 bytes,新增選項後最大首部長度為60 bytes(受限於該欄位長度:4 bit,單位為4bytes)
  • Reserved:該欄位為了以後擴充套件使用,通常設定為0
  • Control Flags:每一位代表一個標誌,順序如上圖:
    • CWR(Congestion Window Reduced):在網路層的七七八八聊過,ECN(Explicit Congestion Notificat)的實現依靠IP首部記錄路由器是否遇到擁塞,在返回包的TCP首部中通知發生擁塞。CWR標誌和ECE標誌設定為1時,會通知對方網路擁塞
    • ECE(ECN-Echo)
    • URG(Urgent Flag):為1時表示該資料段中有需要緊急處理的資料
    • ACK(Acknowledgement Flag):TCP規定除了SYN包之外,該標誌都設定為1,表示應答有效
    • PSH(Push Flag):為1時表示將資料立即傳給上層協議,不進行快取
    • RST(Reset Flag):強制斷開連線
    • SYN(Synchronize Flag):為1時表示想要建立連線,並設定Sequence Number的初始值(握手)
    • FIN(Fin Flag):為1時表示不再傳送資料,希望斷開連線。主機收到設定FIN標誌的包後,兩端主機對對方的FIN標誌包進行確認應答。不必立即回覆,可以等待緩衝區中的所有資料傳送成功並刪除後再回復(揮手)
  • Window Size:從ACK Number的位置開始,最大可以接收的資料,傳送發傳送的資料不能超過該視窗大小。視窗為0時,對端可以傳送視窗探測包。(1 byte)
  • Checksum:校驗資料是否正確,覆蓋TCP首部和Data部分
  • Urgent Pointer:在URG標誌位1時該欄位有效,緊急資料是從資料部分的首位到緊急指標所指的位置為止。Telnet Ctrl + C時會有URG為1的包
  • Options:用於提高TCP的傳輸效能,長度最大為40bytes(首部最大60bytes - 固定部分20bytes),padding同IP首部的padding一樣,作為對options的填充,調整為32 bit的整數倍。options分為多種型別:
    • 型別2:MSS(Maximum Segment Size),在建立連線時(傳送SYN標誌的報文段)中指定MSS,表示本端能接收的最大長度的報文段,通常來說MSS越大,網路利用率越高。長度為 (MTU - IP首部 - TCP首部),對於乙太網MSS長度可達1460bytes,預設為536bytes
    • 型別3:WSOPT-Window Scale,可以提高TCP吞吐量,首部的windows size長度為16位,只能傳送最大64KBytes,使用該選項可以擴充套件到1GBytes位元組,提升了單位RTT的資料傳輸量,從而增加吞吐。
    • 型別8:上面介紹Sequence Number對傳輸資料的位元組進行計數,受32位長度的影響,如果高速傳輸一個很大的資料包,Sequence Number超出了核心解決序號迴繞問題的範圍(迴繞幅度2^31 - 1),那麼接收端就無法判斷正確的序號了,加上該選項可以區分新老序號

資料傳輸

在主機網路卡抓包繁雜資訊太多,在虛擬機器起一個基本的tcp server,回顯客戶端傳送的訊息後關閉,程式碼如下:

import socket
import sys


HOST = "0.0.0.0"
PORT = 8888

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

server.bind((HOST, PORT))
server.listen(10)
connection, addr = server.accept()

data = connection.recv(1024)

print(data.decode())

connection.sendall(data)

connection.close()
server.close()

客戶端輸入訊息傳送,並接收服務端的訊息列印

import socket

HOST = "10.211.55.3"
PORT = 8888

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect((HOST,PORT))

send_info = input("send message: ")
client.send(send_info.encode("utf-8"))

recv_info = client.recv(1024).decode("utf-8")

print(recv_info)
client.close()

三次握手建立連線

建立連線過程

  1. 客戶端發包(active open):開啟SYN標誌,客戶端的initial seq = x

客戶端向服務端發起帶SYN標誌的段,埠就是我們server.py中定義的8888,TCP Segment Len為0表示沒有傳輸資料,Acknowledgment Number為0(第一次發起,沒什麼好應答的),客戶端生成的Sequence Number為4176525122,wireshark幫我們分析的relative sequence number為0(下文使用relative sequence number),客戶端seq = 0

  1. 伺服器回包(passive open):開啟SYN標誌,服務端的initial seq = y和ack,ack的值為x+1(SYN佔用一個序號)

服務端收到請求後,向客戶端傳送SYN標誌的段,資料段長度為0,服務端的seq為2608063952,同樣relative值為0,因為上一個包的SYN是佔用sequence number的,所以ack = 1(客戶端的seq 0 + SYN標誌位1,看raw值的話就是客戶端的seq4176525122 + 1 = 41766525123)

  1. 客戶端回包:開啟ACK標誌,ack的值為服務端的y+1,seq為x+1

客戶端收到服務端的回包後,傳送ACK段,資料段長度為0,客戶端seq為1,同樣ack = 1(服務端seq 0 + SYN標誌位 1,raw值是2608063952 + 1 = 2608063953)

透過三次握手,客戶端與服務端協商好,客戶端下一次從2608063953處接收,服務端從41766525123處接收。

狀態變遷

  1. server.py啟動後服務端處於LISTEN狀態,客戶端client.py傳送SYN標誌包後處於SYN_SENT狀態
  2. 服務端收到該包後會返回SYN標誌的應答包,從LISTEN切換為SYN_RCVD狀態
  3. 客戶端傳送ACK標誌的應答包,切換為ESTABLISHED狀態
  4. 服務端收到ACK標誌的包後,切換為ESTABLISHED狀態

之後就可以進行資料傳輸了。

資料傳輸

  1. 使用我們的client.py給server.py傳送hello,資料段長度為5,根據上面的握手協商,本次傳送資料的seq為41766525123,ack不變



  1. 服務端接收到資料後給客戶端傳送ack,ack的值為 客戶端seq 41766525123 + tcp segment len 5 = 41766525128,服務端的seq為握手協商好的2608063953

  1. 應答後我們的server.py要將hello發回客戶端,於是資料段長度為5,seq為2608063953,ack跟第2步一樣

  1. 客戶端收到包後給服務端傳送ack應答,ack值為服務端seq 2608063953 + 資料段長度 5 = 2608063958

四次揮手斷開連線

TCP是雙向傳輸的(全雙工),兩端都各自維護一份連線狀態,因此每個方向必須單獨的進行關閉,所以終止連線需要四次揮手(每一方都需要給對端傳送FIN標誌位的包,並且都需要給對方回覆一個應答包):

  1. 為了實現四次揮手,TCP提供了半關閉的能力,即客戶端傳送FIN包後表示不會再傳送資料,但是仍然可以接收資料,伺服器會應答該FIN包。
  2. 期間服務端可以繼續向客戶端傳送未完成的資料,服務端也不需要再傳送時,會向客戶端傳送FIN包,客戶端應答該FIN包,雙方連線徹底關閉。

斷開連線過程

斷開的請求可以由任意一端發起,server.py是讓服務端將客戶端發來的內容傳送出去後直接關閉連線,所以本次抓包是從服務端發起斷開的

  1. 服務端發起關閉:開啟FIN標誌,seq = x,len = 0,上一個包沒有資料,ACK不變

因為之前應答過客戶端的hello,服務端seq變為2608063958,之前資料段的應答包已經傳送過了(有時候會合併傳送),所以ack不變為4176525128

  1. 客戶端回包:對FIN標誌段進行應答,資料段長度為0,ack = x + 1,seq = y,len = 0

同樣因為應答過服務端的hello,客戶端seq為4176525128,FIN標誌佔用一個seq,所以ack為服務端seq 2608063958 + FIN標誌 1 = 2608063959

  1. 客戶端發起關閉:開啟FIN標誌,seq = y, ack = x + 1,len = 0

客戶端發起關閉,開啟FIN標誌,seq為4176525128,第2步回包已經應答過服務端的FIN包,ack不變

  1. 服務端回包:對FIN標誌段進行應答,seq = x + 1, ack = y + 1, len = 0

服務端發起應答,第2步客戶端會FIN包後告訴服務端下次從seq = 2608063959傳送,所以seq = 2608063959,ack為客戶端seq 4176525128 + FIN標誌 1 = 4176525129

至此,雙方連線徹底關閉。

狀態

借用劉超老師的圖,根據我們抓包的情況把左邊看成服務端,右邊看成客戶端

  1. 傳輸資料過程中客戶端和服務端都是ESTABLISHED狀態,服務端發出FIN標誌資料段後進入FIN_WAIT_1,等待客戶端回包。如果服務端收不到ACK回包,會重傳該報文(重傳次數由tcp_orphan_retries控制),超時會斷開連線。
  2. 客戶端收到FIN包後,傳送ACK包並進入CLOSE_WAIT狀態,如果這個ACK丟失,服務端沒有收到,服務端會重傳FIN包再次等待客戶端的ACK。
  3. 服務端收到ACK包後進入FIN_WAIT_2狀態,等待客戶端的FIN包發來。
    1. 如果呼叫close關閉連線,超過tcp_fin_timeout規定的時間,客戶端沒有發來FIN包,那麼服務端會直接關閉
    2. 如果服務端呼叫shutdown來關閉連線,仍然可以接收資料,如果客戶端沒有傳送FIN標誌的包,那麼服務端會一直處於FIN_WAIT_2狀態
  4. 客戶端傳送FIN包後進入LAST_ACK,等待服務端的ACK,如果等不到會重傳FIN包,超過tcp_orphan_retries就會斷開連線
  5. 服務端收到客戶端的FIN包,併傳送ACK回包後進入TIME_WAIT狀態,等待2MSL的時間後關閉連線,如果中間收到了客戶端重發的FIN包,會重置2MSL的定時器。如果沒有進行2MSL的等待直接退出,可能會出現客戶端的FIN包無法收到ACK的情況,這時客戶端再次傳送FIN包會收到服務端RST的回包(Connection reset by peer)
  6. 客戶端收到服務端的ACK後徹底關閉
TIME_WAIT的2MSL

MSL(Maximum Segment Lifetime)是報文最大生存時間,MSL>=TTL的時間,確保了超過該時間報文會在網路傳輸中被丟棄,TIME_WAIT設定為2倍MSL的設值允許報文至少被丟棄1次,linux停留在TIME_WAIT的時間為60秒,所以我們的server.py啟動並執行完之後立馬再次啟動會bind失敗,告知埠占用。

如果TIME_WAIT等待的時間不夠可能會將舊的seq插入新的連線資料中(序列號迴繞並延遲到達)

異常斷開連線

TCP是透過核心管理的,應用層需要透過send/recv來傳送和接受資料,如果連線斷開後應用層繼續recv,會收到Connection reset by peer(之前的專案中,Prometheus相關的日誌會出現大量的Connection reset by peer,原因是後端收集資料太慢,而server又使用python的WSGI server,無法支援長連線,導致讀取時對端已經關閉的情況),如果是send則會收到Broken pipe

無資料傳輸異常斷開(tcp keepalive)

socket透過設定SO_KEEPALIVE啟動keepalive,可以透過sysctl -a檢視系統設定,若開啟了keepalive,兩端沒有資料傳輸,一端崩潰,另一端傳送探測包經過 7200 + 75 * 9 = 7875秒後認為對端掛了

# 過了7200秒後無資料互動啟動探測
net.ipv4.tcp_keepalive_time = 7200
# 探測間隔時間為75秒
net.ipv4.tcp_keepalive_intvl = 75
# 一共探測9次,一直無響應就認為對端掛了,關閉連線
net.ipv4.tcp_keepalive_probes = 9

如果服務端沒有開啟keepalive,這個tcp連線會一直處於ESTABLISHED狀態,服務端重啟失效

埠失效(RST標誌)

一般異常關閉連線的時候會使用RST標誌,發出或收到該標誌的核心會清理該連線相應的記憶體資源、埠。接收到RST的一端會收到Connection reset或者Connection refused。大概情況:

  1. 埠現在不可用
  2. socket關閉
    1. 客戶端訊息傳送完之前關閉了socket,會發一個RST到服務端
    2. 服務端關閉了socket,客戶端再傳送訊息,服務端會回覆一個RST包
程式崩潰

上面說到TCP棧是由作業系統管理的,如果一方程式發生了崩潰並被系統感知,作業系統會與對端進行四次揮手結束連線

資料傳輸中主機崩潰
  1. 客戶端崩潰後重啟,服務端利用超時重傳機制重傳報文,客戶端之前連線的上下文都不存在了,會傳送RST包到服務端關閉連線
  2. 客戶端永久下線,服務端超時重傳達到最大超時時間最大重傳次數,服務端會斷開連線並透過socket傳送ETIMEOUT到應用程式

TCP狀態機

該狀態圖中A為客戶端,B為服務端。由客戶端發起連線,也由客戶端發起關閉。

  • (1)(2)(3)(4)(5)表示發起連線的狀態,可以對照三次握手
  • (一)(二)(三)(四)(五)(六)表示斷開連線的狀態,可以對照四次揮手
  • 實線為客戶端A,虛線為服務端B

觸發重傳機制

超時重傳

上面的連線建立、傳輸資料、連線關閉都會出現資料包未被接收的情況,傳送端觸發重傳機制,TCP提供可靠傳輸依靠確認接收端已經收到了資料,也就是說透過資料發出去到收到接收端發來的ack報文才算是完成這一報文段的傳輸,用收到ack的時間戳減去傳送資料的時間戳得到的差值就是這個包的RTT(Round-Trip Time),其中的問題是出去的包可能會丟失,對端收到資料後返回的ack也可能丟失。TCP透過在傳送時設定一個定時器,如果超過定時器就重傳資料,具體的問題就在於如何設定定時器間隔時間和重傳的頻率。

  1. 如果定時器時間設定過大,會出現網路利用率低的情況,丟了很久了才重傳
  2. 如果定時器時間設定過小,可能網路只是延遲略大,第一個包還沒到,觸發重傳的第二個包就發出來了,給鏈路增加了不必要的負載

所以重傳定時器的時間應略大於RTT比較合適,而網路環境的速率是經常變化的。所以跟蹤測量RTT並且根據該值來動態設定重傳定時器RTO (Retransmission Time Out)

快速重傳

快速重傳的機制擁塞控制會用到,如果傳送端接收到了3個相同的ack後,會在超時重傳的定時器過期之前重傳丟失的seq,比如傳送了seq1~seq5,但是seq2、seq3都丟失了,接收端回覆ack時回覆的都是seq2的ack,再收到seq3的3個ack後,才能再重傳seq3的ack。網路利用率嚴重下降。現在Linux中會開啟net.ipv4.tcp_sack=1net.ipv4.tcp_dsack=1,分別對應SACK( Selective Acknowledgment)重傳機制和Duplicate SACK重傳機制。

SACK

在TCP首部options裡設定SACK,接收端將已經收到的資料資訊傳送給傳送端,這樣傳送端在收到三次重複ACK後啟動快速重傳機制,但是根據這個欄位可以看到丟失的資料,重發則傳送丟失的那些seq就可以了

Duplicate SACK

SACK主要是告訴傳送端,哪些資料是重複傳送了。可以判斷出是ack應答丟了導致的重發,還是傳送方的資料包延時到達導致的重發。

流量控制

滑動視窗

在我們資料傳輸部分的抓包中,本地網路棧加上資料包較小,都是一發一答的順序來進行的。如果是遠端伺服器,RRT時間較長的話傳輸會變得低效,應答包不承載資料,只是告知傳送端我的資料接收到哪裡了,你下次從哪個seq開始傳送,如果應答包丟了,傳送端還得等著超時重傳再收到ack後傳送下一段。所以如果要提高傳輸效率,引入了滑動視窗的概念:接收端可以告訴客戶端,我的緩衝區能放多少資料,你看著發。至於接收端發回的ack,如果中間的某次ack走丟了,比如200299的ack收到了,300399的走丟了,400~499的收到了,那麼傳送端就認為,500之前的所有資料接收端都收到了,不用重傳,繼續傳送。或者是接收端先不傳送ack,直接傳送一個500的ack,這種方式叫做累計應答。傳送端和接收端都要維護一個視窗用來限制收發資料的大小。

傳送端視窗1

  • seq 1、2、3都傳送並收到了確認
  • seq 4~9是已經傳送但是未收到ack確認的
  • seq 10~12是傳送端可以接著傳送的
  • seq 13~15超過了當前視窗大小,傳送端不能傳送,否則發出去也會被接收端丟掉

接下來傳送端繼續傳送10、11、12

服務端接收視窗1

  • 藍色部分已經接收併傳送了ack,但是應用層還沒有讀取,不佔用視窗大小
  • 橙色部分是已經收到了資料,還沒有確認,此時可以直接確認ack=7,傳送端收到ack後就會知道4、5、6的資料包接收端已經接收到了。因為存在7、8、9還沒有收到,所以無法傳送ack=11的確認。
  • 紅色部分超出視窗大小,無法接收

接下來服務端傳送ack=7確認4、5、6已經收到,視窗滑動後如下

服務端接收視窗2

  • 4、5、6已經ack,視窗向右移動3個
  • 之前不能接收的13、14、15現在可以接收

客戶端傳送視窗2

  • 4、5、6已經ack,之前不可傳送13、14、15已經傳送等待服務端確認

視窗大小變化

應用程式無法及時讀取快取內容

  1. 視窗的大小是透過兩端互動資料段中TCP首部的windows指定的,視窗大小即當前系統給TCP分配的快取區大小受系統繁忙程度的影響,系統繁忙,應用層無法及時讀取TCP緩衝區中的內容(圖中藍色部分),那麼TCP的視窗大小就要減小,告訴傳送端,那麼傳送端下次傳送的資料就減少。
  2. 極端情況下減小到0,傳送端不能再傳送任何資料,這時會啟動定時器來傳送探測包看視窗何時變大,如果回覆的windows大小仍然為0,就重新啟動定時器。

作業系統減小TCP快取

第一種情況緩衝區大小不變,只是改變接收視窗的大小。而更糟糕的情況是作業系統減小接收端緩衝區大小,TCP規定必須先減小視窗大小,然後才能減小緩衝區。如果傳送端按照上次視窗的大小傳送了120位元組的資料,而應用層還沒有處理緩衝區中的資料,作業系統將緩衝區減小60位元組,此時接收端的視窗為60位元組,傳送視窗通告告訴客戶端,但是訊息已經發出,120位元組的資料超過了當前視窗大小,發生丟包。

糊塗視窗綜合症(Silly Window Syndrome)

接收端會通告一個小視窗,比方5位元組的視窗,TCP首部的固定長度就有20位元組,再加上IP首部等長度,傳送端的一個包實際傳輸了5位元組,但是包大小就有幾十位元組。顯然網路利用率大大降低,這個症狀就是糊塗視窗綜合症。
為了避免該現象發生,根據TCP首部選項裡的MSS大小做控制:

  1. 傳送端滿足以下條件之一才能傳送
    1. 可以傳送>=MSS長度的報文段
    2. 資料長度至少為接收端通告視窗大小的一半
    3. 之前資料的ack接收到之後
  2. 服務端通告視窗大小的方式
    1. 如果視窗大小小於MSS與1/2快取大小中最小的一個,關閉視窗
    2. 視窗大小至少增長到MSS,或者超過1/2快取大小,開啟視窗

擁塞控制

流量控制是根據主機緩衝區大小調節滑動視窗來限制客戶端的傳送,而擁塞控制相當於慢慢試探網路頻寬有多大,擁塞狀況如何來調整傳送端的傳送速率,這裡引入了擁塞視窗,實際的傳送視窗等於滑動視窗和擁塞視窗中最小的一個。

擁塞視窗

擁塞視窗是由傳送端決定的,試探的意思就是慢慢的增大擁塞視窗,如果觸發了超時重傳,就認為是網路擁塞,較小擁塞視窗

慢啟動

建立連線後,每收到一個ACK就將擁塞視窗加1(單位為1個MSS):

  1. 收到第一個ACK時,擁塞視窗為1+1 = 2.
  2. 傳送兩個報文,收到兩個ACK,擁塞視窗為2+2=4
  3. 4+4 = 8,指數型增長

增長到ssthresh(slow start threshold)65535 bytes這個值後,啟用擁塞避免

擁塞避免

  1. 上面擁塞視窗增長到8,收到8個ACK後,每個增長1/8
  2. 下次傳送9個,收到9個ACK後,每個增長1/9
  3. 下次傳送10個,線性增長

增長到觸發重傳機制後,表示網路發生擁塞,啟動快速重傳

快速重傳

比如當前視窗是12,將之前的擁塞視窗減半為6,再將減半的值設定給ssthresh=6,再進入快速重傳:

  1. 如果收到了3個重複的ACK(類似快速重傳演算法),擁塞視窗+3(為了儘快將丟失的包重傳)
  2. 重傳丟失的包
  3. 再收到重複的ACK後,把擁塞視窗+1
  4. 直到收到了新的ACK,再將ssthresh設定為進入快速重傳之前的值6
  5. 進入避免擁塞演算法

UDP

與TCP不同,UDP的首部格式簡單,傳輸時也不需要事先建立連線,即不需要客戶端和服務端維護雙方互動的狀態;因此TCP可以以資料流的形式傳送,而程式產生一個UDP資料包,組裝成一份待傳送的IP資料包,只能傳送一個資料包或者接收一個資料包,加上UDP並無控制可言,所以很多行為與IP層類似。

首部格式

各欄位含義

  • Source Port:傳送端埠號,如果不需要回復訊息,該欄位設定為0
  • Destination Port:接收端埠號
  • Length:UDP首部長度+Data長度(因為IP資料包的最大長度為65535,UDP頭+Data實際長度不大於 65535 - IP頭20 = 65515)
  • Checksum:與TCP相同,校驗和覆蓋UDP的首部和Data部分,校驗資料包的正確性。與TCP不同的是,UDP可以設定為0表示不校驗(包括IP首部的地址和UDP首部都不校驗)。與IP層相同,如果傳送端沒有計算校驗和而接收端發現校驗和有差錯,那麼資料包會被直接丟棄而不產生差錯報文

場景

  1. UDP適用於對丟包不敏感並要求時延極低的應用,不考慮網路擁塞,一股腦往出發。
  2. 因為UDP並不需要維護端對端連線,可以應用於廣播或者多播協議。例如基於UDP協議的TFTP、DHCP、VXLAN等
  3. 因為自身的簡單,只承載傳輸的任務。所以應用層可以更靈活的按照自己的需求來做定製開發,把需要維護的狀態放在應用層來做。

傳輸層的埠號

傳輸層的埠號用來分辨交給哪個應用程式處理

埠的使用

  1. 對於UDP和TCP在核心中是獨立存在的,所以可以繫結相同的地址和埠
  2. 如果兩個TCP程式要繫結相同的埠,那麼需要繫結不同的IP地址
  3. 對於TCP,如果上文重啟server.py後會提示Address already in use,開啟多路複用(socket設定SO_REUSEPORT)允許第一個socket處於TIME_WAIT的情況下,第二個socket可以使用該地址和埠

埠範圍

知名埠號(0~1023)

相當於一種約定,例如HTTP服務用80埠,HTTPs用443埠,FTP用21埠,SSH用22埠

登記埠號(1024~49151)

我們自己實現的伺服器,從該範圍內申請埠長時間使用

客戶端埠號(49152~65535)

操縱系統動態分派,客戶端通訊臨時使用的,用完之後連線關閉,作業系統回收埠號,分配給其他的程式使用

學習自:

《趣談網路協議》劉超
《圖解TCP/IP》
《圖解HTTP》
《網路是怎樣連線的》
《TCP/IP詳解 卷一》
小林coding
https://www.xiaolincoding.com/network/3_tcp/tcp_down_and_crash.html
https://xiaolincoding.com/network/3_tcp/tcp_feature.html
https://blog.csdn.net/GV7lZB0y87u7C/article/details/121186808

相關文章