TCP 通訊流程總覽
Server 端流程
socket() 建立通訊端
bind() 綁定本地 IP:Port
listen() 監聽連線請求
accept() 接受連線,回傳新 socket
read()/write() 或 recv()/send() 資料收發
close() 關閉連線
Client 端流程
socket() 建立通訊端
connect() 發起連線
read()/write() 資料收發
close() 關閉連線
完整 Server 範例碼 (Fork 並發 + 訊號處理)
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PORT 11910
#define BACKLOG 5
#define BUFSIZE 1024
int ss, sc; // server socket, client socket
void sig_handler(int signo) {
printf("Catch signal %d\n", signo);
if (signo == SIGTSTP) {
close(ss);
close(sc);
exit(0);
}
}
void process_client(int client_sock) {
char buf[BUFSIZE];
ssize_t n;
while (1) {
// 讀取客戶端資料
n = read(client_sock, buf, BUFSIZE - 1);
if (n <= 0) break; // 連線關閉或錯誤
buf[n] = '\0';
printf("Received: %s", buf);
// 收到 quit 結束
if (strcmp(buf, "quit\n") == 0) {
close(client_sock);
return;
}
// 回傳接收位元組數
char resp[64];
snprintf(resp, sizeof(resp), "%zd bytes received\n", n);
write(client_sock, resp, strlen(resp));
}
close(client_sock);
}
int main() {
struct sockaddr_in server_addr, client_addr;
socklen_t addrlen;
pid_t pid;
// 1. socket
ss = socket(AF_INET, SOCK_STREAM, 0);
if (ss < 0) { perror("socket"); exit(1); }
// 訊號註冊 (優雅關閉)
signal(SIGTSTP, sig_handler);
signal(SIGINT, sig_handler);
// 2. bind
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(PORT);
if (bind(ss, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("bind"); exit(1);
}
// 3. listen
if (listen(ss, BACKLOG) < 0) { perror("listen"); exit(1); }
printf("Server listening on port %d...\n", PORT);
// 4. accept loop + fork
while (1) {
addrlen = sizeof(client_addr);
sc = accept(ss, (struct sockaddr*)&client_addr, &addrlen);
if (sc < 0) {
perror("accept");
continue;
}
printf("Client connected: %s:%d\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
pid = fork();
if (pid < 0) {
perror("fork");
close(sc);
} else if (pid == 0) {
// 子進程:處理客戶端
close(ss); // 關閉監聽 socket
process_client(sc);
exit(0);
} else {
// 父進程:繼續監聽
close(sc); // 關閉客戶端 socket
}
}
return 0;
}
Client 範例碼
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PORT 11910
#define BUFSIZE 1024
int sockfd;
void sig_handler(int signo) {
if (signo == SIGPIPE) {
printf("Server disconnected (SIGPIPE)\n");
close(sockfd);
exit(0);
}
}
int main(int argc, char *argv[]) {
struct sockaddr_in server_addr;
char buf[BUFSIZE];
if (argc != 2) {
fprintf(stderr, "Usage: %s \n", argv[0]);
exit(1);
}
signal(SIGPIPE, sig_handler);
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) { perror("socket"); exit(1); }
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
if (inet_pton(AF_INET, argv[1], &server_addr.sin_addr) <= 0) {
perror("inet_pton"); exit(1);
}
if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("connect"); exit(1);
}
printf("Connected to server.\n");
while (1) {
printf("> ");
if (fgets(buf, BUFSIZE, stdin) == NULL) break;
write(sockfd, buf, strlen(buf));
if (strcmp(buf, "quit\n") == 0) break;
ssize_t n = read(sockfd, buf, BUFSIZE - 1);
if (n <= 0) break;
buf[n] = '\0';
printf("Server: %s", buf);
}
close(sockfd);
return 0;
}
編譯與測試
# 編譯
gcc -o server server.c
gcc -o client client.c
# 執行
# 終端機 1
./server
# 終端機 2
./client 127.0.0.1
# 或跨機器
./client 192.168.1.100
關鍵技術點解析
- Fork 並發:每連線一進程,簡單穩定,但進程開銷大,適合中低並發
- 雙重 close:父關閉
sc、子關閉 ss,避免檔案描述符洩漏導致連線無法正常關閉
- 訊號處理:
SIGPIPE 防止寫入已斷開連線導致進程崩潰;SIGINT/SIGTSTP 優雅關閉監聽 socket
- 網路位元組序:
htons/htonl (主機→網路)、ntohs/ntohl (網路→主機)
- 錯誤處理:所有系統呼叫必須檢查回傳值,
perror 輸出錯誤描述
進階延伸
- 使用
select/poll/epoll 實現 I/O 多路複用 (高並發)
- Thread Pool 替代 Fork (較輕量)
- SSL/TLS 加密通訊 (OpenSSL)
- 協議設計:長度前綴、分隔符、序列化 (Protobuf/JSON)