我正在添加从接受创build的sockfds到一个epoll实例有下列事件:
const int EVENTS = ( EPOLLET | EPOLLIN | EPOLLRDHUP | EPOLLONESHOT | EPOLLERR | EPOLLHUP);
一旦事件被触发,我将它传递给一个处理程序线程,读取,然后通过使用相同标志的epoll_ctl
重新启用sockfd。 不过,我只收到一次EPOLLIN
事件。 另外,如果我在第一个事件收到后随时杀死客户端,我也不会挂断事件。 从阅读手册页,我认为我了解EdgeTriggered和OneShot的正确方法。
以下是我正在使用的过程的一些伪代码:
const int EVENTS = ( EPOLLET | EPOLLIN | EPOLLRDHUP | EPOLLONESHOT | EPOLLERR | EPOLLHUP); void event_loop() { struct epoll_event event; struct epoll_event *events; events = calloc(100, sizeof event); while (1) { int x; int num_events = epoll_wait(epfd, events, 100, -1); for (x = 0; x < num_events; x++) { another_thread(fd); } } } void another_thread(int fd) { // Read stuff until EAGAIN struct epoll_event event; event.data.fd = fd; event.events = EVENTS; epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &event); }
当我执行EPOLL_CTL_MOD
操作时,我没有收到任何错误,但从来没有得到其他事件的通知。 如果我在第一个事件之后重复读取循环,它将读取客户端发送的所有后续数据,所以我知道数据正在进入,并且fd仍处于打开状态并正在工作。
从检查strace
,线程是从克隆创build的,并具有标志CLONE_FILES
,所以所有的线程共享相同的CLONE_FILES
表。
什么是从一个单独的线程重新启用fd读取事件的正确方法?
不过,我只收到一次EPOLLIN事件。 另外,如果我在第一个事件收到后随时杀死客户端,我也不会挂断事件。
epoll_ctl(2)的手册页说:
EPOLLONESHOT(自Linux 2.6.2开始)设置关联文件描述符的一次性行为。 这意味着在事件被epoll_wait(2)拉出之后,关联的文件描述符被内部禁用,epoll接口不会报告其他事件。 用户必须使用EPOLL_CTL_MOD调用epoll_ctl()以使用新的事件掩码重新装入文件描述符。
在你的情况下,当你得到第一个事件,epoll禁用你的sockfd。 当您使用EPOLL_CTL_MOD
重新启用EPOLL_CTL_MOD
,它将在重新注册后通知内核收到的所有事件。 因此,第一次通知和重新注册之间的任何事件都将丢失。 这可能是没有得到任何挂断事件或数据的原因。
从事件中删除EPOLLONESHOT
将更正您的代码,最终不需要重新启用sockfd。
而且由于您使用EPOLLET
,所以也不会有任何性能问题。