Как epoll обнаруживает закрытие на стороне клиента в Python?

Asked
Viewd9812

4

Вот мой сервер

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

Ввод на стороне клиента

 [email protected]:/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.
 

вывод на стороне сервера

 [email protected]:/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?

10 ответов

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() 
 
0

После перемещения кода обработки select.EPOLLHUP в строку перед select.EPOLLIN событие 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, не знаю почему

0
 if event & select.EPOLLHUP:
    epoll.unregister(fd)
 
0
 elif event & (select.EPOLLERR | select.EPOLLHUP):
    epoll.unregister(fileno)
    cs[fileno].close()
    del cs[fileno]
 
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:
 
  • Мне легче всегда относиться к POLLIN и POLLHUP одинаково, вот так. Вы получаете гораздо более конкретные ошибки, просто читая, и вы все равно хотите их обрабатывать.

    Rhamphoryncus13 мая 2009, 21:12
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())
 

Для получения дополнительной информации см. selectmodule. c в репозитории Python:

0

Разве вам не нужно просто комбинировать маски, чтобы одновременно использовать EPOLLHUP и EPOLLIN:


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

Хотя, честно говоря, я не очень хорошо знаком с библиотекой epoll, так что это просто предложение ...

  • Привет, Джон, спасибо за ответ. Я пробовал, но тоже не вышло. Кажется, что EPOLLERR и EPOLLHUP устанавливаются автоматически, явно не нужно устанавливать.

    28 апреля 2009, 00:10
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)
 
3

EPOLLERR и EPOLLHUP никогда не встречаются в коде, вставленном в сообщение, потому что они всегда встречались вместе с EPOLLIN или EPOLLOUT (несколько из них могут быть установлены одновременно), поэтому if / then / else всегда взял EPOLLIN или EPOLLOUT.

Экспериментируя, я обнаружил, что EPOLLHUP происходит только в сочетании с EPOLLERR, причиной этого может быть способ взаимодействия python с epoll и низкоуровневым вводом-выводом, обычно recv возвращает -1 и устанавливает для errno значение EAGAIN, когда ничего не доступно на неблокирующий recv, однако python использует '' (ничего не возвращается) для сигнализации EOF.

Закрытие сеанса telnet закрывает только этот конец tcp-соединения, поэтому вызов recv на вашей стороне по-прежнему вполне допустим, в буферах приема tcp могут быть ожидающие данные, которые ваше приложение еще не прочитало, поэтому не вызовет состояние ошибки.

Кажется, что EPOLLIN и recv, возвращающие пустую строку, указывают на то, что другой конец закрыл соединение, однако, используя старую версию python (до появления epoll) и простой выбор на канале, я обнаружил, что чтение, которое вернулось "", не указывает на EOF, просто на отсутствие доступных данных.