epoll开发实践
https://man7.org/linux/man-pages/man2/epoll_ctl.2.html
除了上面已知的陷阱epoll的陷阱实践,在开发中初学者还会遇到一些问题,下面就一一列出:
epoll_ctl传入参数
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
很多资料介绍的时候,都是给最后一个参数struct epoll_event
赋值当前的监听事件:EPOLLIN
或者EPOLLOUT
。在开发中这是远远不够的,一般我们都需要传入一个指针,指针中保存了需要的各种信息。实际上struct epoll_event
也提供了这个用法,struct epoll_event
结构如下
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
epoll_data_t
是一个union类型,有一个void*ptr可以用来实现我们的要求。注意这个是union,所以里面的参数只能使用一个,会相互覆盖。
当epoll_wait
返回的时候,通过epoll_ctl
添加的这个参数也会返回,这样就可以拿到该事件对应的接口指针了。
监听客户端断开
如果客户端直接断开或者杀死程序,我们希望直接知道,而不是等待心跳超时,这是可以监听EPOLLIN
或者EPOLLRDHUP
。当客户端直接断开时,这两个消息都会返回,EPOLLRDHUP
就表示客户端断开:
EPOLLHUP
挂起中断在相应的文件描述符上发生。
epoll_wait(2)
会一直等待这个事件,不需要通过epoll_ctl()
设置到事件中。
注意,当从一个通道比如管道或者流式套接字读取数据的时候,这个事件仅仅是指明对方关闭了通道。后续从这个通道读取数据会在通道中未使用的数据被消耗完之后返回0(文件结束符)
上面说epoll会一直监听这个事件,事实上通过测试发现,如果在epoll_ctl()
添加事件的时候没有指明EPOLLRDHUP
,epoll返回的时候是没有这个事件的。
也可以监听EPOLLIN
,当客户端端开始这个事件也会返回,那么如何区分是真的数据到达还是断开链接呢,从上面的官方说明可以看出,如果读取返回0,就表示断开了,也可以从下面官方文档得到证明:
https://man7.org/linux/man-pages/man2/recvmsg.2.html
返回数据
这个调用返回接收了多少数据,如果是-1,表示出错。错误保存在errno里面。
如果一个流式的套接字对方断掉了,就会返回0(文件结束符)。
数据包的套接字,在各种域名下(比如UNIX和网络域名)允许0字节长度的数据包。当接收到这种数据包时,返回0。
如果你请求数据的缓存是0,那么也会返回0。
所以从上面描述可以得知,如果我们使用的是TCP,接收缓存不是0,那么当返回0的时候,就表示中断了。
EINTR
有时候我们除了处理EAGAIN之外,还需要处理EINTR。这个信号是什么呢,可以参考信号。
实际上就是,如果有慢I/O操作,当有一个信号过来中断了这个慢I/O操作时,就会触发这个消息。什么是慢I/O操作呢,阻塞的套接字操作都是慢I/O,最具代表性的就是epoll_wait
。如果遇到这个信号,有的可以设置自动从打断的地方开始,有的需要处理。由于我们的这些操作都可以重新开始,所以遇到这个信号的时候,按照与EAGAIN一样的处理,重新调用一下对应的I/O函数就可以了。