Socket 是網路程式設計的基石,也是應用層與傳輸層(TCP/UDP)的介面。在 Linux 核心中,Socket 不僅是一個 API,更是一套精密的分層架構:BSD Socket 層(通用介面) → INET Socket 層(TCP/IP 協定族實作) → 協定驅動層。本文深入剖析 Socket 地址結構、位元組序轉換、核心資料結構(sock/socket/proto_ops)、連線建立全流程與核心系統呼叫實作。
struct sockaddr定義於 <sys/socket.h>,作為所有協定族地址的「基類」介面:
struct sockaddr {
sa_family_t sa_family; // 協定族:AF_INET, AF_INET6, AF_UNIX...
char sa_data[14]; // 14 bytes 具體協定地址內容
};
struct sockaddr_in定義於 <netinet/in.h>,大小與 sockaddr 相同,便於強制轉型:
struct sockaddr_in {
sa_family_t sin_family; // AF_INET
in_port_t sin_port; // 16-bit port (網路位元組序)
struct in_addr sin_addr; // 32-bit IPv4 位址
unsigned char sin_zero[8]; // 填充對齊至 16 bytes
};
struct in_addr {
in_addr_t s_addr; // 32-bit IPv4 (網路位元組序)
};
struct sockaddr_in6struct sockaddr_in6 {
sa_family_t sin6_family; // AF_INET6
in_port_t sin6_port; // 16-bit port
uint32_t sin6_flowinfo; // 流量標籤/優先級
struct in6_addr sin6_addr; // 128-bit IPv6 位址
uint32_t sin6_scope_id; // 介面範圍 ID
};
struct in6_addr {
unsigned char s6_addr[16]; // 128-bit IPv6
};
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(3333); // 主機序轉網路序
servaddr.sin_addr.s_addr = inet_addr("166.111.160.10");
// 或 servaddr.sin_addr.s_addr = INADDR_ANY; // 伺服器綁定任意介面
memset(&servaddr.sin_zero, 0, sizeof(servaddr.sin_zero));
inet_pton(AF_INET, "192.168.1.1", &addr):現代 API,支援 IPv4/IPv6,成功回傳 1。inet_ntop(AF_INET, &addr, buf, len):二進位轉字串,執行緒安全。inet_aton、inet_ntoa(非執行緒安全,僅 IPv4),新代碼請避免使用。#include <arpa/inet.h> // 或 <netinet/in.h> uint32_t htonl(uint32_t hostlong); // Host TO Network Long (32-bit) uint16_t htons(uint16_t hostshort); // Host TO Network Short (16-bit) uint32_t ntohl(uint32_t netlong); // Network TO Host Long uint16_t ntohs(uint16_t netshort); // Network TO Host Short
鐵律:sin_port、sin_addr.s_addr、sin6_port、sin6_addr 統統存網路序。應用層資料(如 HTTP 內文)若雙方協定格式,可不轉換。
struct proto_ops定義於 include/linux/net.h,對應 BSD Socket 系統呼叫的核心實作入口(類比 VFS file_operations):
struct proto_ops {
int family;
int (*bind) (struct socket*, struct sockaddr*, int);
int (*connect) (struct socket*, struct sockaddr*, int, int);
int (*listen) (struct socket*, int);
int (*accept) (struct socket*, struct socket*, int);
int (*sendmsg) (struct socket*, struct msghdr*, int, struct scm_cookie*);
int (*recvmsg) (struct socket*, struct msghdr*, int, int, struct scm_cookie*);
// ... setsockopt, getsockopt, shutdown, poll, ioctl, mmap, sendpage 等
};
struct socket面向使用者程序與系統呼叫介面:
struct socket {
socket_state state; // SS_UNCONNECTED, SS_CONNECTING 等
unsigned long flags;
struct proto_ops *ops; // 指向協定族操作向量
struct inode *inode; // 對應 sockfs inode
struct file *file; // 檔案指標回指
struct sock *sk; // 關鍵:指向 INET 層 sock 結構
wait_queue_head_t wait;
short type; // SOCK_STREAM, SOCK_DGRAM...
};
struct sock定義於 include/net/sock.h(超過 800 行),面向協定驅動層,含:
struct proto *prot(TCP/UDP 具體實作)。sk->sk_socket 指回 socket,socket->sk 指向 sock。inet_protos 向量核心啟動時,各協定 (TCP/UDP/ICMP...) 向 inet_protos[IPPROTO_MAX] 註冊處理函數:
struct inet_protocol {
int (*handler)(struct sk_buff *skb); // 正常封包處理
void (*err_handler)(struct sk_buff*, u32); // ICMP 錯誤處理
// ...
};
socket() 系統呼叫路徑:sys_socket() → sock_create() → __sock_create() → pf->create() (PF_INET 對應 inet_create)。
socket 結構,掛載 proto_ops (inet_stream_ops / inet_dgram_ops / inet_raw_ops)。sock 結構 (sk_alloc),初始化 proto (tcp_prot / udp_prot / raw_prot)。socket↔sock 雙向綁定。bind() 綁定位址socket->state == TCP_CLOSE。sockaddr 擷取 IP/埠,存入 sock->inet_rcv_saddr (哈希查找用) 與 inet_saddr (發送用)。listen() 時才加入 tcp_listen_hash。connect() 主動連線SS_UNCONNECTED。listen,其 sock 已在 tcp_listen_hash,封包可快速定位。listen() 監聽TCP_LISTEN。sock 加入 tcp_listen_hash (以 inet_rcv_saddr + inet_num 為鍵)。request_sock (半連線),放入 icsk_accept_queue。accept() 接受連線icsk_accept_queue 取出已完成三向交握的 request_sock。sock (完整連線),與新 socket 綁定。socket 繼續監聽。| 函數 | 用途 | 關鍵參數 |
|---|---|---|
getsockname() | 取得本端綁定的 IP/埠 | sockfd, sockaddr*, socklen_t* |
getpeername() | 取得對端 IP/埠 (需已連線) | sockfd, sockaddr*, socklen_t* |
gethostbyname() | 主機名解析 IP (舊, 非執行緒安全) | name → hostent* |
gethostbyaddr() | IP 反解主機名 | addr, len, type → hostent* |
getaddrinfo() | 現代標準,支援 IPv4/6、執行緒安全 | node, service, hints, res |
getservbyname() | 服務名查埠號 (查 /etc/services) | name, proto → servent* |
Socket 地址為非字串二進位資料,需二進位安全函數:
bzero(), bcopy(), bcmp()。memset(), memcpy(), memcmp() (推薦使用)。memcpy 不支援重疊記憶體區域,重疊請用 memmove。sockaddr (介面) vs sockaddr_in/in6 (實作),傳參時 強制轉型。htons/htonl),應用層資料視協定而定。socket (BSD 介面) ↔ sock (INET 核心) 雙結構分工與雙向指標。socket → bind → listen → accept (Server) / socket → connect (Client) 標準流程。getaddrinfo 取代舊 API,支援 IPv6 與執行緒安全。深入核心視角理解 Socket,不僅能寫出正確的網路程式,更能在效能調優、疑難雜症除錯(如 TIME_WAIT 暴增、連線重置、封包丟失)時具備系統級診斷能力。