Відразу хочу засмутити Вас, Дорогий Читачу. 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
[в]
Відкритий обробник, який повертається функцією
IcmpCreateFile
.
адреса призначення
[в]
Адреса призначення відлуння запиту
IPv4
, задається у вигляді
IPAddr
структури.
RequestData
[в]
Вказівник на буфер, який містить запит, що посилається.
RequestSize
[in
]
Розмір, у байтах, буфера даних запиту, який вказує параметр
RequestData
.
RequestOptions
[в, необов'язково]
Вказівник на опції IP заголовка для запиту задається у вигляді
IP_OPTION_INFORMATION
структури. На 64-бітних платформах задається у вигляді
IP_OPTION_INFORMATION32
структури.
Цей параметр може мати значення NULL , якщо параметри IP заголовка не уточнюються.
ReplyBuffer
[вихід]
Буфер, що утримує кілька відповідей на відлуння запит. Після повернення буффер містить масив структури
ICMP_ECHO_REPLY
наступних параметрів і даних для відповіді. Буффер повинен бути великим, щоб містити щонайменше одну
ICMP_ECHO_REPLY
структуру, а також
RequestSize
байтів даних
.
На 64-бітних платформах після повернення буфер містить масив ICMP_ECHO_REPLY32 структури.
ReplySize
[в]
Вказує розмір у байтах для буфера відповіді. Буффер повинен бути великим, щоб містити щонайменше одну
ICMP_ECHO_REPLY
структуру, а також
RequestSize
байтів даних
.
на 64-бітних платформах буффер повинен бути достатньо великим, щоб містити щонайменше одну
ICMP_ECHO_REPLY32
структуру та плюс
RequestSize
байтів даних.
Цей буфер повинен бути також більшим для утримання понад 8 байтів даних (Розмір повідомлення помилки ICMP ).
Час очікування
[в]
Час очікування у мілісекундах.
Повертається значення
Функція 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-адресу та кнопка запуску.
віджет.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-адресу.
Демонстрацію роботи програми наведено у відеоуроці.
Без строки
в заголовочном файле не работает валидатор.