首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

Linux TCP 服務實戰:帶超時處理的並發伺服器完整實作

     編輯:關於C語言

概覽

本文完整呈現一個基於 select() 多路復用、fork() 多進程並發、支援連線超時的 TCP 伺服器與對應客戶端。涵蓋 Socket 建立、綁定、監聽、超時檢測、資料收發封裝、進程管理等核心環節,程式碼可直接編譯執行。

核心知識點速覽

項目說明
埠號範圍0-1023 保留(需 root);1024-49151 登記;49152-65535 動態/私有
Socket 類型SOCK_STREAM (TCP) / SOCK_DGRAM (UDP) / SOCK_RAW
建立 Socketsocket(AF_INET, SOCK_STREAM, 0) 最後參數通常為 0

關鍵封裝函式:健壯的讀寫迴圈

// 確保讀滿 n 位元組或遇到 EOF/錯誤
ssize_t readn(int fd, void *ptr, size_t n) {
    size_t nleft = n;
    ssize_t nread;
    char *buf = ptr;

    while (nleft > 0) {
        if ((nread = read(fd, buf, nleft)) < 0) {
            if (errno == EINTR) { nread = 0; } // 被訊號中斷,重試
            else return -1; // 硬錯誤
        } else if (nread == 0) break; // EOF

        nleft -= nread;
        buf += nread;
        if (*buf == '\0') break; // 遇字串結束符提前退出(視協定而定)
    }
    return n - nleft;
}

// 確保寫滿 n 位元組
ssize_t writen(int fd, const void *ptr, size_t n) {
    size_t nleft = n;
    ssize_t nwritten;
    const char *buf = ptr;

    while (nleft > 0) {
        if ((nwritten = write(fd, buf, nleft)) <= 0) {
            if (errno == EINTR) { nwritten = 0; }
            else return -1;
        }
        nleft -= nwritten;
        buf += nwritten;
    }
    return n - nleft;
}

伺服器主流程

int main() {
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in servaddr = {0};
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(1253);

    bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
    listen(listenfd, 32); // 佇列長度 32

    printf("server wait....\n");

    fd_set readset;
    struct timeval timeout = {1, 0}; // 1 秒超時

    while (1) {
        FD_ZERO(&readset);
        FD_SET(listenfd, &readset);

        // select 監聽新連線,帶超時
        if (select(listenfd + 1, &readset, NULL, NULL, &timeout) < 0) {
            perror("select error");
            continue;
        }

        if (FD_ISSET(listenfd, &readset)) {
            struct sockaddr_in cliaddr;
            socklen_t clilen = sizeof(cliaddr);
            int connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &clilen);

            pid_t pid = fork();
            if (pid == 0) { // 子進程處理業務
                close(listenfd); // 關閉監聽描述符

                char buf[100];
                bzero(buf, sizeof(buf));
                readn(connfd, buf, sizeof(buf));
                printf("server read: %s", buf);

                const char *resp = "hello\n";
                writen(connfd, resp, strlen(resp) + 1);
                printf("server send: %s", resp);

                close(connfd);
                exit(0);
            } else if (pid > 0) { // 父進程
                close(connfd); // 父進程不處理連線,關閉連線描述符
            } else {
                perror("fork error");
                close(connfd);
            }
        } else {
            // 超時:印一個點示意存活
            putchar('.');
            fflush(stdout);
        }
    }
}

客戶端程式碼

int main() {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in servaddr = {0};
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    servaddr.sin_port = htons(1253);

    connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));

    const char *msg = "myhaspl\n";
    writen(sockfd, msg, strlen(msg) + 1);
    printf("client send: %s", msg);

    char buf[100];
    bzero(buf, sizeof(buf));
    readn(sockfd, buf, sizeof(buf));
    printf("client read: %s", buf);

    close(sockfd);
    return 0;
}

執行示例

$ ./server
server wait....
.........
server read: myhaspl
server send: hello

$ ./client
client send....
client send: myhaspl
client read: hello

關鍵設計解析

  1. select + timeout:避免 accept() 永久阻塞,定期輪詢實現心跳、優雅關閉等邏輯。
  2. fork 並發:每連線一進程,簡單可靠;高併發場景建議改用 epoll + 線程池。
  3. readn/writen:處理「短讀/短寫」與 EINTR,保證應用層協定完整性。
  4. 描述符管理:父關 connfd、子關 listenfd,避免洩漏與干擾。

編譯與執行

gcc -o server server.c
gcc -o client client.c
./server &
./client

延伸閱讀

  • man 2 select、man 2 poll、man 2 epoll
  • UNP 卷一 第 6 章 I/O 多路復用
  • Linux 高效能伺服器架構:Reactor/Proactor 模式
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved