Определение разрыва TCP-соединения

Автор: Андрей Елсуков
Источник: RSDN Magazine #1-2004
Опубликовано: 03.10.2004
Версия текста: 1.0
Введение
Таймер keep-alive
Заключение

Исходные коды

Введение

Протокол TCP, в отличие от UDP, осуществляет доставку дейтаграмм, называемых сегментами, в виде байтовых потоков с установлением соединения. TCP применяется в тех случаях, когда требуется гарантированная доставка сообщений. Он использует контрольные суммы пакетов для проверки их целостности, контролирует последовательность передаваемых данных и избавляет программиста от многих рутинных задач. В качестве примеров прикладных протоколов, использующих TCP, можно назвать протокол FTP, HTTP, SMTP и многие другие.

ПРИМЕЧАНИЕ

В данной статье речь будет идти о реализациях стека TCP/IP в Microsoft Windows NT/2000/XP и более поздних версиях, а также реализации Windows Sockets версии 2 и более поздних.

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

В архитектуре клиент-сервер довольно часто встречаются реализации, в которых клиент, отправив запрос на сервер, долгое время ожидает ответа сервера. Еще более актуальная ситуация - в реализациях TCP-сервера необходимо точно знать, сколько из соединившихся клиентов реально существуют. Многие из прикладных протоколов применяют для этого «пустую операцию» (NOP), которая время от времени производится между клиентом и сервером для проверки наличия соединения. Данный подход хорош тем, что он не зависит от реализации стека TCP/IP. Есть и другой метод – таймер контроля работоспособности (keep-alive).

Таймер keep-alive

Принцип работы этого таймера предельно прост. Если канал между клиентом и сервером пассивен некоторое время (по умолчанию 2 часа), то посылается служебное сообщение, если ответа на него не поступило, через 75 секунд (обычно) сообщение посылается повторно и так несколько раз. Если ответ получен – таймер сбрасывается, и отчёт начинается заново. Если после нескольких повторов ответа так и не поступило, то соединение разрывается. Число повторов зависит от реализации стека TCP/IP, в некоторых реализациях может изменяться, в некоторых – нет…

При использовании сокетов в Windows keep-alive сообщения по умолчанию выключены. Включить их можно с помощью функции setsockopt:

      int setsockopt(
  SOCKET s,
  int level,
  int optname,
  constchar* optval,
  int optlen
);

В качестве параметров в функцию передаются:

Для включения/выключения посылки keep-alive используется опция SO_KEEPALIVE уровня SOL_SOCKET. Параметр optval интерпретируется функцией как булево значение, для включения посылки он должен иметь значение TRUE, иначе – FALSE.

Пример: включение посылки keep-alive
DWORD  dwRet = 1;

if (setsockopt(sock, SOL_SOCKET,SO_KEEPALIVE,
  reinterpret_cast<char*>(&dwRet),sizeof(DWORD)))
{
  std::cerr << "setsockopt fail with code "<<
    WSAGetLastError() << "\n";
}

На практике нет никакого смысла ждать 2 часа до посылки keep-alive сообщения, куда интереснее более реальные интервалы времени (несколько десятков секунд или минут). Для изменения этого интервала в Winsock2 предусмотрена функция WSAIoctl:

      int WSAIoctl(
  SOCKET s,
  DWORD dwIoControlCode,
  LPVOID lpvInBuffer,
  DWORD cbInBuffer,
  LPVOID lpvOutBuffer,
  DWORD cbOutBuffer,
  LPDWORD lpcbBytesReturned,
  LPWSAOVERLAPPED lpOverlapped,
  LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);

В качестве параметров в функцию передаются:

Для изменения интервалов времени нужно передать функции структуру tcp_keepalive с кодом операции SIO_KEEPALIVE_VALS, которые определены в заголовочном файле mstcpip.h.

Структура tcp_keepalive:
      struct tcp_keepalive {
    u_long  onoff;
    u_long  keepalivetime;
    u_long  keepaliveinterval;
};

Поле onoff отвечает за включение/выключение посылки keep-alive сообщений. Если его значение отлично от нуля – посылка будет осуществляться, иначе - нет. Поле keepalivetime определяет интервал между посылкой сообщений при пассивном состоянии канала. Поле keepaliveinterval определяет интервал посылки сообщений, если ответ не получен. Интервалы задаются в миллисекундах. Значения интервалов имеют силу только в пределах соединения, связанного с сокетом s.

Пример: изменение интервалов времени посылки keep-alive
      struct tcp_keepalive alive;
DWORD dwRet, dwSize;

alive.onoff = 1;
alive.keepalivetime = 10000;
alive.keepaliveinterval = 1000;

dwRet = WSAIoctl(sock, SIO_KEEPALIVE_VALS, &alive, sizeof(alive),
  NULL, 0, reinterpret_cast<DWORD*>(&dwSize), NULL, NULL);
if (dwRet == SOCKET_ERROR)
{
  std::cerr << "WSAIoctl fail with code " << WSAGetLastError() << "\n";
}

Заключение

В MSDN очень скупо описано все, что связано с keep-alive. После практических испытаний я обнаружил, что функция WSAIoctl перекрывает действие setsockopt. Точнее, действие setsockopt мною не проверено, потому как ждать 2 часа у меня терпения не хватает. А вызов setsockopt после WSAIoctl ни к чему не приводит. Если кто-то осведомлён о работе этих функций больше меня, буду рад выслушать пояснения.

Для проверки работы keep-alive я написал простенькую программку, которая соединяется с 21-м портом по заданному адресу. Интервал посылки keep-alive сообщений устанавливается равным 10 секундам, с повтором через 1 секунду. Далее я пользовался анализатором трафика и пакетным фильтром. С помощью пакетного фильтра создал ситуацию потери связи с сервером, в результате которой после нескольких безответных посылок keep-alive возникает событие FD_CLOSE.

Исходник проекта (MSVC 7.0) прилагается в архиве.


Эта статья опубликована в журнале RSDN Magazine #1-2004. Информацию о журнале можно найти здесь