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

SO_REUSEADDR选项失效:重启监听端口时绑定失败问题排查

问题排查与解决

核心原因分析

  1. SO_REUSEADDR选项未正确启用
    代码中int optval = 0;是关闭SO_REUSEADDR选项,而非开启——该选项需要设置为1才能允许进程复用处于TIME_WAIT状态的端口。

  2. Socket资源未彻底释放
    cleanup函数仅调用shutdown关闭读写通道,但未调用close释放Socket文件描述符。内核只有在Socket被close后,才会彻底回收端口绑定资源,单纯shutdown不会释放端口。

  3. Accept调用参数错误
    acceptThread中accept的第三个参数传递的是sockaddrLength的值,而非指针,这属于未定义行为,会导致accept无法正确返回,线程可能持续阻塞,间接导致端口无法释放。

解决步骤

1. 正确开启SO_REUSEADDR

修改listen函数中的选项设置:

void Server::listen() {
    std::cout<<"Server::listen\n";
    m_listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (m_listenSocket < 0) {
        std::cout<<"Failed to open listen socket.\n";
        return;
    }
        
    int optval = 1; // 改为1,启用端口复用
    if (setsockopt(m_listenSocket, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0) {
        std::cout<<"Failed to set SO_REUSEADDR option.\n";
        close(m_listenSocket);
        return;
    }
        
    // 其余代码保持不变
}

2. 彻底释放Socket资源

更新cleanup函数,在shutdown后调用close并重置Socket变量:

void Server::cleanup(){
    std::cout<<"Server::cleanup.\n";
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        m_messageQueue.clear();
        m_sendThreadWait.notify_one();
    }
    // 关闭读写通道并释放Socket资源
    if (m_listenSocket != -1) {
        ::shutdown(m_listenSocket, SHUT_RDWR);
        ::close(m_listenSocket);
        m_listenSocket = -1; // 标记为无效,避免重复操作
    }
    if (m_socketId != -1) {
        ::shutdown(m_socketId, SHUT_RDWR);
        ::close(m_socketId);
        m_socketId = -1;
    }
    joinThreads();
}

3. 修复Accept调用参数错误

修正acceptThread中的accept调用,传递正确的指针参数:

void Server::acceptThread() {
    try {
        std::cout<<"Server::acceptThread::started.\n";
        socklen_t sockaddrLength = sizeof(struct sockaddr_in);
        // 第三个参数改为指针
        int incomingSocketId = accept(m_listenSocket, NULL, &sockaddrLength);
        if (incomingSocketId < 0) {
            std::cout<<"Failed to accept the client connection: "<<strerror(errno)<<"\n";
            return;
        }
    
        // 其余代码保持不变
    } catch (const std::exception& e) {
        std::cout<<"socket closed by remote. "<<e.what()<<"\n";
        return;
    }
    std::cout<<"Server::acceptThread completed.\n";
}

4. 初始化Socket变量

在类构造函数中初始化Socket描述符为无效值(-1),避免对无效描述符操作:

Server::Server(int port) : m_port(port), m_listenSocket(-1), m_socketId(-1) {
    // 其他初始化逻辑
}

验证效果

修改后,执行重置操作时:

  • SO_REUSEADDR正确启用,允许端口复用
  • Socket资源被彻底释放,内核不会保留端口绑定
  • Accept线程能正确响应Socket关闭并退出
    此时重新绑定端口不会再出现"Address already in use"错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 20:54:55