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

Socket accept() 參數陷阱:socklen_t 必須初始化,否則首次連接獲取錯誤客戶端地址

     編輯:關於C語言

現象重現

簡單 TCP 服務器,客戶端連接後即退出。首次 accept() 返回的客戶端 IP 顯示非 127.0.0.1 (如亂碼 IP),端口顯示 1023 或 0。加上 bzero(&cin, sizeof(cin)) 後變為 0.0.0.0:0。

根本原因

accept() 最後一個參數 socklen_t *addrlen 是 **值-結果參數** (Value-Result Argument):

  1. 輸入:調用者必須設定為 sizeof(struct sockaddr_in),告知內核緩衝區大小。
  2. 輸出:內核寫入實際填寫的地址結構大小。

未初始化 時,棧上隨機值被當作緩衝區長度傳給內核,導致內核寫入越界或截斷,返回垃圾數據。

正確代碼模式

[c]
struct sockaddr_in cin;
socklen_t len = sizeof(cin); // 必須在 accept 前賦值!
int fc = accept(fs, (struct sockaddr*)&cin, &len);
if (fc < 0) { perror("accept"); return -1; }

char ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &cin.sin_addr, ip, sizeof(ip));
printf("client ip:%s, port:%d\n", ip, ntohs(cin.sin_port));

關於 NULL 傳遞的測試

[c]
fc = accept(fs, (struct sockaddr*)&cin, NULL); // 部分舊系統允許,現代 Linux 返回 -1 並置 errno=EINVAL

測試發現 fc == -1,**強烈建議永不傳 NULL**,始終提供合法 socklen_t*。

完整服務器參考實現

[c]
#include 
#include 
#include 
#include 
#include 
#include 

#define MAX_LINE 100
#define PORT 8000

void to_lower(char *p) {
    for (; *p; p++) if (*p >= 'A' && *p <= 'Z') *p = *p - 'A' + 'a';
}

int main() {
    struct sockaddr_in sin, cin;
    int fs, fc;
    socklen_t len;
    char buf[MAX_LINE];
    char ip[INET_ADDRSTRLEN];
    int n;

    fs = socket(AF_INET, SOCK_STREAM, 0);
    memset(&sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = INADDR_ANY;
    sin.sin_port = htons(PORT);

    bind(fs, (struct sockaddr*)&sin, sizeof(sin));
    listen(fs, 10);
    printf("Server listening on port %d\n", PORT);

    while (1) {
        len = sizeof(cin); // 關鍵:每次循環重新賦值
        fc = accept(fs, (struct sockaddr*)&cin, &len);
        if (fc < 0) { perror("accept"); continue; }

        n = read(fc, buf, MAX_LINE);
        inet_ntop(AF_INET, &cin.sin_addr, ip, sizeof(ip));
        printf("Client %s:%d -> %.*s\n", ip, ntohs(cin.sin_port), n, buf);

        to_lower(buf);
        write(fc, buf, n);
        close(fc);
    }
    close(fs);
    return 0;
}

避坑清單

  1. socklen_t len = sizeof(struct sockaddr_in); 放在 while 循環內,每次 accept 前重置。
  2. 使用 inet_ntop 替代過時 inet_ntoa (執行緒不安全、靜態緩衝區)。
  3. 檢查 accept 返回值,EAGAIN/EWOULDBLOCK (非阻塞)、ECONNABORTED (連接中斷) 需區分處理。
  4. 生產環境需設定 SO_REUSEADDR、非阻塞、信號處理、優雅關閉。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved