Персональный блог Игоря Антонова aka "spider_net"

Пишем программу для подсчета трафика на Delphi


Рубрика: Delphi -> Программирование -> Журнал Хакер -> Статьи
Метки: |
Просмотров: 7080
Пишем программу для подсчета трафика на 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 столбцов:

  • Интерфейс
  • Тип интерфейса
  • Физический адрес
  • Скорость
  • Отправлено
  • Принято
  • ErrorOut
  • 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 в месяц.

    Если у тебя что-то не заработало - не отчаивайся, а скорее вставляй наш 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/

    Оставьте комментарий!
    comments powered by HyperComments