Тестировали ли вы TCP соединение, включая хендшейк?
Что проверяют
Понимаете ли вы, что происходит на транспортном уровне, и отличаете ли «порт отвечает» от «сервис работает». Для питониста это ещё и вопрос про socket, таймауты и поведение кода, когда сеть отвечает медленно или не отвечает совсем.
Как отвечать
Сначала честно про опыт: чаще проверяют доступность и поведение при сбоях, а сам обмен пакетами смотрят, когда что-то уже сломалось. Дальше по существу.
Рукопожатие трёхстороннее: клиент шлёт SYN, сервер отвечает SYN-ACK, клиент подтверждает ACK. Обычный connect возвращает управление после этого, то есть успешное подключение само по себе означает, что рукопожатие прошло. Увидеть пакеты можно только сниффером, например tcpdump -i any port 5432 -nn или Wireshark с фильтром по флагу SYN.
import socket
def check(host: str, port: int, timeout: float = 3.0) -> bool:
try:
with socket.create_connection((host, port), timeout=timeout):
return True
except OSError:
return Falsecreate_connection сам разрешает имя и перебирает адреса, включая IPv6, а контекстный менеджер закрывает сокет при любом исходе.
Сверх этого стоит назвать разницу отказов: RST в ответ означает, что порт закрыт, и приходит он мгновенно, а молчание (пакеты режет фаервол) даёт ожидание до таймаута, поэтому таймаут обязателен. И про полуоткрытые соединения: если пир умер без FIN, сокет считается живым, пока не сработает keepalive или прикладной пинг, из-за этого приложение и виснет на чтении.
Где спотыкаются
Говорят, что проверили хендшейк через telnet или nc. Эти инструменты показывают только исход, установилось соединение или нет. Флаги они не показывают, для этого нужен сниффер.
Второе: пишут проверку без таймаута. Блокирующий сокет ждёт системного значения, а это десятки секунд, и одна недоступная зависимость подвешивает весь сервис.
Третье: закрывают сокет в finally по переменной, которая могла не создаться. Вместо понятной сетевой ошибки получается NameError, и настоящая причина теряется. Контекстный менеджер снимает проблему целиком.