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

同端口存在listenfd时socket bind失败原因及端口复用问题

问题现象
  • 运行环境为Ubuntu 20.04,代码中NetFunc.h已引入必要依赖,且做了#define SOCKET int宏定义,配套NetFunc.cpp实现了服务端监听套接字、客户端套接字的创建逻辑,且已为套接字设置SO_REUSEADDR选项。
  • 注释test()函数中19287端口的监听套接字相关代码时,程序可正常运行输出;取消该部分注释后,客户端套接字绑定19287端口会持续失败,无限输出Unable to bind报错。
  • 核心疑问:
    1. 上述绑定失败的根本原因是什么?
    2. 同一组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+端口同时承担客户端、服务端角色的实现方案

  • 该需求完全可以实现,但不能用两个独立套接字分别绑定同一个端口做监听和主动连接,正确做法是复用同一个套接字:
    1. 创建单个套接字,设置SO_REUSEADDR选项后绑定到目标IP+端口。
    2. 调用listen()将套接字转为监听状态,通过accept()接收外部传入的连接,和普通服务端逻辑一致。
    3. 需要主动对外发起连接时,直接使用这个已经完成绑定的监听套接字调用connect()即可,不需要新建套接字重复绑定。内核会自动用绑定好的源IP端口作为连接的源地址发起TCP握手,不会产生端口冲突。
  • 该实现符合TCP协议规范:TCP连接由源IP+源端口+目标IP+目标端口四元组唯一标识,只要主动连接的目标地址不同,四元组就不会和监听套接字、已接收的连接产生冲突。注意不要尝试用和已有连接完全相同的四元组发起连接,否则依然会触发冲突错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 16:06:13