Закрытие соединений
Пассивное завершение соединения TCP
Когда сокет занят соединением TCP с другим хостом сети, этот хост может решить завершить соединение. Это делается либо последовательностью «плавного разъединения» («FIN-ACK-FIN-ACK»), либо сбросом (пакет «RST»).
В обоих случаях сокет обработает закрытие соединения автоматически, без какой-либо помощи вашей программы на Tibbo BASIC/C или VM. Более того, сокет примет завершение соединения, даже если VM остановлена (например, приостановлена на вашей точке останова). Всю связь обрабатывает главный процесс, поэтому сокету не нужна VM, чтобы завершить соединение.
В процессе завершения соединения есть тонкая деталь, которую нужно хорошо понимать. Когда VM не работает, сокет может принять входящее соединение (активное открытие) и может принять активное закрытие. Однако принять следующее входящее соединение сокет не сможет, пока VM не получит возможность запуститься и выполнить обработчик события on_sock_event (см. раздел Проверка состояния соединения).
Вот почему: во многих случаях после окончания предыдущего соединения и до начала нового нужно выполнить определённые действия (например, очистить буферы, инициализировать переменные и т. п.). Не позволяя сокету принять следующее соединение, пока ваша программа не успеет отреагировать на завершение предыдущего, мы этого и добиваемся!
Активное закрытие соединений TCP
Соединение TCP можно закрыть, сбросить (прервать) или отбросить тремя разными методами: sock.close, sock.reset и sock.discard.
Рекомендуемый (и вежливый!) способ закрыть соединение TCP - метод sock.close. Он запускает правильную последовательность закрытия («FIN-ACK-FIN-ACK»), известную как «плавное разъединение».
Сброс соединения немного «грубее»: сокет просто сообщает другой стороне, что «всё кончено» (отправляя пакет «RST»).
Наконец, метод sock.discard просто заставляет сокет забыть о соединении, другая сторона при этом вообще не уведомляется.
Как и при установке соединения, ход его завершения можно отслеживать - см. раздел Проверка состояния соединения.
Обратите внимание: в зависимости от состояния сокета в момент вызова метода sock.close сокету может потребоваться прибегнуть к более простому способу закрытия - сбросу или отбрасыванию. Точно так же использование sock.reset иногда приводит к отбрасыванию. Подробнее читайте в описаниях sock.close, sock.reset и sock.discard.
Активное закрытие соединений UDP
Для UDP, каким бы методом вы ни закрывали «соединение», результат будет таким же, как после вызова sock.discard. Дело в том, что в действительности корректного завершения соединения UDP не существует, поэтому единственное, что остаётся сокету, - просто «забыть» о соединении.
Не забывайте: работа с соединениями полностью асинхронна!
Помните, что объект sock. обрабатывает обмен данными асинхронно. Когда VM выполняет методы sock.close, sock.reset или sock.discard, это не значит, что к моменту перехода VM к следующей инструкции программы с соединением уже покончено. Выполнение этих методов лишь поручает главному процессу завершить соединение. Это может занять некоторое время, и вашему приложению не нужно ждать завершения. В разделе Проверка состояния соединения объясняется, как в любой момент узнать фактическое состояние соединения (см. свойства sock.state и sock.statesimple, только чтение).
Асинхронная природа объекта sock. имеет несколько интересных следствий. В теме Подробнее об асинхронной природе сокета есть важная информация на этот счёт, обязательно прочитайте её!
Повторное использование сокета после закрытия соединения
Когда соединение сокета завершается, сокет готов принять другое входящее соединение или установить новое исходящее (если он так настроен), но с одной небольшой оговоркой! Во встроенный специальный механизм заложено, что у вашего приложения будет возможность отреагировать после завершения предыдущего соединения и до установки следующего.
Например, перед каждым новым входящим соединением TCP может понадобиться очистить некоторые буферы. Естественно, это нужно сделать до того, как новое соединение будет фактически принято.
Обычно программа делает это, выполняя код в обработчике события on_sock_event (это событие описано в разделе Проверка состояния соединения). Сокет не сможет вступить в новое соединение, пока on_sock_event не получит возможность выполниться. Интересный пример на эту тему есть в теме Подробнее об асинхронной природе сокета.
Тайм-ауты соединения
Свойство sock.connectiontout позволяет автоматически завершать соединение, по которому в течение заданного времени не было обмена данными. Для TCP используется сброс (прерывание), а «соединения» UDP просто отбрасываются. Тайм-аут соединения - удобный способ избавиться от «зависших» соединений (в больших сетях с TCP это случается часто).
Свойство sock.toutcounter (только чтение) сообщает приложению, сколько времени прошло с последнего обмена данными по соединению. Каждый раз, когда данные отправляются или принимаются, sock.toutcounter сбрасывается в ноль. Пока через сокет не идут данные, значение свойства растёт с шагом 0,5 секунды.
Если sock.connectiontout не равно 0, sock.toutcounter растёт, пока не достигнет значения sock.connectiontout, после чего соединение завершается. Затем sock.toutcounter остаётся равным значению sock.connectiontout.
Если sock.connectiontout равно 0, максимальное значение, которого может достичь sock.toutcounter, - 1. То есть sock.toutcounter равно 0 сразу после обмена данными и 1, если с последнего обмена прошло не менее 0,5 секунды.
Обычно HTTP-соединения закрываются автоматически
Есть случай, когда сокет выполняет активное плавное разъединение без вашего вызова метода sock.close. Это случай, когда сокет работает в режиме TCP-HTTP.
В разделе Приём входящих соединений мы объясняли, что сокет автоматически переключается в режим HTTP, если соединение TCP принято на одном из портов из sock.httpportlist. Сокет можно переключить в режим HTTP и программно, с помощью свойства sock.httpmode.
Стандартная функциональность HTTP требует закрывать соединение TCP, как только HTTP-сервер закончит отправку «ответа» (то есть HTML-страницы или другого запрошенного файла). В такой ситуации сокету не нужен вызов sock.close из вашей программы - соединение завершится автоматически. Более того, когда сокет находится в режиме HTTP, ваши вызовы sock.close, sock.reset и sock.discard будут просто игнорироваться. Стандартное поведение сокета в режиме HTTP изменяет свойство sock.httpnoclose. Задайте ему значение 1 - YES, и соединение останется открытым даже после того, как сокет отправит весь HTTP-ответ.
Как и во всех остальных случаях, новое соединение на сокете не будет принято, пока ваша программа не получит возможность отреагировать - об этом говорилось выше.