当前位置: 首页 > news >正文

setnonblocking函数用途和使用案例

setnonblocking 函数详解:非阻塞 I/O 的核心工具

setnonblocking 是网络编程中用于将文件描述符(包括套接字)设置为非阻塞模式的关键函数。它通过修改文件描述符的标志位,改变 I/O 操作的行为方式,是实现高性能网络服务器的核心技术之一。

核心概念

阻塞 vs 非阻塞 I/O

特性阻塞模式非阻塞模式
read()行为无数据时线程休眠立即返回 EAGAIN/EWOULDBLOCK
write()行为缓冲区满时线程休眠立即返回部分写入字节数
accept()行为无连接时线程休眠立即返回错误
线程状态可能阻塞永不阻塞
适用场景简单同步程序高性能异步/事件驱动程序

函数实现原理

POSIX 标准实现

#include <fcntl.h>
#include <unistd.h>int setnonblocking(int fd) {int flags = fcntl(fd, F_GETFL, 0);if (flags == -1) return -1;  // 获取标志失败// 添加 O_NONBLOCK 标志位flags |= O_NONBLOCK;if (fcntl(fd, F_SETFL, flags) == -1) return -1;  // 设置标志失败return 0;  // 成功
}

Windows 平台实现

#ifdef _WIN32
#include <winsock2.h>int setnonblocking(SOCKET fd) {unsigned long mode = 1;return (ioctlsocket(fd, FIONBIO, &mode) == 0) ? 0 : -1;
}
#endif

典型使用场景

1. 事件驱动服务器 (epoll/kqueue)

// 创建监听套接字
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);// 设置为非阻塞模式
setnonblocking(listen_fd);// 绑定并监听
bind(listen_fd, ...);
listen(listen_fd, SOMAXCONN);// 添加到epoll
struct epoll_event ev;
ev.events = EPOLLIN | EPOLLET;  // 边缘触发模式
ev.data.fd = listen_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &ev);

2. 高性能连接接受

while (1) {struct sockaddr_in client_addr;socklen_t addrlen = sizeof(client_addr);// 非阻塞acceptint client_fd = accept4(listen_fd, (struct sockaddr*)&client_addr,&addrlen, SOCK_NONBLOCK);  // Linux特有非阻塞acceptif (client_fd == -1) {if (errno == EAGAIN || errno == EWOULDBLOCK) {// 无新连接,返回事件循环break;} else {perror("accept");continue;}}// 添加到epoll监控setnonblocking(client_fd);  // 如果accept4未设置add_to_epoll(client_fd);
}

3. 非阻塞读写处理

// 边缘触发模式下的读处理
void handle_read(int fd) {char buffer[4096];ssize_t total = 0;while (1) {ssize_t n = read(fd, buffer + total, sizeof(buffer) - total);if (n > 0) {total += n;} else if (n == 0) {// 连接关闭close_connection(fd);return;} else if (n == -1) {if (errno == EAGAIN || errno == EWOULDBLOCK) {// 数据已读完if (total > 0) process_data(buffer, total);return;} else {// 真实错误perror("read");close_connection(fd);return;}}}
}

关键注意事项

1. 必须与非阻塞 I/O 配合使用

// 错误:非阻塞套接字使用阻塞操作
int fd = ...;
setnonblocking(fd);
read(fd, buf, size);  // 可能返回-1 (EAGAIN)// 正确:循环处理直到EAGAIN
while (1) {ssize_t n = read(fd, buf, size);if (n >= 0) break;if (errno != EAGAIN) handle_error();
}

2. 与多路复用结合的性能优势

读事件
写事件
主线程
epoll_wait
事件就绪
非阻塞read
非阻塞write
处理数据
发送数据

3. 常见错误处理

// 处理非阻塞连接
int connect_nonblock(int sockfd, const struct sockaddr *addr, socklen_t addrlen) {int rc = connect(sockfd, addr, addrlen);if (rc == 0) return 0;  // 立即连接成功if (errno != EINPROGRESS) return -1;  // 真实错误// 等待连接完成fd_set wset;FD_ZERO(&wset);FD_SET(sockfd, &wset);struct timeval tv = {.tv_sec = 5, .tv_usec = 0};  // 5秒超时rc = select(sockfd+1, NULL, &wset, NULL, &tv);if (rc <= 0) return -1;  // 超时或错误// 检查套接字错误状态int error = 0;socklen_t len = sizeof(error);if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)return -1;return (error == 0) ? 0 : -1;
}

性能优化技巧

1. 批量处理连接

// 一次性接受多个连接
#define ACCEPT_BATCH 32for (int i = 0; i < ACCEPT_BATCH; i++) {int client_fd = accept(listen_fd, NULL, NULL);if (client_fd == -1) {if (errno == EAGAIN) break;  // 无更多连接perror("accept");continue;}setnonblocking(client_fd);add_to_epoll(client_fd);
}

2. 零拷贝技术结合

// 使用sendfile减少数据拷贝
off_t offset = 0;
ssize_t sent = sendfile(client_fd, file_fd, &offset, file_size);if (sent == -1) {if (errno == EAGAIN) {// 注册EPOLLOUT事件,稍后继续发送modify_epoll_events(client_fd, EPOLLOUT);} else {handle_error();}
}

3. 缓冲区管理

// 为每个连接维护写缓冲区
struct connection {int fd;char *write_buf;size_t write_len;size_t write_sent;
};// 非阻塞写处理
void handle_write(struct connection *conn) {while (conn->write_sent < conn->write_len) {ssize_t n = send(conn->fd, conn->write_buf + conn->write_sent,conn->write_len - conn->write_sent,MSG_DONTWAIT);  // 非阻塞发送if (n > 0) {conn->write_sent += n;} else if (n == -1) {if (errno == EAGAIN) {// 注册EPOLLOUT等待下次可写return;}close_connection(conn);return;}}// 全部发送完成free(conn->write_buf);conn->write_buf = NULL;
}

平台差异处理

功能LinuxWindows跨平台方案
设置非阻塞fcntl(fd, F_SETFL, O_NONBLOCK)ioctlsocket(fd, FIONBIO, &1)封装抽象层
非阻塞acceptaccept4(fd, ..., SOCK_NONBLOCK)需手动设置先accept再设置非阻塞
非阻塞connect返回EINPROGRESS返回WSAEWOULDBLOCK统一检查EINPROGRESS
错误码EAGAIN/EWOULDBLOCKWSAEWOULDBLOCK定义宏转换
// 跨平台错误处理
#ifdef _WIN32
#define IS_EAGAIN(err) (err == WSAEWOULDBLOCK)
#else
#define IS_EAGAIN(err) (err == EAGAIN || err == EWOULDBLOCK)
#endifif (send(fd, buf, len, 0) == -1) {int err = sock_errno();if (IS_EAGAIN(err)) {// 正常非阻塞返回} else {// 真实错误}
}

最佳实践总结

  1. 统一设置原则
    所有网络套接字在创建后立即设为非阻塞模式:

    int sock = socket(AF_INET, SOCK_STREAM, 0);
    setnonblocking(sock);  // 立即设置
    
  2. 边缘触发必须使用
    当使用 EPOLLET 时,必须配合非阻塞 I/O:

    event.events = EPOLLIN | EPOLLET;  // 边缘触发
    epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
    setnonblocking(fd);  // 必须设置!
    
  3. 资源释放保障
    非阻塞操作需确保资源正确释放:

    while (bytes_to_send > 0) {ssize_t sent = send(fd, data, bytes_to_send, MSG_DONTWAIT);if (sent > 0) {data += sent;bytes_to_send -= sent;} else if (IS_EAGAIN(sock_errno())) {// 保存状态,稍后继续save_send_state(fd, data, bytes_to_send);return;} else {// 错误处理free_resources();return;}
    }
    free_resources();  // 完成时释放
    
  4. 超时机制结合
    非阻塞操作应设置超时保护:

    struct timeval start, current;
    gettimeofday(&start, NULL);while (1) {gettimeofday(&current, NULL);if (timediff(current, start) > 5000) { // 5秒超时handle_timeout();break;}// 尝试非阻塞操作if (do_nonblock_op() == SUCCESS) break;
    }
    

💡 性能提示:在 Linux 4.18+ 内核中,使用 io_uring 异步 I/O 接口可以获得比 epoll + 非阻塞模式更高的性能,但非阻塞模式仍是大多数高性能网络应用的基础。

http://www.dtcms.com/a/301401.html

相关文章:

  • 在本地环境中运行 ‘dom-distiller‘ GitHub 库的完整指南
  • OSPF路由协议 多区域
  • 【ESP32】无法找到: “${env:IDF_PATH}/components/“的路径报错问题以及CMAKE构建不成功问题
  • Cursor报错解决【持续更新中】
  • 金融科技中的远程开户、海外个人客户在线开户、企业客户远程开户
  • 深入解析Java运行机制与JVM内存模型
  • 【Web APIs】JavaScript 节点操作 ⑩ ( 节点操作综合案例 - 动态生成表格案例 )
  • windows 11 JDK11安装
  • LeetCode 239:滑动窗口最大值
  • 五自由度磁悬浮轴承转子不平衡振动抑制破局:不平衡前馈补偿+自抗扰控制实战解析
  • MySQL 全详解:从入门到精通的实战指南
  • 第二阶段-第二章—8天Python从入门到精通【itheima】-138节(MySQL的综合案例)
  • 设备分配与回收
  • 数据处理实战(含代码)
  • OpenFeign-远程调用((Feign的使用方法))
  • Spring Boot 配置文件常用配置属性详解(application.properties / application.yml)
  • 【PCIe 总线及设备入门学习专栏 5.3.4 -- PCIe PHY Firmware 固件加载流程】
  • 如何思考一个动态规划问题需要几个状态?
  • [每周一更]-(第150期):AI Agents:从概念到实践的智能体时代
  • net8.0一键创建支持(Elastic)
  • 2025C卷 - 华为OD机试七日集训第1期 - 按算法分类,由易到难,循序渐进,玩转OD
  • Spring 容器注入时查找 Bean 的完整规则
  • Flutter中 Provider 的基础用法超详细讲解(二)之ChangeNotifierProvider
  • 力扣热题100----------53最大子数组和
  • 咨询进阶——解读40页公司战略解码方法【附全文阅读】
  • sed命令
  • 通信名词解释:I2C、USART、SPI、RS232、RS485、CAN、TCP/IP、SOCKET、modbus
  • 【通识】设计模式
  • catkin_make生成的编译文件夹目录结构说明
  • uart通信