ev_io
是这对I/O
观察者, 通过注册组合事件来监视状态变更.
I/O
观察者会在每次事件循环迭代中检查注册的文件描述符是否可读、可写. 准确来说:
每次报告读事件时, 要么内核缓冲区有可被读取的数据, 要么文件描述符状态变更(关闭、出错)等.
每次报告写事件时, 要么内核缓冲区空闲可被写入数据, 要么文件描述符状态变更(连接建立、失败)等.
如果期间对事件后不做任何处理(关闭、停止), 那么观察者每次都会重复的报告事件. 这种行为我们称之为”level-triggering
(水平触发
)”.
通常我们打开/创建的文件描述符的默认是阻塞(block
), 这意味着我们每次读取数据时都会将进程陷入到内核态. 所以将所有文件描述符都设置为非阻塞模式一般来说是一个好主意.
但, 当您的文件描述符无法在非阻塞模式下运行, 那么您必须自己解决如下一些问题:
用额外的事件驱动接口保证事件的准确性.
增加定时器、SIGALRM
信号等来确保进程不会永久阻塞.
这通常会需要使用者做更多复杂的操作, 所以说如非必要请置为非阻塞(non-blocking)
.
最后, 当您不想再处理一个文件描述符的I/O
的事件时请主动停止观察者.
通常我们在使用一些”后端“(epoll
、kqueue
、linux aio
)的时候, 在注册感兴趣的文件描述符后会主动调用例如dup2
、close
等函数, 这些函数会直接影响文件描述符在这些事件接口里的状态.
导致它们可能会有一些出乎意料的行为, 如: 默默丢弃已注册的事件, 让内部发生异常状态. 这时候如果Libev
也无法有效分辨这个文件描述符的真实性与有效性.
注意: 为了避免这种类似的情况出现, 每次调用这些方法之前最好先停止事件观察者.
File
)的事件 许多人希望将文件的fd
(使用open
打开)注册到事件接口中, 借此希望磁盘访问的读、写也是这样. 然而这样的想法无法如”预期”那般到达, 只要是已打开的FILE
文件, 那么每次注册后都会立刻获得“读”、“写”事件.
由于高级通知机制通常不太支持文件(FILE
), 所以您应该如上述那般无法使用它. 但是Libev
会尽可能的模拟POSIX
的相关行为, 因为这样做对下面这些行为能提供方便:
关注控制台的 stdin
、stdout
.
tty
、pipe/sockpair
、unix-domain-socket
.
其它特殊设备.
总结来说: 即使文件(FILE
)因为使用异步I/O来提供服务, 但是当它仍然”能行”的时候选择它也不错.
SIGPIPE
的特殊问题 人们似乎都很容易忘记它: 当写入已关闭的管道后您的程序会收到一个SIGPIPE
, 默认情况下它会中止您的程序. 这在编码、调试期间是非常明智的行为, 但是对于后端守护进程来说这是灾难性行为.
所以. 当您无法解释程序为什么会悄无声息退出时, 请注意注册信号并忽略SIGPIPE
(或记录进程退出状态, 这会在您事后排查得到很大线索).
accept
失败的问题 许多POSIX
实现的accept
函数都不会从队列中删除异常的连接. 例如: 大型服务器经常因为文件描述符用完而导致接受失败(ENFILE
), 但是, Libev
还是会在下次事件迭代的时候发出事件. 如果您因此没有做好处理, 则可能在无法排查到问题的情况下发现CPU
飙升到100%
.
在每次启动服务之前最好将open files
调整到一个合理的值, 可以有效的避免此类事情发生.
配置一个
ev_io
观察者, 参数fd
则是文件描述符.events
则是EV_READ
、EV_WRITE
、EV_READ | EV_WRITE
.
类似于
ev_io_set
, 但是这函数仅更改events
事件. (在某些支持的后端下这个操作可能会更快)使用这个函数
Libev
可以假设fd可以引用一个相同的底层文件描述符. 使用ev_io_set
时则无法做到.
在前面初始化完毕后, 调用这个方法会将
ev_io
注册到内部并启动.
注册一个来自控制台的ev_io
输入事件, 并将输其输出返回到控制台. 直到使用ctrl + c
退出:
1 |
|