基於 TCP 的 Socket 通信遵循經典的「服務器被動打開,客戶端主動連接」模型。以下是 Linux 環境下的完整實現。
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main() {
int res, listenfd, clientfd;
char buff[BUFSIZ] = "";
int len;
struct sockaddr_in serveraddr;
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET; // IPv4
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); // 綁定所有本地接口
serveraddr.sin_port = htons(50002); // 端口 50002
len = sizeof(serveraddr);
listenfd = socket(AF_INET, SOCK_STREAM, 0); // 創建流式套接字
bind(listenfd, (struct sockaddr*)&serveraddr, len); // 綁定地址
listen(listenfd, 23); // 監聽,最大排隊 23
printf("等待客戶端連接...\n");
clientfd = accept(listenfd, NULL, NULL); // 阻塞等待連接
printf("已接受一個客戶端連接\n");
while (strncmp(buff, "end", 3)) {
res = read(clientfd, buff, BUFSIZ); // 讀取客戶端數據
if (res <= 0) break; // 連接關閉或出錯
write(STDOUT_FILENO, buff, res); // 輸出到屏幕(文件描述符 1)
write(clientfd, "server", 6); // 回應客戶端
}
close(clientfd);
close(listenfd);
return 0;
}
| 函數 | 作用 | 關鍵參數 |
|---|---|---|
socket() | 創建套接字 | AF_INET(IPv4), SOCK_STREAM(TCP) |
bind() | 將套接字綁定到本地 IP/端口 | sockaddr_in 結構體 |
listen() | 轉為被動監聽狀態 | backlog: 內核排隊最大連接數 |
accept() | 從排隊中取出連接,返回新套接字 | 新套接字用於具體通信 |
read()/write() | 數據收發 | 返回實際字節數,0 表示對端關閉 |
htons() / htonl():主機序 → 網絡序(大端)ntohs() / ntohl():網絡序 → 主機序
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[]) {
int res, connfd, len;
struct sockaddr_in connaddr;
char buff[BUFSIZ];
if (argc < 2) {
printf("用法: %s <服務器IP>\n", argv[0]);
exit(1);
}
bzero(&connaddr, sizeof(connaddr));
connaddr.sin_family = AF_INET;
connaddr.sin_addr.s_addr = inet_addr(argv[1]); // 命令行參數指定服務器 IP
connaddr.sin_port = htons(50002); // 必須與服務器一致
connfd = socket(AF_INET, SOCK_STREAM, 0);
len = sizeof(connaddr);
if (connect(connfd, (struct sockaddr*)&connaddr, len) < 0) {
perror("連接失敗");
exit(1);
}
write(connfd, "Hello!", 7); // 發送初始消息
while ((res = read(connfd, buff, BUFSIZ)) > 0) {
printf("收到 %d 字節: ", res);
write(STDOUT_FILENO, buff, res); // 顯示服務器回應
res = read(STDIN_FILENO, buff, BUFSIZ); // 從標準輸入讀取
if (res > 0) write(connfd, buff, res); // 發送給服務器
}
close(connfd);
return 0;
}
inet_addr() 將點分十進制 IP 字符串轉為網絡序整數。connect() 主動發起三次握手。
# 服務器
gcc server.c -o server
./server
# 客戶端 (另一終端,假設服務器 IP 為 127.0.0.1)
gcc client.c -o client
./client 127.0.0.1
文章正確指出:
read() 返回 0(EOF)。read() 返回 -1,errno 置 ECONNRESET。read() 讀到。因此循環條件 while (read(...) > 0) 是正確模式,0 即跳出關閉套接字。
fork()、pthread、select/poll/epoll 處理多客戶端。shutdown(fd, SHUT_WR) 半關閉,配合讀取對端 EOF。EINTR 重試、EAGAIN/EWOULDBLOCK 非阻塞模式。掌握此基礎模型,是深入網絡編程、理解高性能服務器架構的必經之路。