引言
在上一篇博客中,我們簡單的介紹了一些Linux網絡一些比較基本的概念。本篇博客我們將開始正式學習Linux網絡套接字的內容,那么我們開始吧!
1.網絡中的地址管理
大家一定知道每一臺主機都會存在一個ip地址,其實并不是這么簡單,今天我們就來系統介紹一下。
1.1ip地址
IP協議有兩個版本, IPv4和IPv6. 我們整個的課程, 凡是提到IP協議, 沒有特殊說明的, 默認都是指IPv4。IPv6其實是針對IPv4地址不足提出的解決方案,目前世界上IPV6做的比較好的國家就是我們。
- IP地址是在IP協議中, 用來標識網絡中不同主機的地址;具有唯一性。
- 對于IPv4來說, IP地址是一個4字節, 32位的整數;
- 對于IPv6來說,IP地址是一個16個字節,128位的整數。
- 我們通常也使用 “點分十進制” 的字符串表示IP地址, 例如 192.168.0.1 ; 用點分割的每一個數字表示一個字節, 范圍是 0 - 255;
1.1.1源IP和目的IP
在IP數據包頭部中, 有兩個IP地址, 分別叫做源IP地址, 和目的IP地址,用來表示這個數據是從哪臺主機發出來的,要發給哪一臺主機。
1.2MAC地址
每一臺連入網路的設備都必須需要網卡,每一張網卡在出廠時都有一個唯一性的編號,這個標號就是MAC地址。MAC同樣具有全網內唯一性,通常用于處于局域網中主機之間相互通信。
MAC地址長度為6個字節,48個比特位。一般用16進制加上冒號的形式來表示,例如:
08:00:27:33:fd:45。
1.3兩套地址體系的區別
在Linux下查看ip地址和MAC地址的命令為:
[user@VM-8-5-centos ~]$ ifconfig
在這張圖片中,展示的是Linux系統下通過ifconfig
命令查看的網絡配置信息,特別是關于以太網接口eth0
的詳細配置。接下來,我們分析一下這些內容:
-
以太網接口eth0:
- 狀態標志(flags):
4163<UP,BROADCAST,RUNNING,MULTICAST>
表示該接口已啟用(UP)、支持廣播(BROADCAST)、正在運行(RUNNING)以及支持多播(MULTICAST)。 - MTU(最小傳輸單元):
mtu 1500
表示該接口的最大傳輸單元為1500字節,這是以太網的標準MTU值。 - IPv4地址和子網掩碼:
inet 10.0.8.5 netmask 255.255.252.0
表示該接口的IPv4地址是10.0.8.5,子網掩碼是255.255.252.0,用于確定IP地址的網絡部分和主機部分。 - 廣播地址:
broadcast 10.0.11.25
是該網絡中的廣播地址,用于向同一子網內的所有設備發送數據包。 - IPv6地址和前綴長度:
inet6 fe80::5054:ff:fe35:3d28 prefixlen 64 scopeid 0x20<
表示該接口還配置了一個IPv6的鏈路本地地址,前綴長度為64位。注意這里文本被截斷,但基本意思是明確的。 - MAC地址:
ether 52:54:00:35:3d:28
是該接口的物理地址,也稱為MAC地址,用于在鏈路層唯一標識網絡接口。
- 狀態標志(flags):
-
接收(RX)和發送(TX)統計:
- 顯示了接口接收和發送的數據包數量、字節數以及相關的錯誤統計(如錯誤、丟棄、溢出等),這些信息對于診斷網絡性能問題非常有用。
-
環回接口(lo):
lo
是環回接口,用于本機內部通信。它也有自己的IPv4和IPv6地址(127.0.0.1和::1),以及相應的接收和發送統計。
錯誤消息:
- 圖片中并未直接顯示明顯的錯誤消息,但提到了“RX errors 0 dropped 0 overruns 0 frame 0”和“TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0”,這些值都為0,表示在接收和發送過程中沒有遇到錯誤、丟棄、溢出、幀錯誤、傳輸錯誤、丟棄、溢出、載波丟失或沖突。
接下來,張三又上場了。張三買了一輛自行車,他想挑戰一下自己,所以他覺定騎車從北京到上海。如圖:
北京是起點,上海是終點。從北京到上海沿途要經過很過地方,例如天津。
假如張三途中經過了天津,然后然后不知道該往哪個方向走了,邊找個一個當地人說:“你好,我從北京來,要騎自行車去上海,來到了咱們這里,不知道該怎么走了,你可以幫幫我嗎?”。這個當當地人便熱情的對張三說:“你應該繼續往西走,然后二十公里處,你再找人問問”。就這樣張三又繼續向西走了二十公里。然后又找了一個人問路,路人得知他要去上海,從西邊來,就告訴他應該轉彎向南走。
張三口中有幾套地址呢? - 從哪來,到哪去。到哪去是終極目標,就是目的IP。從哪來是源IP。:為我們未來的每個階段提供方向目標,方便路徑選擇。IP地址提供的是方向選擇。
- 上一站從哪來,下一站到哪去。張三每到一處,這個地址就會發生變化。像這種從一個節點,跳轉到下一個相鄰的節點的地址,我們稱之為MAC地址。
如圖所示,跨網絡數據進行傳輸時,需要路由器。所以路由器必須要同時連接兩個甚至多個局域網。數據該往哪個方向傳輸由IP地址決定。但是數據在長距離傳輸的過程中會經過多個路由節點,相鄰路由節點的選擇由MAC地址決定。
2.端口號
IP(公網IP)地址決定網絡中主機的唯一性。但是僅僅需要IP地址就可以實現數據的傳輸嗎?
打開快手刷視頻時,為什么視頻資源可以準確的顯示在快手APP頁面,而不是出現在微信APP頁面呢?
我們把數據從主機A傳輸到主機B是目的嗎?真正通信的不是這兩個機器,而是這兩個機器上的應用(人)。但是有可能主機A上不止一個應用(進程),可能同時還會有其他的進程,例如快手打開的同時,微信也開著。那么用什么來標識客戶端或者服務器主機進程的唯一性呢?端口號
為了更好的表示一臺主機上,服務器進程或者客戶端進程的唯一性,我們采用端口號來標識主機上的不同進程。端口號保證主機唯一性即可,即一臺主機上一個端口號只能綁定一個進程,不同主機上的相同端口號綁定的進程可以不同。
公網IP地址(標識主機全網唯一性)+主機上的端口號=表示該進程在全網中的唯一性
所以,網絡通信的本質就是進程間通信嘛!其中的臨界資源就是網絡。
通信是在做什么?通信不就是IO的過程嘛。所以,我們所有的網絡行為只有兩種:①從網絡中接收數據②發送數據到網絡中。
IP保證全網唯一,port保證本機唯一。一個進程可以綁定多個端口號。
進程已經有pid了,為什么還要有端口號呢?
①系統是系統,網絡是網絡。做到互相解耦。維護成本低
②一般都是客戶端主動向服務器發送請求。需要客戶端快速的找到服務器進程。決定了服務器的IP和端口不能隨便改變。所以決定了不能使用輕易會改變的值。pid不太滿足這個條件。
所以,客戶端向服務器發送消息時,要不要把客戶端的ip和端口號發給服務器呢?要,因為服務器還有將消息發回給客戶端。這就決定了在發送數據時,一定會多發一部分數據——以協議的形式呈現。
3.網絡字節序
我們已經知道,內存中的多字節數據相對于內存地址有大端和小端之分, 磁盤文件中的多字節數據相對于文件中的偏移地址也有大端小端之分, 網絡數據流同樣有大端小端之分. 那么如何定義網絡數據流的地址呢?
- 發送主機通常將發送緩沖區中的數據按內存地址從低到高的順序發出;
- 接收主機把從網絡上接到的字節依次保存在接收緩沖區中,也是按內存地址從低到高的順序保存;
- 因此,網絡數據流的地址應這樣規定:先發出的數據是低地址,后發出的數據是高地址.
- TCP/IP協議規定,網絡數據流應采用大端字節序,即低地址高字節.
- 不管這臺主機是大端機還是小端機, 都會按照這個TCP/IP規定的網絡字節序來發送/接收數據;
- 如果當前發送主機是小端, 就需要先將數據轉成大端; 否則就忽略, 直接發送即可。
那么,如果需要轉化的話,轉化的過程需要誰來完成呢?需要程序員自己來完成轉換工作。當然,操作系統也給我們提供了相應的接口,我們調用即可。
接下來,我們簡單介紹一下這些接口
#include <arpa/inet.h>uint32_t htonl(uint32_t hostlong);uint16_t htons(uint16_t hostshort);uint32_t ntohl(uint32_t netlong);uint16_t ntohs(uint16_t netshort);
這些函數名很好記,h表示host,n表示network,l表示32位長整數,s表示16位短整數。
例如htonl表示將32位的長整數從主機字節序轉換為網絡字節序,例如將IP地址轉換后準備發送。
如果主機是小端字節序,這些函數將參數做相應的大小端轉換然后返回;
如果主機是大端字節序,這些 函數不做轉換,將參數原封不動地返回。
4.套接字
IP+port就可以表示一臺主機的進程在網絡中的唯一性,其中IP+Port合起來被稱為網絡套接字。
接下里,我們先見一下關于網絡套接字創建,綁定端口,使用的相關函數。
// 創建 socket 文件描述符 (TCP/UDP, 客戶端 + 服務器)
int socket(int domain, int type, int protocol);
// 綁定端口號 (TCP/UDP, 服務器)
int bind(int socket, const struct sockaddr *address,
socklen_t address_len);
// 開始監聽socket (TCP, 服務器)
int listen(int socket, int backlog);
// 接收請求 (TCP, 服務器)
int accept(int socket, struct sockaddr* address,
socklen_t* address_len);
// 建立連接 (TCP, 客戶端)
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
套接字的種類其實是比較多的。有
- 網絡套接字:主要用于網絡跨主機之間通信,同時支持本地通信。
- 原始套接字:我們一般的套接字訪問的都是傳輸層的接口,原始套接字可以繞過傳輸層訪問底層的數據和接口。
- Unix域間套接字:只能夠支持本地通信。
由于有三套不同的套接字,所以按理來說,操作系統要設計三套不同的接口分別對應三套不同的套接字。但是這對使用者來說簡直是災難,使用者要同時掌握三套接口。所以,為了方便使用,操作系統進行了如下的設計:
我們發現:struct sockaddr_in和struct sockaddr_un的接口不同,這使用起來就很麻煩。所以操作系統就設計了struct sockaddr結構。在使用時,就必須進行強制類型轉換。
寫到這里,本篇博客的內容就結束了,我們下期博客再見!