事件驱动模型是一种常见的
计算机编程模型,用于处理用户输入或系统事件。当用户执行操作如鼠标点击、移动或键盘按键等,操作系统将产生相应事件,随后应用程序接收并处理这些事件。
基本模型
Introduction
在编写服务器处理程序时,有多种模型可供选择:
- 创建新进程以处理每个请求,这种方法虽然简单,但由于创建新进程的开销较大,可能导致服务器性能较差。
- 创建新线程以处理每个请求,这种方法可能会涉及线程同步,从而导致潜在的死锁问题。
- 将每个请求放入事件列表中,由主进程通过非阻塞 I/O 方式处理请求。尽管这种方法在编写应用程序代码时逻辑较为复杂,但在大多数网络服务器中广泛采用。
Select
Select 是一种跨平台的事件驱动库,适用于
Linux 和 Windows 平台。使用 select 库的典型步骤包括:
1. 创建关注事件的描述符集 (fd_set),通常包括读、写和异常事件的描述符集。
2. 调用 `select()` 函数,等待事件发生。`select()` 的阻塞行为与其是否设置非阻塞 I/O 无关。函数原型如下:
int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, const struct timeval* timeout);
```
其中,`timeout` 参数可用于设置 `select()` 等待的时间。若设置为 0,则 `select()` 在有事件发生时立即返回。否则,`select()` 将等待指定时间后再返回。函数返回值指示发生的 fd 数量。
3. 遍历所有的 fd_set 中的每个 fd,检查是否有相关事件发生,并进行处理。
Poll
Poll 库自
Linux 2.1.23 版本起可用,Windows 平台不支持。Poll 与 Select 的基本原理相同,均包含创建描述符集、等待事件发生以及遍历描述符集检查事件的过程。Poll 的使用步骤大致如下:
1. 创建描述符集并设置关注的事件。
2. 调用 `poll()` 函数,等待事件发生。函数原型如下:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
```
`poll()` 同样允许设置等待时间,其效果与 `select()` 相同。
3. 遍历描述符集,检查事件并进行处理。
Poll 与 Select 的主要区别在于,Select 需要分别为读、写、异常事件创建单独的描述符集,而在遍历时需要分别遍历这三组集合。相比之下,Poll 只需一个集合,并在每个描述符对应的结构上分别设置读、写、异常事件,最终遍历时可以同时检查三种事件。
Epoll
Epoll 是 Linux 2.5.44 版本引入的一种高效事件驱动库,它是 Poll 的变种。Epoll 的特点是能够提高描述符较多情况下的处理效率,因为它将描述符列表交由内核管理,并在事件发生时直接通知进程。Epoll 的使用步骤如下:
1. 创建一个 Epoll 描述符,通过调用 `epoll_create()` 实现。该函数带有一个整型参数 `size`,用于指定要创建的描述符列表大小。
int epoll_create(int size);
```
2. 设置描述符的关注事件,并将其添加到内核的事件列表中,此操作可通过 `epoll_ctl()` 完成。
```C++
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
```
`op` 参数有三种取值,分别表示添加、删除或修改描述符及其关注的事件。
3. 等待内核通知事件发生,获取已发生事件的描述符结构列表,这一过程由 `epoll_wait()` 完成。
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
```
在使用 Epoll 时,需要注意以下两点:
- Edge Triggered (ET) 触发模式下,事件由数据达到边界触发。因此,在处理读、写时应不断调用 `read()/write()`,直至返回 EAGAIN 错误码,之后再调用 `epoll_wait()` 等待下一个事件。ET 模式的使用原则包括:
- 使用非阻塞 I/O;
- 直至 `read()/write()` 返回 EAGAIN 时,才等待下一次事件的发生。
- Level Triggered (LT) 触发模式下,只要仍有未读/未写的剩余数据,调用 `epoll_wait()` 时就会触发事件。相比 Poll,Epoll 在 LT 模式下的处理速度可能更快。