Windows XP Windows 7 Windows 2003 Windows Vista Windows 綜合教學 Linux 系統教學
Windows 10 Windows 8 Windows 2008 Windows NT Windows Server 電腦軟體教學

Linux TCP Socket 程式設計完整範例:Server/Client 架構與 Fork 並發模型

編輯:Linux 系統常見問題解答

TCP 通訊流程總覽

Server 端流程

  1. socket() 建立通訊端
  2. bind() 綁定本地 IP:Port
  3. listen() 監聽連線請求
  4. accept() 接受連線,回傳新 socket
  5. read()/write() 或 recv()/send() 資料收發
  6. close() 關閉連線

Client 端流程

  1. socket() 建立通訊端
  2. connect() 發起連線
  3. read()/write() 資料收發
  4. 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)
Copyright © Windows教程網 All Rights Reserved