網(wǎng)友說socket通信講的不徹底,原來這才是Socket
關于對 Socket 的認識,大致分為下面幾個主題,Socket 是什么,Socket 是如何創(chuàng)建的,Socket 是如何連接并收發(fā)數(shù)據(jù)的,Socket 套接字的刪除等。
Socket 是什么以及創(chuàng)建過程
一個數(shù)據(jù)包經(jīng)由應用程序產(chǎn)生,進入到協(xié)議棧中進行各種報文頭的包裝,然后操作系統(tǒng)調(diào)用網(wǎng)卡驅動程序指揮硬件,把數(shù)據(jù)發(fā)送到對端主機。整個過程的大體的圖示如下。

我們大家知道,協(xié)議棧其實是位于操作系統(tǒng)中的一些協(xié)議的堆疊,這些協(xié)議包括?TCP、UDP、ARP、ICMP、IP等。通常某個協(xié)議的設計都是為了解決某些問題,比如 TCP 的設計就負責安全可靠的傳輸數(shù)據(jù),UDP 設計就是報文小,傳輸效率高,ARP 的設計是能夠通過 IP 地址查詢物理(Mac)地址,ICMP 的設計目的是返回錯誤報文給主機,IP 設計的目的是為了實現(xiàn)大規(guī)模主機的互聯(lián)互通。
應用程序比如瀏覽器、電子郵件、文件傳輸服務器等產(chǎn)生的數(shù)據(jù),會通過傳輸層協(xié)議進行傳輸,而應用程序是不會和傳輸層直接建立聯(lián)系的,而是有一個能夠連接應用層和傳輸層之間的套件,這個套件就是 Socket。
在上面這幅圖中,應用程序包含 Socket 和解析器,解析器的作用就是向 DNS 服務器發(fā)起查詢,查詢目標 IP 地址。
應用程序的下面就是操作系統(tǒng)內(nèi)部,操作系統(tǒng)內(nèi)部包括協(xié)議棧,協(xié)議棧是一系列協(xié)議的堆疊。操作系統(tǒng)下面就是網(wǎng)卡驅動程序,網(wǎng)卡驅動程序負責控制網(wǎng)卡硬件,驅動程序驅動網(wǎng)卡硬件完成收發(fā)工作。
在操作系統(tǒng)內(nèi)部有一塊用于存放控制信息的存儲空間,這塊存儲空間記錄了用于控制通信的控制信息。其實這些控制信息就是 Socket 的實體,或者說存放控制信息的內(nèi)存空間就是套接字的實體。
這里大家有可能不太清楚所以然,所以我用了一下 netstat 命令來給大伙看一下套接字是啥玩意。
我們在 Windows 的命令提示符中輸入:
netstat -ano# netstat 用于顯示套接字內(nèi)容 , -ano 是可選選項# a 不僅顯示正在通信的套接字,還顯示包括尚未開始通信等狀態(tài)的所有套接字# n 顯示 IP 地址和端口號# o 顯示套接字的程序 PID
我的計算機會出現(xiàn)下面結果:

圖中的每一行都相當于一個套接字,每一列也被稱為一個元組,所以一個套接字就是五元組(協(xié)議、本地地址、外部地址、狀態(tài)、PID)。有的時候也被叫做四元組,四元組不包括協(xié)議。
比如圖中的第一行,它的協(xié)議就是 TCP,本地地址和遠程地址都是 0.0.0.0,這表示通信還沒有開始,IP 地址暫時還未確定,而本地端口已知是 135,但是遠程端口還未知,此時的狀態(tài)是 LISTENING,LISTENING 表示應用程序已經(jīng)打開,正在等待與遠程主機建立連接(關于各種狀態(tài)之間的轉換,大家可以閱讀筆者的這篇文章 TCP ,丫的終于來了?。。┳詈笠粋€元組是 PID,即進程標識符,PID 就像我們的身份證號碼,能夠精確定位唯一的進程。
現(xiàn)在你可能對 Socket 有了一個基本的認識,現(xiàn)在喝口水,休息一下,讓我們繼續(xù)探究 Socket。
現(xiàn)在我有個問題,Socket 是如何創(chuàng)建的呢?
Socket 是和應用程序一起創(chuàng)建的。應用程序中有一個 socket 組件,在應用程序啟動時,會調(diào)用 socket 申請創(chuàng)建套接字,協(xié)議棧會根據(jù)應用程序的申請創(chuàng)建套接字:首先分配一個套接字所需的內(nèi)存空間,這一步相當于是為控制信息準備一個容器,但只有容器并沒有實際作用,所以你還需要向容器中放入控制信息;如果你不申請創(chuàng)建套接字所需要的內(nèi)存空間,你創(chuàng)建的控制信息也沒有地方存放,所以分配內(nèi)存空間,放入控制信息缺一不可。至此套接字的創(chuàng)建就已經(jīng)完成了。
套接字創(chuàng)建完成后,會返回一個套接字描述符給應用程序,這個描述符相當于是區(qū)分不同套接字的號碼牌。根據(jù)這個描述符,應用程序在委托協(xié)議棧收發(fā)數(shù)據(jù)時就需要提供這個描述符。
【文章福利】小編推薦自己的Linux內(nèi)核技術交流群:【865977150】整理了一些個人覺得比較好的學習書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦!??!前100名進群領取,額外贈送一份價值699的內(nèi)核資料包(含視頻教程、電子書、實戰(zhàn)項目及代碼)

學習直通車:
Linux內(nèi)核源碼/內(nèi)存調(diào)優(yōu)/文件系統(tǒng)/進程管理/設備驅動/網(wǎng)絡協(xié)議棧-學習視頻教程-騰訊課堂ke.qq.com/course/4032547?flowToken=1040236ke.qq.com/course/4032547?flowToken=1040236ke.qq.com/course/4032547?flowToken=1040236
內(nèi)核資料直通車:
Linux內(nèi)核源碼技術學習路線+視頻教程內(nèi)核源碼docs.qq.com/doc/DTXFKZlZ4YmFYQWZS

套接字連接
套接字創(chuàng)建完成后,最終還是為數(shù)據(jù)收發(fā)服務的,在數(shù)據(jù)收發(fā)之前,還需要進行一步 connect,也就是建立連接的過程。這個連接并不是真實的連接:用一根水管插在兩個電腦之間。

而是應用程序通過 TCP/IP 協(xié)議標準從一個主機通過網(wǎng)絡介質傳輸?shù)搅硪粋€主機的過程。
套接字剛剛創(chuàng)建完成后,還沒有數(shù)據(jù),也不知道通信對象。在這種狀態(tài)下,即使你讓客戶端應用程序委托協(xié)議棧發(fā)送數(shù)據(jù),它也不知道發(fā)送到哪里。所以瀏覽器需要根據(jù)網(wǎng)址來查詢服務器的 IP 地址,做這項工作的協(xié)議是 DNS,查詢到目標主機后,再把目標主機的 IP 告訴協(xié)議棧,至此,客戶端這邊就準備好了。
在服務器上,與客戶端一樣也需要創(chuàng)建套接字,但是同樣的它也不知道通信對象是誰,所以我們需要讓客戶端向服務器告知客戶端的必要信息:IP 地址和端口號。
現(xiàn)在通信雙方建立連接的必要信息已經(jīng)具備,只欠一股東南風了。通信雙方收到數(shù)據(jù)之后,還需要一塊位置來存放,這個位置就是緩沖區(qū),它是內(nèi)存的一部分,有了緩沖區(qū),就能夠進行數(shù)據(jù)的收發(fā)操作了。
OK,現(xiàn)在客戶端想要給服務器發(fā)送一條數(shù)據(jù),該進行哪些操作呢?
首先,客戶端應用程序需要調(diào)用 Socket 庫中的 connect 方法,提供 socket 描述符和服務器 IP 地址、端口號。
connect(<描述符>、<服務器IP地址和端口號>)
這些信息會傳遞給協(xié)議棧中的 TCP 模塊,TCP 模塊會對請求報文進行封裝,再傳遞給 IP 模塊,進行 IP 報文頭的封裝,然后傳遞給物理層,進行幀頭封裝,之后通過網(wǎng)絡介質傳遞給服務器,服務器上會對幀頭、IP 模塊、TCP 模塊的報文頭進行解析,從而找到對應的套接字,套接字收到請求后,會寫入相應的信息,并且把狀態(tài)改為正在連接。請求過程完成后,服務器的 TCP 模塊會返回響應,這個過程和客戶端是一樣的(如果大家不太清楚報文頭的封裝過程,可以閱讀筆者的這篇文章 TCP/IP 基礎知識總結)
在一個完整的請求和響應過程中,控制信息起到非常關鍵的作用(具體的作用我們后面會說)。
SYN 就是同步的縮寫,客戶端會首先發(fā)送 SYN 數(shù)據(jù)包,請求服務端建立連接。
ACK 就是相應的意思,它是對發(fā)送 SYN 數(shù)據(jù)包的響應。
FIN 是終止的意思,它表示客戶端/服務器想要終止連接。
由于網(wǎng)絡環(huán)境的復雜多變,經(jīng)常會存在數(shù)據(jù)包丟失的情況,所以雙方通信時需要相互確認對方的數(shù)據(jù)包是否已經(jīng)到達,而判斷的標準就是 ACK 的值。(通信雙方連接的建立會經(jīng)過三次握手流程,對三次握手詳細的介紹可以閱讀筆者的這篇文章 TCP 基礎知識)當所有建立連接的報文都能夠正常收發(fā)之后,此時套接字就已經(jīng)進入可收發(fā)狀態(tài)了,此時可以認為用一根管理把兩個套接字連接了起來。當然,實際上并不存在這個管子。建立連接之后,協(xié)議棧的連接操作就結束了,也就是說 connect 已經(jīng)執(zhí)行完畢,控制流程被交回給應用程序。
收發(fā)數(shù)據(jù)
當控制流程從 connect 回到應用程序之后,接下來就會直接進入數(shù)據(jù)收發(fā)階段,數(shù)據(jù)收發(fā)操作是從應用程序調(diào)用 write 將要發(fā)送的數(shù)據(jù)交給協(xié)議棧開始的,協(xié)議棧收到數(shù)據(jù)之后執(zhí)行發(fā)送操作。
協(xié)議棧不會關心應用程序傳輸過來的是什么數(shù)據(jù),因為這些數(shù)據(jù)最終都會轉換為二進制序列,協(xié)議棧在收到數(shù)據(jù)之后并不會馬上把數(shù)據(jù)發(fā)送出去,而是會將數(shù)據(jù)放在發(fā)送緩沖區(qū),再等待應用程序發(fā)送下一條數(shù)據(jù)。
為什么收到數(shù)據(jù)包不會直接發(fā)送出去,而是放在緩沖區(qū)中呢?
因為只要一旦收到數(shù)據(jù)就會發(fā)送,就有可能發(fā)送大量的小數(shù)據(jù)包,導致網(wǎng)絡效率下降。所以協(xié)議棧需要將數(shù)據(jù)積攢到一定數(shù)量才能將其發(fā)送出去。至于協(xié)議棧會向緩沖區(qū)放多少數(shù)據(jù),這個不同版本和種類的操作系統(tǒng)有不同的說法,不過,所有的操作系統(tǒng)和種類都會遵循下面這幾個標準:
第一個判斷要素是每個網(wǎng)絡包能夠容納的數(shù)據(jù)長度,判斷的標準是 MTU,它表示的是一個網(wǎng)絡包的最大長度。最大長度包含頭部,所以如果單論數(shù)據(jù)區(qū)的話,就會用 MTU - 包頭長度,由此的出來的最大數(shù)據(jù)長度被稱為 MSS。

另一個判斷標準是時間,當應用程序產(chǎn)生的數(shù)據(jù)比較少,協(xié)議棧向緩沖區(qū)放置數(shù)據(jù)效率不高時,如果每次都等到 MSS 再發(fā)送的話,可能因為等待時間太長造成延遲,在這種情況下,即使數(shù)據(jù)長度沒有到達 MSS,也應該把數(shù)據(jù)發(fā)送出去。
協(xié)議棧并沒有告訴我們怎樣平衡這兩個因素,如果數(shù)據(jù)長度優(yōu)先,那么效率有可能比較低;如果時間優(yōu)先,那又會降低網(wǎng)絡的效率。
經(jīng)過了一段時間。。。。。。

假設我們使用的是長度有限法則,此時緩沖區(qū)已滿,協(xié)議棧要發(fā)送數(shù)據(jù)了,協(xié)議棧剛要把數(shù)據(jù)發(fā)送出去,卻發(fā)現(xiàn)無法一次性傳輸這么大數(shù)據(jù)量(相對的)的數(shù)據(jù),那怎么辦呢?
在這種情況下,發(fā)送緩沖區(qū)中的數(shù)據(jù)就會超過 MSS 的長度,發(fā)送緩沖區(qū)中的數(shù)據(jù)會以 MSS 大小為一個數(shù)據(jù)包進行拆分,拆分出來的每塊數(shù)據(jù)都會加上 TCP,IP,以太網(wǎng)頭部,然后被放進單獨的網(wǎng)絡包中。

到現(xiàn)在,網(wǎng)絡包已經(jīng)準備好發(fā)往服務器了,但是數(shù)據(jù)發(fā)送操作還沒有結束,因為服務器還未確認是否已經(jīng)收到網(wǎng)絡包。因此在客戶端發(fā)送數(shù)據(jù)包之后,還需要服務器進行確認。
TCP 模塊在拆分數(shù)據(jù)時,會計算出網(wǎng)絡包偏移量,這個偏移量就是相對于數(shù)據(jù)從頭開始計算的第幾個字節(jié),并將算好的字節(jié)數(shù)寫在 TCP 頭部,TCP 模塊還會生成一個網(wǎng)絡包的序號(SYN),這個序號是唯一的,這個序號就是用來讓服務器進行確認的。
服務器會對客戶端發(fā)送過來的數(shù)據(jù)包進行確認,確認無誤之后,服務器會生成一個序號和確認號(ACK)并一起發(fā)送給客戶端,客戶端確認之后再發(fā)送確認號給服務器。
我們來看一下實際的工作過程:

首先,客戶端在連接時需要計算出序號初始值,并將這個值發(fā)送給服務器。接下來,服務器通過這個初始值計算出 確認號并返回給客戶端。初始值在通信過程中有可能會丟棄,因此當服務器收到初始值后需要返回確認號用于確認。同時,服務器也需要計算出從服務器到客戶端方向的序號初始值,并將這個值發(fā)送給客戶端。然后,客戶端也需要根據(jù)服務器發(fā)來的初始值計算出確認號發(fā)送給服務器,至此,連接建立完成,接下來就可以進入數(shù)據(jù)收發(fā)階段了。
數(shù)據(jù)收發(fā)階段中,通信雙方可以同時發(fā)送請求和響應,雙方也可以同時對請求進行確認。
請求 - 確認機制非常強大,通過這一機制,我們可以確認接收方有沒有收到某個包,如果沒有收到則重新發(fā)送,這樣一來,但凡網(wǎng)絡中出現(xiàn)的任何錯誤,我們都可以即使發(fā)現(xiàn)并補救。
網(wǎng)卡、集線器、路由器都沒有錯誤補救機制,一旦檢測到錯誤就會直接丟棄數(shù)據(jù)包,應用程序也沒有這種機制,起作用的只是 TCP/IP 模塊。
由于網(wǎng)絡環(huán)境復雜多變,所以數(shù)據(jù)包會存在丟失情況,因此發(fā)送序號和確認號也存在一定規(guī)則,TCP 會通過窗口管理確認號,我們這篇文章不再贅述,大家可以閱讀筆者的這篇文章 TCP 基礎知識 來尋找答案。
斷開連接
當通信雙方不再需要收發(fā)數(shù)據(jù)時,需要斷開連接。不同的應用程序斷開連接的時機不同。以 Web 為例,瀏覽器向 Web 服務器發(fā)送請求消息,Web 服務器再返回響應消息,這時收發(fā)數(shù)據(jù)就全部結束了,服務器可能會首先發(fā)起斷開響應,當然客戶端也有可能會首先發(fā)起(誰先斷開連接是應用程序做出的判斷),與協(xié)議棧無關。

無論哪一方發(fā)起斷開連接的請求,都會調(diào)用 Socket 庫的 close 程序。我們以服務器斷開連接為例,服務器發(fā)起斷開連接請求,協(xié)議棧會生成斷開連接的 TCP 頭部,其實就是設置 FIN 位,然后委托 IP 模塊向客戶端發(fā)送數(shù)據(jù),與此同時,服務器的套接字會記錄下斷開連接的相關信息。
收到服務器發(fā)來 FIN 請求后,客戶端協(xié)議棧會將套接字標記為斷開連接狀態(tài),然后,客戶端會向服務器返回一個確認號,這是斷開連接的第一步,在這一步之后,應用程序還會調(diào)用 read 來讀取數(shù)據(jù)。等到服務器數(shù)據(jù)發(fā)送完成后,協(xié)議棧會通知客戶端應用程序數(shù)據(jù)已經(jīng)接收完畢。
只要收到服務器返回的所有數(shù)據(jù),客戶端就會調(diào)用 close 程序來結束收發(fā)操作,這時客戶端會生成一個 FIN 發(fā)送給服務器,一段時間后服務器返回 ACK 號,至此,客戶端和服務器的通信就結束了。
刪除套接字
通信完成后,用來通信的套接字就不再會使用了,此時我們就可以刪除這個套接字了。不過,這時候套接字不會馬上刪除,而是等過一段時間再刪除。
等待這段時間是為了防止誤操作,最常見的誤操作就是客戶端返回的確認號丟失,至于等待多長時間,和數(shù)據(jù)包重傳的方式有關。
騰訊首發(fā)Linux內(nèi)核源碼《嵌入式開發(fā)進階筆記》差距差的不止一點點哦
