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

Linux Socket 網絡編程實戰:TCP 服務端/客戶端、fork 多進程、select I/O 多路復用、getaddrinfo 現代寫法

     編輯:關於C語言

Linux Socket 網絡編程實戰:TCP 服務端/客戶端、fork 多進程、select I/O 多路復用、getaddrinfo 現代寫法

本文從零構建三個版本的 TCP 回聲服務器,展示 Socket 編程的演進:

  1. 基礎版:單進程阻塞 accept + fork 多進程並發;
  2. 進階版:select I/O 多路復用單進程並發;
  3. 現代版:getaddrinfo 協議無關地址解析 + IPv4/IPv6 雙棧。

版本一:fork 多進程並發服務器

服務端關鍵流程

int main(int argc, char *argv[]) {
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in srv = { .sin_family=AF_INET, .sin_addr.s_addr=htonl(INADDR_ANY), .sin_port=htons(port) };
    bind(lfd, (struct sockaddr*)&srv, sizeof(srv));
    listen(lfd, 5);

    signal(SIGCHLD, sig_handler); // 回收僵屍進程

    while (1) {
        int cfd = accept(lfd, (struct sockaddr*)&cli, &len);
        if (fork() == 0) {          // 子進程處理客戶端
            close(lfd);
            while (read(cfd, buf, sizeof(buf)) > 0) {
                // 轉大寫回射
                for (i=0; i

關鍵點

  • fork() 複製文件描述符表,**父子各自 close 不需要的 fd**;
  • SIGCHLD + waitpid(-1, &stat, WNOHANG) 非阻塞回收僵屍進程;
  • accept 被信號中斷返回 EINTR 需重試。

版本二:select I/O 多路復用

核心邏輯

fd_set master, read_fds;
FD_ZERO(&master); FD_SET(lfd, &master);
int fdmax = lfd;

while (1) {
    read_fds = master;
    select(fdmax+1, &read_fds, NULL, NULL, NULL);
    for (int i=0; i<=fdmax; i++) if (FD_ISSET(i, &read_fds)) {
        if (i == lfd) { // 新連接
            int cfd = accept(lfd, ...);
            FD_SET(cfd, &master);
            if (cfd > fdmax) fdmax = cfd;
        } else { // 客戶端數據
            int n = read(i, buf, sizeof(buf));
            if (n <= 0) { close(i); FD_CLR(i, &master); }
            else { /* 處理並 write 回射 */ }
        }
    }
}

select 限制

  • 單進程可監控 fd 數受限 FD_SETSIZE (通常 1024);
  • 每次調用需全量拷貝 fd_set 到內核,O(N) 掃描;
  • **水平觸發**:只要緩衝區有數據就持續就緒,需讀空或移出集合。

版本三:getaddrinfo 現代地址解析

為什麼棄用 gethostbyname/inet_addr

  • 不支援 IPv6;
  • 非線程安全;
  • 無法統一處理服務名 (如 "http") 與端口號。

getaddrinfo 統一模型

struct addrinfo hints = {
    .ai_family = AF_UNSPEC,   // IPv4/IPv6 皆可
    .ai_socktype = SOCK_STREAM,
    .ai_flags = AI_PASSIVE    // 服務端綁定通配地址
};
struct addrinfo *res;
getaddrinfo(NULL, "1234", &hints, &res);

for (struct addrinfo *p = res; p; p = p->ai_next) {
    int fd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
    bind(fd, p->ai_addr, p->ai_addrlen);
    listen(fd, 10);
    break; // 取第一個成功的
}
freeaddrinfo(res);

IPv4/IPv6 兼容地址轉字符串

void *get_in_addr(struct sockaddr *sa) {
    return (sa->sa_family == AF_INET) ?
        (void*)&(((struct sockaddr_in*)sa)->sin_addr) :
        (void*)&(((struct sockaddr_in6*)sa)->sin6_addr);
}
inet_ntop(sa->sa_family, get_in_addr(sa), ipstr, sizeof(ipstr));

客戶端通用代碼

int main(int argc, char *argv[]) {
    struct addrinfo hints = { .ai_family=AF_UNSPEC, .ai_socktype=SOCK_STREAM }, *res;
    getaddrinfo(argv[1], argv[2], &hints, &res);
    int fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
    connect(fd, res->ai_addr, res->ai_addrlen);
    freeaddrinfo(res);
    // 循環 read(STDIN) -> write(fd) -> read(fd) -> write(STDOUT)
}

總結對比

版本並發模型優點缺點
fork多進程簡單、隔離性好進程開銷大、難共享數據
select單進程多路復用單線程、無鎖、支援數千連接FD_SETSIZE 限制、O(N) 掃描
poll/epoll/kqueue單進程多路復用無 fd 上限、O(1) 就緒返回API 較複雜、平台差異

下一步學習路徑

  1. 掌握 epoll (Linux) / kqueue (BSD/macOS) / IOCP (Windows) 邊緣觸發模式;
  2. 學習非阻塞 I/O + 事件循環框架:libevent, libuv, asio;
  3. 理解 TLS/SSL 集成 (OpenSSL) 與應用層協議設計 (HTTP, WebSocket, 自定義二進制協議)。

Socket 編程是系統程序員的分水嶺。從阻塞 accept 到事件驅動架構,每一步都在解決「如何高效管理成千上萬個並發連接」這一核心問題。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved