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

客户端getaddrinfo()异常及connect()连接超时问题排查

问题分析与解决方案:Linux(Fedora)下TCP客户端连接失败及地址不一致问题

问题描述

在Linux(Fedora)平台编写TCP服务端与客户端代码时,遇到两个核心问题:

  1. 客户端connect()调用超时失败;
  2. getaddrinfo()返回的用于connect()的地址与传入hints结构体的预期地址不一致。

问题原因分析

1. 客户端connect()超时失败的原因

服务端代码缺陷

  • 野指针导致绑定无效地址:服务端通过memcpy(&sa, p, sizeof(sa))拷贝struct addrinfo结构体,但addrinfo中的ai_addr是指向动态分配内存的指针,拷贝后仅复制了指针而非实际地址数据。后续freeaddrinfo(result)会释放该内存,导致sa.ai_addr变为野指针,最终bind和listen操作的是无效地址,服务端未真正监听端口。
  • 监听地址范围受限:服务端仅绑定并监听第一个IPv6地址,若系统默认开启IPV6_V6ONLY选项,IPv6套接字无法接收IPv4连接;若客户端尝试连接的地址与服务端监听地址不匹配,就会触发超时。
  • 子进程处理逻辑错误:服务端fork()后,子进程直接关闭客户端套接字reqSocket,虽不影响连接建立,但会导致连接建立后立即断开(不过用户遇到的是超时,此为次要问题)。

客户端代码缺陷

  • 错误设置AI_PASSIVE标志:该标志用于服务端绑定通配符地址,客户端设置后会导致getaddrinfo返回不符合预期的地址,干扰连接逻辑。
  • 强制限定地址族:客户端强制设置hints.ai_family = AF_INET6,若服务端实际监听IPv4地址,或客户端传入IPv4地址,会导致getaddrinfo返回无效地址,引发连接失败。

2. getaddrinfo()返回地址与预期不一致的原因

  • AI_PASSIVE标志误用:客户端设置该标志后,getaddrinfo会返回通配符地址(如::或0.0.0.0),而非传入的具体主机地址,导致返回地址与用户传入的argv[1]不符。
  • 地址解析逻辑问题:若传入的主机名解析出多个地址,getaddrinfo会返回所有符合条件的结果,但客户端仅取第一个IPv6地址,可能与预期地址不匹配。

解决方案

服务端代码修正要点

  1. 移除错误的结构体拷贝逻辑:直接使用getaddrinfo返回的p指针进行socket、bind、listen操作,避免野指针问题。
  2. 绑定通配符地址:将getaddrinfo的主机参数设为NULL,配合AI_PASSIVE标志,让服务端监听所有可用的IPv4和IPv6地址。
  3. 开启IPv6/IPv4双栈支持:对IPv6套接字设置IPV6_V6ONLY=0,允许其接收IPv4连接。
  4. 修正子进程处理逻辑:子进程应保留客户端套接字并处理连接,而非直接关闭。

客户端代码修正要点

  1. 移除AI_PASSIVE标志:客户端无需该标志,避免干扰地址解析结果。
  2. 取消地址族强制限定:将hints.ai_family设为AF_UNSPEC,让getaddrinfo返回所有符合条件的地址,客户端可逐个尝试连接直到成功。
  3. 增强错误排查:使用perror打印connect失败的具体错误信息,便于定位问题。

修正后的代码

Server.cpp

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <iostream>
#include <string.h>
using namespace std;

int main(int argc, char *argv[]) {
    if (argc != 2) {
        cout << "Usage: " << argv[0] << " port-number" << endl;
        exit(1);
    }
    cout << "Server(), incoming port: " << argv[1] << endl;

    int sockDescriptor = -1;
    struct addrinfo hints, *result, *p;
    memset(&hints, 0, sizeof(hints));

    hints.ai_flags = AI_PASSIVE;
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

    if (getaddrinfo(NULL, argv[1], &hints, &result) != 0) {
        perror("Server(), getaddrinfo() failed");
        exit(1);
    }

    bool bindSuccess = false;
    for (p = result; p != NULL; p = p->ai_next) {
        sockDescriptor = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
        if (sockDescriptor == -1) {
            perror("Server(), socket() failed");
            continue;
        }

        if (p->ai_family == AF_INET6) {
            int opt = 0;
            if (setsockopt(sockDescriptor, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)) == -1) {
                perror("Server(), setsockopt(IPV6_V6ONLY) failed");
                close(sockDescriptor);
                continue;
            }
        }

        if (bind(sockDescriptor, p->ai_addr, p->ai_addrlen) == -1) {
            perror("Server(), bind() failed");
            close(sockDescriptor);
            continue;
        }

        char buf[126];
        if (p->ai_family == AF_INET) {
            inet_ntop(AF_INET, &((struct sockaddr_in*)p->ai_addr)->sin_addr, buf, sizeof(buf)-1);
        } else {
            inet_ntop(AF_INET6, &((struct sockaddr_in6*)p->ai_addr)->sin6_addr, buf, sizeof(buf)-1);
        }
        cout << "Server(), bound to address: " << buf << endl;
        bindSuccess = true;
        break;
    }

    freeaddrinfo(result);

    if (!bindSuccess) {
        cout << "Server(), failed to bind any address" << endl;
        exit(1);
    }
    cout << "Server(), getaddrinfo() succeeded..." << endl;

    if (listen(sockDescriptor, 5) == -1) {
        perror("Server(), listen() failed");
        close(sockDescriptor);
        exit(1);
    }

    cout << "Server(), listening for connections..." << endl;

    for(;;) {
        struct sockaddr_storage clientAddr;
        socklen_t addrLen = sizeof(clientAddr);
        int reqSocket = accept(sockDescriptor, (struct sockaddr*)&clientAddr, &addrLen);
        if (reqSocket == -1) {
            perror("Server(), accept() failed");
            continue;
        }

        char clientBuf[126];
        getnameinfo((struct sockaddr*)&clientAddr, addrLen, clientBuf, sizeof(clientBuf), NULL, 0, NI_NUMERICHOST);
        cout << "Server(), accepted connection from: " << clientBuf << endl;

        pid_t childpid = fork();
        if (childpid < 0) {
            perror("Server(), fork() failed");
            close(reqSocket);
            continue;
        } else if (childpid == 0) {
            close(sockDescriptor);
            char msg[] = "Hello from server!";
            send(reqSocket, msg, strlen(msg), 0);
            close(reqSocket);
            exit(0);
        } else {
            close(reqSocket);
        }
    }

    return 0;
}

Client.cpp

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <iostream>
#include <string.h>
using namespace std;

int main(int argc, char *argv[]) {
    if (argc != 3) {
        cout << "Usage: " << argv[0] << " host-address port-number" << endl;
        exit(1);
    }
    cout << "Client(), incoming address: " << argv[1] << endl;
    cout << "Client(), incoming port: " << argv[2] << endl;

    int sockDescriptor = -1;
    struct addrinfo hints, *result, *p;
    memset(&hints, 0, sizeof(hints));

    hints.ai_flags = AI_NUMERICHOST;
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

    if (getaddrinfo(argv[1], argv[2], &hints, &result) != 0) {
        perror("Client(), getaddrinfo() failed");
        exit(1);
    }

    bool connectSuccess = false;
    for (p = result; p != NULL; p = p->ai_next) {
        sockDescriptor = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
        if (sockDescriptor == -1) {
            perror("Client(), socket() failed");
            continue;
        }

        if (connect(sockDescriptor, p->ai_addr, p->ai_addrlen) == -1) {
            char buf[126];
            if (p->ai_family == AF_INET) {
                inet_ntop(AF_INET, &((struct sockaddr_in*)p->ai_addr)->sin_addr, buf, sizeof(buf)-1);
            } else {
                inet_ntop(AF_INET6, &((struct sockaddr_in6*)p->ai_addr)->sin6_addr, buf, sizeof(buf)-1);
            }
            cout << "Client(), connect() failed for address: " << buf << endl;
            perror("Connect error");
            close(sockDescriptor);
            continue;
        }

        cout << "Client(), connection to server succeeded." << endl;
        char buf[128];
        ssize_t recvLen = recv(sockDescriptor, buf, sizeof(buf)-1, 0);
        if (recvLen > 0) {
            buf[recvLen] = '\0';
            cout << "Client(), received: " << buf << endl;
        }
        connectSuccess = true;
        break;
    }

    freeaddrinfo(result);

    if (!connectSuccess) {
        cerr << "Client(), failed to connect to server" << endl;
        exit(1);
    }

    close(sockDescriptor);
    return 0;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 22:07:11