非阻塞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
相关产品推荐
相关产品推荐

