客户端getaddrinfo()异常及connect()连接超时问题排查
问题分析与解决方案:Linux(Fedora)下TCP客户端连接失败及地址不一致问题
问题描述
在Linux(Fedora)平台编写TCP服务端与客户端代码时,遇到两个核心问题:
- 客户端
connect()调用超时失败; 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地址,可能与预期地址不匹配。
解决方案
服务端代码修正要点
- 移除错误的结构体拷贝逻辑:直接使用
getaddrinfo返回的p指针进行socket、bind、listen操作,避免野指针问题。 - 绑定通配符地址:将
getaddrinfo的主机参数设为NULL,配合AI_PASSIVE标志,让服务端监听所有可用的IPv4和IPv6地址。 - 开启IPv6/IPv4双栈支持:对IPv6套接字设置
IPV6_V6ONLY=0,允许其接收IPv4连接。 - 修正子进程处理逻辑:子进程应保留客户端套接字并处理连接,而非直接关闭。
客户端代码修正要点
- 移除
AI_PASSIVE标志:客户端无需该标志,避免干扰地址解析结果。 - 取消地址族强制限定:将
hints.ai_family设为AF_UNSPEC,让getaddrinfo返回所有符合条件的地址,客户端可逐个尝试连接直到成功。 - 增强错误排查:使用
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
相关产品推荐
相关产品推荐

