EPOLLRDHUP vs EPOLLHUP

18 篇文章 0 订阅

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]: <socket.socket fd=15, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=6, laddr=('192.168.1.168', 7777), raddr=('192.168.1.120', 8790)>

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) 得到一样的结果。 

EPOLLRDHUP 可以作为一种读关闭的标志,注意不能读的意思内核不能再往内核缓冲区中增加新的内容。已经在内核缓冲区中的内容,用户态依然能够读取到。

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)。

hex(17) 为 0x11,刚好是  EPOLLIN | EPOLLHUP.

3、对端发送 RST.

发送 RST 的常见场景。

1)  系统崩溃重启(进程崩溃,只要内核是正常工作都还能兜底,发送的是FIN,不是这里讨论的RST),四元组消失。此时收到任何数据,都会响应 RST.

2)设置 linger 参数,l_onoff 为 1 开启,但是 l_linger = 0 超时参数为0. 此时close() 将直接发送 RST.

3)  接收缓冲区中还有数据,直接 close(), 接收缓冲区中的内容丢弃,直接发送 RST.

4)调用 close 时,close 会立马发送一个 FIN。注意:仅仅从 FIN 数据包上,无法断定对端是 close 还是仅仅 shutdown(SHUT_WR) 半关闭。往对端发送数据,若对端已经 close(),对端会回复 RST.

这里仅仅使用第 2 种场景来产生 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]: <socket._socketobject at 0x1cca0c0>

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 将不会有任何返回。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值