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

Beej's Guide to Network Programming 系列連載 13:accept() — 取出連線的藝術

     編輯:關於C語言

前言:accept() 的奇妙機制

accept() 是伺服器端從「等待佇列」取出一個已完成三向交握的連線,並回傳一個全新的 socket 描述符。原本的 listening socket 繼續監聽,新 socket 專責與該客戶端通訊。這是「一個監聽,多個通訊」並發模型的基石。

函數原型

#include <sys/types.h>
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

參數說明

  • sockfd:listen() 過的 listening socket
  • addr:輸出參數,客戶端位址資訊 (指向 sockaddr_storage 以容納 IPv4/IPv6)
  • addrlen:輸入輸出參數,輸入緩衝區大小,輸出實際寫入大小

完整伺服器範例:從 socket 到 accept

#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define MYPORT "3490"
#define BACKLOG 10

int main(void) {
    struct sockaddr_storage their_addr;  // 足夠大容納 IPv4/IPv6
    socklen_t addr_size;
    struct addrinfo hints, *res;
    int sockfd, new_fd;

    // 1. 解析位址
    memset(&hints, 0, sizeof(hints));
    hints.ai_family   = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags    = AI_PASSIVE;
    getaddrinfo(NULL, MYPORT, &hints, &res);

    // 2. 建立、綁定、監聽
    sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
    bind(sockfd, res->ai_addr, res->ai_addrlen);
    listen(sockfd, BACKLOG);

    // 3. 等待並接受連線
    addr_size = sizeof(their_addr);
    new_fd = accept(sockfd, (struct sockaddr*)&their_addr, &addr_size);
    if (new_fd == -1) {
        perror("accept");
        exit(1);
    }

    // 4. 新 socket 專屬通訊!
    // send(new_fd, ...); recv(new_fd, ...);
    
    // 若只處理單一連線,可關閉 listening socket
    // close(sockfd);  

    freeaddrinfo(res);
    return 0;
}

關鍵觀念:兩個 Socket 的分工

取得客戶端 IP 與埠號

char client_ip[INET6_ADDRSTRLEN];
int client_port;

if (their_addr.ss_family == AF_INET) {
    struct sockaddr_in *s = (struct sockaddr_in*)&their_addr;
    inet_ntop(AF_INET, &s->sin_addr, client_ip, sizeof(client_ip));
    client_port = ntohs(s->sin_port);
} else {
    struct sockaddr_in6 *s = (struct sockaddr_in6*)&their_addr;
    inet_ntop(AF_INET6, &s->sin6_addr, client_ip, sizeof(client_ip));
    client_port = ntohs(s->sin6_port);
}
printf("Accepted connection from %s:%d\n", client_ip, client_port);

回傳值與錯誤處理

  • 成功:回傳 新的 socket 描述符 (non-negative integer)
  • 失敗:回傳 -1,設定 errno
  • 常見錯誤:
    • EWOULDBLOCK / EAGAIN:非阻塞模式下無連線待接受
    • EMFILE / ENFILE:檔案描述符耗盡
    • ENOTSOCK:參數非 socket
    • EINVAL:socket 未 listen

並發處理模式

模式一:單執行緒循序處理 (教學用)

while (1) {
    new_fd = accept(sockfd, ...);
    handle_client(new_fd);  // 阻塞直到客戶端斷線
    close(new_fd);
}

模式二:多進程

while (1) {
    new_fd = accept(sockfd, ...);
    if (fork() == 0) {
        close(sockfd);  // 子進程不需 listening socket
        handle_client(new_fd);
        close(new_fd);
        exit(0);
    }
    close(new_fd);  // 父進程關閉 connected socket
}

模式三:I/O 多路複用 (select/poll/epoll/kqueue) — 生產環境標準

// 將 listening socket 加入監控集合
// 當 listening socket 可讀 → 呼叫 accept()
// 當 connected socket 可讀/可寫 → 處理資料

結語

accept() 是伺服器端「接待客人」的關鍵動作,產出專屬通訊 socket。理解 listening socket 與 connected socket 的分工,掌握 sockaddr_storage 統一處理 IPv4/IPv6 位址,並選擇適合的並發模型 (生產環境首選 I/O 多路複用),即能建構健壯的網路伺服器。下一章將探討 send() 與 recv() — 在 connected socket 上實現資料傳輸。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
Socket用途生命週期
sockfd (listening)監聽埠口,接受新連線伺服器啟動期間持續存在
new_fd (connected)與特定客戶端雙向通訊該連線存續期間