前言:網(wǎng)絡(luò)知識(shí)非常的重要,如果你不是做程序的,那么一些網(wǎng)絡(luò)常識(shí)還是得知道的;而做程序的,就更不用說了,不僅需要了解一些網(wǎng)絡(luò)知識(shí),還是知道其原理,如果不了解原理,不敢說他不是程序員,但是總?cè)绷它c(diǎn)意思,就像去北京沒去過長城一樣。
網(wǎng)絡(luò)原理系列文章:
一、五分鐘了解網(wǎng)絡(luò)連接(已完成)
二、收發(fā)數(shù)據(jù)的原理(上)(已完成)
三、收發(fā)數(shù)據(jù)的原理(下)(已完成)
四、收發(fā)數(shù)據(jù)的番外篇(未完成)
上一篇五分鐘了解網(wǎng)絡(luò)連接講了網(wǎng)絡(luò)連接的大概流程,并且文末講到客戶端委托協(xié)議棧收發(fā)數(shù)據(jù)可以總結(jié)為四步:
1、創(chuàng)建套接字(創(chuàng)建套接字階段)
2、將管道連接到服務(wù)器端的套接字上(連接階段)
3、收發(fā)操作(通信階段)
4、斷開管道并刪除套接字(斷開階段)
本文會(huì)對(duì)前兩個(gè)步驟展開描述,后面兩個(gè)步驟,下一篇文章介紹,敬請(qǐng)關(guān)注!由于網(wǎng)絡(luò)知識(shí)點(diǎn)非常繁雜,讀者請(qǐng)沉住氣閱讀此文,我盡可能詳細(xì)介紹,盡可能的不那么枯燥,所以本文介紹也有側(cè)重點(diǎn)。讀完本文,你可能會(huì)對(duì)一些知識(shí)恍然大悟,哦,原來是這樣啊!好了,廢話不多說,直接進(jìn)入主題。
創(chuàng)造套接字
協(xié)議棧的內(nèi)部結(jié)構(gòu)

上面是協(xié)議棧的內(nèi)部結(jié)構(gòu)。分為接個(gè)部分。上面部分會(huì)向下面的部分委派工作。
應(yīng)用程序的下面是Socket庫,其中包括解析器,解析器向DNS服務(wù)器發(fā)出查詢,它的工作過程我們?cè)?a href="http://www.itdecent.cn/p/e287c0887f98" target="_blank">上一篇已經(jīng)介紹過了。
再下面就是操作系統(tǒng)內(nèi)部了,其中包括協(xié)議棧。協(xié)議棧的上半部分有兩塊,分別是負(fù)責(zé)用TCP協(xié)議收發(fā)數(shù)據(jù)的部分和負(fù)責(zé)用UDP協(xié)議收發(fā)數(shù)據(jù)的部分,它們會(huì)接受應(yīng)用程序的委托執(zhí)行收發(fā)數(shù)據(jù)的操作。關(guān)于TCP和UDP,會(huì)在后面文章講解,目前只要知道像瀏覽器、郵件等一般的應(yīng)用程序、QQ文件傳輸都是使用TCP收發(fā)數(shù)據(jù)的,而像DNS查詢、QQ語音 、QQ視頻等收發(fā)較短的控制數(shù)據(jù)的時(shí)候則使用UDP。
協(xié)議棧的下半部分是利用IP協(xié)議控制網(wǎng)絡(luò)包收發(fā)數(shù)據(jù)的部分,在互聯(lián)網(wǎng)中發(fā)送數(shù)據(jù),需要將數(shù)據(jù)分成一個(gè)個(gè)小的網(wǎng)絡(luò)包,然后將網(wǎng)絡(luò)包發(fā)送給通信對(duì)象就是由IP負(fù)責(zé)的。另外,ICMP用于錯(cuò)誤提示以及各種控制消息,ARP則是用于查詢IP相應(yīng)的以太網(wǎng)MAC地址?,F(xiàn)在只需要大概知道這個(gè)名詞,后面才會(huì)細(xì)講。
IP下面是驅(qū)動(dòng)程序負(fù)責(zé)控制網(wǎng)卡硬件,最下面的網(wǎng)卡則是負(fù)責(zé)完成實(shí)際的收發(fā)操作——對(duì)網(wǎng)線中的信號(hào)執(zhí)行發(fā)送和接受操作。
套接字的實(shí)體
實(shí)際上套接字并沒存在實(shí)體,只是一個(gè)概念。在協(xié)議棧內(nèi)部有一塊存放控制信息的內(nèi)存空間,用于記錄通信操作的控制信息。比如通信對(duì)象的IP地址、端口號(hào)、通信操作的狀態(tài)等。所以硬要說套接字是個(gè)實(shí)體,那就是這些控制信息或者是保存這些控制信息的內(nèi)存空間。
協(xié)議棧執(zhí)行操作時(shí)需要參閱這些控制信息。來決定下一步該做什么。比如:發(fā)送數(shù)據(jù)時(shí),看看IP地址和端口號(hào);發(fā)送數(shù)據(jù)后,協(xié)議棧需要等待對(duì)方返回?cái)?shù)據(jù)的響應(yīng)信息,但是數(shù)據(jù)可能會(huì)半途丟失。我們不可能一直等待,所以套接字中需要記錄是否已經(jīng)收到或者發(fā)送數(shù)據(jù)了多久,才方便知道是否要重發(fā)數(shù)據(jù)。套接字的控制信息還有很多作用,在此不一一列舉了。
協(xié)議棧是根據(jù)套接字中記錄的控制信息工作的。
調(diào)用Socket庫中的組件
創(chuàng)建套接字時(shí),需要調(diào)用Socket庫中的socket組件,注意這里,大寫的是Socket,小寫是庫中的一個(gè)程序組件。
應(yīng)用程序調(diào)用socket程序申請(qǐng)創(chuàng)建套接字,而協(xié)議棧則根據(jù)應(yīng)用程序的申請(qǐng)執(zhí)行創(chuàng)建套接字的操作。
在創(chuàng)建過程中,協(xié)議棧會(huì)分配一個(gè)用于存放套接字所需的內(nèi)存空間,用于存放記錄套接字操作的控制信息。創(chuàng)建套接字時(shí),數(shù)據(jù)收發(fā)操作還沒開始,所以把這初始狀態(tài)的信息存入內(nèi)存空間中。到此,創(chuàng)建套接字操作完成。
創(chuàng)建套接字,不僅要分配空間,而且需要初始化狀態(tài)信息。
然后,套接字需要將它的描述符告訴應(yīng)用程序。描述符相當(dāng)于車庫號(hào),告訴我車庫號(hào),我才知道哪個(gè)才是我要的車庫。同樣,描述符是用在應(yīng)用程序委托協(xié)議棧收發(fā)數(shù)據(jù)的時(shí)候。套接字包含了通信對(duì)象的信息,比如已經(jīng)說過的IP地址、端口號(hào),所以應(yīng)用程序收到套接字的描述符,應(yīng)用程序再提供給協(xié)議棧,協(xié)議棧就知道了套接字中所包含的通信對(duì)象信息,就可以準(zhǔn)備連接通信對(duì)象了。
連接服務(wù)器
關(guān)于連接
因?yàn)闅v史原因,其實(shí)這里的“連接”就更像是通信前的準(zhǔn)備,叫“準(zhǔn)備”其實(shí)更適合
調(diào)用socket程序申請(qǐng)創(chuàng)建完套接字,然后應(yīng)用程序會(huì)調(diào)用Socket庫的另外一個(gè)程序組件connect,這樣協(xié)議棧就會(huì)將本地的套接字與服務(wù)器的套接字進(jìn)行連接。這里的連接是指通信雙方交換控制信息,在套接字記錄一些必要信息并準(zhǔn)備數(shù)據(jù)收發(fā)的一連串操作。
我們說的連接不是指網(wǎng)線一直插著的連接,不是指通信過程中將數(shù)據(jù)轉(zhuǎn)換成電信號(hào)。而是當(dāng)應(yīng)用程序委托發(fā)送數(shù)據(jù)時(shí),協(xié)議棧通過描述符找到的套接字取得通信對(duì)象的IP地址和端口號(hào)等信息。這屬于連接操作的目的之一。
說完應(yīng)用程序,再說下服務(wù)器那邊,服務(wù)器也會(huì)創(chuàng)建套接字,但是服務(wù)器的協(xié)議棧和客戶端這邊一樣,沒有類似一個(gè)描述符的東西就沒辦法知道通信對(duì)象,沒法開始通信。所以得有客戶端先開始請(qǐng)求,告訴服務(wù)器必要信息。比如“我要和你請(qǐng)求,我的IP地址是10.10.1.118,端口號(hào)是8900”。所以,應(yīng)用程序向服務(wù)器發(fā)送請(qǐng)求,也是連接操作的目的之一。
連接實(shí)際上通信雙方交換控制信息,在套接字中記錄必要信息并準(zhǔn)備數(shù)據(jù)收發(fā)的一連串操作。
控制信息,是控制數(shù)據(jù)收發(fā)操作的一些信息。IP地址、端口號(hào)就屬于其中的信息。其余的控制信息,我們后面再介紹。雙方通過通信規(guī)則進(jìn)行信息交換從而完成數(shù)據(jù)收發(fā)準(zhǔn)備。收發(fā)操作,需要一塊臨時(shí)存放要收發(fā)的數(shù)據(jù)的內(nèi)存空間,這塊內(nèi)存空間叫做緩沖區(qū),它是在連接操作過程中分配的。
關(guān)于控制信息頭部
控制信息可以分為兩類。一是客戶端和服務(wù)器相互聯(lián)絡(luò)時(shí)交換的控制信息。二是保存在套接字中,用來控制協(xié)議棧操作的信息。
第一類:客戶端和服務(wù)器交換的控制信息,不僅是在連接時(shí)需要,包括數(shù)據(jù)收發(fā)和斷開連接操作在內(nèi),整個(gè)通信過程都需要。我們前面說過,數(shù)據(jù)會(huì)被分成一個(gè)個(gè)網(wǎng)絡(luò)包發(fā)送,而這些信息就是被添加在客戶端與服務(wù)器傳遞的網(wǎng)絡(luò)包的開頭。在連接階段,由于數(shù)據(jù)收發(fā)還沒有開始,網(wǎng)絡(luò)包中沒有實(shí)際數(shù)據(jù),只有控制信息。這些控制信息位于網(wǎng)絡(luò)包的開頭,因此成為頭部。當(dāng)然,以太網(wǎng)和IP協(xié)議也有自己的頭部(包含著控制信息),為了避免不同頭部混淆,我們一般記作TCP頭部,以太網(wǎng)頭部、IP頭部。本文主要講解TCP頭部,其余知識(shí)后面再講,讀者有興趣可先自行查閱。
客戶端和服務(wù)器在通信中會(huì)將必要的信息存放在頭部并相互確認(rèn)。大家現(xiàn)在要知道的就是頭部是用來記錄和交換控制信息的。
第二類:套接字中的控制信息。這里會(huì)保存應(yīng)用程序傳遞來的信息以及從通信對(duì)象接收到的信息,還有收發(fā)數(shù)據(jù)操作的執(zhí)行狀態(tài)等信息也會(huì)保存于此,協(xié)議棧根據(jù)這些信息來執(zhí)行每一步操作。
通信操作中使用的控制信息分為兩類:
(1)頭部中的信息
(2)套接字(協(xié)議棧的內(nèi)存空間)中記錄的信息
連接操作的實(shí)際過程
上面我們已經(jīng)介紹了連接(準(zhǔn)備)操作的含義,接下來看一下具體操作過程。首先是應(yīng)用程序調(diào)用Socket庫中的connect,類似下面??
connect (<描述符>,<服務(wù)器IP地址>,<服務(wù)器端口號(hào)>,...)
上面的調(diào)用提供了服務(wù)器的IP地址和端口號(hào),這些信息會(huì)傳遞給協(xié)議棧中的TCP模塊。TCP模塊就會(huì)與該IP地址對(duì)應(yīng)的對(duì)象,也就是與服務(wù)器的TCP模塊交換控制信息。

seq:(Sequence Number):本報(bào)文段數(shù)據(jù)的第一個(gè)字節(jié)的序號(hào)
ack:(Acknowledgment Number):確認(rèn)號(hào)——期望收到對(duì)方下個(gè)報(bào)文段的第一個(gè)數(shù)據(jù)字節(jié)的序號(hào)
控制位包含以下幾部分
URG:緊急指針有效位。
SYN:建立連接,當(dāng)需要建立連接時(shí),他的值為1.即SYN=1
ACK:確認(rèn)連接,當(dāng)ACK=1是才有效,ACK=0是此控制位無效。
FIN:斷開連接,提出斷開連接這一方的值為1.
RST:重新建立連接,值為1時(shí)代表重新建立連接。
PSH:要求接收方將數(shù)據(jù)盡快將數(shù)據(jù)段送達(dá)應(yīng)用層
上圖主要介紹了TCP頭部。其中TCP頭部目前要認(rèn)知的有發(fā)送方和接收方的端口號(hào),序號(hào)(seq)是發(fā)送方告訴接收方本報(bào)文段數(shù)據(jù)的第一個(gè)字節(jié)的序號(hào),ack號(hào)是接收方告訴發(fā)送方已收到了所有數(shù)據(jù)的第幾個(gè)字節(jié)。其中ack是Acknowledgment Number的縮寫??赡茏x者納悶IP地址在哪,IP地址其實(shí)在IP頭部才有。由于本文重點(diǎn)是介紹TCP,所以下方只給出IP頭部圖,讀者自行閱讀
應(yīng)用程序與服務(wù)器的TCP模塊交換控制信息這一過程包含幾個(gè)步驟:首先,客戶端先創(chuàng)建一個(gè)包含表示開始數(shù)據(jù)收發(fā)操作的控制信息的頭部(上面所說網(wǎng)絡(luò)包中開頭的控制信息),頭部包含很多字段,其中要關(guān)注的重點(diǎn)是客戶端和服務(wù)器的端口號(hào),也就是說,客戶端的套接字知道了連接服務(wù)器的哪個(gè)套接字。然后,我們把頭部中的控制位的SYN設(shè)置為1,大家可以認(rèn)為它表示連接。此外,還需設(shè)置適當(dāng)?shù)男蛱?hào)和窗口大小。
連接操作的第一步是在TCP模塊處創(chuàng)建表示連接控制信息的頭部
通過TCP頭部中的發(fā)送方和接收方的端口號(hào)可以找到要連接的套接字
當(dāng)TCP頭部創(chuàng)建好之后,接下來TCP模塊會(huì)將信息傳給IP模塊并委托它進(jìn)行發(fā)送。IP模塊執(zhí)行網(wǎng)絡(luò)包發(fā)送操作后,網(wǎng)絡(luò)包就會(huì)通過網(wǎng)絡(luò)發(fā)送到服務(wù)器的IP模塊,再由服務(wù)器的IP模塊把接收到的數(shù)據(jù)傳給服務(wù)器自身的TCP模塊,這時(shí),服務(wù)器的TCP模塊會(huì)根據(jù)TCP頭部的信息找到端口號(hào)對(duì)應(yīng)的套接字,然后套接字就會(huì)寫入相應(yīng)的信息,并把狀態(tài)改成正在連接。
TCP模塊、IP模塊分別屬于網(wǎng)絡(luò)原理中OSI模型7層結(jié)構(gòu)的傳輸層、網(wǎng)絡(luò)層,而傳輸層處于網(wǎng)絡(luò)層的上一層,也就是高一層,要完成傳送數(shù)據(jù),必須從通信一方的高層傳到低層,再通過網(wǎng)絡(luò)傳給通信另外一方的低層,再到那一方的高層完成接收。所以發(fā)送數(shù)據(jù)得從高一層的TCP到低層的IP模塊逐一傳遞。
上述操作完成后,服務(wù)器的TCP模塊會(huì)返回響應(yīng),這個(gè)過程跟客戶端發(fā)送數(shù)據(jù)給服務(wù)端一樣,需要在TCP頭部中設(shè)置發(fā)送方和接收方端口以及SYN比特。另外,客戶端向服務(wù)器發(fā)送第一個(gè)網(wǎng)絡(luò)包時(shí),由于服務(wù)器還沒有接受過網(wǎng)絡(luò)包,所以ACK比特設(shè)為0,那么在返回響應(yīng)就需要將ACK控制位設(shè)為1,表示已經(jīng)收到相應(yīng)的網(wǎng)絡(luò)包。網(wǎng)絡(luò)中經(jīng)常發(fā)生錯(cuò)誤,網(wǎng)絡(luò)包也會(huì)丟失。因此通信雙方必須相互確認(rèn)網(wǎng)絡(luò)包是否已經(jīng)送達(dá),通信雙方如何確認(rèn)?其實(shí)就是通過設(shè)置ACK。接下來,服務(wù)器TCP模塊會(huì)講TCP頭部傳給IP模塊,并委托IP模塊向客戶端返回響應(yīng)。
然后,網(wǎng)絡(luò)包就會(huì)返回客戶端,通過IP模塊到達(dá)TCP模塊,并通過TCP頭部信息確認(rèn)連接服務(wù)器的操作是否成功。如果SYN為1,則表示連接成功,這時(shí)會(huì)向套接字中寫入服務(wù)器的IP地址、端口號(hào)等信息,同時(shí)還會(huì)將狀態(tài)改成連接完畢。到這里,客戶端的操作就已經(jīng)完成。但其實(shí)還剩下一個(gè)步驟,客戶端收到數(shù)據(jù)后,也要像服務(wù)器那樣把把ACK設(shè)置為1,并發(fā)回給服務(wù)器,告訴服務(wù)器,我已經(jīng)收到服務(wù)器發(fā)來的響應(yīng)包,當(dāng)服務(wù)器收到這個(gè)返回包后,連接操作才算全部完成。
有沒有覺得上面的雙方相互確認(rèn)網(wǎng)絡(luò)包操作,似乎很熟悉,沒錯(cuò)!它其實(shí)就是tcp的三次握手。
TCP三次握手
1.A向B發(fā)起建立連接請(qǐng)求:
2.B收到A的發(fā)送信號(hào),并且向A發(fā)送確認(rèn)信息
3.A收到B的確認(rèn)信號(hào),并且向B發(fā)送確認(rèn)信號(hào)
連接(準(zhǔn)備)操作完成后,套接字可以隨時(shí)進(jìn)行收發(fā)數(shù)據(jù)了,這個(gè)時(shí)候我們可以理解通信雙方已經(jīng)有一條相連的管道,這條管道連接著雙方的套接字。當(dāng)然這條管道并不真正存在,只是業(yè)界為了方便理解,比喻而已。
建立連接后,協(xié)議棧的連接操作就結(jié)束了。也就是說,當(dāng)初應(yīng)用程序調(diào)用Socket庫中connect程序組件操作已經(jīng)執(zhí)行完畢,控制流程又重新交回到客戶端。等到后面的收發(fā)數(shù)據(jù)操作。
在此,收發(fā)數(shù)據(jù)的創(chuàng)建套接字階段、連接階段已經(jīng)講完,剩下的通信階段、斷開階段留到下次再講。網(wǎng)絡(luò)的東西很枯燥,并且并不是那么可視化,學(xué)會(huì)的要訣是沉住氣,看到這里,如果你現(xiàn)在還不能講到收發(fā)數(shù)據(jù)的前兩個(gè)步驟,請(qǐng)回到文章頂部,再看一遍。Over and over again,你就會(huì)學(xué)有所成。
歡迎關(guān)注技術(shù)公眾號(hào)「程序員大咖秀」