使用epoll实现物联网设备通信的IO多路复用
- 实现步骤
- 创建epoll实例:使用
epoll_create
函数创建一个epoll实例,返回一个文件描述符epfd
。例如:int epfd = epoll_create1(0);
,epoll_create1
比epoll_create
更简洁,参数为0时功能和epoll_create
相同。
- 添加设备连接的文件描述符到epoll:对于每个物联网设备连接对应的文件描述符
fd
,使用epoll_ctl
函数将其添加到epoll实例中。例如:
struct epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN | EPOLLET; // 监听读事件,采用边缘触发模式
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
- **等待事件发生**:调用`epoll_wait`函数等待设备有事件发生。`epoll_wait`会阻塞,直到有事件发生或超时。例如:
struct epoll_event events[1024];
int nfds = epoll_wait(epfd, events, 1024, -1); // -1表示永久阻塞
for (int i = 0; i < nfds; ++i) {
int fd = events[i].data.fd;
// 处理该fd对应的设备通信事件,如读取数据
char buffer[1024];
ssize_t read_bytes = read(fd, buffer, sizeof(buffer));
// 处理读取到的数据
}
- 优点
- 支持大量并发连接:epoll基于事件驱动,在处理大量文件描述符时性能远优于
select
和poll
。它采用红黑树管理文件描述符,查找和添加效率高。
- 低开销:
epoll_wait
只返回有事件发生的文件描述符,不像select
需要遍历所有的文件描述符,也不像poll
每次都需要将所有的文件描述符从用户态拷贝到内核态,因此开销较低。
- 支持边缘触发模式:可以更高效地处理事件,减少不必要的系统调用,对于物联网设备这种可能快速产生数据的场景,能及时响应。
- 缺点
- 编程复杂度较高:相比于
select
和poll
,epoll的接口相对复杂,需要开发者对事件驱动编程模型有更深入的理解,特别是边缘触发模式下的编程,需要小心处理以避免丢失事件。
- 平台兼容性:虽然在Linux系统上广泛支持,但在其他操作系统(如Windows)上没有原生支持,移植性相对较差。
不选择select和poll的原因
- select
- 文件描述符数量限制:
select
有文件描述符数量的限制(通常是1024),在物联网大规模设备连接场景下难以满足需求。
- 性能问题:每次调用
select
都需要遍历所有文件描述符,时间复杂度为O(n),随着文件描述符数量增多性能急剧下降。
- 内核态和用户态数据拷贝:每次调用
select
都需要将文件描述符集合从用户态拷贝到内核态,开销较大。
- poll
- 性能问题:
poll
虽然解决了文件描述符数量限制的问题,但仍然需要遍历所有文件描述符来检查事件,时间复杂度同样为O(n),在处理大量文件描述符时性能不佳。
- 数据拷贝问题:每次调用
poll
同样需要将文件描述符集合从用户态拷贝到内核态,开销较大。