Использование TCP-соединения для получения среднего времени ответа сервера

Недавно мы представили реализацию функции получения среднего времени ответа сервера с помощью команды ping. После публикации статьи нам поступило определенное количество справедливой критики по поводу ненадежности команды ping. По этой причине было решено опубликовать этот рецепт с реализацией того же функционала без использования ping.

Команда ping имеет ряд существенных минусов, из-за которых наша предыдущая реализация может плохо или вообще не работать:

  • Зависимость от операционной системы и прав на исполнение команды ping;
  • Зависимость от сторонней утилиты (ping);
  • На сервере, который пингуем, могут быть заблокированы ICMP-пакеты.

Самым надежным и простым, на наш взгляд, способом получения времени ответа сервера является получение времени открытия TCP-соединения. Открываем соединение, дожидаемся момента, когда оно будет открыто, считаем разницу времени между началом открытия и получением подтверждения об открытии, закрываем соединение. Для надежности повторяем X раз и считаем среднее время ответа. Этот способ не имеет указанных выше недостатков команды ping, кроме того, можно указать нужный нам порт (это одновременно и минус, так как ICMP вообще не использует это понятие).

Реализацию мы описали в 2 функции: однократное обращение к серверу и многократное многопоточное обращение с использованием первой функции:

import std.stdio;

void main()
{
  averageResponseTime("lhs-blog.info", 443).writeln;
}

long pingTime(string host, ushort port, ushort timeout = 400)
{
  import std.socket;
  import std.datetime;

  auto socket = new TcpSocket();

  // таймауты отправки и приема данных
  socket.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, dur!"msecs"(timeout));
  socket.setOption(SocketOptionLevel.SOCKET, SocketOption.SNDTIMEO, dur!"msecs"(timeout));
  socket.setOption(SocketOptionLevel.SOCKET, SocketOption.TCP_NODELAY, 1);
  InternetAddress addr = new InternetAddress(host, port);

  // получить текущее системное время
  immutable auto startTime = MonoTime.currTime;

  try
  {
    // открываем соединение
    socket.connect(addr);
  }
  catch (Exception e)
  {
    // если что-то пошло не так, то возвращаем -1
    return -1;
  }
  
  // код ниже выполнится только если и после того, как socket.connect
  // вернет успех, то есть соединение будет установлено
	
  immutable auto stopTime = MonoTime.currTime;

  try
  {
    // 
    socket.close;
  }
  catch (Exception e)
  {
    return -1;
  }

  return (stopTime - startTime).total!"msecs";
}

long averageResponseTime(string host, ushort port = 80, ushort attempts = 3, ushort timeout = 400)
{
  import std.parallelism;
  import std.algorithm;

  auto pings = new long[attempts];

  // параллельно заполняем массив
  foreach (i, ref num; pings.parallel)
  {
    num = pingTime(host, port, timeout);
  }

  return cast(long) pings.sum / pings.length;
}

Эта реализация успешно используется в некоторых наших проектах. Если вам есть что сказать по этой теме (например, предложить свой вариант), ждем в наших социальных сетях (ссылки есть вверху сайтбара).

Добавить комментарий