📘
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. 5. System call 或 Bust

5.1. getaddrinfo()-準備開始!

這是個有很多選項的工作馬(workhorse)函式,但是卻相當容易上手。它幫你設定之後需要的 struct。

談點歷史:它前身是你用來做 DNS 查詢的 gethostbyname()。而當時你需要手動將資訊載入 struct sockaddr_in,並在你的呼叫中使用。

感謝老天,現在已經不用了。[如果你想要設計能通用於 IPv4 與 IPv6 的程式也不用!]在現代,你有 getaddrinfo() 函式,可以幫你做許多事情,包含 DNS 與 service name 查詢,並填好你所需的 structs。

讓我們來看看!

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

int getaddrinfo(const char *node, // 例如: "www.example.com" 或 IP
                const char *service, // 例如: "http" 或 port number
                const struct addrinfo *hints,
                struct addrinfo **res);

你給這個函式三個輸入參數,結果它會回傳給你一個指向鏈結串列的指標 - res。

node 參數是要連線的主機名稱,或者一個 IP address(位址)。

下一個參數是 service,這可以是 port number,像是 "80",或者特定服務的名稱[可以在你 UNIX 系統上的 IANA Port List [17] 或 /etc/services 檔案中找到],像是 "http" 或 "ftp" 或 "telnet" 或 "smtp" 諸如此類的。

最後,hints 參數指向一個你已經填好相關資訊的 struct addrinfo。

這裡是一個呼叫範例,如果你是一部 server(伺服器),想要在你主機上的 IP address 及 port 3490 執行 listen。要注意的是,這邊實際上沒有做任何的 listening 或網路設定;它只有設定我們之後要用的 structures 而已。

int status;
struct addrinfo hints;c
struct addrinfo *servinfo; // 將指向結果

memset(&hints, 0, sizeof hints); // 確保 struct 為空
hints.ai_family = AF_UNSPEC; // 不用管是 IPv4 或 IPv6
hints.ai_socktype = SOCK_STREAM; // TCP stream sockets
hints.ai_flags = AI_PASSIVE; // 幫我填好我的 IP 

if ((status = getaddrinfo(NULL, "3490", &hints, &servinfo)) != 0) {
  fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
  exit(1);
}

// servinfo 目前指向一個或多個 struct addrinfos 的鏈結串列

// ... 做每件事情,一直到你不再需要 servinfo  ....

freeaddrinfo(servinfo); // 釋放這個鏈結串列

注意一下,我將 ai_family 設定為 AF_UNSPEC,這樣代表我不用管我們用的是 IPv4 或 IPv6 address。如果你想要指定的話,你可以將它設定為 AF_INET 或 AF_INET6。

還有,你會在這裡看到 AI_PASSIVE 旗標;這個會告訴 getaddrinfo() 要將我本機的位址(address of local host)指定給 socket structure。這樣很棒,因為你就不用把位址寫死了[或者你可以將特定的位址放在 getaddrinfo() 的第一個參數中,我現在寫 NULL 的那個參數]。

然後我們執行呼叫,若有錯誤發生時[getaddrinfo 會傳回非零的值],如你所見,我們可以使用 gai_strerror() 函式將錯誤印出來。若每件事情都正常運作,那麼 serinfo 就會指向一個 struct addrinfos 的鏈結串列,串列中的每個成員都會包含一個我們之後會用到的某種 struct sockaddr。

最後,當我們終於使用 getaddrinfo() 配置的鏈結串列完成工作後,我們可以[也應該]要呼叫 freeaddrinfo() 將鏈結串列全部釋放。

這邊有一個呼叫範例,如果你是一個想要連線到特定 server 的 client(客戶端),比如是:"www.example.net" 的 port 3490。再次強調,這裡並沒有真的進行連線,它只是設定我們之後要用的 structure。

int status;
struct addrinfo hints;
struct addrinfo *servinfo; // 將指向結果

memset(&hints, 0, sizeof hints); // 確保 struct 為空
hints.ai_family = AF_UNSPEC; // 不用管是 IPv4 或 IPv6
hints.ai_socktype = SOCK_STREAM; // TCP stream sockets

// 準備好連線
status = getaddrinfo("www.example.net", "3490", &hints, &servinfo);

// servinfo 現在指向有一個或多個 struct addrinfos 的鏈結串列

我一直說 serinfo 是一個鏈結串列,它有各種的位址資訊。讓我們寫一個能快速 demo 的程式,來呈現這個資訊。這個小程式 [18] 會印出你在命令列中所指定的主機之 IP address:
/*
** showip.c -- 顯示命令列中所給的主機 IP address
*/
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>

int main(int argc, char *argv[])
{
  struct addrinfo hints, *res, *p;
  int status;
  char ipstr[INET6_ADDRSTRLEN];

  if (argc != 2) {
    fprintf(stderr,"usage: showip hostname\n");
    return 1;
  }

  memset(&hints, 0, sizeof hints);
  hints.ai_family = AF_UNSPEC; // AF_INET 或 AF_INET6 可以指定版本
  hints.ai_socktype = SOCK_STREAM;

  if ((status = getaddrinfo(argv[1], NULL, &hints, &res)) != 0) {
    fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status));
    return 2;
  }

  printf("IP addresses for %s:\n\n", argv[1]);

  for(p = res;p != NULL; p = p->ai_next) {
    void *addr;
    char *ipver;

    // 取得本身位址的指標,
    // 在 IPv4 與 IPv6 中的欄位不同:
    if (p->ai_family == AF_INET) { // IPv4
      struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;
      addr = &(ipv4->sin_addr);
      ipver = "IPv4";
    } else { // IPv6
      struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;
      addr = &(ipv6->sin6_addr);
      ipver = "IPv6";
    }

    // convert the IP to a string and print it:
    inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr);
    printf(" %s: %s\n", ipver, ipstr);
  }

  freeaddrinfo(res); // 釋放鏈結串列

  return 0;
}

如你所見,程式碼使用你在命令列輸入的參數呼叫 getaddrinfo(),它填好 res 所指的鏈結串列,並接著我們就能重複那行並印出東西或做點類似的事。

[有點不好意思!我們在討論 struct sockaddrs 它的型別差異是因 IP 版本而異之處有點鄙俗。我不確定是否有較優雅的方法。]

在下面執行範例!來看看大家喜歡看的執行畫面:

$ showip www.example.net
IP addresses for www.example.net:

  IPv4: 192.0.2.88

$ showip ipv6.example.com
IP addresses for ipv6.example.com:

  IPv4: 192.0.2.101
  IPv6: 2001:db8:8c00:22::171

現在已經在我們的掌控之下,我們會將 getaddrinfo() 傳回的結果送給其它的 socket 函式,而且終於可以建立我們的網路連線了!

讓我們繼續看下去!

Previous5. System call 或 BustNext5.2. socket()-取得 File Descriptor!

Last updated 2 years ago

[17]

[18]

[19]

http://www.iana.org/assignments/port-numbers
http://beej.us/guide/bgnet/examples/showip.c
http://tools.ietf.org/html/rfc1413