? TCP 編程是網絡通信的核心,其 API 圍繞面向連接的特性設計,涵蓋服務端和客戶端的交互流程。以下是基于 ?C 語言的 TCP 編程核心 API 及使用流程的詳細解析:
? 核心 API 概覽
?函數 | ?角色 | ?描述 |
---|---|---|
socket() | 通用 | 創建套接字,指定協議族(IPv4/IPv6)和類型(SOCK_STREAM )。 |
bind() | 服務端 | 將套接字綁定到特定 IP 地址和端口。 |
listen() | 服務端 | 將套接字設為監聽模式,等待客戶端連接請求。 |
accept() | 服務端 | 接受客戶端連接,返回用于通信的新套接字。 |
connect() | 客戶端 | 客戶端主動連接服務端。 |
send() /write() | 通用 | 發送數據(TCP 保證數據順序,可能拆包/粘包)。 |
recv() /read() | 通用 | 接收數據(需處理部分讀取和緩沖區管理)。 |
close() | 通用 | 關閉套接字,終止連接。 |
shutdown() | 通用 | 優雅關閉連接(可選關閉讀/寫方向)。 |
1、socket函數
函數原型與頭文件
#include <sys/types.h>
#include <sys/socket.h>int socket(int domain, int type, int protocol);
? 參數詳解
? domain
(協議族/地址族)?
? 定義通信使用的協議族,常見值包括:
?值 | ?描述 |
---|---|
AF_INET | IPv4 協議(最常用) |
AF_INET6 | IPv6 協議 |
AF_UNIX /AF_LOCAL | 本地進程間通信(UNIX 域套接字) |
AF_PACKET | 底層數據包接口(如原始以太網幀捕獲) |
? 如果是IPV6編程,要使用struct sockddr_in6結構體(man 7 IPV6),通常使用struct sockaddr_storage來編程。
? type
(套接字類型)?
指定數據傳輸的語義,常用類型:
?值 | ?描述 |
---|---|
SOCK_STREAM | 面向連接的流式套接字(TCP,可靠傳輸) |
SOCK_DGRAM | 無連接的數據報套接字(UDP,盡最大努力交付) |
SOCK_RAW | 原始套接字(直接訪問 IP/ICMP 等協議) |
protocol
(具體協議?)
通常設為 0
,表示根據 domain
和 type
?自動選擇默認協議。例如:
SOCK_STREAM
默認使用IPPROTO_TCP
SOCK_DGRAM
默認使用IPPROTO_UDP
若需顯式指定協議,可用:
?值 | ?描述 |
---|---|
IPPROTO_TCP | 強制使用 TCP 協議 |
IPPROTO_UDP | 強制使用 UDP 協議 |
IPPROTO_ICMP | 用于原始套接字的 ICMP 協議 |
返回值
- ?成功:返回一個非負整數?(套接字文件描述符),后續操作(如
bind
,connect
)均基于此描述符。 - ?失敗:返回
-1
,并設置errno
表示錯誤原因(如EACCES
,EAFNOSUPPORT
)。
典型使用場景
創建 TCP 套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {perror("socket() failed");exit(EXIT_FAILURE);
}
- 用于 HTTP、FTP 等需要可靠傳輸的應用。
?創建 UDP 套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd == -1) {perror("socket() failed");exit(EXIT_FAILURE);
}
- 用于 DNS 查詢、實時音視頻傳輸等場景。
創建原始套接字(需 root 權限)?
int sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (sockfd == -1) {perror("socket() failed");exit(EXIT_FAILURE);
}
- 可手動構造 IP 或 ICMP 頭部,用于網絡探測工具(如
ping
)。
2、bind函數
? bind()
函數是網絡編程中用于將套接字(socket)?與特定的IP地址和端口綁定的關鍵步驟,常用于服務端設置監聽地址。以下是 bind()
的詳細解析,包含函數原型、參數解釋、使用示例及常見問題:
函數原型
#include <sys/types.h>
#include <sys/socket.h>int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
參數
?參數 | ?類型 | ?描述 |
---|---|---|
sockfd | int | 已創建的套接字描述符(由socket() 返回) |
addr | struct sockaddr* | 指向保存綁定地址信息的結構體指針(需根據協議族填充對應的結構類型) |
addrlen | socklen_t | 地址結構體的長度(字節數) |
地址結構體
?1. IPv4 地址結構 (struct sockaddr_in)
struct sockaddr_in {sa_family_t sin_family; // 地址族(如 AF_INET)in_port_t sin_port; // 端口號(需用 `htons()` 轉換字節序)struct in_addr sin_addr; // IPv4 地址(需用 `inet_pton()` 或 `htonl(INADDR_ANY)`)unsigned char sin_zero[8]; // 填充字段(一般置零)
};struct in_addr {uint32_t s_addr; // 32位 IPv4 地址(網絡字節序)
};
?2. IPv6 地址結構 (struct sockaddr_in6)
struct sockaddr_in6 {sa_family_t sin6_family; // 地址族(AF_INET6)in_port_t sin6_port; // 端口號(網絡字節序)uint32_t sin6_flowinfo; // IPv6 流信息(通常為0)struct in6_addr sin6_addr; // IPv6 地址uint32_t sin6_scope_id; // 接口范圍標識符(用于本地鏈路地址)
};struct in6_addr {unsigned char s6_addr[16]; // 128位 IPv6 地址
};
?3. 通用地址結構 (struct sockaddr)
struct sockaddr {sa_family_t sa_family; // 地址族(AF_xxx)char sa_data[14];// 具體地址數據(由子結構展開填充)
};
? 使用場景與示例
?1. 服務端綁定 IP 和端口
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) { perror("socket"); exit(1); }// 允許地址重用(避免服務端重啟時 bind 失敗)
int optval = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));struct sockaddr_in addr = {0};
addr.sin_family = AF_INET; // IPv4
addr.sin_addr.s_addr = htonl(INADDR_ANY); // 綁定所有本地 IP
addr.sin_port = htons(8080); // 綁定端口 8080if (bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {perror("bind");close(server_fd);exit(1);
}// 后續可調用 listen() 啟動監聽
?2. 客戶端綁定特定源地址
int client_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in client_addr = {0};
client_addr.sin_family = AF_INET;
client_addr.sin_port = htons(12345); // 指定客戶端端口
inet_pton(AF_INET, "192.168.1.100", &client_addr.sin_addr); // 指定源 IPbind(client_fd, (struct sockaddr*)&client_addr, sizeof(client_addr));
connect(client_fd, server_addr, sizeof(server_addr));
3、listen函數
函數原型
#include <sys/types.h>
#include <sys/socket.h>int listen(int sockfd, int backlog);
? 參數詳解
?參數 | ?類型 | ?描述 |
---|---|---|
sockfd | int | 已通過 bind() 綁定地址的套接字描述符(必須是 ?SOCK_STREAM 類型)。 |
backlog | int | 已建立連接(完成三次握手)的隊列最大長度,決定同時等待 accept() 處理的連接數。 |
? 核心作用
-
?轉換套接字狀態:
- 將套接字從主動模式?(默認)轉為被動模式,使其能夠接收客戶端的連接請求。
- 未調用
listen()
的套接字無法調用accept()
。
-
?管理連接隊列:
- 內核為監聽套接字維護兩個隊列?(具體實現可能因操作系統而異):
- ?未完成隊列(SYN_RCVD 狀態)?:客戶端已發送 SYN,但未完成三次握手。
- ?已完成隊列(ESTABLISHED 狀態)?:已完成三次握手,等待
accept()
取出。
backlog
參數通常指已完成隊列的最大長度?(Linux 中默認上限由/proc/sys/net/core/somaxconn
定義)。
- 內核為監聽套接字維護兩個隊列?(具體實現可能因操作系統而異):
? 使用場景與示例
?1. 服務端啟動監聽
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
// ...綁定地址(bind())...// 設置監聽隊列長度為 128
if (listen(server_fd, 128) == -1) {perror("listen() failed");close(server_fd);exit(EXIT_FAILURE);
}// 循環接受客戶端連接
while (1) {struct sockaddr_in client_addr;socklen_t addrlen = sizeof(client_addr);int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &addrlen);// ...處理 client_fd...
}
?2. backlog 的合理取值
?經驗值:通常設為 SOMAXCONN(系統定義的最大值,如 Linux 默認 4096)。
?調整方法?(Linux):# 臨時修改 somaxconn
echo 4096 > /proc/sys/net/core/somaxconn# 永久修改(需編輯 /etc/sysctl.conf)
net.core.somaxconn = 4096
- ?注意:實際允許的連接數受系統資源和并發模型(如多線程、epoll)影響。
4、accept函數
函數原型
#include <sys/types.h>
#include <sys/socket.h>int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
? 參數詳解
?參數 | ?類型 | ?描述 |
---|---|---|
|
| 處于監聽狀態的套接字描述符(由 |
|
| 輸出參數,用于存儲客戶端地址信息(如 IP 和端口)。若不需要可設為 |
|
| 輸入輸出參數:傳入 |
? 返回值
-
?成功:返回一個新的已連接套接字描述符?(非負整數),專門用于與客戶端通信。
-
?失敗:返回
-1
,并設置errno
(如EINTR
、ECONNABORTED
)。
? 核心作用
-
?提取連接:從監聽套接字的已完成連接隊列?(已完成三次握手)中取出一個客戶端連接。
-
?生成新套接字:返回的已連接套接字與客戶端一一對應,原監聽套接字繼續接受其他連接。
-
?獲取客戶端地址:通過
addr
參數獲取客戶端的 IP 地址和端口(可選)。
5、connect函數
函數原型
#include <sys/types.h>
#include <sys/socket.h>int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
? 參數詳解
?參數 | ?類型 | ?描述 |
---|---|---|
sockfd | int | 客戶端套接字描述符(由 socket() 創建)。 |
addr | struct sockaddr* | 指向服務端地址結構體的指針(如 sockaddr_in )。 |
addrlen | socklen_t | 地址結構體的長度(單位:字節)。 |
? 返回值
- ?成功:返回
0
,套接字進入已連接狀態(TCP)或設置默認地址(UDP)。 - ?失敗:返回
-1
,并設置errno
表示錯誤原因。