5

这是我的服务器

"""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 事件中一无所获,将连接视为关闭是否正确?

4

10 回答 10

5

EPOLLERR 和 EPOLLHUP 永远不会出现在贴在帖子中的代码中,因为它们总是与 EPOLLIN 或 EPOLLOUT 一起发生(其中几个可以同时设置),所以 if/then/else 总是选择一个EPOLLIN 或 EPOLLOUT。

实验我发现 EPOLLHUP 仅与 EPOLLERR 一起发生,其原因可能是 python 与 epoll 和低级 IO 接口的方式,通常当非阻止recv,但是python使用''(没有返回)来表示EOF。

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

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

于 2009-05-05T17:56:54.047 回答
2

如果套接字仍然打开但没有可用的读/写可用 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)
于 2009-11-17T15:49:38.810 回答
1

我绕过这个问题的临时解决方案

--- 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:
于 2009-04-28T10:17:46.050 回答
1

您没有在代码中检测到 EPOLLHUP/EPOLLERR 的问题是因为您正在执行按位操作。查看套接字何时准备好读取 epoll 将抛出一个位 1 的标志,该标志等于 select.EPOLLIN (select.EPOLLIN == 1)。现在假设客户端挂断(无论是否正常),服务器上的 epoll 将抛出一个第 25 位的标志,该标志等于 EPOLLIN+EPOLLERR+EPOLLHUP。因此,使用第 25 位(代码中的事件变量),您可以看到 EPOLLERR 没有被检测到,因为您的所有 elif 语句(EPOLLOUT 行除外)都不返回 0,因此执行第一个 elif 语句,例如:

>>> from select import EPOLLIN,EPOLLOUT,EPOLLHUP,EPOLLERR
>>> event = 25
>>> event & EPOLLIN
1
>>> event & EPOLLERR
8
>>> event & EPOLLHUP
16
>>> event & EPOLLOUT
0

注意前三个如何不返回 0?这就是为什么您的代码没有正确检测 EPOLLERR/EPOLLHUP 的原因。当客户端挂起时,您仍然可以从套接字读取,因为服务器端仍然处于运行状态(当然,如果您这样做,它将返回 0 数据)因此 EPOLLIN 但由于客户端挂起它也是 EPOLLHUP 并且因为它是 EPOLLHUP 它也是 EPOLLERR 为挂断有点错误。我知道我对此发表评论很晚,但我希望我能帮助那里的人哈哈

这是我重写代码以更好地表达我所说的内容的一种方式:

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()
read_only = select.EPOLLIN | select.EPOLLPRI | select.EPOLLHUP | select.EPOLLERR
read_write = read_only | select.EPOLLOUT
biterrs = [25,24,8,16,9,17,26,10,18] #Bitwise error numbers
epoll.register(s.fileno(),read_only)

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(),read_only)

        elif (event is select.EPOLLIN) or (event is select.EPOLLPRI):
            data = cs[fileno].recv(4)
            print 'recv ', data
            epoll.modify(fileno, read_write)
        elif event is select.EPOLLOUT:
            print 'send ', data
            cs[fileno].send(data)
            data = ''
            epoll.modify(fileno, read_only)

        elif event in biterrs:
            print 'err'
            epoll.unregister(fileno)
于 2014-08-08T22:29:29.097 回答
0

我有另一种方法..

try:
    data = s.recv(4096)
except socket.error:
    if e[0] in (errno.EWOULDBLOCK, errno.EAGAIN): # since this is a non-blocking socket..
        return # no error
    else:
        # error
        socket.close()

if not data: #closed either
    socket.close() 
于 2011-06-17T00:47:19.380 回答
0

你不只需要将掩码组合在一起即可同时使用 EPOLLHUP 和 EPOLLIN:


epoll.register(sk.fileno(), select.EPOLLIN | select.EPOLLHUP)

虽然说实话我对 epoll 库并不是很熟悉,所以这只是一个建议......

于 2009-04-27T15:53:41.710 回答
0

在我将 select.EPOLLHUP 处理代码移动到 select.EPOLLIN 之前的行后,仍然无法在“telnet”中获取 hup 事件。但巧合的是,我发现如果我使用自己的客户端脚本,就会出现 hup 事件!奇怪的...

根据 man epoll_ctl

   EPOLLRDHUP (since Linux 2.6.17)
          Stream socket peer closed connection, or shut down writing half of connection.  (This flag is especially useful for writing simple code  to
          detect peer shutdown when using Edge Triggered monitoring.)

   EPOLLERR
          Error  condition  happened on the associated file descriptor.  epoll_wait(2) will always wait for this event; it is not necessary to set it
          in events.

   EPOLLHUP
          Hang up happened on the associated file descriptor.  epoll_wait(2) will always wait for this event; it  is  not  necessary  to  set  it  in
          events.

似乎远程关闭连接时应该有一个EPOLLRDHUP事件,这不是python实现的,不知道为什么

于 2009-05-15T04:01:51.587 回答
0
elif event & (select.EPOLLERR | select.EPOLLHUP):
    epoll.unregister(fileno)
    cs[fileno].close()
    del cs[fileno]
于 2012-02-23T11:36:01.183 回答
0

EPOLLRDHUP标志没有在 Python 中无缘无故地定义如果您的 Linux 内核 >= 2.6.17,您可以定义它并在 epoll 中注册您的套接字,如下所示:

import select
if not "EPOLLRDHUP" in dir(select):
    select.EPOLLRDHUP = 0x2000
...
epoll.register(socket.fileno(), select.EPOLLIN | select.EPOLLRDHUP)

然后,您可以使用相同的标志 ( EPOLLRDHUP ) 捕获所需的事件:

elif event & select.EPOLLRDHUP:
     print "Stream socket peer closed connection"
     # try shutdown on both side, then close the socket:
     socket.close()
     epoll.unregister(socket.fileno())

有关更多信息,您可以查看python 存储库中的selectmodule.c :

于 2010-10-21T13:53:54.037 回答
0
if event & select.EPOLLHUP:
    epoll.unregister(fd)
于 2011-10-18T06:01:59.460 回答