您當前的位置:首頁 > 攝影

網路程式設計懶人入門(十四):到底什麼是Socket?一文即懂!

作者:由 Jack Jiang 發表于 攝影時間:2022-02-16

本文由cxuan分享,原題“原來這才是 Socket”,有修訂。

1、引言

本系列文章前面那些主要講解的是計算機網路的理論基礎,但對於即時通訊IM這方面的應用層開發者來說,跟計算機網路打道的其實是各種API介面。

本篇文章就來聊一下網路應用程式設計師最熟悉的Socket這個東西,拋開生澀的計算機網路理論,從應用層的角度來理解到底什麼是Socket。

對於 Socket 的認識,本文將從以下幾個方面著手介紹:

1)

Socket 是什麼;

2)

Socket 是如何建立的;

3)

Socket 是如何連線的;

4)

Socket 是如何收發資料的;

5)

Socket 是如何斷開連線的;

6)

Socket 套接字的刪除等。

特別說明:

本文中提到的“Socket”、“網路套接字”、“套接字”,如無特殊指明,指的都是同一個東西哦。

網路程式設計懶人入門(十四):到底什麼是Socket?一文即懂!

2、Socket 是什麼

一個數據包經由應用程式產生,進入到協議棧中進行各種報文頭的包裝,然後作業系統呼叫網絡卡驅動程式指揮硬體,把資料傳送到對端主機。

整個過程的大體的圖示如下:

網路程式設計懶人入門(十四):到底什麼是Socket?一文即懂!

我們大家知道,協議棧其實是位於作業系統中的一些協議的堆疊,這些協議包括 TCP、UDP、ARP、ICMP、IP等。

通常某個協議的設計都是為了解決特定問題的,比如:

1)

TCP 的設計就負責安全可靠的傳輸資料;

2)

UDP 設計就是報文小,傳輸效率高;

3)

ARP 的設計是能夠透過 IP 地址查詢物理(Mac)地址;

4)

ICMP 的設計目的是返回錯誤報文給主機;

5)

IP 設計的目的是為了實現大規模主機的互聯互通。

應用程式比如瀏覽器、電子郵件、檔案傳輸伺服器等產生的資料,會透過傳輸層協議進行傳輸。而應用程式是不會和傳輸層直接建立聯絡的,而是有一個能夠連線應用層和傳輸層之間的套件,這個套件就是

Socket

在上面這幅圖中,應用程式包含 Socket 和解析器,解析器的作用就是向 DNS 伺服器發起查詢,查詢目標 IP 地址(關於DNS請見《理論聯絡實際,全方位深入理解DNS》)。

應用程式的下面:

就是作業系統內部,作業系統內部包括協議棧,協議棧是一系列協議的堆疊。

作業系統下面:

就是網絡卡驅動程式,網絡卡驅動程式負責控制網絡卡硬體,驅動程式驅動網絡卡硬體完成收發工作。

在作業系統內部有一塊用於存放控制資訊的儲存空間,這塊儲存空間記錄了用於控制通訊的控制資訊。其實這些控制資訊就是 Socket 的實體,或者說存放控制資訊的記憶體空間就是Socket的實體。

這裡大家有可能不太清楚所以然,所以我用了一下 netstat 命令來給大夥看一下Socket是啥玩意。

我們在 Windows 的命令提示符中輸入:

netstat-ano

# netstat 用於顯示Socket內容 , -ano 是可選選項

# a 不僅顯示正在通訊的Socket,還顯示包括尚未開始通訊等狀態的所有Socket

# n 顯示 IP 地址和埠號

# o 顯示Socket的程式 PID

我的計算機會出現下面結果:

網路程式設計懶人入門(十四):到底什麼是Socket?一文即懂!

如上圖所示:

1)

每一行都相當於一個Socket;

2)

每一列也被稱為一個元組。

所以,一個Socket就是五元組:

1)

協議;

2)

本地地址;

3)

外部地址;

4)

狀態;

5)

PID。

PS:

有的時候也被叫做四元組,四元組不包括協議。

我們來解讀一下上圖中的資料,比如圖中的第一行:

1)

它的協議就是 TCP,本地地址和遠端地址都是

0。0。0。0

(這表示通訊還沒有開始,IP 地址暫時還未確定)。

2)

而本地埠已知是 135,但是遠端埠還未知,此時的狀態是 LISTENING(LISTENING 表示應用程式已經開啟,正在等待與遠端主機建立連線。關於各種狀態之間的轉換,大家可以閱讀《通俗易懂-深入理解TCP協議(上):理論基礎》)。

3)

最後一個元組是 PID,即程序識別符號,PID 就像我們的身份證號碼,能夠精確定位唯一的程序。

3、Socket 是如何建立的

透過上節的講解,現在你可能對 Socket 有了一個基本的認識,先喝口水,休息一下,讓我們繼續探究 Socket。

現在我有個問題,

Socket 是如何建立的呢?

Socket 是和應用程式一起建立的。

應用程式中有一個 socket 元件,在應用程式啟動時,會呼叫 socket 申請建立Socket,協議棧會根據應用程式的申請建立Socket:首先分配一個Socket所需的記憶體空間,這一步相當於是為控制資訊準備一個容器,但只有容器並沒有實際作用,所以你還需要向容器中放入控制資訊;如果你不申請建立Socket所需要的記憶體空間,你建立的控制資訊也沒有地方存放,所以分配記憶體空間,放入控制資訊缺一不可。至此Socket的建立就已經完成了。

Socket建立完成後,會返回一個Socket描述符給應用程式,這個描述符相當於是區分不同Socket的號碼牌。根據這個描述符,應用程式在委託協議棧收發資料時就需要提供這個描述符。

4、Socket 是如何連線的

Socket建立完成後,最終還是為資料收發服務的。但是,在資料收發之前,還需要進行一步“連線”(術語就是 connect),建立連線有一整套過程。

這個“連線”並不是真實的連線(用一根水管插在兩個電腦之間?不是你想的這樣。。。)。

網路程式設計懶人入門(十四):到底什麼是Socket?一文即懂!

實際上這個“連線”是應用程式透過 TCP/IP 協議標準從一個主機透過網路介質傳輸到另一個主機的過程。

Socket剛剛建立完成後,還沒有資料,也不知道通訊物件。

在這種狀態下:

即使你讓客戶端應用程式委託協議棧傳送資料,它也不知道傳送到哪裡。所以瀏覽器需要根據網址來查詢伺服器的 IP 地址(做這項工作的協議是 DNS),查詢到目標主機後,再把目標主機的 IP 告訴協議棧。至此,客戶端這邊就準備好了。

在伺服器上:

與客戶端一樣也需要建立Socket,但是同樣的它也不知道通訊物件是誰,所以我們需要讓客戶端向伺服器告知客戶端的必要資訊:

IP 地址和埠號

現在通訊雙方建立連線的必要資訊已經具備,可以開始“連線”過程了。

首先:

客戶端應用程式需要呼叫 Socket 庫中的 connect 方法,提供 socket 描述符和伺服器 IP 地址、埠號。

以下是connect的偽碼呼叫:

connect(<描述符>、<伺服器IP地址和埠號>)

這些資訊會傳遞給協議棧中的 TCP 模組,TCP 模組會對請求報文進行封裝,再傳遞給 IP 模組,進行 IP 報文頭的封裝,然後傳遞給物理層,進行幀頭封裝。

之後透過網路介質傳遞給伺服器,伺服器上會對幀頭、IP 模組、TCP 模組的報文頭進行解析,從而找到對應的Socket。

Socket收到請求後,會寫入相應的資訊,並且把狀態改為正在連線。

請求過程完成後:

伺服器的 TCP 模組會返回響應,這個過程和客戶端是一樣的(如果大家不太清楚報文頭的封裝過程,可以閱讀《快速理解TCP協議一篇就夠》)。

在一個完整的請求和響應過程中,控制資訊起到非常關鍵的作用:

1)

SYN 就是同步的縮寫,客戶端會首先發送 SYN 資料包,請求服務端建立連線;

2)

ACK 就是相應的意思,它是對傳送 SYN 資料包的響應;

3)

FIN 是終止的意思,它表示客戶端/伺服器想要終止連線。

由於網路環境的複雜多變,經常會存在資料包丟失的情況,所以雙方通訊時需要相互確認對方的資料包是否已經到達,而判斷的標準就是 ACK 的值。

上面的文字不夠生動,動畫可以更好的說明這個過程:

網路程式設計懶人入門(十四):到底什麼是Socket?一文即懂!

▲ 上圖引用自《跟著動畫來學TCP三次握手和四次揮手》

PS:

這個“連線”的詳細理論知識,可以閱讀《理論經典:TCP協議的3次握手與4次揮手過程詳解》、《跟著動畫來學TCP三次握手和四次揮手》,這裡不再贅述。)

當所有建立連線的報文都能夠正常收發之後,此時套接字就已經進入可收發狀態了,此時可以認為用一根管理把兩個套接字連線了起來。當然,實際上並不存在這個管子。建立連線之後,協議棧的連線操作就結束了,也就是說 connect 已經執行完畢,控制流程被交回給應用程式。

另外:

如果你對Socket程式碼更熟悉的話,可以先讀讀這篇《手把手教你寫基於TCP的Socket長連線》。

5、Socket 是如何收發資料的

當控制流程上節中的連線過程回到應用程式之後,接下來就會直接進入資料收發階段。

資料收發操作是從應用程式呼叫 write 將要傳送的資料交給協議棧開始的,協議棧收到資料之後執行傳送操作。

協議棧不會關心應用程式傳輸過來的是什麼資料,因為這些資料最終都會轉換為二進位制序列,協議棧在收到資料之後並不會馬上把資料傳送出去,而是會將資料放在傳送緩衝區,再等待應用程式傳送下一條資料。

為什麼收到資料包不會直接傳送出去,而是放在緩衝區中呢?

因為只要一旦收到資料就會發送,就有可能傳送大量的小資料包,導致網路效率下降(所以協議棧需要將資料積攢到一定數量才能將其傳送出去)。

至於協議棧會向緩衝區放多少資料,這個不同版本和種類的作業系統有不同的說法。

不過,所有的作業系統都會遵循下面這幾個標準:

1)第一個判斷要素:

是每個網路包能夠容納的資料長度,判斷的標準是 MTU,它表示的是一個網路包的最大長度。最大長度包含頭部,所以如果單論資料區的話,就會用 MTU - 包頭長度,由此的出來的最大資料長度被稱為 MSS。

網路程式設計懶人入門(十四):到底什麼是Socket?一文即懂!

2)另一個判斷標準:

是時間,當應用程式產生的資料比較少,協議棧向緩衝區放置資料效率不高時,如果每次都等到 MSS 再發送的話,可能因為等待時間太長造成延遲。在這種情況下,即使資料長度沒有到達 MSS,也應該把資料傳送出去。

但協議棧並沒有告訴我們怎樣平衡這兩個因素,如果資料長度優先,那麼效率有可能比較低;如果時間優先,那又會降低網路的效率。

經過了一段時間……

網路程式設計懶人入門(十四):到底什麼是Socket?一文即懂!

假設我們使用的是長度有限法則:此時緩衝區已滿,協議棧要傳送資料了,協議棧剛要把資料傳送出去,卻發現無法一次性傳輸這麼大資料量(相對的)的資料,那怎麼辦呢?

在這種情況下,傳送緩衝區中的資料就會超過 MSS 的長度,傳送緩衝區中的資料會以 MSS 大小為一個數據包進行拆分,拆分出來的每塊資料都會加上 TCP,IP,乙太網頭部,然後被放進單獨的網路包中。

網路程式設計懶人入門(十四):到底什麼是Socket?一文即懂!

到現在,網路包已經準備好發往伺服器了,但是資料傳送操作還沒有結束,因為伺服器還未確認是否已經收到網路包。因此在客戶端傳送資料包之後,還需要伺服器進行確認。

TCP 模組在拆分資料時,會計算出網路包偏移量,這個偏移量就是相對於資料從頭開始計算的第幾個位元組,並將算好的位元組數寫在 TCP 頭部,TCP 模組還會生成一個網路包的序號(SYN),這個序號是唯一的,這個序號就是用來讓伺服器進行確認的。

伺服器會對客戶端傳送過來的資料包進行確認,確認無誤之後,伺服器會生成一個序號和確認號(ACK)並一起傳送給客戶端,客戶端確認之後再發送確認號給伺服器。

我們來看一下實際的工作過程:

網路程式設計懶人入門(十四):到底什麼是Socket?一文即懂!

首先:

客戶端在連線時需要計算出序號初始值,並將這個值傳送給伺服器。

接下來:

伺服器透過這個初始值計算出確認號並返回給客戶端(初始值在通訊過程中有可能會丟棄,因此當伺服器收到初始值後需要返回確認號用於確認)。

同時:

伺服器也需要計算出從伺服器到客戶端方向的序號初始值,並將這個值傳送給客戶端。然後,客戶端也需要根據伺服器發來的初始值計算出確認號傳送給伺服器。

至此:

連線建立完成,接下來就可以進入資料收發階段了。

資料收發階段中,通訊雙方可以同時傳送請求和響應,雙方也可以同時對請求進行確認。

請求 - 確認機制非常強大:

透過這一機制,我們可以確認接收方有沒有收到某個包,如果沒有收到則重新發送,這樣一來,但凡網路中出現的任何錯誤,我們都可以即使發現並補救。

上面的文字不夠生動,動畫可以更好的理解請求 - 確認機制:

網路程式設計懶人入門(十四):到底什麼是Socket?一文即懂!

▲ 上圖引用自《跟著動畫來學TCP三次握手和四次揮手》

網絡卡、集線器、路由器(見《史上最通俗的集線器、交換機、路由器功能原理入門》)都沒有錯誤補救機制,一旦檢測到錯誤就會直接丟棄資料包,應用程式也沒有這種機制,起作用的只是 TCP/IP 模組。

由於網路環境複雜多變,所以資料包會存在丟失情況,因此傳送序號和確認號也存在一定規則,TCP 會透過視窗管理確認號,我們這篇文章不再贅述,大家可以閱讀《通俗易懂-深入理解TCP協議(下):RTT、滑動視窗、擁塞處理》來尋找答案。

PS:

另一篇《我們在讀寫Socket時,究竟在讀寫什麼?》中用動畫詳細說明了這個過程,有興趣可以讀一讀。

6、Socket 是如何斷開連線的

當通訊雙方不再需要收發資料時,需要斷開連線。不同的應用程式斷開連線的時機不同。

以 Web 為例:

瀏覽器向 Web 伺服器傳送請求訊息,Web 伺服器再返回響應訊息,這時收發資料就全部結束了,伺服器可能會首先發起斷開響應,當然客戶端也有可能會首先發起(誰先斷開連線是應用程式做出的判斷),與協議棧無關。

網路程式設計懶人入門(十四):到底什麼是Socket?一文即懂!

無論哪一方發起斷開連線的請求,都會呼叫 Socket 庫的 close 程式。

我們以伺服器斷開連線為例:

伺服器發起斷開連線請求,協議棧會生成斷開連線的 TCP 頭部,其實就是設定 FIN 位,然後委託 IP 模組向客戶端傳送資料,與此同時,伺服器的Socket會記錄下斷開連線的相關資訊。

收到伺服器發來 FIN 請求後:

客戶端協議棧會將Socket標記為斷開連線狀態,然後,客戶端會向伺服器返回一個確認號,這是斷開連線的第一步,在這一步之後,應用程式還會呼叫 read 來讀取資料。等到伺服器資料傳送完成後,協議棧會通知客戶端應用程式資料已經接收完畢。

只要收到伺服器返回的所有資料,客戶端就會呼叫 close 程式來結束收發操作,這時客戶端會生成一個 FIN 傳送給伺服器,一段時間後伺服器返回 ACK 號。至此,客戶端和伺服器的通訊就結束了。

上面的文字不夠生動,動畫可以更好的說明這個過程:

網路程式設計懶人入門(十四):到底什麼是Socket?一文即懂!

▲ 上圖引用自《跟著動畫來學TCP三次握手和四次揮手》

PS:

斷開連線的詳細理論知識,可以閱讀《理論經典:TCP協議的3次握手與4次揮手過程詳解》、《跟著動畫來學TCP三次握手和四次揮手》,這裡不再贅述。

7、Socket的刪除

上述通訊過程完成後,用來通訊的Socket就不再會使用了,此時我們就可以刪除這個Socket了。

不過,這時候Socket不會馬上刪除,而是等過一段時間再刪除。

等待這段時間是為了防止誤操作,最常見的誤操作就是客戶端返回的確認號丟失,至於等待多長時間,和資料包重傳的方式有關,這裡我們就深入展開討論了。

關於Socket操作的全過程,如果從系統的角度來看,可能會更深入一些,建議可以深入閱讀張彥飛的《深入作業系統,從核心理解網路包的接收過程(Linux篇)》一文。

8、系列文章

本文是系列文章中的第14篇,本系列文章的大綱如下:

[1] 網路程式設計懶人入門(一):快速理解網路通訊協議(上篇)

[2] 網路程式設計懶人入門(二):快速理解網路通訊協議(下篇)

[3] 網路程式設計懶人入門(三):快速理解TCP協議一篇就夠

[4] 網路程式設計懶人入門(四):快速理解TCP和UDP的差異

[5] 網路程式設計懶人入門(五):快速理解為什麼說UDP有時比TCP更有優勢

[6] 網路程式設計懶人入門(六):史上最通俗的集線器、交換機、路由器功能原理入門

[7] 網路程式設計懶人入門(七):深入淺出,全面理解HTTP協議

[8] 網路程式設計懶人入門(八):手把手教你寫基於TCP的Socket長連線

[9] 網路程式設計懶人入門(九):通俗講解,有了IP地址,為何還要用MAC地址?

[10] 網路程式設計懶人入門(十):一泡尿的時間,快速讀懂QUIC協議

[11] 網路程式設計懶人入門(十一):一文讀懂什麼是IPv6

[12] 網路程式設計懶人入門(十二):快速讀懂Http/3協議,一篇就夠!

[13] 網路程式設計懶人入門(十三):一泡尿的時間,快速搞懂TCP和UDP的區別

[14] 網路程式設計懶人入門(十四):到底什麼是Socket?一文即懂!(

* 本文

9、參考資料

[1] TCP/IP詳解 - 第17章·TCP:傳輸控制協議

[2] TCP/IP詳解 - 第18章·TCP連線的建立與終止

[3] TCP/IP詳解 - 第21章·TCP的超時與重傳

[4] 快速理解網路通訊協議(上篇)

[5] 快速理解網路通訊協議(下篇)

[6] 面視必備,史上最通俗計算機網路分層詳解

[7] 假如你來設計網路,會怎麼做?

[8] 假如你來設計TCP協議,會怎麼做?

[10] 淺析TCP協議中的疑難雜症(下篇)

[11] 關閉TCP連線時為什麼會TIME_WAIT、CLOSE_WAIT

[12] 從底層入手,深度分析TCP連線耗時的秘密

學習交流:

- 移動端IM開發入門文章:《新手入門一篇就夠:從零開發移動端IM》

- 開源IM框架原始碼:

https://

github。com/JackJiang201

1/MobileIMSDK

(本文已同步釋出於:

http://www。

52im。net/thread-3821-1-

1。html

標簽: Socket  TCP  客戶端  伺服器  協議