epoll如何在Python中检测clientside close?

2024-05-20 16:25:01 发布

您现在位置: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仍然可以轮询recv并发送事件!为什么EPOLLERR事件从来没有发生过?如果你用EPOLLHUP也是一样的。

我注意到EPOLLERR事件只在您尝试编写关闭的连接时发生。 除此之外,是否还有其他方法来判断连接是否已关闭?

如果在一个epolin事件中什么也得不到,那么将连接视为关闭是正确的吗?


Tags: importsenddatasocketeventscsselectsk
3条回答

如果套接字仍处于打开状态,但没有可用的读/写,则epoll.poll将超时。

如果数据可以从对等端获得,那么您将得到一个epolin,并且数据将是可用的。

如果套接字被对等方关闭,您将得到一个epolin,但当您读取它时,它将返回“”。

然后,您可以通过关闭套接字并获取生成的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从不出现在粘贴在post中的代码中,因为它们总是与EPOLLIN或EPOLLOUT一起出现(其中一些可以同时设置),因此if/then/else总是拾取EPOLLIN或EPOLLOUT。

实验我发现EPOLLHUP只与EPOLLERR一起发生,原因可能是python与epoll和lowlevel IO接口的方式,通常recv会返回-1并将errno设置为EAGAIN(当非阻塞recv上没有可用的内容时),但是python使用“”(没有返回)来表示EOF。

关闭telnet会话只会关闭tcp连接的那一端,因此在您这边调用recv仍然是完全有效的,tcp接收缓冲区中可能有应用程序尚未读取的挂起数据,因此不会触发错误条件。

似乎EPOLLIN和返回空字符串的recv表示另一端已关闭连接,但是,使用较旧版本的python(在引入epoll之前)和管道上的纯选择,我曾体验到,返回“”的读取并不表示EOF只是缺少可用数据。

我的特别解决方案绕过了这个问题

--- 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:

相关问题 更多 >