實驗環境與架構
- 協議:TCP(面向連接、可靠傳輸)
- 模型:單進程阻塞 I/O,服務端循環
accept,客戶端單次連接收發後退出
- 固定參數:端口
0x8888 (34952),服務端綁定 INADDR_ANY,客戶端連接 192.168.1.104(實際部署請修改為真實服務器 IP)
服務端核心流程(server.c)
socket(AF_INET, SOCK_STREAM, 0) 創建監聽套接字
bind() 綁定 IP:PORT,listen(sfp, 5) 啟動監聽,backlog=5
- 進入
while(1) 循環 accept() 阻塞等待客戶端連接
- 連接建立後
write() 發送歡迎消息,close() 關閉該連接套接字,繼續循環等待下一個
關鍵代碼片段
sfp = socket(AF_INET, SOCK_STREAM, 0);
bzero(&s_add, sizeof(struct sockaddr_in));
s_add.sin_family = AF_INET;
s_add.sin_addr.s_addr = htonl(INADDR_ANY); // 綁定所有本地網卡
s_add.sin_port = htons(portnum);
bind(sfp, (struct sockaddr*)&s_add, sizeof(struct sockaddr));
listen(sfp, 5);
while (1) {
sin_size = sizeof(struct sockaddr_in);
nfp = accept(sfp, (struct sockaddr*)&c_add, &sin_size); // 阻塞點
// 獲取客戶端 IP:PORT
printf("Server get connect from %#x : %#x\n", ntohl(c_add.sin_addr.s_addr), ntohs(c_add.sin_port));
write(nfp, "hello,welcome to my server \r\n", 32);
close(nfp);
}
客戶端核心流程(client.c)
socket() 創建套接字
- 構造服務端地址結構體:
inet_addr("192.168.1.104") 將點分十進制 IP 轉網絡序整數
connect() 主動發起連接
read() 接收服務端數據並打印,getchar() 暫停以便 netstat 觀察連接狀態,最後 close()
關鍵代碼片段
cfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&s_add, sizeof(struct sockaddr_in));
s_add.sin_family = AF_INET;
s_add.sin_addr.s_addr = inet_addr("192.168.1.104"); // ★修改為實際服務端IP
s_add.sin_port = htons(portnum);
connect(cfd, (struct sockaddr*)&s_add, sizeof(struct sockaddr));
recbytes = read(cfd, buffer, 1024);
buffer[recbytes] = '\0';
printf("%s\n", buffer);
getchar(); // 方便 netstat -an | grep 34952 觀察 ESTABLISHED 狀態
close(cfd);
編譯與運行驗證
# 服務端
gcc server.c -o server
./server
# 輸出: Hello,welcome to my server! ... listen ok
# 客戶端(另一終端/機器)
gcc client.c -o client
./client
# 輸出: connect ok! read ok REC: hello,welcome to my server
字節序深度解析:為什麼要 htons/htonl?
- 網絡傳輸統一使用大端序(高位字節在前)
- x86 CPU 為小端序(低位字節在前)
htons (Host TO Network Short) 將 2 字節端口號轉為網絡序;htonl 處理 4 字節 IP
- 接收端對應使用
ntohs/ntohl 轉回主機序
- 口訣:「發送前 hton,接收後 ntoh,跨平台不崩潰」
常見坑點與調試技巧
| 現象 | 原因 | 解決 |
| bind 失敗 "Address already in use" | 端口被佔用或 TIME_WAIT 狀態 | 修改端口、設 SO_REUSEADDR、等待 1-2 分鐘 |
| connect 超時 | IP 錯誤、防火牆攔截、服務端未啟動 | ping 測通、關閉防火牆/開放端口、確認 server 進程 |
| read 返回 0 | 對端正常關閉連接 (FIN) | 屬正常行為,處理 EOF 邏輯 |
| write 返回 -1, errno=EPIPE | 對端已關閉,本端繼續寫入 | 捕獲 SIGPIPE 信號或檢查連接狀態 |
進階擴展方向
- 多進程/多線程併發服務端(
fork() / pthread_create())
- I/O 多路復用(
select/poll/epoll)支撐高併發
- 應用層協議設計:定長包頭+消息體、心跳保活、優雅關閉