《Linux網路開發必學教程》3_深入淺出 IP 地址

TianSong發表於2022-05-02
問題:網路程式設計介面中一些引數的意義是什麼?
sock = socket(PF_INET, SOCK_STREAM, 0);

socket 引數詳解

int socket(int domain, int type, int protocal);
引數意義
domain套接字中使用的協議族資訊
type套接字資料傳輸型別資訊
prorocol裝置間通訊使用的協議資訊
socket() 中的 domain 引數(協議族)
  • PF_INET → IPv4 網際網路協議族
  • PF_INET6 → IPv6 網際網路協議族
  • PF_LOCAL → 本地通訊的協議族
  • PF_PACKET → 底層資料收發協議
  • PF_IPX → Novell 專用協議(網際網路分組交換協議)
  • ...
注意:不同協議中的地址表現形式可能不同,網路程式設計時地址型別必須和協議型別匹配
socket() 中的 type 和 protocol 引數
  • type : 用於指定協議型別

    • SOCK_STREAM : 流式資料 (TCP)
    • SOCK_UGRAM : 報文式資料(UDP)
  • protocol :用於指定協議族符合型別的具體協議

    • domain 和 type 幾乎可以唯一確定一種協議,因此,這個引數通常為 0
    • 即:0 代表 domain 和 type 指定後的預設協議
關於埠號和 IP 地址
  • 埠號是一個 2 位元組資料(無符號)
  • 0 - 1024 作為特定埠被預定義(分配給特定應用程式)
  • IP 地址是一個 4 位元組無符號地址族 (可分為 5 類地址)

image.png

深入解析 IP 地址

IP 地址分為 網路標識主機標識 兩部分
  • 網路標識:標識網路主機(裝置)所在的網路
  • 主機標識:標識網路主機(裝置)的具體地址

image.png

問題:一個 IP 地址就 4 個位元組,那麼如何區分網路標識和主機標識呢?

image.png

  • IP 地址子網掩碼 配合使用區分 網路標識 和 主機標識
  • 子網掩碼的表現形式也是一個 4 位元組的整型數(無符號)
  • 子網掩碼用於從 IP 地址中提取 網路標識 (& 操作)

image.png

深入理解子網掩碼
設:子網掩碼為 M.N.P.Q,則子網可用 IP 地址數量 n = (256 - M) * (256 - N) * (256 - P) * (256 - Q)

例:IP 地址 211.99.34.33,掩碼 255.255.2555.248,因此:211.99.34.33 所在子網有 8 個 IP 地址
所在子網地址: 211.99.34.32 
廣播地址:211.99.34.39
6個可分配地址:211.99.34.33 ... 211.99.34.38

注:子網地址即為網路標識

image.png

ip 地址 211.99.34.33, 掩碼 255.255.225.248
可知 211.99.34.33 所在子網有 8 個 IP 地址, 且 8 = 2^3(二的三次方),所以 Y = 32 - 3 = 29
可表示為 211.99.34.33 / 29 【簡寫形式】

注:29 為32位子網掩碼的高位

算一算
IP 地址 192.168.3.44,掩碼 255.255.255.0

問:
    子網地址是什麼?廣播地址是什麼?可用地址有多少?簡潔表示法是什麼?

答:
    子網地址:192.168.3.44
    廣播地址:192.168.3.255
    可用地址:254 [256 - 0(子網地址) - 255(廣播地址)]
    簡潔表示:192.168.3.44 / 24
特殊的地址
  • 0.0.0.0 / 0 - 保留,常用於代表 “預設網路”
  • 127.0.0.0 / 8 - 迴環地址,常用於本地軟體會送測試
  • 255.255.255.255 / 32 - 廣播地址
網路程式設計中的地址型別
int sock = 0;
struct sockaddr_in addr = {0};

sock = socket(PF_INET, SOCK_STREAM, 0);  // Protocol Family,協議族

if (sock == -1) {
    pritnf("socket error\n");
    return;
}

addr.sin_family = AF_INET;  // Address Family,地址族
addr.sin_addr.s_addr = inet_addr("192.168.3.241");
addr.sin_addr.port = htons(8899);

if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
    printf("connect error\n");
    return -1;
}

問:(struct sockaddr*)&addr 的強制型別轉換不會出問題嗎?

地址資料型別解析

image.png

  • struct sockaddr 可理解為頂層地址型別父類,其與子類的記憶體佈局相同
  • connect() 根據 addr.sin_family 來判斷是哪一種地址型別並進行相應解析

IP 地址相關函式

#include <arpa/inet.h>

函式原型功能描述
in_addr_t inet_addr(const char* strptr);將 IP 字串轉換為符合網路位元組序的整數
int inet_aton(const char *cp, struct_addr *inp);將 IP 字串轉換為符合網路位元組序的整數,成功返回 1, 失敗返回 0
char *inet_ntoa(struct in_addr in)將符合網路位元組序的整數地址轉換為字串形式

程式設計實驗:地址函式實驗

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <malloc.h>

int main()
{
    unsigned int addr = inet_addr("1.2.3.4");
    struct in_addr addr1 = {0x09080706};
    struct in_addr addr2 = {0x05040302};
    char *s1 = inet_ntoa(addr1);
    char *s1_s = strcpy(malloc(32), s1);
    char *s2 = inet_ntoa(addr2);
    char *s2_s = strcpy(malloc(32), s2);

    printf("addr = %x\n", addr);

    printf("addr1 = %x\n", addr1.s_addr);
    printf("addr2 = %x\n", addr2.s_addr);

    printf("s1 = %s\n", s1);
    printf("s2 = %s\n", s2);               // 注意這裡 !!
    printf("s1 == s2 : %d\n", s1 == s2);   // 注意這裡 !!

    printf("s1_s = %s\n", s1_s);
    printf("s2_s = %s\n", s2_s);
    printf("s1_s == s2_s : %d\n", s1_s == s2_s);

    if (inet_aton("D.T.Software", &addr1)) {  // 注意這裡 !!
        printf("addr1 = %x\n", addr1.s_addr);
    }
    
    free(s1_s);
    free(s2_s);

    return 0;
}

輸出

addr = 4030201
addr1 = 9080706
addr2 = 5040302
s1 = 2.3.4.5
s2 = 2.3.4.5   // 注意,轉換結果被覆蓋 !!
s1 == s2 : 1   // 兩次為同一個地址 !!
s1_s = 6.7.8.9
s2_s = 2.3.4.5
s1_s == s2_s : 0

遺留的問題:如何增強服務端能力,同時支援多個客戶端?什麼是多播?什麼是廣播?

相關文章