Проблема с использованием select () и TCP Keepalive с сокетами Python - PullRequest
0 голосов
/ 30 января 2019

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

Мой код выглядит так:

#!/usr/bin/env python3

import select, socket 

# Listen Port
LISTEN_PORT = 1234

# Create socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# Setup the socket
server.setblocking(0)
server.bind(('0.0.0.0', LISTEN_PORT))
server.listen(5)

# Make socket reusable
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

# Setup TCP Keepalive
server.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
server.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 1)
server.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 3)
server.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 5)

# Tell user we are listening
print("Listening on port %s" % LISTEN_PORT)

inputs = [server]
outputs = []

while True:
    # Detecting clients that disappeared does NOT work when we ARE
    # watching if any sockets are writable
    #readable, writable, exceptional = select.select(inputs, outputs, inputs)

    # Detecting clients that disappeared works when we aren't watching
    # if any sockets are writable
    readable, writable, exceptional = select.select(inputs, [], inputs)

    for s in readable:
        if s is server:
            connection, client_address = s.accept()

            print("New client connected: %s" % (client_address,))

            connection.setblocking(0)

            inputs.append(connection)
            outputs.append(connection)
        else:
            try:
                data = s.recv(1024)
            except TimeoutError:
                print("Client dropped out")

                inputs.remove(s)

                if s in outputs:
                    outputs.remove(s)
                    continue

            if data:
                print("Data from %s: %s" % (s.getpeername(), data.decode('ascii').rstrip()))
            else:
                print("%s disconnected" % (s.getpeername(),))

    for s in writable:
        s.send(b".")

КакВы видите, я использую TCP Keepalive, чтобы я мог видеть, исчез ли клиент.Проблема, с которой я сталкиваюсь, заключается в следующем:

  • , когда у меня НЕ наблюдаются select () для отслеживания записываемых сокетов, когда клиент исчезает, select () прекращает блокировку после истечения времени ожидания Keepalive TCP,и сокет будет в списке readable, так что я могу удалить клиент, который исчез из input и output (что хорошо)
  • , когда у меня есть select (), наблюдающий за записываемыми сокетамиКогда клиент исчезает, select () НЕ прекращает блокировку после истечения времени ожидания TCP Keepalive, и сокет клиента никогда не попадает в список readable или writable, поэтому он никогда не удаляется

Я использую telnet с другого компьютера в качестве клиента.Чтобы реплицировать исчезновение клиента, я использую iptables, чтобы заблокировать общение клиента с сервером, пока клиент подключен.

Кто-нибудь знает, что происходит?

1 Ответ

0 голосов
/ 30 января 2019

Как уже упоминалось в комментариях к вашему вопросу, материал TCP_KEEPALIVE не будет иметь никакого значения для вашего варианта использования.TCP_KEEPALIVE - это механизм уведомления программы, когда одноранговый узел на другой стороне его TCP-соединения исчезает в противном случае простаивающего TCP-соединения .Поскольку вы регулярно отправляете данные по TCP-соединению (ям), функциональность TCP_KEEPALIVE никогда не вызывается (или не требуется), потому что сама по себе отправка данных по соединению уже достаточна, чтобы стек TCP распознавал как можно скореекогда удаленный клиент ушел.

Тем не менее, я изменил / упростил ваш пример серверного кода, чтобы он работал (как можно более правильно) на моей машине (Mac, FWIW).Я сделал следующее:

  1. Переместил socket.setsockopt(SO_REUSEADDR) до строки bind(), чтобы bind() не завершился ошибкой после того, как вы убьете и перезапустите программу.

  2. Изменен вызов select() для отслеживания доступных для записи сокетов.

  3. Добавлена ​​обработка исключений для вызовов send().

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

Обратите внимание, что ожидаемое поведение для TCPчто если вы аккуратно выйдете из клиента (например, с помощью control-C'ing, или убьете его через диспетчер задач, или иным образом заставите его выйти таким образом, чтобы его стек TCP хоста все еще мог связываться с сервером, чтобы сообщитьсервер, который клиент мертв), то сервер должен более или менее немедленно распознать мертвого клиента.

Если, с другой стороны, подключение клиента к сети внезапно отключается (например, из-за того, что кто-то выдернул Ethernet клиента).или кабель питания) тогда яПрограмма сервера может занять несколько минут, чтобы обнаружить, что клиент ушел, и это ожидаемое поведение, поскольку у сервера нет возможности сказать (в этой ситуации), мертв ли ​​клиент или нет.(т.е. он не хочет разрывать жизнеспособное TCP-соединение просто потому, что маршрутизатор отбросил несколько TCP-пакетов, что вызывает временное прерывание связи с еще живым клиентом)

Если вы хотите попытаться отброситьклиенты быстро в этом сценарии, вы можете попытаться потребовать от клиентов send() фиктивных данных на сервер каждую секунду или около того.Сервер может отслеживать метку времени, когда он последний раз получал какие-либо данные от каждого клиента, и принудительно закрывать любые клиенты, от которых он не получал никаких данных, в «слишком длинном» (для чего бы вы ни думали о слишком длинном).Это более или менее сработает, хотя при этом вы рискуете получить ложные срабатывания (т. Е. Отбросить клиентов, которые все еще живы, просто медленны или страдают от потери пакетов), если вы установите слишком низкий порог времени ожидания.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...