Как узнать, закрыт ли неблокирующий сокет? - PullRequest
2 голосов
/ 01 июля 2019

Я пытаюсь написать мой собственный TCP неблокирующий сервер для обработки нескольких длительных соединений с сокетами, а не для открытия множества потоков для их обработки.

Я написал свой слишком сложный, сложный в использовании синтаксис, но у меня возникают проблемы, когда я пытаюсь обнаружить закрытый сокет.

В обычном многопоточном сокет-сервере TCP я использовал бы обнаруженный b'' из функции socket.read(size), однако это невозможно с неблокирующим сокетом, поскольку он всегда будет возвращать BlockingIOError

Я также пытался перехватить эти следующие события

except BrokenPipeError:
    conn.abort()
except ConnectionResetError:
    conn.abort()
except ConnectionAbortedError:
    conn.abort()
except socket.error:
    conn.abort()

(conn - это класс, в котором находится клиентский сокет и адрес из socket.accept())

Я не уверен, что делать, но вот очень упрощенная выдержка из моего кода:

def loop_listen(self):
    while self.running == True:
    cr, addr = self.server.accept()
    crs = SocketHandler(self, cr, addr)
    self.client_handler(crs)
    self.connections.append(crs)
    crs.events["open"]()
    crs.cr.setblocking(0)

def loop_recv(self):
    while self.running == True:
        time.sleep(self.poll_time)

        for conn in self.connections:
        try:
            data = conn.cr.recv(self.poll_size)
            print(data)
            if (data == b''):
                conn.abort()
        except BlockingIOError:
            data = None
        except BrokenPipeError:
            conn.abort()
        except ConnectionResetError:
            conn.abort()
        except ConnectionAbortedError:
            conn.abort()
        except socket.error:
            conn.abort()
        if (data != None):
            conn.events["msg"](data)

(оба цикла являются отдельными потоками)

И васхотел, вот класс conn

class SocketHandler:
    def __init__(self, server, cr, addr):
        self.server = server
        self.cr = cr
        self.addr = addr
        self.events = {"msg": emptyCallback, "close": "emptyCallback","open":emptyCallback}
        self.cache = b""

    def message(self, func):
        self.events["msg"] = func

    def close(self, func):
        self.events["close"] = func

    def open(self, func):
        self.events["open"] = func

    def send(self, data):
        self.cr.send(data)
    def abort(self):
        self.cr.close()
        self.events["close"]()
        self.server.connections.remove(conn)

Это прекрасно работает в Windows, но в Ubuntu он не вызывает conn.abort().

Любая помощь будет принята с благодарностью.

Спасибо, Сэм.

1 Ответ

1 голос
/ 01 июля 2019

Официальный способ обнаружения закрытого соединения в неблокирующем сокете точно такой же, как и в блокирующем сокете.Они возвращают пустые данные из recv().

Пример:

# Server
import socket
import time

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('localhost', 12345))
s.listen(1)

while True:
    conn, addr = s.accept()
    conn.setblocking(0)
    print("New connection from " + str(addr) + ".")
    while True:
        try:
            data = conn.recv(1024)
            if not data:
                break
            print("Received:", data)
        except BlockingIOError:
            time.sleep(0.001) 

    print("Closed.")
# Client
import socket
import time

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('localhost', 12345))

for i in range(5):
    time.sleep(0.3)
    s.send(str(i).encode('utf-8'))
s.close()

Существует один особый случай, когда это не сработает, как описано в официальных документах, раздел Когда умирают розетки .Это происходит, когда розетки не закрываются изящно.В принципе, recv() не может определить, когда сокет мертв, без постепенного отключения.Возможно, это то, что вы видите.

Есть несколько способов решить эту проблему.Например, создайте некоторый тайм-аут, который закрывает и сбрасывает сокет, если он не получал сообщения в течение разумного периода времени.Во-вторых, вы можете активно отправлять сообщения.Обнаружение мертвого сокета намного проще для send(), чем для recv().

Кроме того, это работает в Linux.Я не проверял это на Windows.Внутренняя реализация класса sockets сильно зависит от платформы, поэтому это может быть ошибка Windows.

...