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

Socket accept 函式陷阱:addrlen 參數必須初始化,否則首次連線取得錯誤客戶端地址

     編輯:關於C

問題現象

簡單 TCP 伺服器,客戶端連線後印出 IP:Port。首次連線顯示:

  • IP 非 127.0.0.1(如亂碼 IP)
  • Port 為 1023(非預期)

加上 bzero(&cin, sizeof(cin)) 後,首次變為 0.0.0.0:0,仍錯誤。

根本原因

accept 最後一個參數 socklen_t *addrlen 是值-結果參數:

  • 輸入:呼叫者必須設定為緩衝區大小(sizeof(struct sockaddr_in)),告知核心可寫入多少位元組。
  • 輸出:核心寫入實際地址結構大小。

未初始化 導致核心讀取到隨機/零值,認為緩衝區太小,不寫入地址,殘留堆疊垃圾資料。

正確寫法

struct sockaddr_in cin;
socklen_t len;

while (1) {
    len = sizeof(cin);                              // 每次 accept 前必須重新賦值
    int fc = accept(fs, (struct sockaddr *)&cin, &len);
    if (fc < 0) { perror("accept"); continue; }

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

關鍵點總結

  1. 每次呼叫 accept 前必須重設 len = sizeof(addr),因核心可能修改其值。
  2. 不可傳 NULL:雖然部分文件說可傳 NULL(不需地址),但某些核心版本會回傳錯誤(如本案 fc == -1)。
  3. bzero 不能替代 len 初始化:bzero 清空地址結構體,但 len 仍為未定義值。
  4. 使用 socklen_t 而非 int,確保型別正確(POSIX 標準)。

完整伺服器範例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>

#define PORT 8000
#define MAX_LINE 100

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

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

    bzero(&sin, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = INADDR_ANY;
    sin.sin_port = htons(PORT);

    fs = socket(AF_INET, SOCK_STREAM, 0);
    bind(fs, (struct sockaddr *)&sin, sizeof(sin));
    listen(fs, 10);

    int reuse = 1;
    setsockopt(fs, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));

    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; }

        inet_ntop(AF_INET, &cin.sin_addr, ip, sizeof(ip));
        printf("Client connected: %s:%d\n", ip, ntohs(cin.sin_port));

        int n = read(fc, buf, MAX_LINE);
        if (n > 0) {
            buf[n] = '\0';
            printf("Received: %s\n", buf);
            to_lower(buf);
            write(fc, buf, n);
        }
        close(fc);
    }
    close(fs);
    return 0;
}

延伸:為何有的教材傳 NULL?

  • 舊版 BSD/部分 Unix 允許 accept(fd, NULL, NULL) 僅取得連線 fd。
  • Linux man 2 accept 註明:addrlen 不可為 NULL 若 addr 非 NULL;若兩者皆 NULL 則不回傳地址。
  • 最佳實踐:永遠提供有效緩衝區與初始化的 len,兼容性最好。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved