Счетчик трафика на Delphi



Во времена сумасшедшего использования таких технологий, как 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;

pIfTable - указатель на структуру MIB_IFTABLE
pwdSize - буфер для получения данных таблицы MIB_IFTABLE
bOrder – сортировка

При успешном выполнении функция возвращает NO_ERROR, в противном случае - код ошибки. После выполнения функции все данные запишутся в структуру pIfTable, указатель которой передается в первом параметре. Структура MIB_IF_TABLE выглядит следующим образом:

TMibIfTable = packed record
dwNumEntries : DWORD;
Table : TMibIfArray;
end;

dwNumEntries - количество сетевых интерфейсов
table - массив структур MIB_IF_ROW

После успешного выполнения в 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;

Не пугайся такого большого количества свойств :), сейчас я поясню, что они собой представляют:
wszName - имя сетевого интерфейса. В последних версиях Windows это свойство заменяет Alias.
dwIndex - порядковый номер соответствующего интерфейса.
dwType - тип интерфейса. Может быть:
· IF_TYPE_OTHER (1) - неизвестный сетевой интерфейс;
· IF_TYPE_ETHERNET_CSMACD (6) – Ethernet;
· IF_TYPE_ISO88025_TOKENRING (9) - Token ring;
· IF_TYPE_PPP (23) – PPP;
· IF_TYPE_SOFTWARE_LOOPBACK (24) – Lookback;
· IF_TYPE_ATM (37) – ATM;
· IF_TYPE_IEEE80211 - IEEE 802.11;
· IF_TYPE_TUNNEL (131) – tunnel;
· IF_TYPE_IEEE1394 (144) - IEEE 1394.
dwMTU - максимальная скорость передачи.
dwSpeed - скорость передачи данных (биты/сек).
dwPhysAddrLen - длина физического адреса устройства.
bPhysAddr - физический адрес интерфейса.
dwAdminStatus - активность интерфейса. Описание принимаемых значений смотри в MSDN.
dwOperStatus - текущий статус интерфейса. Опять же может принимать множество значений, поэтому чтобы не тратить место в статье, снова направляю тебя к MSDN.
dwLastChange - последний измененный статус.
dwInOctets - количество байт, принятых через определенный интерфейс.
dwInUcastPkts - количество направленных пакетов, принятых интерфейсом.
dwInNUCastPkts - количество ненаправленных пакетов, принятых интерфейсом.
dwInDiscards - количество входящих забракованных пакетов.
dwInErrors - количество принятых пакетов, содержащих ошибки.
dwInUnknownProtos - количество принятых забракованных пакетов с неизвестным протоколом.
dwOutOctets - количество байт, отправленных через определенный интерфейс.
dwOutUCastPkts - противоположно dwInUcastPkts.
dwOutNUCastPkts - противоположно dwInNUCastPkts.
dwOutDiscards - противоположно dwInDiscards.
dwOutErrors - противоположно dwInErrors.
dwOutQLen - длина очереди данных.
dwDescrLen - размер bDescr.
bDescr - описание интерфейса.

Более полное описание этой структуры ты найдешь в MSDN.

Пример использования

Теперь, когда мы владеем всей необходимой информацией, самое время написать примерчик. Итак, запускай Delphi, создавай новый проект типа Application и кидай на форму таймер и ListView. Создай в нем 8 столбцов:

1. Интерфейс
2. Тип интерфейса
3. Физический адрес
4. Скорость
5. Отправлено
6. Принято
7. ErrorOut
8. ErrorIn

В 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 в месяц.

Листинг

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].bP
hysAddr), 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);

By: Антонов Игорь aka Spider_NET
E-mail:
WWW: www.vr-online.ru

ВложениеРазмер
traffic_source.rar30.32 КБ
traffic_source_rar_XE6.rar11.45 КБ

Комментарии

22 комментария(ев)
аватар: zahod5277
zahod5277
Дата: Втр, 04/05/2010 - 17:27
Звание: Посвященный
Сообщений: 398

Все хочу написать такой счетчик, да постоянно забываю Smile Когда соберусь - обязательно обращусь к этой статье

аватар: Spider_NET
Spider_NET
Дата: Втр, 04/05/2010 - 21:39
Звание: Мастер
Сообщений: 2455

Собирайся давай =) Я потом планирую написать одноименную статью, только применительно к C#.

аватар: Va-Bank
Va-Bank
Дата: ЧТ, 06/05/2010 - 02:08
Звание: Гуру
Сообщений: 8234

Я чего то не врубился. Вот на одной сетевой карте может быть несколько сетевых интерфейсов. Твоя прога перечисляет именно их или она перечисляет количество физических сетевых карточек?
И еще трафик инета от локального отдельно считается? Если нет, то смысл в проге? Может я чего-то не догоняю? Одни вопросы...

аватар: Эмиль Темербулатов
Эмиль Темербулатов
Дата: СБ, 14/05/2011 - 00:04
Звание: Наблюдатель
Сообщений: 1

У меня такой вопрос, можно ли как то убрать интерфейсы, у которых трафик 0 ?
у меня вот такая пробелма ]]>http://i044.radikal.ru/1105/b9/17ff0603b229.jpg]]>

аватар: shilovec5377
shilovec5377
Дата: ПТ, 13/04/2012 - 22:57
Звание: Наблюдатель
Сообщений: 35

запускаю эту прогу, но чегото выдает ошибку после запуска.]]>ошибка]]>

аватар: Va-Bank
Va-Bank
Дата: СБ, 14/04/2012 - 01:51
Звание: Гуру
Сообщений: 8234

Access Violation значит что ты пытаешься обратиться к какому-то объекту/переменной/т.п., которой не существует.

аватар: osiris11
osiris11
Дата: Втр, 28/07/2015 - 20:23
Звание: Наблюдатель
Сообщений: 7

в Delphi 7 работает нормально.
в Delphi XE6 тот же код выпадает в ошибку!

аватар: Va-Bank
Va-Bank
Дата: Втр, 28/07/2015 - 23:19
Звание: Гуру
Сообщений: 8234

Ну телепатов нет, а гадальные карты я где-то потерял, х.з. что там у тебя

аватар: osiris11
osiris11
Дата: СР, 29/07/2015 - 00:57
Звание: Наблюдатель
Сообщений: 7

это не у меня, это в выложенном исходнике Laughing out loud

аватар: Va-Bank
Va-Bank
Дата: СР, 29/07/2015 - 16:20
Звание: Гуру
Сообщений: 8234

Да я имею ввиду что за ошибка-то? При компиляции, в работе самой проги или что? Ясен пень что делфи7 от ХЕ кардинально отличается.