04.10.2022

Автотрассировка

Сделал простенькую программку, которая отдаленно напоминает PingPlotter. Пока что никаких полезных действий она делать не умеет, кроме наблюдения за состоянием сети с ограниченным набором тестируемых хостов. Интерфейсик простенький, корявенький и наверняка имеет какие-то глюки. Тем не менее, наблюдать она уже могёт.

Если вдруг кому нужно помониторить сеть, то программка лежит здесь. Ее можно просто положить в какое-нибудь удобное место на локальном комьютере и оттуда запускать.

Реализовал я ее в многопоточном режиме, программа создает кучу потоков, которые просто заняты тем, что параллельно отправляют ICMP пакеты и ждут ответа.
Наверное, не самый оптимальный вариант, было бы лучше это сделать в асинхронном режиме и такой вариант ICMP API у Microsoft есть, но мне пока лень разбираться, потому что сделан он не очень-то прозрачным.

Из любопытного: как я уже писал, за основу я взял какой-то вариант из интернета, неизвестного мне автора и причесал его слегка. В процессе отладки долго мучился и не мог понять, почему мне сообщение о превышении периода ожидания не приходит. А как разобрался, так обнаружил и ошибку в исходном коде, которая приводит к утечке памяти. И связана она с использованием в одной процедуре двух подходов к обработке ошибки: условиям и исключительным ситуациям.

Вот фрагмент кода, содержащего ошибку:

try
  ...
  GetMem(pIpe,...);
  ...
  IcmpSendEcho(...);
  error := GetLastError();
  if (error <> 0) then
    Exit;
  Result:=pIpe.Status=IP_SUCCESS;
finally
  ...
  FreeMem(pIpe);
end;

Ошибка с утечкой памяти связана с тем, что если функция IcmpSendEcho завершилась с ошибкой, то мы выходим из функции, минуя блок finally, и соответственно, не очищаем память, выделенную под ответ на ICMP-запрос, заодно не очищаем ресурсы, выделенные под WSA.
В принципе, это не очень страшно, с учетом того, сколько памяти в наше время имеется у компьютеров, да еще и с механизмом ее виртуализации. Видимо, на практике дождаться возникновения каких-то проблем было бы очень сложно 😀.
(Как верно указал мне в комментариях Иван, все это не верно, так как даже при вызове Exit в защищаемом блоке, finally все равно срабатывает. Видимо, исключением из этого правила может быть только при переходе на метку за пределами защищаемого блока. Хотя, если подумать, то наверное, это должно по хорошему приводить к ошибке или хотя бы предупреждению компиляции.)

С этой же темой связана и ошибка, точнее, отсутствие ее при превышении периода ожидания ответа. Дело в том, что IcmpSendEcho возвращает сетевые ошибки (ошибки, возникшие в процессе следования IP-пакета по маршруту), в той структуре pIpe, которую мы передали функции при ее вызове.
А вот в случае превышения времени ожидания функция
IcmpSendEcho завершается с ошибкой и код ошибки (превышено время ожидания) храниться не в структуре pIpe, а в переменной error.

2 комментария:

  1. > мы выходим из функции, минуя блок finally

    Хм, а это точно так в Паскале? Во всех языках что я довольно хорошо знаю, finally всегда выполняется. Да вроде и в паскале также, если я смотрю в правильное место.

    https://docwiki.embarcadero.com/RADStudio/Sydney/en/Exceptions_(Delphi)#Try...finally_Statements

    "If a call to the Exit, Break, or Continue procedure causes control to leave statementList1, statementList2 is automatically executed. Thus the finally clause is always executed, regardless of how the try clause terminates."

    ОтветитьУдалить
    Ответы
    1. О да, ты прав. Finally всегда выполняется, это я неправильно понимал принцип работы блоков try вместе с exit и break. Поправил текст поста.

      Удалить