本文由cxuan分享,原題“原來這才是 Socket”,有修訂。
1、引言
本系列文章前面那些主要講解的是計算機網路的理論基礎,但對於即時通訊IM這方面的應用層開發者來說,跟計算機網路打道的其實是各種API介面。
本篇文章就來聊一下網路應用程式設計師最熟悉的Socket這個東西,拋開生澀的計算機網路理論,從應用層的角度來理解到底什麼是Socket。
對於 Socket 的認識,本文將從以下幾個方面著手介紹:
1)Socket 是什麼;
2)Socket 是如何建立的;
3)Socket 是如何連線的;
4)Socket 是如何收發資料的;
5)Socket 是如何斷開連線的;
6)Socket 套接字的刪除等。
特別說明:本文中提到的“Socket”、“網路套接字”、“套接字”,如無特殊指明,指的都是同一個東西哦。
學習交流:
- 移動端IM開發入門文章:《新手入門一篇就夠:從零開發移動端IM》
- 開源IM框架原始碼:https://github.com/JackJiang2...
(本文已同步釋出於:http://www.52im.net/thread-38...)
2、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
我的計算機會出現下面結果:
如上圖所示:
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),建立連線有一整套過程。
這個“連線”並不是真實的連線(用一根水管插在兩個電腦之間?不是你想的這樣。。。)。
實際上這個“連線”是應用程式通過 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 的值。
上面的文字不夠生動,動畫可以更好的說明這個過程:
▲ 上圖引用自《跟著動畫來學TCP三次握手和四次揮手》
(PS:這個“連線”的詳細理論知識,可以閱讀《理論經典:TCP協議的3次握手與4次揮手過程詳解》、《跟著動畫來學TCP三次握手和四次揮手》,這裡不再贅述。)
當所有建立連線的報文都能夠正常收發之後,此時套接字就已經進入可收發狀態了,此時可以認為用一根管理把兩個套接字連線了起來。當然,實際上並不存在這個管子。建立連線之後,協議棧的連線操作就結束了,也就是說 connect 已經執行完畢,控制流程被交回給應用程式。
另外:如果你對Socket程式碼更熟悉的話,可以先讀讀這篇《手把手教你寫基於TCP的Socket長連線》。
5、Socket 是如何收發資料的
當控制流程上節中的連線過程回到應用程式之後,接下來就會直接進入資料收發階段。
資料收發操作是從應用程式呼叫 write 將要傳送的資料交給協議棧開始的,協議棧收到資料之後執行傳送操作。
協議棧不會關心應用程式傳輸過來的是什麼資料,因為這些資料最終都會轉換為二進位制序列,協議棧在收到資料之後並不會馬上把資料傳送出去,而是會將資料放在傳送緩衝區,再等待應用程式傳送下一條資料。
為什麼收到資料包不會直接傳送出去,而是放在緩衝區中呢?
因為只要一旦收到資料就會傳送,就有可能傳送大量的小資料包,導致網路效率下降(所以協議棧需要將資料積攢到一定數量才能將其傳送出去)。
至於協議棧會向緩衝區放多少資料,這個不同版本和種類的作業系統有不同的說法。
不過,所有的作業系統都會遵循下面這幾個標準:
1)第一個判斷要素:是每個網路包能夠容納的資料長度,判斷的標準是 MTU,它表示的是一個網路包的最大長度。最大長度包含頭部,所以如果單論資料區的話,就會用 MTU - 包頭長度,由此的出來的最大資料長度被稱為 MSS。
2)另一個判斷標準:是時間,當應用程式產生的資料比較少,協議棧向緩衝區放置資料效率不高時,如果每次都等到 MSS 再傳送的話,可能因為等待時間太長造成延遲。在這種情況下,即使資料長度沒有到達 MSS,也應該把資料傳送出去。
但協議棧並沒有告訴我們怎樣平衡這兩個因素,如果資料長度優先,那麼效率有可能比較低;如果時間優先,那又會降低網路的效率。
經過了一段時間。。。。。。
假設我們使用的是長度有限法則:此時緩衝區已滿,協議棧要傳送資料了,協議棧剛要把資料傳送出去,卻發現無法一次性傳輸這麼大資料量(相對的)的資料,那怎麼辦呢?
在這種情況下,傳送緩衝區中的資料就會超過 MSS 的長度,傳送緩衝區中的資料會以 MSS 大小為一個資料包進行拆分,拆分出來的每塊資料都會加上 TCP,IP,乙太網頭部,然後被放進單獨的網路包中。
到現在,網路包已經準備好發往伺服器了,但是資料傳送操作還沒有結束,因為伺服器還未確認是否已經收到網路包。因此在客戶端傳送資料包之後,還需要伺服器進行確認。
TCP 模組在拆分資料時,會計算出網路包偏移量,這個偏移量就是相對於資料從頭開始計算的第幾個位元組,並將算好的位元組數寫在 TCP 頭部,TCP 模組還會生成一個網路包的序號(SYN),這個序號是唯一的,這個序號就是用來讓伺服器進行確認的。
伺服器會對客戶端傳送過來的資料包進行確認,確認無誤之後,伺服器會生成一個序號和確認號(ACK)並一起傳送給客戶端,客戶端確認之後再傳送確認號給伺服器。
我們來看一下實際的工作過程:
首先:客戶端在連線時需要計算出序號初始值,並將這個值傳送給伺服器。
接下來:伺服器通過這個初始值計算出確認號並返回給客戶端(初始值在通訊過程中有可能會丟棄,因此當伺服器收到初始值後需要返回確認號用於確認)。
同時:伺服器也需要計算出從伺服器到客戶端方向的序號初始值,並將這個值傳送給客戶端。然後,客戶端也需要根據伺服器發來的初始值計算出確認號傳送給伺服器。
至此:連線建立完成,接下來就可以進入資料收發階段了。
資料收發階段中,通訊雙方可以同時傳送請求和響應,雙方也可以同時對請求進行確認。
請求 - 確認機制非常強大:通過這一機制,我們可以確認接收方有沒有收到某個包,如果沒有收到則重新傳送,這樣一來,但凡網路中出現的任何錯誤,我們都可以即使發現並補救。
上面的文字不夠生動,動畫可以更好的理解請求 - 確認機制:
▲ 上圖引用自《跟著動畫來學TCP三次握手和四次揮手》
網路卡、集線器、路由器(見《史上最通俗的集線器、交換機、路由器功能原理入門》)都沒有錯誤補救機制,一旦檢測到錯誤就會直接丟棄資料包,應用程式也沒有這種機制,起作用的只是 TCP/IP 模組。
由於網路環境複雜多變,所以資料包會存在丟失情況,因此傳送序號和確認號也存在一定規則,TCP 會通過視窗管理確認號,我們這篇文章不再贅述,大家可以閱讀《通俗易懂-深入理解TCP協議(下):RTT、滑動視窗、擁塞處理》來尋找答案。
PS:另一篇《我們在讀寫Socket時,究竟在讀寫什麼?》中用動畫詳細說明了這個過程,有興趣可以讀一讀。
6、Socket 是如何斷開連線的
當通訊雙方不再需要收發資料時,需要斷開連線。不同的應用程式斷開連線的時機不同。
以 Web 為例:瀏覽器向 Web 伺服器傳送請求訊息,Web 伺服器再返回響應訊息,這時收發資料就全部結束了,伺服器可能會首先發起斷開響應,當然客戶端也有可能會首先發起(誰先斷開連線是應用程式做出的判斷),與協議棧無關。
無論哪一方發起斷開連線的請求,都會呼叫 Socket 庫的 close 程式。
我們以伺服器斷開連線為例:伺服器發起斷開連線請求,協議棧會生成斷開連線的 TCP 頭部,其實就是設定 FIN 位,然後委託 IP 模組向客戶端傳送資料,與此同時,伺服器的Socket會記錄下斷開連線的相關資訊。
收到伺服器發來 FIN 請求後:客戶端協議棧會將Socket標記為斷開連線狀態,然後,客戶端會向伺服器返回一個確認號,這是斷開連線的第一步,在這一步之後,應用程式還會呼叫 read 來讀取資料。等到伺服器資料傳送完成後,協議棧會通知客戶端應用程式資料已經接收完畢。
只要收到伺服器返回的所有資料,客戶端就會呼叫 close 程式來結束收發操作,這時客戶端會生成一個 FIN 傳送給伺服器,一段時間後伺服器返回 ACK 號。至此,客戶端和伺服器的通訊就結束了。
上面的文字不夠生動,動畫可以更好的說明這個過程:
▲ 上圖引用自《跟著動畫來學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連線耗時的祕密
(本文已同步釋出於:http://www.52im.net/thread-38...)