Socket 是網絡編程的基石。本文系統梳理 **TCP/IP 協議分層、Socket 封裝位置、三次握手建立連接、四次揮手斷開連接、核心 API (socket/bind/listen/accept/connect/send/recv/close)、服務端/客戶端完整 C 代碼實戰**,並附帶狀態遷移圖解與常見坑點。
應用層 (HTTP, FTP, DNS, 自定義協議)
│
▼
傳輸層 (TCP/UDP) ←── Socket API 位於此層與應用層交界
│
▼
網絡層 (IP, ICMP, ARP)
│
▼
數據鏈路層 (Ethernet, WiFi)
│
▼
物理層
Socket 封裝了 TCP/UDP 協議細節,提供**文件描述符**式的統一接口:`read/write/send/recv/close`。
客戶端 服務端 │ │ │----- SYN (seq=x) ----->│ SYN_SENT │ │ │<--- SYN+ACK (seq=y, ack=x+1) ---│ SYN_RCVD │ │ │----- ACK (ack=y+1) --->│ ESTABLISHED │ │ ESTABLISHED
客戶端 (主動關閉) 服務端 (被動關閉) │ │ │----- FIN (seq=u) ----->│ FIN_WAIT_1 │ │ │<--- ACK (ack=u+1) ----│ CLOSE_WAIT │ (服務端仍可發數據) │ │ │ │<--- FIN (seq=v) ------│ LAST_ACK │ │ │----- ACK (ack=v+1) --->│ TIME_WAIT (2MSL) │ │ │ (等待 2MSL) │ CLOSED │ │ ▼ CLOSED ▼ CLOSED
| 函數 | 原型 | 關鍵說明 |
|---|---|---|
| socket | `int socket(int domain, int type, int protocol)` | 創建套接字,返回 fd。`AF_INET` + `SOCK_STREAM` = TCP |
| bind | `int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)` | 綁定本地 IP:Port。**服務端必須調用**,客戶端通常不調用 (內核自動分配臨時端口) |
| listen | `int listen(int sockfd, int backlog)` | 轉為被動套接字,`backlog` 為全連接隊列最大長度 (內核 2.2+ 含半連接隊列) |
| accept | `int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)` | 從全連接隊列取出連接,**返回新 fd** (connfd) 用於通信,原 fd 繼續監聽 |
| connect | `int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)` | 客戶端主動發起連接,阻塞至三次握手完成 |
| send/recv | `ssize_t send(int fd, const void *buf, size_t len, int flags)` `ssize_t recv(int fd, void *buf, size_t len, int flags)` | 發送/接收數據。`recv` 返回 0 表示對方關閉 (收到 FIN),-1 表示錯誤 |
| close | `int close(int fd)` | 關閉 fd,引用計數歸零時發起四次揮手 |
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 8887
#define BACKLOG 20
#define BUF_SIZE 1024
int main() {
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) { perror("socket"); exit(1); }
struct sockaddr_in server_addr = {0};
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 綁定所有網卡
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("bind"); close(server_fd); exit(1);
}
if (listen(server_fd, BACKLOG) == -1) {
perror("listen"); close(server_fd); exit(1);
}
printf("Server listening on port %d...\n", PORT);
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int conn_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
if (conn_fd == -1) { perror("accept"); close(server_fd); exit(1); }
printf("Client connected: %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
char buf[BUF_SIZE];
while (1) {
memset(buf, 0, BUF_SIZE);
ssize_t len = recv(conn_fd, buf, BUF_SIZE, 0);
if (len == 0) { printf("Client disconnected\n"); break; }
if (len == -1) { perror("recv"); break; }
buf[len] = '\0';
printf("Received: %s", buf);
if (strcmp(buf, "exit\n") == 0) break;
send(conn_fd, buf, len, 0); // 回射
}
close(conn_fd);
close(server_fd);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 8887
#define BUF_SIZE 1024
int main() {
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (sock_fd == -1) { perror("socket"); exit(1); }
struct sockaddr_in server_addr = {0};
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr); // 服務端 IP
if (connect(sock_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("connect"); close(sock_fd); exit(1);
}
printf("Connected to server\n");
char send_buf[BUF_SIZE], recv_buf[BUF_SIZE];
while (fgets(send_buf, BUF_SIZE, stdin) != NULL) {
send(sock_fd, send_buf, strlen(send_buf), 0);
if (strcmp(send_buf, "exit\n") == 0) break;
ssize_t len = recv(sock_fd, recv_buf, BUF_SIZE, 0);
if (len <= 0) break;
recv_buf[len] = '\0';
printf("Server echo: %s", recv_buf);
}
close(sock_fd);
return 0;
}
# 編譯 gcc server.c -o server gcc client.c -o client # 運行 ./server # 終端 1 ./client # 終端 2 # 測試 # 客戶端輸入: hello # 服務端輸出: Received: hello # 服務端回射: hello # 客戶端顯示: Server echo: hello
Linux Socket 編程核心掌握:**TCP 狀態機 (三次握手/四次揮手)、核心 API 語義與調用順序、阻塞模式完整流程、服務端/客戶端代碼模板**。在此基礎上,理解 `listen` 隊列、TIME_WAIT 影響、Nagle 算法、滑動窗口、零拷貝 (sendfile/splice)、epoll 邊緣/水平觸發,即可構建高性能網絡服務器。