許多教學讓初學者死記 PF_INET、SOCK_STREAM、0 三個參數,實則硬編碼是大忌。現代正確做法:直接使用 getaddrinfo() 回傳的結果填充 socket() 參數,達成完全的協議無關性。
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
| 參數 | IPv4 值 | IPv6 值 | 說明 |
|---|---|---|---|
| domain | PF_INET | PF_INET6 | 協議族 |
| type | SOCK_STREAM | SOCK_STREAM | TCP 串流 |
| type | SOCK_DGRAM | SOCK_DGRAM | UDP 資料報 |
| protocol | 0 或 IPPROTO_TCP | 0 或 IPPROTO_TCP | 通常填 0 讓系統自選 |
PF_INET / PF_INET6:Protocol Family,用於 socket() 的 domain 參數AF_INET / AF_INET6:Address Family,用於 sockaddr 結構的 sa_family 欄位int s;
struct addrinfo hints, *res;
// 1. 設定 hints 並呼叫 getaddrinfo()
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; // 不指定 IPv4/IPv6
hints.ai_socktype = SOCK_STREAM; // TCP
getaddrinfo("www.example.com", "http", &hints, &res);
// 2. 直接使用 res 內容建立 socket
// 實務上應遍歷 res 鏈結串列尋找可用節點,此處簡化示意
s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (s == -1) {
perror("socket");
// 應嘗試 res->ai_next 節點...
}
ai_protocol 可能非 0(某些特殊協議),直接使用避免假設hints 設定處-1,並設定全域 errnoEMFILE (進程檔案描述符耗盡)、ENFILE (系統檔案表滿)、EPROTONOSUPPORT (協議不支援)socket() 只是取得一個「通訊端點代號」,真正的協議綁定發生在 bind()/connect() 階段。永遠從 getaddrinfo() 取得參數,是寫出現代、可維護、支援雙協議棧網路程式的鐵律。下一章將探討 bind() — 如何將這個描述符綁定到特定埠口。