Как работают повторные подключения TCP
У объекта sock. есть уникальная возможность - поддержка «повторных подключений» (reconnects). Чтобы проще было объяснить, начнём с описания реальной проблемы.
Когда повторные подключения выручают
Допустим, хост в сети установил соединение TCP с одним из сокетов объекта sock.. По этому соединению данные передаются от хоста к нашему сокету. Какое-то время всё идёт хорошо, а потом у хоста на мгновение пропадает питание, и он перезагружается. Наш сокет об этом ничего не знает: с его точки зрения соединение TCP по-прежнему в порядке. То, что от хоста не приходят данные, ещё не значит, что есть проблема!
Тем временем хост перезагрузился и пытается установить новое соединение с сокетом - и получает отказ! Дело в том, что сокет считает, что он уже занят соединением TCP - тем самым, которое осталось «висеть» после отключения хоста! Это зависшее соединение сохранится, пока не будет закрыто по тайм-ауту - если тайм-ауты вообще включены через свойство sock.connectiontout.
Повторные подключения - удобный выход из такой ситуации. Они включаются свойством sock.reconmode (режим повторного подключения). Для приведённого примера обычно задают sock.reconmode = 2 - PL_SOCK_RECONMODE_2. Этот режим означает: если сокет уже занят соединением и поступает попытка входящего соединения от того же хоста (с любого порта), сокет забудет всё о прежнем соединении и примет новое.
В нашем примере это и есть решение: после перезагрузки хост пытается установить новое соединение с нашим сокетом. Сокет «понимает», что новое соединение пытается установить тот же хост, что и в текущем соединении, отбрасывает прежнее соединение и принимает новое!
Повторное подключение должно быть направлено на тот же порт и интерфейс!
Даже если у сокета несколько портов прослушивания (например, sock.localportlist = "1000,1001"), повторное подключение будет принято, только если оно направлено на тот же локальный порт сокета, который занят в «заменяемом» текущем соединении. Иными словами, чтобы повторное подключение удалось, оно должно быть направлено на порт, который сейчас возвращает свойство sock.localport (только чтение).
Интерфейс также должен совпадать. Хост не может установить исходное соединение через Ethernet, а затем подключиться повторно через Wi-Fi.
Какой режим выбрать?
Как видите, свойство sock.reconmode задаёт несколько «уровней строгости» при обработке повторных подключений. Какой выбрать? Объясним, почему чаще всего выбирают sock.reconmode = 2 - PL_SOCK_RECONMODE_2. Обычно, устанавливая исходящее соединение, хост использует постоянно меняющийся номер порта. По сути, для этого существует «пул» портов, и каждое новое соединение хост устанавливает с очередного порта из пула.
При каждом подключении к нашему сокету порт на хосте может быть другим. Поэтому имеет смысл принимать повторные подключения с того же IP-адреса, но с любого порта. Недостаток? Любое соединение, исходящее от этого хоста, по сути будет считаться тем же самым и единственным соединением!
Некоторые программы (их немного!) устанавливают соединения с определённого, заранее заданного порта. Причины могут быть разные - не будем здесь на них останавливаться, скажем лишь, что иногда так бывает. В таком случае можно смело задавать sock.reconmode = 1 - PL_SOCK_RECONMODE_1. Тогда повторные подключения будут приниматься только с того же IP-адреса и того же порта, что и исходное соединение.
Полная вседозволенность - режим 3!
Наконец, есть режим (sock.reconmode = 3 - PL_SOCK_RECONMODE_3), в котором сокет принимает повторное подключение с любого хоста и порта. По сути, это значит, что любое текущее соединение будет прервано и заменено любым другим входящим соединением.
Не используйте повторные подключения для HTTP-сокетов!
Повторные подключения и HTTP плохо уживаются вместе. Когда запрашивается HTML-страница, может генерироваться сразу несколько HTTP-запросов (один для самой страницы, несколько для рисунков на ней и т. д.). Каждый из этих запросов использует отдельный сокет TCP, поэтому несколько сокетов открываются (почти) одновременно. Что произойдёт, если повторные подключения включены хотя бы на одном «HTML-сокете» вашего приложения? Этот единственный сокет перехватит все HTML-запросы. Если для загрузки HTML-страницы нужны три отдельных запроса и сеанса TCP, все они достанутся этому сокету, и каждое следующее открытие сеанса будет отбрасывать предыдущее. Результат будет неприглядным!