Python中epoll如何检测客户端关闭?
这是我的服务器
"""Server using epoll method"""
import os
import select
import socket
import time
from oodict import OODict
addr = ('localhost', 8989)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(addr)
s.listen(8)
s.setblocking(0) # Non blocking socket server
epoll = select.epoll()
epoll.register(s.fileno(), select.EPOLLIN) # Level triggerred
cs = {}
data = ''
while True:
time.sleep(1)
events = epoll.poll(1) # Timeout 1 second
print 'Polling %d events' % len(events)
for fileno, event in events:
if fileno == s.fileno():
sk, addr = s.accept()
sk.setblocking(0)
print addr
cs[sk.fileno()] = sk
epoll.register(sk.fileno(), select.EPOLLIN)
elif event & select.EPOLLIN:
data = cs[fileno].recv(4)
print 'recv ', data
epoll.modify(fileno, select.EPOLLOUT)
elif event & select.EPOLLOUT:
print 'send ', data
cs[fileno].send(data)
data = ''
epoll.modify(fileno, select.EPOLLIN)
elif event & select.EPOLLERR:
print 'err'
epoll.unregister(fileno)
客户端输入
ideer@ideer:/home/chenz/source/ideerfs$ telnet localhost 8989
Trying ::1...
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
123456
123456
^]
telnet> q
Connection closed.
服务器端输出
ideer@ideer:/chenz/source/ideerfs$ python epoll.py
Polling 0 events
Polling 0 events
Polling 1 events
('127.0.0.1', 53975)
Polling 0 events
Polling 1 events
recv 1234
Polling 1 events
send 1234
Polling 1 events
recv 56
Polling 1 events
send 56
Polling 0 events
Polling 0 events
Polling 0 events
Polling 1 events
recv
Polling 1 events
send
Polling 1 events
recv
Polling 1 events
send
Polling 1 events
recv
Polling 1 events
send
Polling 1 events
recv
^CTraceback (most recent call last):
File "epoll.py", line 23, in <module>
time.sleep(1)
KeyboardInterrupt
奇怪的是,客户端关闭连接后,epoll仍然可以轮询recv并发送事件! 为什么 EPOLLERR 事件从未发生? 如果使用 EPOLLHUP 也是一样的。
我注意到 EPOLLERR 事件仅在您尝试写入关闭的连接时发生。 除此之外,还有其他方法可以判断连接是否已关闭吗?
如果在 EPOLLIN 事件中没有得到任何结果,将连接视为已关闭是否正确?
Here is my server
"""Server using epoll method"""
import os
import select
import socket
import time
from oodict import OODict
addr = ('localhost', 8989)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(addr)
s.listen(8)
s.setblocking(0) # Non blocking socket server
epoll = select.epoll()
epoll.register(s.fileno(), select.EPOLLIN) # Level triggerred
cs = {}
data = ''
while True:
time.sleep(1)
events = epoll.poll(1) # Timeout 1 second
print 'Polling %d events' % len(events)
for fileno, event in events:
if fileno == s.fileno():
sk, addr = s.accept()
sk.setblocking(0)
print addr
cs[sk.fileno()] = sk
epoll.register(sk.fileno(), select.EPOLLIN)
elif event & select.EPOLLIN:
data = cs[fileno].recv(4)
print 'recv ', data
epoll.modify(fileno, select.EPOLLOUT)
elif event & select.EPOLLOUT:
print 'send ', data
cs[fileno].send(data)
data = ''
epoll.modify(fileno, select.EPOLLIN)
elif event & select.EPOLLERR:
print 'err'
epoll.unregister(fileno)
client side input
ideer@ideer:/home/chenz/source/ideerfs$ telnet localhost 8989
Trying ::1...
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
123456
123456
^]
telnet> q
Connection closed.
server side output
ideer@ideer:/chenz/source/ideerfs$ python epoll.py
Polling 0 events
Polling 0 events
Polling 1 events
('127.0.0.1', 53975)
Polling 0 events
Polling 1 events
recv 1234
Polling 1 events
send 1234
Polling 1 events
recv 56
Polling 1 events
send 56
Polling 0 events
Polling 0 events
Polling 0 events
Polling 1 events
recv
Polling 1 events
send
Polling 1 events
recv
Polling 1 events
send
Polling 1 events
recv
Polling 1 events
send
Polling 1 events
recv
^CTraceback (most recent call last):
File "epoll.py", line 23, in <module>
time.sleep(1)
KeyboardInterrupt
It's strange that after the client has closed the connection, epoll still can poll recv and send events! Why does EPOLLERR event never happen? it's the same if you use EPOLLHUP.
I notice that the EPOLLERR event only happens when you try to write a closed connection.
Besides this, is there another way to tell that whether the connection has been closed or not?
Is it correct to treat the connection as closed if you get nothing in a EPOLLIN event?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(10)
EPOLLERR 和 EPOLLHUP 永远不会在帖子中粘贴的代码中发生,因为它们总是与 EPOLLIN 或 EPOLLOUT 一起发生(其中几个可以一次设置),因此 if/then/else 总是选择一个EPOLLIN 或 EPOLLOUT。
实验我发现EPOLLHUP仅与EPOLLERR一起发生,其原因可能是python与epoll和低级IO的接口方式,通常recv会返回-1并将errno设置为EAGAIN,当非非-上没有可用的时候阻塞recv,但是python使用''(没有返回)来表示EOF。
关闭 telnet 会话仅关闭 tcp 连接的那一端,因此在您这边调用 recv 仍然完全有效,tcp 接收缓冲区中可能有待处理的数据,您的应用程序尚未读取这些数据,因此不会触发错误条件。
似乎EPOLLIN和返回空字符串的recv表明另一端已关闭连接,但是,使用旧版本的python(在引入epoll之前)和管道上的普通选择,我经历过read 返回的 '' 并不表示 EOF 只是缺少可用数据。
EPOLLERR and EPOLLHUP never happens in the code pasted in the post is because they've always occurred in conjunction with an EPOLLIN or an EPOLLOUT (several of these can be set at once), so the if/then/else have always picked up an EPOLLIN or EPOLLOUT.
Experimenting I've found that EPOLLHUP only happens in conjunction with EPOLLERR, the reason for this may be the way python interfaces with epoll and lowlevel IO, normally recv would return a -1 and set errno to EAGAIN when nothing is available on a non-blocking recv, however python uses '' (nothing returned) to signal EOF.
Closing your telnet-session only closes that end of the tcp-connection, so it's still perfectly valid to call recv on your side, there may be pending data in the tcp receive buffers which your application hasn't read yet so that won't trigger an error-condition.
It seems that EPOLLIN and a recv that returns an empty string is indicative of the other end having closed the connection, however, using an older version of python (before epoll were introduced) and plain select on a pipe, I've experienced that a read that returned '' did not indicate EOF just a lack of available data.
如果套接字仍然打开,但没有可用的读/写,epoll.poll 将超时。
如果数据可从对等方获得,您将获得 EPOLLIN 并且数据将可用。
如果套接字被对等方关闭,您将收到一个 EPOLLIN,但当您读取它时,它将返回“”。
然后,您可以通过关闭套接字并拾取生成的 EPOLLHUP 事件来清理内部结构来关闭套接字。
或执行清理并取消注册 epoll。
If the socket is still open but no read/write available epoll.poll will timeout.
If data if available from the peer, you get an EPOLLIN and data will be available.
If the socket is closed by the peer, you will get an EPOLLIN but when you read it it will return "".
you could then close the socket by shutting it down and picking up the resulting EPOLLHUP event to clean up your internal structures.
or perform cleanup and unregister the epoll.
我的临时解决方案来绕过这个问题
My ad-hoc solution to bypass this problem
您在代码中未检测到 EPOLLHUP/EPOLLERR 的问题是因为您正在执行的按位操作。 当套接字准备好读取时,epoll 将抛出一个位为 1 的标志,该标志等于 select.EPOLLIN (select.EPOLLIN == 1)。 现在假设客户端挂起(无论是否优雅),服务器上的 epoll 将抛出一个位为 25 的标志,该标志等于 EPOLLIN+EPOLLERR+EPOLLHUP。 因此,通过位 25(代码中的事件变量),您可以看到 EPOLLERR 未被检测到,因为所有 elif 语句(EPOLLOUT 行除外)都不返回 0,因此第一个 elif 语句被执行,例如:
注意到前三个不返回 0 吗? 这就是为什么您的代码无法正确检测 EPOLLERR/EPOLLHUP 的原因。 当客户端挂起时,您仍然可以从套接字读取数据,因为服务器端仍然处于运行状态(当然,如果这样做,它会返回 0 数据),因此 EPOLLIN 但由于客户端挂起,它也是 EPOLLHUP,并且由于它是 EPOLLHUP,因此它也是 EPOLLERR挂断有点错误。 我知道我对此发表评论已经晚了,但我希望我能帮助那里的人哈哈,
这是我重写代码以更好地表达我所说的内容的一种方法:
The issue why you're not detecting EPOLLHUP/EPOLLERR in your code is because of the bitwise operations you are doing. See when a socket is ready to read epoll will throw a flag with bit 1 which is equal to select.EPOLLIN (select.EPOLLIN == 1). Now say the client hangs up (gracefully or not) epoll on the server will throw a flag with bit 25 which is equal to EPOLLIN+EPOLLERR+EPOLLHUP. So with the bit 25 (event variable in your code) you can see how EPOLLERR is not being detected because all of your elif statements (with the exception of EPOLLOUT line) don't return 0 so the first elif statement is executed, for example:
Notice how the first three don't return 0? That's why your code isn't detecting EPOLLERR/EPOLLHUP correctly. When a client hangs up you can still read from the socket as the server side is still up (of course it would return 0 data if you did) hence EPOLLIN but since the client hung up it's also EPOLLHUP and since it's EPOLLHUP it's also EPOLLERR as a hangup is somewhat of an error. I know I'm late on commenting on this but I hope I helped someone out there lol
Here is a way I would rewrite your code to express what I'm saying better:
难道你不需要将掩码组合在一起来同时使用 EPOLLHUP 和 EPOLLIN 吗:
虽然说实话我对 epoll 库不太熟悉,所以这只是一个建议......
Don't you just need to combine the masks together to make use of EPOLLHUP and EPOLLIN at the same time:
Though to be honest I'm not really familiar with the epoll library, so it's just a suggestion really...
将 select.EPOLLHUP 处理代码移至 select.EPOLLIN 之前的行后,hup 事件仍然存在
无法进入“telnet”。 但巧合的是我发现如果我使用自己的客户端脚本,就有
是 hup 事件! 奇怪...
根据 man epoll_ctl
似乎当远程端关闭连接时应该有一个 EPOLLRDHUP 事件,这不是由 python 实现的,不知道为什么
After I move select.EPOLLHUP handling code to the line before select.EPOLLIN, hup event still
cant be got in 'telnet'. But by coincidence I found that if I use my own client script, there
are hup events! strange...
And according to man epoll_ctl
Seems there shall be a EPOLLRDHUP event when remote side closed connection, which is not implemented by python, don't know why
Python 中没有无缘无故地定义 EPOLLRDHUP 标志。 如果您的 Linux 内核 >= 2.6.17,您可以定义它并在 epoll 中注册您的套接字,如下所示:
然后您可以使用相同的标志 (EPOLLRDHUP) 捕获所需的事件:
了解更多您可以检查信息 selectmodule.c在 python 的存储库中:
The EPOLLRDHUP flag is not defined in Python for no reason. If your Linux kernel is >= 2.6.17, you can define it and register your socket in epoll like this:
You can then catch the event you need using the same flag (EPOLLRDHUP):
For more info you can check selectmodule.c in python's repository:
我有另一种方法..
I have another approach..