Сходу хочу расстроить Вас, Дорогой Читатель. Qt не обладает функционалом для работы с протоколом ICMP и соответственно придется использовать для этих целей API целевой операционной системы. Впрочем, это не удивительно. Протокол ICMP является низкоуровневым протоколом, и для работы с ним требуется использование сырых сокетов, которые не реализованы в Qt .
Но это не является особой проблемой, поскольку в основных целевых платформах имеется необходимый API для реализации ping посылок. Например Microsoft предоставляет простое использование ICMP протокола на основе функции IcmpSendEcho.
Описание IcmpSendEcho
Функция IcmpSendEcho отсылает эхо запросы IPv4 ICMP и возвращает ответы на эхо запросы. Вызов возвращается когда выходит время ожидания или заполняется буфер ответа.
- DWORD IcmpSendEcho(
- _In_ HANDLE IcmpHandle,
- _In_ IPAddr DestinationAddress,
- _In_ LPVOID RequestData,
- _In_ WORD RequestSize,
- _In_opt_ PIP_OPTION_INFORMATION RequestOptions,
- _Out_ LPVOID ReplyBuffer,
- _In_ DWORD ReplySize,
- _In_ DWORD Timeout
- );
Параметры
IcmpHandle
[in]
Открытый обработчик, возвращаемый функцией
IcmpCreateFile
.
DestinationAddress
[in]
Адрес назначения эхо запроса
IPv4
, задаётся в виде
IPAddr
структуры.
RequestData
[in]
Указатель на буффер, который содержит посылаемый запрос.
RequestSize
[in
]
Размер, в байтах, буффера данных запроса, на который указывает параметр
RequestData
.
RequestOptions
[in, optional]
Указатель на опции IP заголовка для запроса, задаётся в виде
IP_OPTION_INFORMATION
структуры. На 64-битных платформах задаётся в виде
IP_OPTION_INFORMATION32
структуры.
Этот параметр может иметь значение NULL , если опции IP заголовка не уточняются.
ReplyBuffer
[out]
Буфер, удерживающий нескольку ответов на эхо запрос. По возвращении, буффер содержит массив структуры
ICMP_ECHO_REPLY
следующих параметров и данных для ответа. Буффер должен быть большим, для того чтобы содержать по меньшей мере одну
ICMP_ECHO_REPLY
структуру, а также
RequestSize
байтов данных
.
На 64-битных платформах по возвращении буффер содержит массив ICMP_ECHO_REPLY32 структуры.
ReplySize
[in]
Указывает размер, в байтах, для буффера ответа. Буффер должен быть большим, для того чтобы содержать по меньшей мере одну
ICMP_ECHO_REPLY
структуру, а также
RequestSize
байтов данных
.
на 64-битных платформах буффер должен быть достаточно большим, чтобы содержать по меньшей мере одну
ICMP_ECHO_REPLY32
структуру и плюс
RequestSize
байтов данных.
Это буффер должен быть также большим для удержания более 8 байтов данных (Размер сообщения ошибки ICMP ).
Timeout
[in]
Время ожидания в миллисекундах.
Возвращаемое значение
Функция IcmpSendEcho возвращает число ICMP_ECHO_REPLY или ICMP_ECHO_REPLY32 структур, сохранённых в ReplyBuffer . Статус каждого ответа содержится в структуре. Если возвращаемое значение равно 0, вызывается функция GetLastError для дополнительной информации.
Если функция терпит неудачу, то с помощью функции GetLastError возвратит код ошибки со следующими значениями:
- ERROR_INSUFFICIENT_BUFFER - Область передаваемых данных для системного вызова слишком мала. Ошибка возвращается, если параметр ReplySize указывает на буффер ReplyBuffer, который слишком мал.
- ERROR_INVALID_PARAMETER - Неверный параметр был передан в функцию. Эта ошибка возникает в том случае, если параметр IcmpHandle содержит ошибочный обработчик. Эта ошибка может быть возвращена также, если параметр ReplySize имеет значение меньшее чем размер ICMP_ECHO_REPLY или ICMP_ECHO_REPLY32 структур.
- ERROR_NOT_ENOUGH_MEMORY - Недостаточно памяти для завершения операции.
- ERROR_NOT_SUPPORT - Запрос не поддерживается. Эта ошибка возвращается, если нет стека IPv4 на локальном компьютере.
- IP_BUF_TOO_SMALL - Размер ReplyBuffer, указанный в параметре ReplySize является слишком малым.
- Другие - Используйте FormatMessage, для того чтобы получить строковое сообщение о возвращаемой ошибке.
Работа с ICMP
Функция IcmpSendEcho посылает ICMP эхо запрос по указанному адресу и возвращает число пинятых и сохранённых ответов в ReplyBuffer . Функция IcmpSendEcho является синхронной функцией и возвращает значение после окончания времени ожидания для ответа. Если возвращается ноль, то для получения расширенной информации необходимо вызвать функцию GetLastError .
Для того, чтобы использовать данную часть WinAPI, необходимо в .Pro файле проекта прописать две следующих строки:
- LIBS += -lws2_32
- LIBS += -liphlpapi
А также подключить следующие библиотеки:
- #include "winsock2.h"
- #include "iphlpapi.h"
- #include "icmpapi.h"
Главное окно приложения
Для того, чтобы ознакомиться с протоколом ICMP создадим окно со следующим интерфейсом, где будет отображаться ответ на запросы, IP-адрес и кнопка запуска.
widget.h
Кроме объявления слота от кнопки Вы здесь ничего интересного не увидите, но тем не менее привожу заголовочный файл.
- #ifndef WIDGET_H
- #define WIDGET_H
- #include <QWidget>
- #include <QDebug>
- namespace Ui {
- class Widget;
- }
- class Widget : public QWidget
- {
- Q_OBJECT
- public:
- explicit Widget(QWidget *parent = 0);
- ~Widget();
- private slots:
- void on_pushButton_clicked();
- private:
- Ui::Widget *ui;
- };
- #endif // WIDGET_H
widget.cpp
А вот здесь самое интересное. По нажатию кнопки Мы будем пинговать выбранный нами IP-адрес. Для красоты решения я ввёл валидацию IP-адреса в поле lintEdit.
- #include "widget.h"
- #include "ui_widget.h"
- #include "winsock2.h"
- #include "iphlpapi.h"
- #include "icmpapi.h"
- Widget::Widget(QWidget *parent) :
- QWidget(parent),
- ui(new Ui::Widget)
- {
- ui->setupUi(this);
- // Производим валидацию вводимых данных IP-адреса
- QString ipRange = "(?:[0-1]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])";
- QRegExp ipRegex ("^" + ipRange
- + "\\." + ipRange
- + "\\." + ipRange
- + "\\." + ipRange + "$");
- QRegExpValidator *ipValidator = new QRegExpValidator(ipRegex, this);
- ui->lineEdit->setValidator(ipValidator);
- }
- Widget::~Widget()
- {
- delete ui;
- }
- void Widget::on_pushButton_clicked()
- {
- // Объявляем переменные
- HANDLE hIcmpFile; // Обработчик
- unsigned long ipaddr = INADDR_NONE; // Адрес назначения
- DWORD dwRetVal = 0; // Количество ответов
- char SendData[32] = "Data Buffer"; // Буффер отсылаемых данных
- LPVOID ReplyBuffer = NULL; // Буффер ответов
- DWORD ReplySize = 0; // Размер буффера ответов
- // Устанавливаем IP-адрес из поля lineEdit
- ipaddr = inet_addr(ui->lineEdit->text().toStdString().c_str());
- hIcmpFile = IcmpCreateFile(); // Создаём обработчик
- // Выделяем память под буффер ответов
- ReplySize = sizeof(ICMP_ECHO_REPLY) + sizeof(SendData);
- ReplyBuffer = (VOID*) malloc(ReplySize);
- // Вызываем функцию ICMP эхо запроса
- dwRetVal = IcmpSendEcho(hIcmpFile, ipaddr, SendData, sizeof(SendData),
- NULL, ReplyBuffer, ReplySize, 1000);
- // создаём строку, в которою запишем сообщения ответа
- QString strMessage = "";
- if (dwRetVal != 0) {
- // Структура эхо ответа
- PICMP_ECHO_REPLY pEchoReply = (PICMP_ECHO_REPLY)ReplyBuffer;
- struct in_addr ReplyAddr;
- ReplyAddr.S_un.S_addr = pEchoReply->Address;
- strMessage += "Sent icmp message to " + ui->lineEdit->text() + "\n";
- if (dwRetVal > 1) {
- strMessage += "Received " + QString::number(dwRetVal) + " icmp message responses \n";
- strMessage += "Information from the first response: ";
- }
- else {
- strMessage += "Received " + QString::number(dwRetVal) + " icmp message response \n";
- strMessage += "Information from the first response: ";
- }
- strMessage += "Received from ";
- strMessage += inet_ntoa( ReplyAddr );
- strMessage += "\n";
- strMessage += "Status = " + pEchoReply->Status;
- strMessage += "Roundtrip time = " + QString::number(pEchoReply->RoundTripTime) + " milliseconds \n";
- } else {
- strMessage += "Call to IcmpSendEcho failed.\n";
- strMessage += "IcmpSendEcho returned error: ";
- strMessage += QString::number(GetLastError());
- }
- ui->textEdit->setText(strMessage); // Отображаем информацию о полученных данных
- free(ReplyBuffer); // Освобождаем память
- }
Итог
В результате у Вас должно получиться приложение, которое будет пинговать выбранный Вами IP-адрес.
Демонстрация работы приложения приведена в видеоуроке.
Без строки
в заголовочном файле не работает валидатор.