Программирование на Visual С++

  Все выпуски  

Программирование на Visual С++ No.104 Определение разрыва TCP-соединения


Информационный Канал Subscribe.Ru


  ПРОГРАММИРОВАНИЕ  НА  VISUAL  C++
Статьи по программированию под Windows и платформу .NET
РАССЫЛКА САЙТА
RSDN.RU

No. 104 / 2004-10-29
Подписчиков: 26854

РАССЫЛКА ЯВЛЯЕТСЯ ЧАСТЬЮ ПРОЕКТА RSDN, НА САЙТЕ КОТОРОГО ВСЕГДА МОЖНО НАЙТИ ВСЮ НЕОБХОДИМУЮ РАЗРАБОТЧИКУ ИНФОРМАЦИЮ, СТАТЬИ, ФОРУМЫ, РЕСУРСЫ, АРХИВ ВЫПУСКОВ РАССЫЛКИ И МНОГОЕ ДРУГОЕ.

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

Автор: Андрей Елсуков
Источник: RSDN Magazine #1-2004


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


Введение

Протокол 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,
  const char* optval,
  int optlen
);

 

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

  • s - дескриптор сокета, с которым будет связано соединение;
  • level - уровень, для которого определена требуемая опция (SOL_SOCKET, IPPROTO_TCP,…);
  • optname - опция, значение которой нужно изменить;
  • optval - указатель на буфер со значением опции;
  • optlen - размер буфера optval в байтах.

Для включения/выключения посылки 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
);

 

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

  • s - дескриптор сокета, с которым будет связано соединение;
  • dwIoControlCode - код выполняемой операции;
  • lpvInBuffer - указатель на входной буфер;
  • cbInBuffer - размер входного буфера в байтах;
  • lpvOutBuffer - указатель на выходной буфер;
  • cbOutBuffer - размер выходного буфера;
  • lpcbBytesReturned - указатель на число байт, которые реально возвращены;
  • lpOverlapped - указатель на структуру WSAOVERLAPPED;
  • 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


Ведущий рассылки: Алекс Jenter jenter@rsdn.ru
Публикуемые в рассылке материалы принадлежат сайту RSDN.


http://subscribe.ru/
http://subscribe.ru/feedback/
Подписан адрес:
Код этой рассылки: comp.prog.visualc
Отписаться

В избранное