Socket 是網絡通信的基石。本文提供基於 Linux C 的並發 TCP 服務端(fork 多進程)與阻塞客戶端完整代碼,詳細註釋關鍵系統調用、參數含義、錯誤處理、並發模型,並解析 connect/accept 參數易混淆點。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#define PORT 5050
#define BACKLOG 1024
#define BUF_SIZE 1024
void handle_client(int client_fd) {
char buf[BUF_SIZE];
ssize_t n;
while ((n = read(client_fd, buf, BUF_SIZE - 1)) > 0) {
buf[n] = '\0';
printf("Client: %s", buf);
if (strcmp(buf, "exit\n") == 0) break;
write(client_fd, buf, n); // 回射
}
close(client_fd);
printf("Client disconnected\n");
}
int main() {
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t addr_len = sizeof(client_addr);
pid_t pid;
// 1. 創建 Socket
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket");
exit(EXIT_FAILURE);
}
// 2. 設置地址復用(避免 TIME_WAIT 導致重啟失敗)
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// 3. 綁定地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = INADDR_ANY; // 監聽所有網卡
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("bind");
exit(EXIT_FAILURE);
}
// 4. 監聽
if (listen(server_fd, BACKLOG) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
printf("Server listening on port %d...\n", PORT);
// 5. 主循環:accept + fork
while (1) {
client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &addr_len);
if (client_fd < 0) {
perror("accept");
continue;
}
char ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, ip, INET_ADDRSTRLEN);
printf("Accepted: %s:%d\n", ip, ntohs(client_addr.sin_port));
pid = fork();
if (pid < 0) {
perror("fork");
close(client_fd);
} else if (pid == 0) { // 子進程
close(server_fd); // 子進程不需要監聽套接字
handle_client(client_fd);
exit(EXIT_SUCCESS);
} else { // 父進程
close(client_fd); // 父進程不需要連接套接字
// 可選:安裝 SIGCHLD 處理器回收僵屍進程
}
}
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 5050
#define BUF_SIZE 1024
#define SERVER_IP "127.0.0.1"
int main() {
int sockfd;
struct sockaddr_in server_addr;
char send_buf[BUF_SIZE], recv_buf[BUF_SIZE];
// 1. 創建 Socket
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket");
exit(EXIT_FAILURE);
}
// 2. 設置服務端地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {
perror("inet_pton");
exit(EXIT_FAILURE);
}
// 3. 連接
if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("connect");
exit(EXIT_FAILURE);
}
printf("Connected to server. Type 'exit' to quit.\n");
// 4. 通信循環
while (fgets(send_buf, BUF_SIZE, stdin)) {
write(sockfd, send_buf, strlen(send_buf));
ssize_t n = read(sockfd, recv_buf, BUF_SIZE - 1);
if (n <= 0) break;
recv_buf[n] = '\0';
printf("Server echo: %s", recv_buf);
if (strcmp(send_buf, "exit\n") == 0) break;
}
close(sockfd);
return 0;
}
gcc server.c -o server gcc client.c -o client # 終端1 ./server # 終端2 ./client
connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)):第三參數是值 socklen_t(地址結構體大小)。accept(sockfd, (struct sockaddr*)&addr, &addrlen):第三參數是指針 socklen_t*(輸入輸出參數,傳入緩衝區大小,返回實際地址長度)。| 模型 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| fork 多進程 | 隔離性好、簡單穩定 | 進程開銷大、數量受限 | 連接數較少、計算密集型 |
| pthread 多線程 | 輕量、共享內存易 | 同步複雜、一崩全崩 | 高並發、IO 密集型 |
| I/O 多路復用 | 單線程高並發、無上下文切換 | 邏輯複雜、無法利用多核 | 極高並發(C10K/C100K) |
| Reactor/Proactor | 事件驅動、可擴展 | 框架學習成本高 | 生產級高性能服務器 |
父進程需安裝 SIGCHLD 處理器調用 waitpid(-1, NULL, WNOHANG) 回收子進程,或雙重 fork 技巧。
TCP 是流式協議,read/write 不保證消息邊界。實際項目需應用層協議定義消息長度(定長/長度前綴/分隔符)。
<Winsock2.h>,鏈接 ws2_32.libWSAStartup(MAKEWORD(2,2), &wsaData)WSAGetLastError() 而非 errnoclosesocket() 而非 close()SOCKET 而非 int,INVALID_SOCKET 判斷失敗掌握 Socket 基礎 API 與並發模型,是進入網絡編程、分布式系統、高性能服務器開發的敲門磚。