Asyncio закрыть соединение оставить соединение в состоянии TIME_WAIT

Привет, у меня есть скрипт, который сохраняет состояние портов моего устройства, это упрощенная версия.

Когда соединение установлено успешно (устройство существует), я закрываю соединение, состояние соединения становится TIME_WAIT. Со временем это соединение сбивается и достигает максимального соединения, разрешенного ОС (если я помню)

Любая идея, какую часть я должен исправить, например, я использую порт 53, но в реальном приложении я проверяю несколько портов, таких как ssh, vnc и т. д.

Я запускаю скрипт на Ubuntu 18.04 с Python 3.5.6.

import asyncio
import ipaddress
import sys

async def check_port(ip, port, timeout=1):
    conn = None
    response = False
    writer = None

    try:
        conn = asyncio.open_connection(ip, port)
        reader, writer = await asyncio.wait_for(conn, timeout=timeout)
        response = True
    except asyncio.CancelledError:
        print("asyncio cancel")
    except:
        response = False
    finally:
        if writer is not None:
            writer.close()
        if conn is not None:
            conn.close()
        print("Closing connection {}:{}".format(ip, port))

    print("{}:{} {}".format(ip, port, response))

async def poll_status():
    ips = [str(ip) for ip in ipaddress.IPv4Network("192.168.1.0/24")]
    while True:
        try:
            tasks = [check_port(ip, 53) for ip in ips]
            await asyncio.wait(tasks)
        except asyncio.CancelledError:
            break
        except KeyboardInterrupt:
            break
        except:
            pass
        await asyncio.sleep(1)

async def shutdown(task):
    task.cancel()
    await task
    await asyncio.sleep(1)

if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    task = asyncio.ensure_future(poll_status())
    try:
        loop.run_forever()
    except:
        pass
    finally:
        loop.run_until_complete(asyncio.wait([shutdown(task)]))
        loop.close()

Соединение продолжало накапливаться, как это (вывод из «netstat -nput | grep TIME_WAIT») 192.168.1.1 — мой маршрутизатор, поэтому он успешно проверяет порт, но оставляет много незакрытых соединений. Потребовалось много времени, чтобы соединение было удалено

tcp        0      0 192.168.1.4:42102       192.168.1.1:53          TIME_WAIT   -                   
tcp        0      0 192.168.1.4:42582       192.168.1.1:53          TIME_WAIT   -                   
tcp        0      0 192.168.1.4:46560       192.168.1.1:53          TIME_WAIT   -                   
tcp        0      0 192.168.1.4:39428       192.168.1.1:53          TIME_WAIT   -                   
tcp        0      0 192.168.1.4:45806       192.168.1.1:53          TIME_WAIT   -                                     
tcp        0      0 192.168.1.4:44752       192.168.1.1:53          TIME_WAIT   -                                      
tcp        0      0 192.168.1.4:40726       192.168.1.1:53          TIME_WAIT   -                   
tcp        0      0 192.168.1.4:49864       192.168.1.1:53          TIME_WAIT   -                   
tcp        0      0 192.168.1.4:38812       192.168.1.1:53          TIME_WAIT   -                   
tcp        0      0 192.168.1.4:48464       192.168.1.1:53          TIME_WAIT   -                   
tcp        0      0 192.168.1.4:41372       192.168.1.1:53          TIME_WAIT   -                   
tcp        0      0 192.168.1.4:43408       192.168.1.1:53          TIME_WAIT   -                   
tcp        0      0 192.168.1.4:47360       192.168.1.1:53          TIME_WAIT   -                   
tcp        0      0 192.168.1.4:45478       192.168.1.1:53          TIME_WAIT   -                   
tcp        0      0 192.168.1.4:41904       192.168.1.1:53          TIME_WAIT   -                   
tcp        0      0 192.168.1.4:40160       192.168.1.1:53          TIME_WAIT   -                   
tcp        0      0 192.168.1.4:46196       192.168.1.1:53          TIME_WAIT   -                   
tcp        0      0 192.168.1.4:48744       192.168.1.1:53          TIME_WAIT   -                   
tcp        0      0 192.168.1.4:49554       192.168.1.1:53          TIME_WAIT   -                   
tcp        0      0 192.168.1.4:47774       192.168.1.1:53          TIME_WAIT   -                   
tcp        0      0 192.168.1.4:39370       192.168.1.1:53          TIME_WAIT   -                                    
tcp        0      0 192.168.1.4:43994       192.168.1.1:53          TIME_WAIT   - 

person Wiryono Lauw    schedule 08.03.2019    source источник


Ответы (1)


Я не эксперт в сетевых вещах, не уверен, что этот ответ поможет, но вот мои два цента.

Первое, что касается вывода netstat. Это связанный маршрутизатор и, кажется, не связан с ограничениями вашей ОС. Быстрый поиск в Google показывает следующее:

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

Похоже, ваш код закрыл соединение, т.е. все делаете правильно.

Однако я понятия не имею, как маршрутизатор будет обрабатывать увеличивающееся количество таких подключений.


Давайте теперь рассмотрим ваш код.


То, что вы делаете в строке asyncio.wait(tasks), — это параллельное выполнение всех ваших проверок. В зависимости от количества ips может быть слишком много. Высокие шансы, что вы можете извлечь выгоду из использования asyncio.Semaphore ограничить максимальное количество параллельных проверок. Это будет выглядеть следующим образом:

sem = asyncio.Semaphore(100)

async def check_port(ip, port, timeout=1):
    async with sem:
        # main code here

Вы также можете прочитать этот ответ, чтобы увидеть реальный пример использования семафора.


Следующее, что вам может понадобиться исправить, это то, как вы обрабатываете CancelledError:

except asyncio.CancelledError:
    print("asyncio cancel")

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


except:
    response = False

Ты никогда не делаешь такого. Подробнее см. в этой теме.

Вместо этого вы должны хотя бы сделать что-то вроде следующего:

except Exception as exc:    # catch Exception or it's subclasses only
    logging.exception(exc)  # log for purpose not to miss exception you can fix
    response = False
person Mikhail Gerasimov    schedule 08.03.2019
comment
Что касается семпахора, я использую его в своем реальном коде. Это похоже на пул, который помогает управлять всеми запросами ожидания либо функцией сопрограммы, либо нет (запускается с исполнителем). Это очень хорошо. два пальца вверх :D - person Wiryono Lauw; 09.03.2019
comment
Для asyncio.CancelledError я в основном использую для отладки, иногда он отсутствует, поэтому я использую каждую функцию сопрограммы для тестирования: D, мой плохой. У меня есть журнал везде в реальном приложении для исключения, просто удалите его в этом примере для более простой цели. - person Wiryono Lauw; 09.03.2019
comment
Я читал о TIME_WAIT, на самом деле он достигнет предела, когда достигнет 65k (максимальный исходный порт израсходован). При новом открытом соединении он будет использовать другой исходный порт, а не в состоянии TIME_WAIT, которое со временем будет накапливаться. Если я использую SO_REUSEADDR, я думаю, что это не соответствует моему сценарию, потому что мне нужно проверять одно и то же устройство (один и тот же IP-адрес и порт назначения несколько раз) - person Wiryono Lauw; 09.03.2019
comment
В реальном приложении я проверяю несколько портов и другие вещи для одного устройства, каждая проверка открывает соединение. Я использую безагентный подход. так что достижение 65K возможно. Решение, о котором я думаю, состоит в том, чтобы отложить следующую итерацию до минуты (TIME_WAIT удаляется примерно на 1 минуту), но это превзойдет цель... - person Wiryono Lauw; 09.03.2019