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

非阻塞Socket Receive实现:服务器持续发数据时检测客户端输入

实现非阻塞检测客户端数据+持续发送的服务器方案

当然可以搞定这个需求!核心是要避免让服务器在等待客户端数据时阻塞住发送流程,下面给你两种实用的实现思路,都是生产环境里常用的:

方案一:非阻塞套接字 + I/O多路复用(首推)

这种方法不用额外开线程,单线程就能兼顾发送和检测,资源占用更低,适合大多数场景。

第一步:把客户端套接字改成非阻塞模式

在accept拿到客户端连接的sock之后,用系统调用把它设置为非阻塞:

// Linux平台代码示例
int flags = fcntl(sock, F_GETFL, 0);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);

// Windows平台代码示例
u_long nonblock_mode = 1;
ioctlsocket(sock, FIONBIO, &nonblock_mode);

第二步:用select监听可读事件

在你的while(1)循环里,每次发送数据后,用select做个短超时的检测,看看客户端有没有发数据过来。这样既不会耽误发送,又能及时捕捉到"Abort"指令:

while(1) {
    UpdateData();
    // 先尝试发送数据
    int send_ret = send(sock, data, dataLength, 0);
    // 处理非阻塞发送的特殊情况:如果返回-1且errno是EAGAIN,说明发送缓冲区满,后续再试就行
    if (send_ret == -1 && errno != EAGAIN) {
        perror("send failed");
        break;
    }

    // 准备检测客户端是否有数据到达
    fd_set read_fds;
    FD_ZERO(&read_fds);
    FD_SET(sock, &read_fds);
    struct timeval timeout = {0, 10000}; // 10ms超时,平衡检测灵敏度和CPU占用

    int select_ret = select(sock + 1, &read_fds, NULL, NULL, &timeout);
    if (select_ret > 0) {
        if (FD_ISSET(sock, &read_fds)) {
            char recv_buf[128] = {0};
            int recv_ret = recv(sock, recv_buf, sizeof(recv_buf)-1, 0);
            if (recv_ret > 0) {
                // 检查是不是"Abort"指令
                if (strcmp(recv_buf, "Abort") == 0) {
                    printf("Got Abort command, exiting loop\n");
                    break;
                }
            } else if (recv_ret == 0) {
                // 客户端主动断开连接了
                printf("Client disconnected\n");
                break;
            }
            // 非阻塞下recv返回-1且errno是EAGAIN的话,就是没数据,直接忽略
        }
    } else if (select_ret == -1) {
        perror("select error");
        break;
    }
    // 超时的话,直接进入下一轮循环继续发送数据
}

方案二:单独开线程处理客户端输入

如果你的业务逻辑比较简单,不想搞I/O多路复用的话,开个单独的线程专门监听客户端的"Abort"指令也是个不错的选择,代码逻辑更直观:

#include <pthread.h>
#include <unistd.h>

// 用全局变量或者结构体传递客户端套接字和退出标志(注意线程安全)
int client_sock;
volatile int should_exit = 0; // volatile防止编译器优化,或者用原子变量更稳妥

void* abort_check_thread(void* arg) {
    char recv_buf[128] = {0};
    while (!should_exit) {
        int recv_ret = recv(client_sock, recv_buf, sizeof(recv_buf)-1, 0);
        if (recv_ret > 0) {
            if (strcmp(recv_buf, "Abort") == 0) {
                should_exit = 1;
                printf("Received Abort, telling main thread to exit\n");
                break;
            }
            memset(recv_buf, 0, sizeof(recv_buf));
        } else if (recv_ret == 0) {
            // 客户端断开
            should_exit = 1;
            break;
        }
        // 加个短睡眠,避免线程空转吃CPU
        usleep(10000);
    }
    return NULL;
}

// 主线程代码
int main() {
    // ... 省略create、bind、listen、accept拿到client_sock的代码 ...

    pthread_t check_tid;
    pthread_create(&check_tid, NULL, abort_check_thread, NULL);

    while (!should_exit) {
        UpdateData();
        int send_ret = send(client_sock, data, dataLength, 0);
        if (send_ret == -1 && errno != EAGAIN) {
            should_exit = 1;
            break;
        }
        // 可以根据需求调整发送间隔
        usleep(10000);
    }

    // 等待检测线程结束
    pthread_join(check_tid, NULL);
    close(client_sock);
    return 0;
}

几个关键注意点

  • 非阻塞模式下,send和recv返回EAGAIN(Linux)或WSAEWOULDBLOCK(Windows)是正常现象,不是错误,代表当前操作无法立即完成,后续重试即可。
  • 用select的时候,超时时间别设太长或太短:太长会导致"Abort"指令检测延迟,太短会频繁调用select浪费CPU。
  • 多线程方案里,should_exit最好用原子变量(比如C11的_Atomic int)或者加互斥锁,避免出现竞态条件。

内容的提问来源于stack exchange,提问作者Q-bertsuit

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 06:22:32