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

LAN接口变更时TCP服务端程序卡在accept()函数的问题排查与解决方案咨询

问题分析与解决方案

首先纠正你的推测:插拔LAN网线并不会导致系统重置监听端口的路由——因为你的监听socket绑定的是0.0.0.0(所有网络接口),即使某个LAN口链路断开,监听socket依然会正常监听其他可用接口;当该LAN口恢复后,新的连接请求依然能正常送达。你遇到的accept()卡住,本质是阻塞式accept()会一直等待新连接,除非socket出现错误或被外部信号中断;而插拔网线的动作本身不会主动中断阻塞的accept()调用。

另外你的代码有个冗余问题:把bind()和listen()放在外层while(true)循环里,每次内层循环退出后都重复执行绑定和监听。这不仅没必要,还可能引入潜在的资源冲突(即使你用了SO_REUSEADDR)——监听socket只需要创建、绑定、监听一次,之后持续调用accept()处理新连接即可。

保持TCP监听状态的简单方案

你不需要直接切换到完全异步模式,以下几种轻量方案就能解决accept()卡住的问题:

1. 给监听socket设置超时(最简单的阻塞模式改造)

通过SO_RCVTIMEO选项给监听socket设置超时,这样accept()会在指定时间内无新连接时返回EAGAIN/EWOULDBLOCK,你可以借此机会检查状态或重新尝试accept():

#include <sys/time.h>
#include <errno.h>

int main() {
    // 创建监听socket
    int server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (server_socket == -1) {
        cerr << "Can't create a socket! Quitting" << endl;
        return 1;
    }

    int enable = 1;
    if (setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) {
        cerr << GetTime() << "Can't use SO_REUSEADDR" << endl;
    }

    // 设置accept超时(5秒)
    struct timeval timeout;
    timeout.tv_sec = 5;
    timeout.tv_usec = 0;
    if (setsockopt(server_socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) {
        cerr << GetTime() << "Can't set socket timeout" << endl;
    }

    // 绑定、监听只执行一次
    int listenningport = 55555;
    sockaddr_in hint;
    hint.sin_family = AF_INET;
    hint.sin_port = htons(listenningport);
    inet_pton(AF_INET, "0.0.0.0", &hint.sin_addr);

    if (bind(server_socket, (sockaddr*) &hint, sizeof(hint)) == -1) {
        cerr << "Can't bind to IP/port" << endl;
        close(server_socket);
        return 1;
    }

    if (listen(server_socket, SOMAXCONN) == -1) {
        cerr << "Can't listen!" << endl;
        close(server_socket);
        return 1;
    }

    sockaddr_in client;
    socklen_t clientSize = sizeof(client);
    int msgSize = 0; // 初始化消息长度
    char out_data[1024]; // 示例缓冲区,根据你的需求调整

    while (true) {
        int clientSocket = accept(server_socket, (sockaddr*) &client, &clientSize);
        if (clientSocket == -1) {
            // 判断是否是超时错误
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                // 超时,可以在这里检查LAN口状态(比如读取/sys/class/net/ethX/operstate)
                continue;
            } else {
                cerr << "Problem with client connecting!" << endl;
                // 真的出错,关闭socket后重建
                close(server_socket);
                break;
            }
        }

        // 处理客户端数据
        msgSize = 0;
        int bytesReceived = 0;
        while ((bytesReceived = recv(clientSocket, out_data + msgSize, sizeof(out_data) - msgSize - 1, 0)) > 0) {
            msgSize += bytesReceived;
            if (msgSize >= sizeof(out_data) - 1) break; // 防止缓冲区溢出
        }

        if (bytesReceived == -1) {
            cerr << "Error in recv(). BytesReceived = -1" << endl;
        }

        close(clientSocket);
    }

    return 0;
}

2. 用select()实现多路复用(推荐的同步非阻塞方案)

select()可以同时监听监听socket的可读事件(有新连接)和超时事件,不需要完全异步,代码复杂度低且稳定性高:

#include <sys/select.h>
#include <errno.h>

int main() {
    int server_socket = socket(AF_INET, SOCK_STREAM, 0);
    // ... 初始化socket、设置SO_REUSEADDR、bind、listen ...

    fd_set read_fds;
    struct timeval timeout;
    int msgSize = 0;
    char out_data[1024];

    while (true) {
        FD_ZERO(&read_fds);
        FD_SET(server_socket, &read_fds);
        timeout.tv_sec = 5;
        timeout.tv_usec = 0;

        // 等待监听socket可读或超时
        int ret = select(server_socket + 1, &read_fds, NULL, NULL, &timeout);
        if (ret == -1) {
            cerr << "select error!" << endl;
            break;
        } else if (ret == 0) {
            // 超时,可在这里检查网卡链路状态
            continue;
        }

        // 有新连接请求
        if (FD_ISSET(server_socket, &read_fds)) {
            sockaddr_in client;
            socklen_t clientSize = sizeof(client);
            int clientSocket = accept(server_socket, (sockaddr*) &client, &clientSize);
            if (clientSocket != -1) {
                // 处理客户端数据...
                msgSize = 0;
                int bytesReceived = 0;
                while ((bytesReceived = recv(clientSocket, out_data + msgSize, sizeof(out_data) - msgSize - 1, 0)) > 0) {
                    msgSize += bytesReceived;
                    if (msgSize >= sizeof(out_data) - 1) break;
                }
                close(clientSocket);
            }
        }
    }

    close(server_socket);
    return 0;
}

3. 可选增强:监听网卡链路状态

如果你想在LAN口插拔时主动触发处理,可以读取/sys/class/net/ethX/operstate文件(ethX是你的LAN口名称,比如eth0),文件内容为up或down,可在超时检查时读取该文件,判断链路状态变化(一般情况下不需要,因为监听socket本身不受单网卡状态影响)。

异步模式是不是唯一方案?

绝对不是。完全异步模式(比如aio_*函数或libevent/libuv框架)是针对高并发场景的复杂方案,你的轻量应用用上述超时或多路复用方案完全足够,代码改动小,也能稳定解决accept()卡住的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 22:02:47