ev_io

ev_ioI/O观察者, 通过注册组合事件来监视状态变更.

文件描述符的”读”与”写”

I/O观察者会在每次事件循环迭代中检查注册的文件描述符是否可读、可写. 准确来说:

  • 每次报告读事件时, 要么内核缓冲区有可被读取的数据, 要么文件描述符状态变更(关闭、出错)等.

  • 每次报告写事件时, 要么内核缓冲区空闲可被写入数据, 要么文件描述符状态变更(连接建立、失败)等.

如果期间没有对报告的事件做任何处理(关闭、停止), 那么观察者每次都会重复的报告事件. 这种行为我们称之为”level-triggering(水平触发)”.

通常我们打开/创建的文件描述符的默认是阻塞(block), 这意味着我们每次读取数据时都会将进程陷入到内核态. 所以将所有文件描述符都设置为非阻塞模式一般来说是一个好主意.

但, 当您的文件描述符无法在非阻塞模式下运行, 那么您必须自己解决如下一些问题:

  • 用额外的事件驱动接口保证事件的准确性.

  • 增加定时器、SIGALRM信号等来确保进程不会永久阻塞.

这通常会需要使用者做更多复杂的操作, 所以说如非必要请置为非阻塞(non-blocking).

最后, 当您不想再处理一个文件描述符的I/O的事件时请主动停止观察者.

通常我们在使用一些”后端“(epoll、kqueue、linux aio)的时候, 在注册感兴趣的文件描述符后会主动调用例如dup2、close等函数, 这些函数会直接影响文件描述符在这些事件接口里的状态.
导致它们可能会有一些出乎意料的行为, 如: 默默丢弃已注册的事件, 让内部发生异常状态. 这时候如果Libev也无法有效分辨这个文件描述符的真实性与有效性.
注意: 为了避免这种类似的情况出现, 每次调用这些方法之前最好先停止事件观察者.

SIGPIPE问题

当写入已关闭的管道后您的程序会收到一个SIGPIPE, 默认情况下它会中止您的程序. 这在编码、调试期间是非常明智的行为, 但是对于后端守护进程来说这是灾难性行为.

所以. 当您无法解释程序为什么会悄无声息退出时, 请注意注册信号并忽略SIGPIPE(或记录进程退出状态, 这会在您事后排查得到很大线索).

accept失败的问题

许多POSIX实现的accept函数都不会从队列中删除异常的连接. 例如: 大型服务器经常因为文件描述符用完而导致接受失败(ENFILE), 但是, Libev还是会在下次事件迭代的时候发出事件. 如果您因此没有做好处理, 则可能在无法排查到问题的情况下发现CPU飙升到100%.

在每次启动服务之前最好将open files调整到一个合理的值, 可以有效的避免此类事情发生.

相关函数

ev_io_init (ev_io *, callback, int fd, int events);

// 配置一个ev_io观察者, 参数fd则是文件描述符. events则是EV_READ、EV_WRITE、EV_READ | EV_WRITE.
ev_io_set (ev_io *, int fd, int events); 

// 类似于ev_io_set, 但是这函数仅更改events事件. (在某些支持的后端下这个操作可能会更快)
ev_io_modify (ev_io *, int events);

// 在前面初始化完毕后, 调用这个方法会将ev_io注册到内部并启动.
ev_io_start (struct ev_loop , ev_io )

例子

#include <ev.h>
#include <stdio.h>
#include <unistd.h>

void stdin_cb(struct ev_loop *loop, ev_io *w, int revents) {

  char buffer[4096];
  memset(buffer, 4096, 4096);
  read(w->fd, buffer, 4096);
  puts(buffer);
  printf("等待输入: \n");
}

int main (void)
{
  // 可以使用已定义的宏来获取默认的事件循环, 当然你也可以根据自己的需求创建指定的.
  struct ev_loop *loop = EV_DEFAULT;

  ev_io stdin_watcher;

  // 在启动一个I/O观察者之前, 我们需要先初始化它.
  ev_io_init (&stdin_watcher, stdin_cb, /*STDIN_FILENO*/ 0, EV_READ);
  // 启动后意味着观察者将在`stdin`变为可读后触发.
  ev_io_start (loop, &stdin_watcher);
  printf("等待输入: \n");

  // 开始运行事件循环
  ev_run (loop, 0);

  // 如果事件循环退出, 那将会执行到这里.
  return 0;
}