TCP/IP網絡編程_第3章地址族與數據序列 3.1-3.3

TCP/IP網絡編程_第3章地址族與數據序列

第2章中討論了套接字的創建方法, 如果把套接字比喻爲電話, 那麼目前只安裝了電話機. 本章將着重講解給電話機分配號碼的方法. 即給套接字分配 IP 地址和端口號. 這部分內容也相對有些枯燥, 但並不難, 而且是學習後續那些有趣內容必備的基礎知識.

3.1 分配給套機字的 IP 地址 與 端口號

IP 是Internet Protocol (網絡協議) 的簡寫, 是爲收發網絡數據而分配給計算機的值. 端口號並非賦予計算機的值, 而是爲區分程序中創建的套機字而分配給套接字的序號. 下面逐一講解.

網絡地址(Internet Addrsee)
爲了使計算機連接到網絡併發數據, 必需向其分配 IP 地址. IP 地址分爲兩類.
在這裏插入圖片描述
IPv4 與 IPv6 的差別主要是代表 IP 地址所用的字節數, 目前通常的地址 IPv4. IPv6 是爲了應對2010 年前後 IP 地址消耗盡的問題而提取的標準, 即便如此, 現在主要還是使用 IPV4 , IPV6 的普遍即將需要更長時間.

IPV4 標準的4字節IP 地址分爲 網絡地址和主機(指計算機)地址, 且分爲A, B, C, D, E等類型. 圖3-1展示了 IPV4 地址族, 一般不會使用已被預約的 E 地址, 故省略.
在這裏插入圖片描述
網絡地址(網絡ID)是區分網絡而設置的一部分 IP 地址. 假設向 WWW.SEMI.COM 公司傳輸數據, 該公司內部構建了局域網, 把所有計算機連接起來. 因此, 首先應向 SEMI.COM 網絡傳輸數據, 也就是說, 並非一開始就瀏覽所有 4字節IP 地址, 進而找到目標主機; 而是僅瀏覽4字節IP 地址的網絡地址, 先吧數據傳到 SEMI.COM 的網絡. SEMI.COM的網絡(構成網絡的路由器)接收到數據後, 瀏覽器傳輸數據主機地址(主機 ID ) 並將數據傳給目標計算機. 圖 3-2展示數據傳輸過程.
在這裏插入圖片描述
某主機向203.211.172.103 和 203.211.217.202 傳輸數據, 其中203.211.172和203.211.217 爲該網絡的網絡地址(稍後給出網絡地址區分方法). 所以, “向相應網絡傳輸數據” 實際是是向構成網絡的路由器(Router)或交換機(Switch) 傳輸數據, 由接收數據的路由器根據數據中的主機地址向目標主機傳遞數據.
在這裏插入圖片描述
若想構建網絡, 需要一種物理設備完成外圍與本網主機之間的數據交換, 這種設備便是路由器或交換機. 他們實際上也是一種計算機, 只不過是爲了特殊目的而設計運行的, 因此有了別名. 所以, 如果在我們使用計算機上安裝適當的軟件, 也可以將其用作交換機. 另外, 交換機比路由器功能要簡單一些, 而實際用途差別不大.

網絡地址分類與主機地址分界

只需通過 IP 地址的第一個字節即可判斷網絡地址佔用的字節數, 因爲我們根據 IP 地址的邊界區分網絡地址, 如下所示.
在這裏插入圖片描述
還有如下這種表述方式.
在這裏插入圖片描述
正因如此, 通過套接字接收發數據時, 數據傳到網絡後即可輕鬆找到正確的的主機.

用於區分套接字的端口號

IP 用於區分計算機, 只要有 IP 地址就能向目標主機傳輸數據, 但僅憑這些無法傳輸給最終的應用程序. 假設各位欣賞視頻的同時在網上衝浪, 這是至少需要1個接收視頻數據的套接字和**1個接收網頁信息的套接字.**問題在於如何區分兩者. 簡言之, 傳輸到計算機的網絡數據是發給播放器, 還是瀏覽器? 讓我們更精確地描述問題. 假設各位開發瞭如下應用程序:

“我開發了收發數據的 P2P 有了一定了解, 即便不清楚也無所謂. 如上所述, 若想接收多臺計算機發來的數據, 則需要相應個數的套機字. 那麼如何區分這些套接字呢?”

計算機中一般配有 NIC (Network Interface Card (網絡接口卡) 數據傳輸設備. 通過 NIC 向計算機內部傳輸數據時會用到 IP. 操作系統負責把傳輸到內部的數據適當分配套接字, 這時就要利用端口號. 也就是說, 通過 NIC 接收的數據內部有端口號, 操作系統正是參考此端口號把數據傳輸給相應的套接字, 如圖3-3 所示.
在這裏插入圖片描述
端口號就是在同一個操作系統內區分不同套接字而設置的, 因此無法將1個端口號分配給不同套接字. 另外, 端口號由16 位構成的, 可分配的端口號範圍0-65535, 但 0-1023 是知名端口號( Well-known PORT), 一般分配給特定應用程序, 所以應當分配次範圍之內的值. 另外, 雖然端口號不能重複, 但 TCP 套接字 和 UDP 套機字不會共用端口號, 所以允許重複. 例如: 如果某 TCP 套接字使用 9190 號端口, 則其他 TCP 套接字就無法使用該端口號, 但 UDP 套接字可以使用.

總之, 數據傳輸目標地址同時包含 IP 地址和端口號, 只有這樣, 數據纔會被傳輸到最終的目的應用程序(應用程序套機字).

3.2 地址信息的表示

應用程序中使用的 IP 地址 和 端口號以結構體的形式給出了定義. 本節將以 IPV4 爲中心, 圍繞此結構體討論目標地址的表示方法.

表示 IPV4 地址的結構體
填寫地址信息時應以如下提問爲線索進行, 各位讀過下列對話和也會同意這一點.
在這裏插入圖片描述
在這裏插入圖片描述
結構體定義爲如下形態就能回答上述提問, 此結構體將作爲地址信息傳遞給bind 函數.
在這裏插入圖片描述
給結構體中提到另一個結構體 in_addr 定義如下, 它用來存放32 位 IP 地址.
在這裏插入圖片描述
講解以上2個結構體前先觀察一些數據類型, uint16_t , in_addr_t 等類型可以參考 POSIX (Portable Operating system Interface , 可移植操作系統接口). POSIX 是爲 UNIX 系列操作系統設立的標準, 它定義了一些其他數據類型, 如表 3-1 所示.
在這裏插入圖片描述
從這些數據類型聲明也可掌握之前結構體的含義. 那爲什需要額外定義這些數據類型呢?如前所述, 這是考慮到擴展性的結果. 如果使用 int32_t 類型的數據, 就能保住在任何時候都佔用4字節, 即使將來用64 位表示 int 類型也是如此.

結構體 sockaddr_in 的成員分析

接下來重點觀察結構體成員的含義及其包含的信息.

成員 sin_family
每種協議適用的地址族均不同. 比如, IPV4 使用4字節地址族, IPV6 使用16字節地址族.可以參考表 3-2 保存 sin_family 地址信息.
在這裏插入圖片描述
AF_LOCAL 只是爲了說明具有多種地址族而添加, 希望各位不要感到太突然.

成員sin_port
該成員保存16位端口號, 重電在於, 它以網絡字節序保存(關於這一點稍後將給出詳細說明).

成員sin_addr
該成員保存32 位 IP 地址信息, 且也以網絡字節序保存. 爲理解好該成員, 應同時觀察結構體 in_addr . 但結構體 in_addr 聲明爲 uint32_t , 因此只需作爲32 位整數即可.

成員sin_zero
無特殊含義. 只是爲使結構體 sockaddr_in 的大小與 sockaddr 結構體保持一致而插入的成員. 必須填充爲0, 否則無法得到想要的結果. 後面會另外講解 sockaddr.

從以前介紹的代碼也可看出, sockaddr_in 結構體變量地址將以如下方式傳遞給 bing 函數.稍後將給出關於 bind 函數的詳細說明, 希望各位重點關注參數傳遞和類型轉換部分的代碼.
在這裏插入圖片描述
此處重要的是第二個參數的傳遞. 實際上, bind 函數的第二個參數期望得到 sockaddr 結構體變量地址值, 包含地址族, 端口號, IP 地址等. 從下列代碼也可看出, 直接向sockaddr 結構體填充這些信息會帶來麻煩.
在這裏插入圖片描述
此結構體成員 sa_data 保存的地址信息中需要包含 IP 地址和端口號, 剩餘部分因填充0, 這也是bind 函數要求的. 而這對於包含地址信息來講非常麻煩, 繼續而就有了新的結構體 sockaddr_in 若按照之前的講解填寫 sockadr_in 結構體, 則將生成符合bind 函數要求的字節流. 最後轉換爲 sockaddr 型的結構體變量, 再傳遞給bind 函數即可.

在這裏插入圖片描述
socket_in 是保存 IPV4 地址信息的結構體. 那爲何還需要通過sin_family 單獨指定地址信息呢? 這與之前之前講過的 sockaddr 結構體有關. 結構體sockaddr 並非只爲 IPV 4設計, 這從保存地址信息的數組 sa_data長度爲 14 字節也可以看出. 因此, 結構體sockaddr 要求在sin_family 中指定地址族信息. 爲了與 sockaddr 保持一致, sockaddr_in 結構體中也有地址信息.

3.3 網絡字節序與地址變換

不同 CPU 中, 4 字節整數型值1 在內存空間的保存方式是不同的. 4 字節整數型 值 1 可用2進製表示 如下.
在這裏插入圖片描述
有些 CPU 以這種順序保存都內存, 另外一些 CPU 則以 倒序保存.
在這裏插入圖片描述
若不考慮這些就收發數據則會發生問題, 因爲保存順序的不同意味着對接收數據的解析順序也不同.

字節序(Order)與網絡字節序

CPU 向保存數據的方式有2種, 這意味着CPU 解析數據的方式也分爲2種.
在這裏插入圖片描述
僅憑描述很難解析清楚, 下面通過實例進行說明. 假設在 0x20 號開始的地址中保存4字節int 類型數 0x12345678. 大端序 CPU 保存方式如圖 3-4 所示.
在這裏插入圖片描述
整數0x12345678 中, 0x12是最高位字節, 0x78是最低字節. 因此, 大端序中先保存最高位字節0x12 (最高字節0x12存放到低位地址). 小端序保存方式如圖3-5所示.
在這裏插入圖片描述
先保存的是最低位字節0x78. 從以上分析可以看出, 每種CPU 的數據保存方式均不同. 因此, 代表 CPU 數據保存方式的主機字節序(Host Byte Order) 在不同 CPU 中也各不相同. 目前主流的 Intel 系列 CPU 以小端序方式保存數據. 接下來分析2臺字節序不同的計算機之間進行數據傳遞中可能出現的問題, 如圖 3-6 所示.
在這裏插入圖片描述
0x12和0x32 構成的大段序系統值與0x34和0x12 構成的小端序系統值相同, 換言之, 只有改變數據保存順序才能被識別爲同一值. 圖 3-6 中, 大端序系統傳輸數據 0x123時未考慮字節序問題, 而直接以 0x12 、 0x34 的順序發送. 結果接收端以小端方式保存數據. 因此小端序接收的數據變成 0x3412, 而非0x1234. 正因如此, 在通過網絡傳輸數據時約定統一方式, 這種約定稱爲網絡字節序(Network Byte Order), 非常簡單–統一爲大端序.

即, 先把數據數組轉換成大端序格式再進行網絡傳輸. 因此, 所有計算機接收數據時應識別該數據是網絡字節序格式, 小端序系統傳輸數據時應轉化爲大端序排序方式.

字節序轉換(Endian Conversions)

相信大家已經理解了爲何要在填充 sockaddr_in 結構體前將數據轉換成網絡字節序. 接下來介紹幫助轉換字節的函數.
在這裏插入圖片描述
通過函數名應該能掌握其功能, 只需瞭解以下細節.
在這裏插入圖片描述
另外, s指的是 short, 1指的是long (Linux 中 long 類型佔用4字節, 這很關鍵). 因此, htons 是h、to 、n 、s 的組合, 也可以解釋爲 “把short 型數據從主機字節序轉化爲網絡字節序”.

再舉個例子, ntohs 可以解析爲 “把short 型數據從網絡字節序轉化爲主機字節序”.

通常, 以s作爲後綴的函數中, s代表2個字節short, 因此用於端口號轉換; 以 l 作爲後綴的函數中, l 代表4字節, 因此用於 IP 地址轉換. 另外, 有些讀者有如下疑問:

“我的操作系統是大端序的, 爲sockaddr_in 結構體變量賦值前就不需要轉換字節序了吧?”

這麼說也不能算錯. 但我認爲, 有必要編寫與大端序無關的統一代碼. 這樣, 即使在大端序系統中, 最好也經過主機字節序轉換爲網絡字節序的過程. 當然, 此時主機字節序與網絡字節序相同, 不會有任何變化. 下面通過實例說明以上函數的調用過程.

#include <stdio.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
    /* 各保存2個字節, 4個字節的數據. 當然, 若運行程序的CPU 不同, 則保存的字節序各序也不同 */
    unsigned short host_port = 0x1234;
    unsigned short net_port = net_port;
    unsigned long host_addr = 0x12345678;
    unsigned long net_addr;

    /* 變量host_port和host_addr中的數據轉化爲網絡字節序, 若運行環境爲小端序CPU, 則按改變之後的字節序保存 */
    net_port = htons(host_port);
    net_addr = htonl(host_addr);

    printf("Host orderd port: %#x \n", host_port);
    printf("Network orderde port: %#x \n", net_port);
    printf("Host ordered addrress: %#lx \n", host_addr);
    printf("Network ordered address: %#lx \n", net_addr);

    return 0;
}

運行結果:
在這裏插入圖片描述

這就是在小端序 CPU 中運行的結果. 如果在大端序 CPU 中運行, 則變量值不會改變. 大部分朋友都會得到類似的運行結果, 因爲 Intel 和 AMD 系列的 CPU 都採用小端序標準.

在這裏插入圖片描述
也許有讀者認爲: “既然數據傳輸採用網絡字節序, 那在傳輸前應直接把數據轉換成網絡字節序, 接收的數據也需要轉換成主機字節序再保存.” 如果數據收發過程中沒有自動轉換機制, 那當然需要程序員手動轉換. 這關想想就讓人覺得可怕, 難道真的要強求程序員做這些事情嗎? 實際上沒有必要, 這個過程是自動的. 除了向 sockaddr_in 結構體變量填充數據外, 其他情況無需考慮字節序問題.

你可以在下面網站下載這本書:
https://www.jiumodiary.com/

時間2020:05:24

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章