Пишем программу для подсчета трафика на Delphi
Рубрика: Delphi -> Программирование -> Журнал Хакер -> Статьи
Метки: delphi | траффик
Просмотров: 7080
Во времена сумасшедшего использования таких технологий, как GPRS/EDGE, DSL, приходится всерьез задумываться о потраченном трафике. Цены на него, конечно, падают, но и потребности наши возрастают. Если пытаться запомнить, сколько ты скачал вчера, а сколько - сегодня, то можно начинать собираться в психушку, поскольку от таких расчетов мозг, скорее всего, сильно заглючит. Чтобы этого не случилось, мы покажем тебе, как можно автоматизировать процесс подсчета трафика.
Обзор инструментария
Поскольку наша программа будет иметь графический интерфейс, то и писать ее лучше всего на Delphi. Версия любимой среды разработки значения не имеет. Помимо Delphi, нам потребуется доступ в инет для заглядывания в MSDN, а если ты не в ладах с английским языком, то не забудь обзавестись англо-русским словарем, поскольку всю информацию Microsoft приводит на языке посетителей порносайтов.
Теоретическая часть
Перед рассмотрением практического примера, необходимо ознакомиться с теорией, поэтому отложи клаву подальше и приготовься к впитыванию инфы. Получить информацию о трафике можно несколькими способами. Мы воспользуемся самым продвинутым - функцией GetIfTable из библиотеки IPHLPAPI.DLL (в модулях, идущих вместе с Delphi, ее описания нет). Эта функция позволяет с легкостью получить информацию о трафике и сетевых интерфейсах. Работать с ней очень просто, а библиотека, помимо нее, содержит еще массу функций для получения всевозможной информации о работе сети и т.д. Эта библиотека существует и в Windows 9x, и в ее последних версиях (2K/XP/2K3/Vista), где она отличается появлением новых функций (изменение структур). Чтобы не волноваться за работоспособность своей программы, советую обратиться к MSDN и сделать в коде проверку версии Windows. В своем примере я буду ориентироваться на все еще самую популярную в народе Windows XP.
Итак, как я уже сказал, модуля, в котором были бы описаны структуры и функции этой библиотеки, вместе с Delphi не идет, поэтому нам в своем проекте придется их описывать самостоятельно. Первым делом давай рассмотрим функцию, которая нам понадобится.
function GetIfTable(
pIfTable: PMIB_IFTABLE;
pdwSize: PULONG;
bOrder: BOOL;
):DWORD;
При успешном выполнении функция возвращает NO_ERROR, в противном случае - код ошибки. После выполнения функции все данные запишутся в структуру pIfTable, указатель которой передается в первом параметре. Структура MIB_IF_TABLE выглядит следующим образом:
TMibIfTable = packed record
dwNumEntries : DWORD;
Table : TMibIfArray;
end;
После успешного выполнения в dwNumEntries будет содержаться количество сетевых интерфейсов. Пробежавшись по ним в цикле, мы сможем получить всю необходимую нам информацию. Информация о каждом интерфейсе будет храниться в соответствующей MIB-таблице. Структуру MIB таблицы нам также придется объявлять самим.
TMibIfRow = packed record
wszName : array[0..255] of WideChar;
dwIndex : DWORD;
dwType : DWORD;
dwMtu : DWORD;
dwSpeed : DWORD;
dwPhysAddrLen : DWORD;
bPhysAddr : array[0..7] of Byte;
dwAdminStatus : DWORD;
dwOperStatus : DWORD;
dwLastChange : DWORD;
dwInOctets : DWORD;
dwInUcastPkts : DWORD;
dwInNUCastPkts : DWORD;
dwInDiscards : DWORD;
dwInErrors : DWORD;
dwInUnknownProtos : DWORD;
dwOutOctets : DWORD;
dwOutUCastPkts : DWORD;
dwOutNUCastPkts : DWORD;
dwOutDiscards : DWORD;
dwOutErrors : DWORD;
dwOutQLen : DWORD;
dwDescrLen : DWORD;
bDescr : array[0..255] of Char;
end;
Не пугайся такого большого количества свойств :), сейчас я поясню, что они собой представляют:
Более полно описание этой структуры ты найдешь в MSDN.
Пример использования
Теперь, когда мы владеем всей необходимой информацией, самое время написать примерчик. Итак, запускай Delphi, создавай новый проект типа Application и кидай на форму таймер и ListView. Создай в нем 8 столбцов:
В ListView мы будем отображать всю полученную информацию, поэтому нужно придать ему соответствующий вид. Выстави следующие свойства:
GridLines = true
HotTrack = true
RowSelect = true
ViewStyle = vsReport
Форма готова, можно переходить к кодингу. Придвинь к себе клавиатуру и первым делом опиши в модуле проекта все структуры, которые мы разбирали: MibIfRow, MibIfTable. Помимо этого, объяви новый тип TMacAddress=array[1..8] и TMibIfArray=array[0..512] of TMibIfRow. В TMacAddress мы будем хранить физический адрес устройства. Для всех созданных структур создай указатель. Когда опишешь все структуры, не забудь объявить нашу функцию. Делается это следующим образом. После раздела var модуля нашей формы напиши:
function GetIfTable(pIfTable:PMibIfTable; pdwSize: PULONG;
bOrder: boolean ): DWORD; stdCall; external 'IPHLPAPI.DLL';
Если ты работал с библиотеками DLL, то тебе должно быть все понятно, в противном случае знай, что таким образом можно обращаться к функциям, которые находятся в Dll. Создай обработчик события OnTimer для нашего таймера и напиши в нем код из соответствующей врезки, а я объясню, что в нем происходит.
Перед использованием GetIfTable нужно выделить необходимую память под структуру MibIfTable. Память выделяется с помощью New. Все, память выделили, а значит, можно попытаться вызвать функцию GetIfTable. Результат ее выполнения запишется в переменную _error. Теперь проверь значение этой переменной. Если оно не равно NO_ERROR, то это означает, что тебя посетила птица обломинго и нужно показать печальное сообщение, прервать выполнение процедуры и сверить свой код с листингом. Если же все нормально, то в цикле можно начинать разбирать наши данные.
Как я уже говорил, в dwNumEntries структуры TMibIfTable хранится количество интерфейсов. Запускаем цикл от 0 до dwNumEntries-1 и начинаем радоваться полученной информации. При добавлении в ListView я использую функции для преобразования полученных данных. Зачем? Отвечаю. Например, значение dwOutOctets приводится в байтах. Не думаю, что в программе учета трафика будет удобно смотреть на большое количество постоянно меняющихся цифр. Поэтому можно реализовать отображение трафика в привычных нам единицах: Кб, Мб, Гб. Чтобы решить эту задачу, я создал функцию, которая будет высчитывать трафик в определенных единицах измерения информации. Код приводить не буду - если ты немного знаком с Delphi, то проблем с написанием подобного кода у тебя не возникнет. Подобную же функцию я создал для определения скорости соединения. В моем проекте она называется SpeedToStr(). Ее код идентичен функции Traff, изменены только константы, в которых хранятся значения каждой единицы измерения скорости (bps, Kbps, Mbps).
В самом начале статьи я рассказывал тебе про несколько типов возможных сетевых интерфейсов. После выполнения функции мы получаем числовой идентификатор типа, переварить который без заглядывания в руководства сможет разве что Крис Касперски. Чтобы не обидеть абсолютное большинство пользователей, мы используем функцию:
GetInterfaceType(types:integer):string;
В качестве параметра ей нужно передать идентификатор типа интерфейса, а она, в свою очередь, вернет нам его символьное имя. Код функции ты можешь увидеть в исходнике, который дожидается тебя на нашем диске.
Вернемся к основному коду программы. Обрати внимание, как я получаю физический адрес интерфейса. Поскольку адрес хранится в массиве типа byte, нам нужно написать функцию, которая бы приводила его в понятный человеку вид. Чтобы это сделать, я создал еще одну функцию:
GetStrMac(Mac: TMacAddress; size: integer): string;
В качестве параметров ей нужно передать тип TMacAddress (о нем я говорил в самом начале статьи, и ты должен был уже описать его в своем проекте) и длину физического адреса. Все данные берутся из заполненной структуры. Для экономии журнального места я не буду приводить здесь весь код программы - его ты сможешь найти на DVD. Здесь я лишь вкратце расскажу тебе, что в нем происходит. Первым делом в коде функции я делаю проверку параметра size. Если он равен нулю, то физический адрес просто отсутствует. Чтобы как-то представить это пользователю, в качестве результата я просто возвращаю «00» в привычном для отображения MAC-адреса виде. Если же size не равен нулю, в цикле необходимо получать данные из массива, в котором хранится адрес, и с помощью функции IntToHex приводить его к шестнадцатеричному виду и разделять символом «-». После того как разбор завершится, нужно удалить самый последний символ нашего результата, которым всегда будет лишнее «-». Чтобы все было красиво, я его удаляю и возвращаю результат.
Он сказал: «Конец»
Наш пример готов для компиляции и жестокого тестирования. Начало созданию своей программы для учета трафика положено, тебе остается только усовершенствовать пример. После этого ты сможешь следить за тем, сколько драгоценного трафика ты тратишь, или даже и сделать на основе нашего исходника свою платную программу и продавать ее злым буржуям за 19,99 в месяц.
Если у тебя что-то не заработало - не отчаивайся, а скорее вставляй наш DVD в дисковод и смотри мой исходник. Возникшие вопросы смело присылай мне на мыло: a@iantonov.me
Полезные ресурсы
http://msdn.microsoft.com/ - легендарный MSDN;
vr-online.ru - новости, статьи, журналы, форум;
Torry.NET - самый большой архив компонент для Delphi. Здесь можно найти уже готовые компоненты для подсчета трафика.
Копия программы, написание которой мы рассмотрим в этой статье, может стоить до 20 долларов (например, DUmeter). Поэтому впитывай знания и не теряйся :).
Обработчик события OnTimer
var
_MibIfTable:PMibIfTable;
_P:Pointer;
i:integer;
_buflen:dword;
_error:dword;
begin
listview1.Items.Clear;
_buflen:=sizeof(_MibIfTable^);
New(_MibIfTable);
_P:=_MibIfTable;
_error:=GetIfTable(_MibIfTable, @_buflen, false);
if _error <> NO_ERROR then
begin
ShowMessage('Произошла ошибка!');
Exit;
end;
for i:=0 to TMibIfTable(_P^).dwNumEntries-1 do
with ListView1.Items.Add do
begin
caption:=Trim(TMibIfTable(_p^).table[i].bDescr);
subitems.Add(GetInterfaceType(TMibIfTable(_P^).table[i].dwtype));
subitems.Add(GetStrMac(TMacAddress(TMibIfTable(_p^).Table[i].bPhysAddr),
TMibIfTable(_p^).table[i].dwPhysAddrLen));
subitems.add(SpeedToStr(TMibIfTable(_p^).table[i].dwSpeed));
subitems.Add(Traff(TMibIfTable(_p^).table[i].dwOutOctets));
subitems.Add(Traff(TMibIfTable(_p^).table[i].dwInOctets));
subitems.Add(IntToStr(TMibIfTable(_p^).table[i].dwOutErrors));
subitems.Add(IntToStr(TMibIfTable(_p^).table[i].dwInErrors));
end;
dispose(_MibIfTable);
Статья опубликована журнале "Хакер" (http://xakep.ru). Апрель 2007 г. Издательство GameLand.
Ссылка на опубликованную статью сайта издания: http://www.xakep.ru/magazine/xa/100/116/1.asp
Ссылка на журнал: http://www.xakep.ru/magazine/xa/100/