Как epoll обнаруживает близость клиента в Python? - PullRequest
4 голосов
/ 27 апреля 2009

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

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

Ответы [ 10 ]

3 голосов
/ 05 мая 2009

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

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

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

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

2 голосов
/ 17 ноября 2009

Если сокет все еще открыт, но доступ для чтения / записи недоступен, 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)
1 голос
/ 28 апреля 2009

Мое специальное решение, чтобы обойти эту проблему

--- 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:
0 голосов
/ 09 августа 2014

Проблема, по которой вы не обнаруживаете 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 как зависание является своего рода ошибкой. Я знаю, что опоздал комментировать это, но я надеюсь, что помог кому-то там LOL

Вот способ, которым я бы переписал ваш код, чтобы лучше выразить то, что я говорю:

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)
0 голосов
/ 23 февраля 2012
elif event & (select.EPOLLERR | select.EPOLLHUP):
    epoll.unregister(fileno)
    cs[fileno].close()
    del cs[fileno]
0 голосов
/ 18 октября 2011
if event & select.EPOLLHUP:
    epoll.unregister(fd)
0 голосов
/ 17 июня 2011

У меня есть другой подход ..

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 голосов
/ 21 октября 2010

Флаг 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 голосов
/ 15 мая 2009

После того, как я переместил код обработки select.EPOLLHUP в строку перед select.EPOLLIN, событие hup по-прежнему не может быть получен в "Telnet". Но по стечению обстоятельств я обнаружил, что если я использую свой собственный клиентский скрипт, события хуп! странно ...

И, по словам человека 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 голосов
/ 27 апреля 2009

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


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

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

...