我正在使用epoll来编写媒体服务器.fds都设置为非阻塞,我正在使用边缘触发事件.我知道对于EPOLLIN我需要循环读取fd,直到返回EAGAIN.但是写作呢?
当我想写时,我排队数据并在fd上设置EPOLLOUT | EPOLLIN | EPOLLET.当EPOLLOUT事件发生时,我一次写入整个排队的缓冲区:
n = send ( fd, buf, buf_len, MSG_NOSIGNAL );
Run Code Online (Sandbox Code Playgroud)
如果n> 0 && n <buf_len我只是重置EPOLLOUT并返回.我没有看到循环发送的意义(我认为epoll的手册页暗示).似乎发送已经表明它已经完全接受了它并且如果立即被调用将返回EAGAIN.
在这里取消系统调用是最有效的途径吗?
另请参阅此问题,到目前为止尚未回答。
EPOLLHUP即使在man和内核文档中也有很多关于的困惑。人们似乎相信,当一个描述符轮询它返回本地关闭书写,即shutdown(SHUT_WR),即导致相同的呼叫EPOLLRDHUP 在对等。但这是不正确的,在我的实验中,我得到了EPOLLOUT,EPOLLHUP之后没有得到shutdown(SHUT_WR)(是的,变得可写是违反直觉的,因为写作的一半是封闭的,但这不是问题的重点)。
这个人很穷,因为它EPOLLHUP说到挂起发生在关联的文件描述符上时,而没有说“挂断”是什么意思-对方做了什么?发送了什么数据包?另一篇文章只是使事情更加混乱,对我来说似乎是完全错误的。
我的实验表明,EPOLLHUP一旦双向交换EOF(FIN数据包),即双方都发出,就到达了shutdown(SHUT_WR)。它与无关SHUT_RD,我从不称呼它。也与无关close。在数据包方面,我怀疑EPOLLHUP主机发送的FIN会引起确认,即终止发起方在4向关机握手的第3步中引发此事件,而对等方在第4步中引发(请参见此处))。如果得到确认,那就太好了,因为它填补了我一直在寻找的空白,即如何在不使用LINGER的情况下轮询非阻塞套接字以获得最终的确认。它是否正确?
(注意:我正在使用ET,但我认为与此无关)
该代码在一个框架之中,我提取它的肉,用之外TcpSocket::createListener,TcpSocket::connect和TcpSocket::accept,它做你所期望(这里没有显示)。
void registerFd(int pollFd, int fd, const char* description)
{
epoll_event ev = {
EPOLLIN | EPOLLOUT | EPOLLRDHUP | EPOLLET,
const_cast<char*>(description) // union aggregate initialisation, initialises …Run Code Online (Sandbox Code Playgroud) 我对基于事件的编程很陌生。我正在尝试使用epoll的边缘模式,该模式显然仅信号通知已准备好进行读/写的文件(而不是级别模式,该信号通知所有就绪文件,无论是否已经就绪或刚刚就绪)。
对我来说还不清楚的是:在边缘模式下,我是否得知我不epoll_wait读书时发生的准备事件?尚未恢复的单发文件事件如何处理?
为了说明我为什么要问这个问题,请考虑以下情形:
epoll_ctl为在边缘模式+ oneshot中准备好读取套接字时做出反应:EPOLLET | EPOLLONESHOT | EPOLLINepoll_wait 要发生的事情(最多报告10个事件)read并处理数据套接字#1(直到E_AGAIN)read并处理数据套接字2(直到E_AGAIN)epoll_ctlin EPOLL_CTL_MOD模式重新触发了文件epoll_wait下一批事件好吧,那么是否epoll_wait 总是会通知套接字S已准备就绪?如果S为#1(即不重新设置),是否会发生事件?
是否可以epoll在单次触发水平模式下使用?
搜索时找不到任何信息;似乎每个人都使用边缘触发模式。
这是我的服务器的样子:
-WorkerThread(s):
-IOThread(s)
问题是,该服务器不会发送所有排队的数据(它们留在 SendBuffer 中),并且“未发送”数据的数量随着客户端数量的增加而增长。为了测试我只使用 1 个工作线程和 1 个 iothread,但如果我使用更多似乎没有任何区别。访问全局缓冲区受 pthread_mutex 保护。此外,我的响应数据大小为 130k 字节(它至少需要 3 次发送调用才能发送此数据量)。另一方面是使用阻塞套接字的 Windows 客户端。
非常感谢!兆焦
编辑:
是的,默认情况下我正在等待 EPOLLOUT 事件,即使我没有什么可发送的。为了实现简单性和手册页指南,我是这样做的。另外,我对它的理解是这样的:
即使我当时“错过”了 EPOLLOUT 事件,我也不想发送任何内容,这也没有问题,因为当我想发送数据时,我会调用 send 直到 EAGAIN 和 EPOLLOUT 应该在将来触发(并且大部分时间)
现在我修改了代码以在 IN/OUT 事件之间切换:
在接受:
event.events = …Run Code Online (Sandbox Code Playgroud) epoll联机帮助页说,如果没有读取,则EPOLLET注册的fd(边缘触发)不应通知两次EPOLLIN.
所以在EPOLLIN之后你需要清空缓冲区,然后epoll_wait才能在新数据上返回一个新的EPOLLIN.
但是我遇到了这种方法的问题,因为我看到了未触及的fds的重复EPOLLIN事件.
这是strace输出,0x200是EPOLLRDHUP,在我的glibc头文件中尚未定义,但在内核中定义.
30285 epoll_ctl(3, EPOLL_CTL_ADD, 9, {EPOLLIN|EPOLLPRI|EPOLLERR|EPOLLHUP|EPOLLET|0x2000, {u32=9, u64=9}}) = 0
30285 epoll_wait(3, {{EPOLLIN, {u32=9, u64=9}}}, 10, -1) = 1
30285 epoll_wait(3, {{EPOLLIN, {u32=9, u64=9}}}, 10, -1) = 1
30285 epoll_wait(3, <unfinished ...>
30349 epoll_ctl(3, EPOLL_CTL_DEL, 9, NULL) = 0
30306 recv(9, "7u\0\0\10\345\241\312\t\20\f\32\r\10\27\20\2\30\200\10 \31(C0\17\32\r\10\27\20\2\30"..., 20000, 0) = 20000
30349 epoll_ctl(3, EPOLL_CTL_DEL, 9, NULL) = -1 ENOENT (No such file or directory)
30305 recv(9, " \31(C0\17\32\r\10\27\20\2\30\200\10 \31(C0\17\32\r\10\27\20\2\30\200\10 \31("..., 20000, 0) = 10011
Run Code Online (Sandbox Code Playgroud)
因此,在添加fd编号9之后,我在收到文件描述符之前会收到2个连续的EPOLLIN事件,系统调用跟踪显示我在读取之前如何删除fd,但它应该只发生一次,每个事件一个.
所以要么我没有正确阅读该联机帮助页,要么现在正在这里工作.
epoll在边缘触发模式下是一只奇怪的野兽。它要求该过程跟踪每个受监控FD的最后响应是什么。它要求流程能够无故障地处理所报告的每个事件(否则,我们可能会认为FD实际上没有被边缘触发行为所静音,而是没有报告任何东西)。
什么时候使用epoll这种方式有意义?
epoll ×7
epollet ×7
linux ×5
asyncsocket ×1
c ×1
c++ ×1
io ×1
linux-kernel ×1
networking ×1
tcp ×1