You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C语言Socket编程:首次accept()返回客户端地址全为0的问题

问题:多进程Socket服务器首次accept()无法获取正确客户端地址

给出的多进程Socket服务器代码如下:

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

int main(){
    struct sockaddr_in svraddr;
    int clisock;
    pid_t pid;
    ssize_t nlen;
    struct sockaddr_in cliaddr;
    socklen_t cliaddrlen = sizeof(sockaddr_in);
    char buf[BUFSIZ];
    svraddr.sin_family=AF_INET;
    svraddr.sin_port=htons(49510);
    svraddr.sin_addr.s_addr=htonl(INADDR_ANY);
    int serversock = socket(PF_INET,SOCK_STREAM,0);
    if(serversock==-1){
        perror("socket()");
        exit(1);
    }
    if(bind(serversock,(struct sockaddr*)&svraddr,sizeof(svraddr))==-1){
        perror("bind()");exit(1);
    }
    if(listen(serversock,10)==-1){
        perror("listen()");exit(1);
    }
    while(1){
        clisock = accept(serversock,(struct sockaddr*)&cliaddr,&cliaddrlen);
        switch(pid=fork()){
            case -1:
            perror("fork()");exit(1);
            case 0:
            close(serversock);
            printf("CONN: client %s:%d\n",inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));
            while((nlen=read(clisock,buf,BUFSIZ))!=0){
                perror("read");
                printf("RECV: srcport %d, %s\n",ntohs(cliaddr.sin_port),buf);
                write(clisock,"received successfully",22);
            }
            close(clisock);
            printf("DISCONN: client %d disconnected\n",ntohs(cliaddr.sin_port));
            exit(0);
            break;
            default:
            close(clisock);
        }
    }
}

服务器运行后输出如下:

CONN: client 0.0.0.0:0
DISCONN: client 0 disconnected
CONN: client 127.0.0.1:57078
DISCONN: client 57078 disconnected

可以看到首次调用accept()时,客户端地址显示为0.0.0.0:0,后续调用正常,且Socket I/O功能不受影响,即使memset初始化cliaddr也无法解决问题。


问题原因与解决方案

核心问题

问题出在**cliaddrlen变量的维护上**。POSIX标准规定,accept()的第三个参数是传入传出参数:

  • 调用accept()前,必须将该参数设置为客户端地址缓冲区(即cliaddr)的大小,告诉内核缓冲区的容量;
  • 调用完成后,内核会将该参数修改为实际写入的地址结构的长度。

你的代码中cliaddrlen仅在main函数开头初始化一次,首次调用accept()后,内核会修改它的值。后续循环调用accept()时,使用的是被修改后的cliaddrlen,这会导致内核无法正确识别缓冲区大小,进而首次调用时无法正确填充客户端地址信息(部分系统内核的行为差异会导致此问题显现)。

修复方案

将cliaddrlen的初始化放到while循环内部,确保每次调用accept()前都重置为缓冲区的正确大小:

while(1){
    socklen_t cliaddrlen = sizeof(struct sockaddr_in); // 每次循环重置缓冲区大小
    clisock = accept(serversock,(struct sockaddr*)&cliaddr,&cliaddrlen);
    switch(pid=fork()){
        // 其余代码保持不变
    }
}

额外优化点

代码中read()的循环逻辑存在小问题:无论read()是否成功,都会调用perror("read"),这会在read()成功时打印冗余的read: Success信息。建议修改为仅在read()返回-1(出错)时调用perror:

while((nlen=read(clisock,buf,BUFSIZ))>0){
    printf("RECV: srcport %d, %s\n",ntohs(cliaddr.sin_port),buf);
    write(clisock,"received successfully",22);
}
if(nlen == -1){
    perror("read");
}

内容的提问来源于stack exchange,提问作者Thxx

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 04:02:00