首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

Linux C/C++ 網路程式設計:Socket 基礎與核心資料結構深度解析

     編輯:關於C++

Socket 是網路程式設計的基石,也是應用層與傳輸層(TCP/UDP)的介面。在 Linux 核心中,Socket 不僅是一個 API,更是一套精密的分層架構:BSD Socket 層(通用介面) → INET Socket 層(TCP/IP 協定族實作) → 協定驅動層。本文深入剖析 Socket 地址結構、位元組序轉換、核心資料結構(sock/socket/proto_ops)、連線建立全流程與核心系統呼叫實作。

一、Socket 地址結構體系

1. 通用地址:struct sockaddr

定義於 <sys/socket.h>,作為所有協定族地址的「基類」介面:

struct sockaddr {
    sa_family_t sa_family; // 協定族:AF_INET, AF_INET6, AF_UNIX...
    char        sa_data[14]; // 14 bytes 具體協定地址內容
};

2. IPv4 專用: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 (網路位元組序)
};

3. IPv6 專用:struct sockaddr_in6

struct 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
};

4. 填充範例

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));

二、IP 位址轉換與位元組序

1. 字串 ↔ 二進位 (IPv4)

  • inet_pton(AF_INET, "192.168.1.1", &addr):現代 API,支援 IPv4/IPv6,成功回傳 1。
  • inet_ntop(AF_INET, &addr, buf, len):二進位轉字串,執行緒安全。
  • 舊 API:inet_aton、inet_ntoa(非執行緒安全,僅 IPv4),新代碼請避免使用。

2. 網路位元組序 vs 主機位元組序

  • 網路序 = Big Endian(高位在低址),TCP/IP 標準。
  • 主機序:x86 為 Little Endian,ARM 多為 Little/Big 可切換。
  • 跨平台傳輸必須統一為網路序,否則位址/埠號解析錯誤(如 0x0012 變 0x1200,埠 18 變 4608)。

3. 轉換函數族

#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 內文)若雙方協定格式,可不轉換。

三、核心層資料結構與分層架構

1. 協定操作向量: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 等
};

2. BSD Socket 物件: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...
};

3. INET 核心物件:struct sock

定義於 include/net/sock.h(超過 800 行),面向協定驅動層,含:

  • 協定操作向量 struct proto *prot(TCP/UDP 具體實作)。
  • 接收/發送佇列、重傳定時器、拥塞控制狀態、TCP 狀態機變數。
  • 雙向指標:sk->sk_socket 指回 socket,socket->sk 指向 sock。

4. 協定註冊: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 生命週期核心流程

1. 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 雙向綁定。
  • 在 sockfs (虛擬檔案系統) 建立 inode,取得檔案描述符 (fd)。

2. bind() 綁定位址

  • 檢查 socket->state == TCP_CLOSE。
  • 從 sockaddr 擷取 IP/埠,存入 sock->inet_rcv_saddr (哈希查找用) 與 inet_saddr (發送用)。
  • 埠號為 0 時自動分配臨時埠。
  • 不加入 TCP 雜湊表,僅檢查埠是否衝突;listen() 時才加入 tcp_listen_hash。

3. connect() 主動連線

  • 狀態需為 SS_UNCONNECTED。
  • 觸發 TCP 三向交握:發送 SYN → 收 SYN+ACK → 回 ACK。
  • 若對端正在 listen,其 sock 已在 tcp_listen_hash,封包可快速定位。

4. listen() 監聽

  • 狀態改為 TCP_LISTEN。
  • 將 sock 加入 tcp_listen_hash (以 inet_rcv_saddr + inet_num 為鍵)。
  • 收到 SYN 時,建立新 request_sock (半連線),放入 icsk_accept_queue。

5. accept() 接受連線

  • 從 icsk_accept_queue 取出已完成三向交握的 request_sock。
  • 克隆新 sock (完整連線),與新 socket 綁定。
  • 回傳新 fd 給使用者程序;原監聽 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 地址為非字串二進位資料,需二進位安全函數:

  • BSD 風格:bzero(), bcopy(), bcmp()。
  • ANSI C 標準:memset(), memcpy(), memcmp() (推薦使用)。
  • 注意:memcpy 不支援重疊記憶體區域,重疊請用 memmove。

七、小結:Socket 程式設計核心清單

  1. 分清 sockaddr (介面) vs sockaddr_in/in6 (實作),傳參時 強制轉型。
  2. 埠號、IP 位址務必用網路序 (htons/htonl),應用層資料視協定而定。
  3. 理解 socket (BSD 介面) ↔ sock (INET 核心) 雙結構分工與雙向指標。
  4. 掌握 socket → bind → listen → accept (Server) / socket → connect (Client) 標準流程。
  5. 善用 getaddrinfo 取代舊 API,支援 IPv6 與執行緒安全。

深入核心視角理解 Socket,不僅能寫出正確的網路程式,更能在效能調優、疑難雜症除錯(如 TIME_WAIT 暴增、連線重置、封包丟失)時具備系統級診斷能力。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved