Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Программирование для начинающих и не только


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


Ping своими руками

Ping - одна из важнейших команд, которая используется при настройке и отладке сетей любых уровней сложности. В этой статье мы рассмотрим способы реализации этой команды средствами Delphi

Теория

Команда ping служит для принудительного вызова ответа конкретной машины. Для этого используется дейтаграмма ECHO_REQUEST протокола ICMP. Это протокол низкого уровня, который не требует наличия серверных процессов на зондируемой машине; это хороший способ убедится в том, что питание машины включено и она не отказала. Успешный результат команда PING не обязательно означает, что выполняются какие-то сервисные программы высокого уровня.

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

Практика

Для практической реализации как всегда можно пойти 2-мя путями(вообще-то их как всегда больше чем 2). Первый из них - использование низкоуровневых функций API(к примеру встроенные в библиотеку ICMP.DLL). Второй - использование высокоуровневых компонентов (к примеру Indy IdICMPClient). И в первого и второго способа есть свои позитивные и негативные моменты. Так при использовании функций API откомпилированный код будет гораздо меньших размеров нежели при использовании высокоуровневых компонентов, да и производительность его будет больше (например при одновременном пинге одной подсети с использованием потоков). С другой стороны компоненты можно использовать имея только отдаленное представление о работе с протоколом ICMP, а также об использовании Windows API. Но в то же время компоненты порождают неоправданно большой исполняемый код, да и производительность их на порядок меньше. К счастью сегодня разработчики процессоров и памяти почти стерли грань между производительностью кода написанного с использованием Windows API и с использованием средств более высшего уровня абстракции(т.е. объектов, классов, компонентов...). Но дабы дать читателю возможность самому выбрать свой путь, мы приведем примеры пинга написанного как с использованием WinAPI так и с использованием компонентов Indy.

Windows API

При использовании Windows API для написания функции пинга мы воспользуемся библиотекой ICMP(icmp.dll) которая предоставляет нам интерфейс для работы с одноименным протоколом. В этой библиотеке реализованы 3 функции с которым мы будем в дальнейшем работать. В интерпретации Delphi их объявления выглядят следующим образом:


function IcmpCreateFile:Thandle; StdCall;

function IcmpCloseHandle(H:Thandle):Bool; StdCall;

function IcmpSendEcho(IcmpHandle:Thandle;
                      DestinationAddress:TipAddr;
                      RequestData:pointer;
                      RequestSize:word;
                      RequestOptions:POption_Information;
                      ReplyBuffer:pointer;
                      ReplySize:integer;
                      Timeout:integer):Integer; stdcall;

Первая их них (IcmpCreateFile) - создает соединение с которым мы собираемся работать. Вторая - закрывает его, а третья - посылает через установленное соединение соответствующие данные.

Остановимся подробнее на функции IcmpSendEcho. Принимаемые ею параматры "звучат" следующим образом:

  • IcmpHandle - идентификатор соединения установленного при помощи IcmpCreateFile
  • DestinationAddress - Адрес пингуемого хоста
  • RequestData - буфер с данными, которые посылаются при запросе.
  • RequestSize - размер буфера запроса
  • RequestOptions - дополнительные свойства запроса
  • ReplyBuffer - адрес буфера для приема результата
  • ReplySize - размер буфера приема
  • Timeout - время в течении которого мы ожидаем ответа от хоста
  • Результат функции - количество записей типа ICMP_ECHO_REPLY сохраненных в ReplyBuffer. Статус каждой записи хранится в соответствующем поле этой записи. При неудачном вызове функция возвращает значение NULL, а дополнительная информация доступна при вызове GetLastError.
Структура ICMP_ECHO_REPLY имеет следующий вид:

Ticmp_echo_reply=record
  Address       : TipAddr;               // Ответившый адрес
  Status        : integer;               // Статус ответа
  RoundTripTime : integer;               // Время прохождения пакета
  DataSize      : word;                  // Размер данных ответа в байтах
  Reserved      : word;                  // Зарезервировани
  Data          : pointer;               // Указатель на буффер с ответом
  Options       : Toption_Information;   // Опции ответа.
End;
Кроме нее может мы будем использовать расширенный вариант структуры ICMP_ECHO_REPLY:

TsmICMP_Echo_Reply=record
  Address       : TipAddr;               // Ответившый адрес
  Status        : integer;               // Статус ответа
  RoundTripTime : integer;               // Время прохождения пакета
  DataSize      : word;                  // Размер данных ответа в байтах
  Reserved      : word;                  // Зарезервировани
  Data          : pointer;               // Указатель на буффер с ответом
  Options       : Toption_Information;   // Опции ответа.
  Data: array[0..255] of Char;
end;
Теперь для реализации пинга хоста мы:
  • Создаем соединение
  • Вызываем ICMPSendEcho
  • Обрабатываем результат
  • Закрываем соединение
Эти действия удобно оформить в виде процедуры:

procedure Ping (const Address, EchoString : PChar;
        var PingReply: TsmICMP_Echo_Reply;
        const PingTimeout: Integer = 500);
var
  IPAddress: TipAddr;
  ICMPPort: THandle;
begin
  //  Конвертация IP в понятный для API формат
  IPAddress := inet_addr (Address);
  //  Проверка корректности конвертации
  if (IPAddress = INADDR_NONE) then
  begin
    raise Exception.Create ('Function call inet_addr failed.  ' +
            'The IP address is probably invalid.');
  end;
  //  Открытие соединения
  ICMPPort := IcmpCreateFile ();
  //  Проверка правильности открытия
  if (ICMPPort = INVALID_HANDLE_VALUE) then
  begin
    raise Exception.Create ('Function call IcmpCreateFile failed.');
  end;
  //  Отправка запроса "пинг"
  IcmpSendEcho (ICMPPort, IPAddress,
          EchoString, Length (EchoString), nil,
          @PingReply, SizeOf (PingReply), PingTimeout);
  //  Закрытие соединения
  IcmpCloseHandle (ICMPPort);
end;
Теперь при использовании в коде программы конструкции:
Ping('127.0.0.1',nil,Reply,5000);
В переменной Reply мы получим результат пинга.

Использование Indy

Для тех, кто не знаком с практикой программирования с использованием Windows API, можно воспользоваться встроенными в Delphi компонентами для работы с сетью. В частности для реализации пинга на уровне объектно-ориентированного программирования мы воспользуемся компонентом "IdICMPClient" из состава Indy.

Для этого сначала на пустой форме создадим экземпляр класса TIdICMPClient перетащив его с палитры компонентов "Indy Clients". Затем поместим на форму стандартную кнопку (TButton) и в ее реакции на нажатие мышкой запишем код:


Self.IdIcmpClient1.Host:='localhost';//вместо 127.0.0.1 здесь можно использовать имя "localhost"
Self.IdIcmpClient1.Ping;
И не забудем выставить соответствующий интервал ожидания ответа. По завершении которого (или получении данных от пингуемого) вызывается обработчик
TidIcmpClient.OnReply(Sender:TComponent; const AReplyStatus:TReplyStatus);
В котором мы реализуем вывод данных пинга на экран:

procedure TForm1.IdIcmpClient1Reply(ASender: TComponent;
  const AReplyStatus: TReplyStatus);
begin
 ListBox1.Items.Add('Reply:'+IntToStr(AReplyStatus.MsRoundTripTime));
end;

Послесловие

Конечно для Пинга удаленной машины вы всегда можете обратится к соответствующей програмке комендной строки, поскольку 100% операционных систем эту команду поддерживают. Но не всегда дело ограничивается одинм пингом - особенно если вы реализуете собстреввнй интерфейс поверх IP. В таком случае вызов внешней программы при каждой проверке - слишком большая роскошь.

Кроме того, поняв, как работать с ICMP.DLL, вы сможете использовать эти возможности и в других - в том числе и «военных» - делях

Листинг.1 Определения функций ICMP.DLL


unit icmp;
interface
uses windows;
Const
// IP_STATUS codes returned from IP APIs

   IP_STATUS_BASE                 = 11000;
   IP_SUCCESS                     = 0;
   IP_BUF_TOO_SMALL               = (IP_STATUS_BASE + 1);
   IP_DEST_NET_UNREACHABLE        = (IP_STATUS_BASE + 2);
   IP_DEST_HOST_UNREACHABLE       = (IP_STATUS_BASE + 3);
   IP_DEST_PROT_UNREACHABLE       = (IP_STATUS_BASE + 4);
   IP_DEST_PORT_UNREACHABLE       = (IP_STATUS_BASE + 5);
   IP_NO_RESOURCES                = (IP_STATUS_BASE + 6);
   IP_BAD_OPTION                  = (IP_STATUS_BASE + 7);
   IP_HW_ERROR                    = (IP_STATUS_BASE + 8);
   IP_PACKET_TOO_BIG              = (IP_STATUS_BASE + 9);
   IP_REQ_TIMED_OUT               = (IP_STATUS_BASE + 10);
   IP_BAD_REQ                     = (IP_STATUS_BASE + 11);
   IP_BAD_ROUTE                   = (IP_STATUS_BASE + 12);
   IP_TTL_EXPIRED_TRANSIT         = (IP_STATUS_BASE + 13);
   IP_TTL_EXPIRED_REASSEM         = (IP_STATUS_BASE + 14);
   IP_PARAM_PROBLEM               = (IP_STATUS_BASE + 15);
   IP_SOURCE_QUENCH               = (IP_STATUS_BASE + 16);
   IP_OPTION_TOO_BIG              = (IP_STATUS_BASE + 17);
   IP_BAD_DESTINATION             = (IP_STATUS_BASE + 18);

// The next group are status codes passed up on status indications to
// transport layer protocols.
   IP_ADDR_DELETED                = (IP_STATUS_BASE + 19);
   IP_SPEC_MTU_CHANGE             = (IP_STATUS_BASE + 20);
   IP_MTU_CHANGE                  = (IP_STATUS_BASE + 21);
   IP_UNLOAD                      = (IP_STATUS_BASE + 22);
   IP_GENERAL_FAILURE             = (IP_STATUS_BASE + 50);
   MAX_IP_STATUS                  = IP_GENERAL_FAILURE;
   IP_PENDING                     = (IP_STATUS_BASE + 255);

// Values used in the IP header Flags field.
   IP_FLAG_DF                     = $2;        //  Don't fragment this packet.

// Supported IP Option Types.
// These types define the options which may be used in the OptionsData field
// of the ip_option_information structure.  See RFC 791 for a complete
// description of each.
   IP_OPT_EOL                     = 0;         //  End of list option
   IP_OPT_NOP                     = 1;         //  No operation
   IP_OPT_SECURITY                = $82;       //  Security option
   IP_OPT_LSRR                    = $83;       //  Loose source route
   IP_OPT_SSRR                    = $89;       //  Strict source route
   IP_OPT_RR                      = $7;        //  Record route
   IP_OPT_TS                      = $44;       //  Timestamp
   IP_OPT_SID                     = $88;       //  Stream ID (obsolete)

   MAX_OPT_SIZE                   = 40;        //  Maximum length of IP options in bytes


Type

 TIPAddr=integer;     // An IP address.
 TIPMask=integer;     // An IP subnet mask.
 TIP_STATUS=Integer;  // Status code returned from IP APIs.

POption_Information=^TOption_Information;
TOption_Information=record
                     Ttl:byte;             // Time To Live
                     Tos:byte;             // Type Of Service
                     Flags:byte;           // IP header flags
                     OptionsSize:byte;     // Size in bytes of options data
                     OptionsData:pointer;  // Pointer to options data
                    end;
Picmp_echo_reply=^Ticmp_echo_reply;
Ticmp_echo_reply=record
                    Address:TipAddr;                // Replying address
                    Status:integer;                 // Reply IP_STATUS
                    RoundTripTime:integer;          // RTT in milliseconds
                    DataSize:word;                  // Reply data size in bytes
                    Reserved:word;                  // Reserved for system use
                    Data:pointer;                   // Pointer to the reply data
                    Options:Toption_Information;    // Reply options
                 end;
TsmICMP_Echo_Reply=record
                    Address:TipAddr;                // Replying address
                    Status:integer;                 // Reply IP_STATUS
                    RoundTripTime:integer;          // RTT in milliseconds
                    DataSize:word;                  // Reply data size in bytes
                    Reserved:word;                  // Reserved for system use
                    DataPtr:pointer;                // Pointer to the reply data
                    Options:Toption_Information;    // Reply options
                    Data: array[0..255] of Char;
                 end;

function IcmpCreateFile:Thandle; StdCall;
function IcmpCloseHandle(H:Thandle):Bool; StdCall;
function IcmpSendEcho(IcmpHandle:Thandle;DestinationAddress:TipAddr;
        RequestData:pointer;RequestSize:word;
                      RequestOptions:POption_Information;ReplyBuffer:pointer;
        ReplySize:integer;Timeout:integer):Integer; stdcall;
Implementation
function IcmpCreateFile;        external 'Icmp.Dll';
function IcmpCloseHandle;       external 'Icmp.Dll';
Function IcmpSendEcho;          external 'Icmp.Dll';
end.

Листинг.2 Процедура "Пинга"


unit pingModule;
interface
uses icmp, Windows;
const
   INADDR_NONE: integer = -1;
procedure Ping (const Address, EchoString : PChar;var PingReply: TsmICMP_Echo_Reply;
        const PingTimeout: Integer = 500);
function PingStatusToStr (StatusCode: integer): string;
function inet_addr(IPAddress: PChar): TipAddr; StdCall;
implementation
uses Dialogs, SysUtils;
procedure Ping (const Address, EchoString : PChar;
        var PingReply: TsmICMP_Echo_Reply;
        const PingTimeout: Integer = 500);
var
  IPAddress: TipAddr;
  ICMPPort: THandle;
begin
  IPAddress := inet_addr (Address);
  if (IPAddress = INADDR_NONE) then
  begin
    raise Exception.Create ('Function call inet_addr failed.  ' +
            'The IP address is probably invalid.');
  end;
  ICMPPort := IcmpCreateFile ();
  if (ICMPPort = INVALID_HANDLE_VALUE) then
  begin
    raise Exception.Create ('Function call IcmpCreateFile failed.');
  end;
  IcmpSendEcho (ICMPPort, IPAddress,
          EchoString, Length (EchoString), nil,
          @PingReply, SizeOf (PingReply), PingTimeout);
  IcmpCloseHandle (ICMPPort);
end;

function PingStatusToStr (StatusCode: integer): string;
begin
  case (StatusCode) of
    IP_SUCCESS: Result := 'IP_SUCCESS';
    IP_BUF_TOO_SMALL: Result := 'IP_BUF_TOO_SMALL';
    IP_DEST_NET_UNREACHABLE: Result := 'IP_DEST_NET_UNREACHABLE';
    IP_DEST_HOST_UNREACHABLE: Result := 'IP_DEST_HOST_UNREACHABLE';
    IP_DEST_PROT_UNREACHABLE: Result := 'IP_DEST_PROT_UNREACHABLE';
    IP_DEST_PORT_UNREACHABLE: Result := 'IP_DEST_PORT_UNREACHABLE';
    IP_NO_RESOURCES: Result := 'IP_NO_RESOURCES';
    IP_BAD_OPTION: Result := 'IP_BAD_OPTION';
    IP_HW_ERROR: Result := 'IP_HW_ERROR';
    IP_PACKET_TOO_BIG: Result := 'IP_PACKET_TOO_BIG';
    IP_REQ_TIMED_OUT: Result := 'IP_REQ_TIMED_OUT';
    IP_BAD_REQ: Result := 'IP_BAD_REQ';
    IP_BAD_ROUTE: Result := 'IP_BAD_ROUTE';
    IP_TTL_EXPIRED_TRANSIT: Result := 'IP_TTL_EXPIRED_TRANSIT';
    IP_TTL_EXPIRED_REASSEM: Result := 'IP_TTL_EXPIRED_REASSEM';
    IP_PARAM_PROBLEM: Result := 'IP_PARAM_PROBLEM';
    IP_SOURCE_QUENCH: Result := 'IP_SOURCE_QUENCH';
    IP_OPTION_TOO_BIG: Result := 'IP_OPTION_TOO_BIG';
    IP_BAD_DESTINATION: Result := 'IP_BAD_DESTINATION';
    IP_ADDR_DELETED: Result := 'IP_ADDR_DELETED';
    IP_SPEC_MTU_CHANGE: Result := 'IP_SPEC_MTU_CHANGE';
    IP_MTU_CHANGE: Result := 'IP_MTU_CHANGE';
    IP_UNLOAD: Result := 'IP_UNLOAD';
    IP_GENERAL_FAILURE: Result := 'IP_GENERAL_FAILURE';
  else
    Result := '';
  end;
end;

function inet_addr;          external 'WSock32.Dll';

end.

Листинг. 3. Демонстрация пингов


unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, IdBaseComponent, IdComponent, IdRawBase, IdRawClient,
  IdIcmpClient, StdCtrls, IdUDPBase, IdUDPClient, IdSNTP;

type
  TForm1 = class(TForm)
    Button1: TButton;
    ListBox1: TListBox;
    IdIcmpClient1: TIdIcmpClient;
    IdSNTP1: TIdSNTP;
    procedure Button1Click(Sender: TObject);
    procedure IdIcmpClient1Reply(ASender: TComponent;
      const AReplyStatus: TReplyStatus);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;


implementation
uses pingModule,icmp;
{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var Reply:TsmICMP_Echo_Reply;
begin
Self.IdIcmpClient1.Host:='localhost';
Self.IdIcmpClient1.TTL:=192;
Self.IdIcmpClient1.Ping;
Ping('127.0.0.1',nil,Reply,5000);
ListBox1.Items.Add('RawReply:'+IntToStr(Reply.RoundTripTime));
end;

procedure TForm1.IdIcmpClient1Reply(ASender: TComponent;
  const AReplyStatus: TReplyStatus);
begin
ListBox1.Items.Add('Reply:'+IntToStr(AReplyStatus.MsRoundTripTime));
end;

end.

http://subscribe.ru/
E-mail: ask@subscribe.ru
Отписаться
Убрать рекламу


В избранное