📘
Beej's Guide to Network Programming 正體中文版
  • 簡介
  • 原著資訊
  • 譯者誌謝
  • 進階資料
  • 簡體中文版
  • 中文授權
  • 聯絡譯者
  • 1. 導讀
    • 1.1. 本書的讀者
    • 1.2. 平台與編譯器
    • 1.3. 官方網頁與書本
    • 1.4. Solaris/SunOS 程式設計師該注意的事
    • 1.5. Windows 程式設計師該注意的事
    • 1.6. 來信原則
    • 1.7. 鏡射站台(Mirroring)
    • 1.8. 譯者該注意的
    • 1.9. 版權與散佈
  • 2. 何謂 Socket
    • 2.1 兩種 Internet Sockets
    • 2.2 底層漫談與網路理論
  • 3. IP address、結構與資料轉換
    • 3.1. IPv4 與 IPv6
      • 3.1.1. Sub network (子網段)
      • 3.1.2. Port Number(連接埠號碼)
    • 3.2. Byte Order(位元組順序)
    • 3.3. 資料結構
    • 3.4. IP 位址,續集
      • 3.4.1 Private Network
  • 4. 從 IPv4 移植為 IPv6
  • 5. System call 或 Bust
    • 5.1. getaddrinfo()-準備開始!
    • 5.2. socket()-取得 File Descriptor!
    • 5.3. bind()- 我在哪個 port?
    • 5.4. connect(),嘿!你好。
    • 5.5. listen()-有人會呼叫我嗎?
    • 5.6. accept()- 謝謝你 call 3490 port
    • 5.7. send() 與 recv()- 寶貝,我們來聊天!
    • 5.8. sendto() 與 recvfrom()- 來點 DGRAM
    • 5.9. close() 與 shutdown()- 你消失吧!
    • 5.10. getpeername()-你是誰?
    • 5.11. gethostname()-我是誰?
  • 6. Client-Server 基礎
    • 6.1. 簡單的 Stream Server
    • 6.2. 簡單的 Stream Client
    • 6.3. Datagram Sockets
  • 7. 進階技術
    • 7.1. Blocking(阻塞)
    • 7.2. select():同步 I/O 多工
    • 7.3. 不完整傳送的後續處理
    • 7.4. Serialization:如何封裝資料
    • 7.5. 資料封裝
    • 7.6. 廣播封包:Hello World!
  • 8. 常見的問題
  • 9. Man 使用手冊
    • 9.1. accept()
    • 9.2. bind()
    • 9.3. connect()
    • 9.4. close()
    • 9.5. getaddrinfo(), freeaddrinfo(), gai_strerror()
    • 9.6. gethostname()
    • 9.7. gethostbyname(), gethostbyaddr()
    • 9.8. getnameinfo()
    • 9.9. getpeername()
    • 9.10. errno
    • 9.11. fcntl()
    • 9.12. htons(), htonl(), ntohs(), ntohl()
    • 9.13. inet_ntoa(), inet_aton(), inet_addr
    • 9.14. inet_ntop(), inet_pton()
    • 9.15. listen()
    • 9.16. perror(), strerror()
    • 9.17. poll()
    • 9.18. recv(), recvfrom()
    • 9.19. select()
    • 9.20. setsockopt(), getsockopt()
    • 9.21. send(), sendto()
    • 9.22. shutdown()
    • 9.23. socket()
    • 9.24. struct sockaddr and pals
  • 10. 參考資料
    • 10.1. 書籍
    • 10.2. 網站參考資料
    • 10.3. RFC
  • 11. 原著誌謝
Powered by GitBook
On this page
Edit on GitHub
  1. 3. IP address、結構與資料轉換

3.3. 資料結構

很好,終於講到這裡了,該是談談程式設計的時間了。在本節,我會介紹 socket 介面的各種資料型別,因為它們有些會不太好理解。

首先是最簡單的:socket descriptor,型別如下:

int

就是一般的 int。

從這裡開始會有點不好理解,所以不用問太多,直接讀過就好。

我的第一個 StructTM -struct addrinfo,這個資料結構是最近的發明,用來準備之後要用的 socket 位址資料結構,也用在主機名稱(host name)及服務名稱(service name)的查詢。當我們之後開始實際應用時,才會開始覺得比較合理,現在只需要知道你在建立連線呼叫時會用到這個資料結構。

struct addrinfo {
    int ai_flags; // AI_PASSIVE, AI_CANONNAME 等。
    int ai_family; // AF_INET, AF_INET6, AF_UNSPEC
    int ai_socktype; // SOCK_STREAM, SOCK_DGRAM
    int ai_protocol; // 用 0 當作 "any"
    size_t ai_addrlen; // ai_addr 的大小,單位是 byte
    struct sockaddr *ai_addr; // struct sockaddr_in 或 _in6
    char *ai_canonname; // 典型的 hostname
    struct addrinfo *ai_next; // 鏈結串列、下個節點
};

你可以載入這個資料結構,然後呼叫 getaddrinfo()。它會傳回一個指標,這個指標指向一個新的鏈結串列,這個串列有一些資料結構,而資料結構的內容記載了你所需的東西。

你可以在 ai_family 欄位中設定強制使用 IPv4 或 IPv6,或者將它設定為 AF_UNSPEC,AF_UNSPEC 很酷,因為這樣你的程式就可以不用管 IP 的版本。

要注意的是,這是個鏈結串列:ai_next 是指向下一個元素(element),可能會有多個結果讓你選擇。我會直接用它提供的第一個結果,不過你可能會有不同的個人考量;先生!我不是萬事通。

你會在 struct addrinfo 中看到 ai_addr 欄位是一個指向 struct sockaddr 的指標。這是我們開始要了解 IP 位址結構中有哪些細節的地方。有時候,你需要的是呼叫 getaddrinfo() 幫你填好 struct addrinfo。然而,你必須查看這些資料結構,並將值取出,所以我在這邊會進行說明。

[還有,在發明 struct addrinfo 以前的程式碼都要手動填寫這些資料的每個欄位,所以你會看到很多 IPv4 的程式碼真的用很原始的方式去做這件事。你知道的,本文件在舊版也是這樣做]。

有些 structs 是 IPv4,而有些是 IPv6,有些兩者都是。我會特別註明清楚它們屬於哪一種。

總之,struct sockaddr 記錄了很多 sockets 類型的 socket 的位址資訊。

struct sockaddr {
    unsigned short sa_family; // address family, AF_xxx
    char sa_data[14]; // 14 bytes of protocol address
};

sa_family 可能是任何東西,不過在這份文件中我們會用到的是 AF_INET[IPv4]或 AF_INET6[IPv6]。sa_data 包含一個 socket 的目地位址與 port number。這樣很不方便,因為你不會想要手動的將位址封裝到 sa_data 裡。

為了處理 struct sockaddr,程式設計師建立了對等平行的資料結構:struct sockaddr_in["in" 是代表 "internet"],可用在 IPv4。

而這邊有個重點:指向 struct sockaddr_in 的指標可以轉型(cast)為指向 struct sockaddr 的指標,反之亦然。所以即使 connect() 需要一個 struct sockaddr *,你也可以用 struct sockaddr_in,並在最後的時候對它做型別轉換!

// (IPv4 專用-- IPv6 請見 struct sockaddr_in6)
struct sockaddr_in {
    short int sin_family; // Address family, AF_INET
    unsigned short int sin_port; // Port number
    struct in_addr sin_addr; // Internet address
    unsigned char sin_zero[8]; // 與 struct sockaddr 相同的大小
};

這個資料結構讓它很容易可以參考(reference)socket 位址的成員。要注意的是 sin_zero[這是用來將資料結構補足符合 struct sockaddr 的長度],應該要使用 memset() 函式將 sin_zero 整個清為零。還有,sin_family 是對應到 struct sockaddr 中的 sa_family,並應該設定為 "AF_INET"。最後,sin_port 必須是 Network Byte Order[利用 htons()]。

讓我們再更深入點!你可以在 struct in_addr 裡看到 sin_addr 欄位。

那是什麼?

好,別太激動,不過它是其中一個最恐怖的 union:

// (僅限 IPv4 — Ipv6 請參考 struct in6_addr)
// Internet address (a structure for historical reasons)
struct in_addr {
    uint32_t s_addr; // that's a 32-bit int (4 bytes)
};

哇!好耶,它以前是 union,不過這個包袱現在似乎已經不見了。因此,若你已將 ina 宣告為 struct sockaddr_in 的型別時,那麼 ina.sin_addr.s_addr 會參考到 4-byte 的 IP address(以 Network Byte Order)。要注意的是,如果你的系統仍然在 struct in_addr 使用超恐怖的 union,你依然可以像我上面說的,精確地參考到 4-byte 的 IP address[這是因為有 #define]。

那麼 IPv6 會怎樣呢?

IPv6 也有提供類似的 struct,比如:

// (IPv6 專用-- IPv4 請見 struct sockaddr_in 與 struct in_addr)
struct sockaddr_in6 {
    u_int16_t sin6_family; // address family, AF_INET6
    u_int16_t sin6_port; // port number, Network Byte Order
    u_int32_t sin6_flowinfo; // IPv6 flow 資訊
    struct in6_addr sin6_addr; // IPv6 address
    u_int32_t sin6_scope_id; // Scope ID
};

struct in6_addr {
    unsigned char s6_addr[16]; // IPv6 address
};

要注意到 IPv6 協定有一個 IPv6 address 與一個 port number,就像 IPv4 協定有一個 IPv4 address 與 port number 一樣。

我現在還不會介紹 IPv6 的流量資訊,或是 Scope ID 欄位 … 畢竟這只是一份入門文件嘛 :-)

最後要強調的一點,這個簡單的 struct sockaddr_storage 設計用來足以儲存 IPv4 與 IPv6 structures 的 structure。 [你看看,對於某些 calls,你有時無法事先知道它是否會使用 IPv4 或 IPv6 address 來填好你的 struct sockaddr。所以你用這個平行的 structure 來傳遞,它除了比較大以外,也很類似 struct sockaddr ,因而可以將它轉型為你所需的型別]。

struct sockaddr_storage {
    sa_family_t ss_family; // address family
    // 這裡都是填充物(padding),依實作而定,請忽略它:
    char __ss_pad1[_SS_PAD1SIZE];
    int64_t __ss_align;
    char __ss_pad2[_SS_PAD2SIZE];
};

重點是你可以在 ss_family 欄位看到位址家族(address family),檢查它是 AF_INET 或 AF_INET6(是 IPv4 或 IPv6)。之後如果你願意的話,你就可以將它轉型為 sockaddr_in 或 struct sockaddr_in6。

Previous3.2. Byte Order(位元組順序)Next3.4. IP 位址,續集

Last updated 2 years ago