epoll如何在Python中检测客户端关闭?
这是我的服务器
"""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 仍然可以检测到接收和发送事件!为什么 EPOLLERR 事件从来不会发生?使用 EPOLLHUP 也是一样的情况。
我注意到,EPOLLERR 事件只有在你尝试向一个已经关闭的连接写入数据时才会发生。除此之外,还有其他方法可以判断连接是否已经关闭吗?
如果在 EPOLLIN 事件中什么都没有收到,认为连接已经关闭,这样做是否正确?
10 个回答
我临时想出的一个解决办法来绕过这个问题
--- epoll_demo.py.orig 2009-04-28 18:11:32.000000000 +0800
+++ epoll_demo.py 2009-04-28 18:12:56.000000000 +0800
@@ -18,6 +18,7 @@
epoll.register(s.fileno(), select.EPOLLIN) # Level triggerred
cs = {}
+en = {}
data = ''
while True:
time.sleep(1)
@@ -29,10 +30,18 @@
sk.setblocking(0)
print addr
cs[sk.fileno()] = sk
+ en[sk.fileno()] = 0
epoll.register(sk.fileno(), select.EPOLLIN)
elif event & select.EPOLLIN:
data = cs[fileno].recv(4)
+ if not data:
+ en[fileno] += 1
+ if en[fileno] >= 3:
+ print 'closed'
+ epoll.unregister(fileno)
+ continue
+ en[fileno] = 0
print 'recv ', data
epoll.modify(fileno, select.EPOLLOUT)
elif event & select.EPOLLOUT:
如果这个连接(socket)还在打开状态,但没有可以读或写的数据,epoll.poll 就会超时。
如果对方有数据发送过来,你会收到一个 EPOLLIN 的信号,这时候就可以读取到数据了。
如果对方关闭了连接,你会收到一个 EPOLLIN 的信号,但当你尝试读取时,会得到一个空字符串 ""。
这时你可以通过关闭这个连接来处理,关闭后会收到一个 EPOLLHUP 的事件,这样可以清理你内部的数据结构。
或者你也可以进行清理工作,然后注销这个 epoll。
elif event & select.EPOLLIN:
data = cs[fileno].recv(4)
if not data:
epoll.modify(fileno, 0)
cs[fileno].shutdown(socket.SHUT_RDWR)
在帖子中提到的代码里,EPOLLERR和EPOLLHUP这两个情况从来没有出现过,原因是它们通常会和EPOLLIN或EPOLLOUT一起出现(这些状态可以同时被设置),所以在if/then/else判断中,总是能捕捉到EPOLLIN或EPOLLOUT。
通过实验,我发现EPOLLHUP通常是和EPOLLERR一起出现的。这可能是因为Python与epoll和底层输入输出的接口方式。一般来说,当在非阻塞的recv中没有数据可用时,recv会返回-1,并把errno设置为EAGAIN,而Python则用''(返回空)来表示文件结束(EOF)。
关闭你的telnet会话只会关闭TCP连接的一端,所以在你这边继续调用recv是完全有效的,因为TCP接收缓冲区里可能还有你的应用程序还没读取的数据,这样不会触发错误。
看起来EPOLLIN和recv返回空字符串意味着另一端已经关闭了连接。不过,我在使用较旧版本的Python(在引入epoll之前)和普通的select在管道上时,发现返回''并不表示EOF,只是没有可用的数据。