EPOLLRDHUP 表示读关闭。不是所有的内核版本都支持,没有查证。有两种场景:
1、对端发送 FIN (对端调用close 或者 shutdown(SHUT_WR)).
2、本端调用 shutdown(SHUT_RD). 当然,关闭 SHUT_RD 的场景很少。
测试环境为 Linux localhost.localdomain 3.10.0-957.el7.x86_64 #1 SMP Thu Nov 8 23:39:32 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux。
使用 python 的服务端测试过程,客户端仅仅建立 TCP 连接,不发送任何数据。第 10 条指令 poll 超时,返回一个空的列表。通过关闭本端的读取,再次 poll 可以看到,返回 hex(8193) = 0x2001 表示EPOLLRDHUP 和 EPOLLIN 事件。
In [1]: import socket
In [2]: serv = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
In [3]: serv.bind(("0.0.0.0", 7777))
In [4]: serv.listen(5)
In [5]: import select
In [6]: epoll_fd = select.epoll()
In [7]: client,addr = serv.accept()
In [8]: client
Out[8]:
In [9]: epoll_fd.register(client, select.EPOLLIN | select.EPOLLRDHUP)
In [10]: epoll_fd.poll(5)
Out[10]: []
In [11]: client.shutdown(socket.SHUT_RD)
In [12]: epoll_fd.poll(5)
Out[12]: [(15, 8193)]
In [13]: for i in dir(select):
...: if "EPOLL" in i:
...: print(i, hex(getattr(select, i)))
...:
EPOLLERR 0x8
EPOLLET -0x80000000
EPOLLHUP 0x10
EPOLLIN 0x1
EPOLLMSG 0x400
EPOLLONESHOT 0x40000000
EPOLLOUT 0x4
EPOLLPRI 0x2
EPOLLRDBAND 0x80
EPOLLRDHUP 0x2000
EPOLLRDNORM 0x40
EPOLLWRBAND 0x200
EPOLLWRNORM 0x100
EPOLL_CLOEXEC 0x80000
本端不动,客户端 shutdown(SHUT_WR) 得到一样的结果。
EPOLLHUP 表示读写都关闭。
1、本端调用shutdown(SHUT_RDWR)。 不能是close,close 之后,文件描述符已经失效。
In [9]: epoll_fd.register(client, select.EPOLLIN | select.EPOLLRDHUP)
In [10]: epoll_fd.poll(5)
Out[10]: []
In [11]: client.shutdown(socket.SHUT_RDWR)
In [12]: epoll_fd.poll(5)
Out[12]: [(15, 8209)]
In [13]: hex(8209)
Out[13]: '0x2011'
0x2011 刚好对应 EPOLLIN | EPOLLRDHUP | EPOLLHUP.
2、本端调用 shutdown(SHUT_WR),对端调用 shutdown(SHUT_WR)。
3、对端发送 RST. 1) 对端系统崩溃重启,四元组消失。2)设置 linger 参数,l_onoff 为 1 开启,但是 l_linger = 0 超时参数为0.此时如果本端发送缓冲区中还有数据,本端 close() 将直接发送 RST.
补上测试数据,测试环境 Linux localhost.localdomain 2.6.32-696.el6.x86_64 #1 SMP Tue Mar 21 19:29:05 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux.
In [1]: import socket
In [2]: serv = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
In [3]: serv.bind(("0.0.0.0", 7777))
In [4]: serv.listen(5)
In [5]: import select
In [6]: epoll_fd = select.epoll()
In [7]: client,addr = serv.accept()
In [8]: client
Out[8]:
In [10]: addr
Out[10]: ('192.168.1.237', 59834)
In [11]: client.fileno()
Out[11]: 10
In [13]: client.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 65536)
In [14]: client.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF)
Out[14]: 131072
In [21]: epoll_fd.register(client)
In [22]: epoll_fd.poll(3)
Out[22]: [(10, 4)]
In [23]: client.fileno()
Out[23]: 10
In [24]: epoll_fd.modify(client, select.EPOLLIN)
In [25]: epoll_fd.poll(3)
Out[25]: []
In [26]: epoll_fd.poll(3)
Out[26]: [(10, 1)]
In [27]: epoll_fd.poll(3)
Out[27]: [(10, 25)]
In [28]: hex(25)
Out[28]: '0x19'
客户端:
服务器上获得的接收缓冲区大小为 131072,所以客户端这里发送的数据长度稍微大于 131072 的 131080 字节,填满服务器的接收缓冲区,使得客户端的发送缓冲区中仍然存有数据。这个时候制造发送RST的linger条件后,直接 close 客户端。
客户端发送数据之前,服务端 poll 返回的结果时空(第25条输出)。客户端发送数据之后, close 之前,服务端 poll 的结果是 EPOLLIN(第26条输出 1);一旦客户端 close 发送了 RST,服务器 poll 的结果变成 EPOLLIN | EPOLLERR | EPOLLHUP(第27条输出 25).
服务端的抓包结果截图:
1、最后客户端的确有发送 RST。
2、一旦服务端的接收缓冲区满;服务端将一直给客户端发送零窗口通告,让客户端停止发送。
最后,如果仅仅关闭写(shutdown(SHUT_WR)),epoll 将不会有任何返回。