同端口存在listenfd时socket bind失败原因及端口复用问题
问题现象
- 运行环境为Ubuntu 20.04,代码中
NetFunc.h已引入必要依赖,且做了#define SOCKET int宏定义,配套NetFunc.cpp实现了服务端监听套接字、客户端套接字的创建逻辑,且已为套接字设置SO_REUSEADDR选项。 - 注释
test()函数中19287端口的监听套接字相关代码时,程序可正常运行输出;取消该部分注释后,客户端套接字绑定19287端口会持续失败,无限输出Unable to bind报错。 - 核心疑问:
- 上述绑定失败的根本原因是什么?
- 同一组IP+端口能否同时承担服务端和客户端角色,即既可以主动对外发起连接,又可以被动接收其他端点的连接请求?
示例代码
主程序代码
#include "NetFunc.h" #include <iostream> #include <string> #include <thread> #include <chrono> using namespace std; void test() { // SOCKET listenfd = OpenListenFD("127.0.0.1", "19287"); SOCKET connfd = OpenClientFD("127.0.0.1", "12389", "127.0.0.1", "19287"); send(connfd, "test", 5, 0); close(connfd); using namespace std::chrono_literals; this_thread::sleep_for(100ms); connfd = OpenClientFD("127.0.0.1", "12389", "127.0.0.1", "19287"); send(connfd, "test2", 6, 0); close(connfd); // close(listenfd); cout << "over" << endl; return; } int main() { SOCKET listenfd = OpenListenFD("127.0.0.1", "12389"); sockaddr_storage clientaddr; socklen_t clientlen = sizeof clientaddr; const int maxNameLen = 0x800; char* buffer = new char[0x800 + 1]; char* hostname = new char[maxNameLen + 1], * port = new char[maxNameLen + 1]; std::thread newThread{&test}; newThread.detach(); SOCKET connfd; while (true) { if ((connfd = accept(listenfd, (sockaddr*)&clientaddr, &clientlen)) < 0) { std::cerr << "Not accept correctly at InteractWithClients" << std::endl; } else{ getnameinfo((sockaddr*)&clientaddr, clientlen, hostname, maxNameLen, port, maxNameLen, NI_NUMERICHOST); std::string completeAddr = string(hostname) + ":" + string(port); std::cout << "Addr : " << completeAddr << std::endl; recv(connfd, buffer, 0x800, 0); std::cout << buffer << std::endl; } close(connfd); } delete[] buffer; delete[] hostname; delete[]port; return 0; }
NetFunc.cpp实现代码
#include "NetFunc.h" #include <iostream> #include <string.h> SOCKET OpenListenFD(const char* addr, const char* port) { struct addrinfo hints, * listp, * p; SOCKET listenfd; int optval = 1; /* Get a list of potential server addresses */ memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_socktype = SOCK_STREAM; /* Accept connections */ hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; /* ... on any IP address */ hints.ai_flags |= AI_NUMERICSERV; /* ... using port number */ hints.ai_family = AF_INET; getaddrinfo(addr, port, &hints, &listp); /* Walk the list for one that we can bind to */ for (p = listp; p; p = p->ai_next) { /* Create a socket descriptor */ if ((listenfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) continue; /* Socket failed, try the next */ /* Eliminates "Address already in use" error from bind */ setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (setsockopt_ptr)&optval, sizeof(int)); /* Bind the descriptor to the address */ if (bind(listenfd, p->ai_addr, p->ai_addrlen) == 0) { break; /* Success */ } closesocket(listenfd); /* Bind failed, try the next */ } /* Clean up */ freeaddrinfo(listp); if (!p) /* No address worked */ return -1; /* Make it a listening socket ready to accept connection requests */ if (listen(listenfd, LISTENQ) < 0) { closesocket(listenfd); return -1; } return listenfd; } SOCKET OpenClientFD(const char* serverAddr, const char* port, const char* localAddr, const char* localPort) { struct sockaddr_in my_addr, my_addr1; SOCKET client = socket(AF_INET, SOCK_STREAM, 0); if (client < 0) std::cerr<<"Error in client creating\n"; else std::cout << "Client Created\n"; int optval = 1; setsockopt(client, SOL_SOCKET, SO_REUSEADDR, (setsockopt_ptr)&optval, sizeof(int)); my_addr.sin_family = AF_INET; my_addr.sin_addr.s_addr = INADDR_ANY; my_addr.sin_port = htons(atoi(port)); inet_pton(AF_INET, serverAddr, &my_addr.sin_addr.s_addr); my_addr1.sin_family = AF_INET; my_addr1.sin_addr.s_addr = INADDR_ANY; my_addr1.sin_port = htons(atoi(localPort)); inet_pton(AF_INET, localAddr, &my_addr1.sin_addr.s_addr); if (bind(client, (struct sockaddr*)&my_addr1, sizeof(struct sockaddr_in)) == 0) std::cout << "Binded Correctly\n"; else { std::cerr << "Unable to bind\n"; return -1; } socklen_t addr_size = sizeof my_addr; int con = 0; do { con = connect(client, (struct sockaddr*)&my_addr, sizeof my_addr); } while (con != 0); if (con == 0) std::cout << "Client Connected\n"; else std::cerr << "Error in Connection\n"; return client; }
问题原因与解答
绑定失败的根本原因
- 报错核心是对
SO_REUSEADDR的作用存在误解:这个选项仅允许绑定到处于TIME_WAIT状态的端口,不允许两个独立套接字同时绑定完全相同的源IP+源端口组合。 - 取消
test()中19287端口监听套接字的注释后,该监听套接字会先绑定127.0.0.1:19287并进入监听状态,后续客户端套接字再尝试绑定同一个IP端口时,内核会直接判定端口冲突返回错误,这就是绑定持续失败的原因。 - 代码中客户端连接逻辑写了无间隔无限重试的do-while循环,绑定失败返回-1的无效套接字会被反复用来调用
connect,进一步放大了报错现象。
同一IP+端口同时承担客户端、服务端角色的实现方案
- 该需求完全可以实现,但不能用两个独立套接字分别绑定同一个端口做监听和主动连接,正确做法是复用同一个套接字:
- 创建单个套接字,设置
SO_REUSEADDR选项后绑定到目标IP+端口。 - 调用
listen()将套接字转为监听状态,通过accept()接收外部传入的连接,和普通服务端逻辑一致。 - 需要主动对外发起连接时,直接使用这个已经完成绑定的监听套接字调用
connect()即可,不需要新建套接字重复绑定。内核会自动用绑定好的源IP端口作为连接的源地址发起TCP握手,不会产生端口冲突。
- 创建单个套接字,设置
- 该实现符合TCP协议规范:TCP连接由
源IP+源端口+目标IP+目标端口四元组唯一标识,只要主动连接的目标地址不同,四元组就不会和监听套接字、已接收的连接产生冲突。注意不要尝试用和已有连接完全相同的四元组发起连接,否则依然会触发冲突错误。
内容的提问来源于stack exchange,提问作者o_oTurtle
相关产品推荐
相关产品推荐

