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

C++ TCP端口扫描函数偶发挂起问题排查与修复咨询

TCP端口扫描偶发挂起的问题分析与修复

核心问题根源

1. 非阻塞模式设置失败未处理

set_non_blocking函数在设置非阻塞模式失败时(比如fcntl调用出错)直接返回,未通知调用者,此时socket仍处于阻塞模式。后续connect调用会触发内核级的TCP连接超时(通常长达数分钟),导致函数挂起。

2. Select被信号中断后未重试

当select被系统信号(如SIGPIPE、SIGALRM)中断时,返回-1且errno=EINTR,当前代码直接返回PORT_ERROR,未重新发起select调用,可能导致超时逻辑失效,间接引发挂起。

3. 重复关闭Socket

send_tcp_packet中当connect成功时直接调用close(sock),但tcp_scan_port后续又调用close_socket(sock),触发double close,可能引发未定义行为(包括挂起、崩溃)。

4. Select返回后的状态判断逻辑错误

当前代码仅通过FD_ISSET判断socket是否在可读/可写集合就返回PORT_OPEN,但即使socket被触发,也可能是连接失败(如对方返回RST),需通过getsockopt(SO_ERROR)确认实际连接状态。

修复方案与代码改进

1. 修复非阻塞模式设置函数

让set_non_blocking返回状态,调用者需检查设置是否成功,失败则直接终止扫描流程:

bool set_non_blocking(int sock) {
#ifdef _WIN32
    u_long iMode = 1;
    return ioctlsocket(sock, FIONBIO, &iMode) == 0;
#else
    int flags = fcntl(sock, F_GETFL, 0);
    if (flags == -1) {
        return false;
    }

    return fcntl(sock, F_SETFL, flags | O_NONBLOCK) != -1;
#endif
}

2. 修正扫描入口函数

检查create_socket和set_non_blocking的返回值,避免无效socket进入后续流程:

int tcp_scan_port(const char* host, int port, int timeout_ms){
    int sock = create_socket();
    if (sock == -1) {
        return PORT_ERROR;
    }

    if (!set_non_blocking(sock)) {
        close_socket(sock);
        return PORT_ERROR;
    }

    struct sockaddr_in target_address;
    memset(&target_address, 0, sizeof(target_address));
    target_address.sin_family = AF_INET;
    target_address.sin_port = htons(port);
    if (inet_pton(AF_INET, host, &target_address.sin_addr) != 1) {
        close_socket(sock);
        return PORT_ERROR;
    }

    int result = send_tcp_packet(sock, &target_address, timeout_ms);
    close_socket(sock);
    return result;
}

3. 修复TCP报文发送函数

  • 处理select被信号中断的重试逻辑
  • 移除重复的close(sock)调用
  • 统一通过getsockopt(SO_ERROR)判断连接状态
int send_tcp_packet(int sock, const struct sockaddr_in* target_address, int timeout_ms) {
    int ret = connect(sock, (struct sockaddr*)target_address, sizeof(*target_address));
    if (ret == 0) {
        return PORT_OPEN;
    }

    int error_code = 0;
#ifdef _WIN32
    error_code = WSAGetLastError();
#else
    error_code = errno;
#endif

    if (error_code != EINPROGRESS && error_code != EINTR) {
        return (error_code == ECONNREFUSED) ? PORT_CLOSED : PORT_ERROR;
    }

    fd_set read_fds, write_fds;
    struct timeval tv;
    int result;

    do {
        FD_ZERO(&read_fds);
        FD_ZERO(&write_fds);
        FD_SET(sock, &read_fds);
        FD_SET(sock, &write_fds);

        tv.tv_sec = timeout_ms / 1000;
        tv.tv_usec = (timeout_ms % 1000) * 1000;

        result = select(sock + 1, &read_fds, &write_fds, NULL, &tv);
        if (result == -1 && errno != EINTR) {
            perror("select");
            return PORT_ERROR;
        }
    } while (result == -1 && errno == EINTR);

    if (result == 0) {
        int err;
        socklen_t err_len = sizeof(err);
        if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (void *)&err, &err_len) != 0) {
            perror("getsockopt");
            return PORT_ERROR;
        }
        if (err == ECONNREFUSED || err == ETIMEDOUT) {
            return PORT_CLOSED;
        } else if (err == EHOSTUNREACH || err == EADDRINUSE) {
            return PORT_ERROR;
        } else {
            return PORT_FILTERED;
        }
    } else {
        int err;
        socklen_t err_len = sizeof(err);
        if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (void *)&err, &err_len) != 0) {
            perror("getsockopt");
            return PORT_ERROR;
        }
        if (err == 0) {
            return PORT_OPEN;
        } else if (err == ECONNREFUSED) {
            return PORT_CLOSED;
        } else {
            return PORT_FILTERED;
        }
    }
}

额外建议

  • 确保create_socket函数正确创建TCP socket,并设置SO_REUSEADDR、SO_REUSEPORT选项,避免端口占用问题。
  • 多线程扫描时,控制并发线程数,避免系统资源耗尽导致的异常。
  • 对于域名解析需求,需单独实现异步解析逻辑,避免阻塞扫描流程。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 01:42:25