Привет!
Так получилось, что я пишу свой nuget-пакет.
В ходе своей работы с .NET мне приходилось работать с сетью и в какой-то момент столкнулся с тем, что оказывается у Microsoft в .NET нет встроенной сетевой трассировки.
Это и послужило толчком к написанию небольшой библиотеки-хелпера. За одно, неплохой повод попрактиковаться в их написании кода для комьюнити.
Посмотреть пакет в NuGet Gallery можно ==> тут.
Вкратце о функционале.
Удалось реализовать в библиотеке:
- Отправка ICMP пакетов с различной настройкой.
- Классическая реализация функционала cmd/ping, а так же гибкие его настройки.
- Определение локального IP. ( Взял свою старую готовую реализацию, расширил и довел до ума).
- Проверка открытых TCP портов на удаленном хосте.
Приключения при реализации.
Выбор платформы:
В самом начале, я хотел все написать на .NET 6 и не особо заморачиваться. Почему-то, моя глупость объединила понятия "кроссплатформенность" с понятием "поддержки старых и иных платформ" в .NET.
Углубившись в вопрос и поглядев примеры других пакетов на сайте Microsoft - понял, что мне необходимо смотреть в сторону .NET Standard.
Читая версий спецификаций - решил остановиться на версии 2.0.
Хотя версия 1.5 имеет более широкое покрытие в количестве версии платформ - я побоялся за функционал. Мне очень не хотелось неожиданное столкнутся с тем, что версия 1.5 не понимает какой-то синтаксис или лишится привычного функционала с .NET 6.
Да, в версии 2.0 тоже не все так гладко. Но в основном волнующие меня проблемы в отсутствии синтаксического сахара.
Версия 2.1 - на данный момент последняя и очень интересная в спецификации.
В ней очень много пробелов при указании поддержки версии иных платформ .NET, что меня не устроило.
В особенности, то что платформа .NET Framework не поддерживается, а так же обрезаны версии .NET Core 2.0 - 2.2.
Так как, мне необходима была максимально больше покрытие, то выбор был очевиден -> .NET Standard 2.0.
Создание утилиты трассировки :
Если спросить Google, как реализовать такую трассировку с помощью C# , то мы получим ответ, но не самый оптимальный.
Первый раз я нашел вот этот вариант, использующий рекурсию.
Рекурсия сама по себе не самый оптимальный вариант. По этому пришлось переделывать "под себя", почти полностью.
Если, запустить вариант из StackOverflow и иные варианты от туда, то мы получаем за данные о трассировки, около 10 секунд. Моя реализация делает это за 4-5 секунд.
Увеличение скорости получается добиться за счет управлением ожидания ответа - Timeout.
На моей стороне Timeout реализован 4000 мс, с длиной поля данных в 32 байта. Эти дефолтные настройки по работе ping, взяты из документации Microsoft.
Понижение Timeout ниже 2000 мс - ускорит получение результат, но он станет менее точным.
Самое забавное, что вариант с StackOverflow менее точен чем мой. Хотя там и Timeout больше - аж 10000 мс. С чем это связано я так и не понял.
Проблемы с утилитой парсинга портов:
Самое интересно происходит с проверкой портов.
Изначально я пользовался классом TcpClient. Но по итогу написания кода понял, что порты являются закрытыми, ждут ответа по 20 секунд на каждый. Меня это конечно не устроило.
Начавший копаться еще сильнее удалось прийти к свойствам класса ReceiveTimeout и SendTimeout. Но вот только не задача - данные свойства не работают.
Какое время не выставляй 10, 100, 1000 мс - время ответа не уменьшается. Остается 20-21 секунд работы на 1 порт при его недоступности.
Пойдя дальше, я пришел уже реализации запроса TCP через Socket.Connect, но увы та же беда.
Есть такие же одноименные свойства, которые должны влиять на таймаут, которые не так же работают.
На форуме за 2012 прочитал ответ: "Как выход обычно рекомендуют воспользоваться BeginConnect\EndConnect - как следствие - посмотрите в строну асинхронной передачи данных"
Ну я и попробовал метод BeginConnect - догадаетесь какой результат?
На какой то момент, я не мог приложить ума, как решить такую проблему.
Я думал, что возможно, как то не верно задавал параметры (не в том месте что ли?).
Неожиданно, Visual Studio подсказало мне попробовать метод .SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, 100).
Я решил еще более подробно покопаться, нашел все свойства связанные хоть как то с таймаутом.
Итог не изменился. Время ожидания до сих пор 20 секунд.
Я категорически не понимал, как это победить.
Не бросив искать ответ, я нашел иное решение, через асинхронный подход метода BeginConnect, который был рекомендован на форме ранее.
Благодаря ответу с форума, у меня получилось начать управлять таймаутом ожидания ответа.
Нужно всего лишь, грамотно воспользоваться интерфейсом IAsyncResult и его свойством AsyncWaitHandle.WaitOne.
Выставив его на 1000 мс, я прогнал тесты и !НАКОНЕЦ! получил нужный результат.
Как Вы понимаете время на обработку 1 порта теперь 1 секунда. Но если выставить ее меньше, например в 500, то ответ может быть не точный.
Точность понижается по моим наблюдениям от 1000 мс. Всегда нужно не забывать где Вы располагаетесь географически.
В моем случае из Сибири, стоит рассматривать таймауты с запасом около 2000 мс.
Далее я решил, вместо долгих настроек объекта сокета (new Socket()) сократить все это до класса, который уже использовал ранее:
TcpClient socket = new TcpClient() - в нем тоже есть метод BeginConnect, который мне и нужен был.
На обработку всех 65535 портов с таймаутом 1000 мс у Вас уйдет около 18ч.
Нюанс с встроенной документацией
Как выяснилось позднее, требуется добавить в xaml файл проекта библиотеки вот такую строчку кода. Иначе при скачивании nuget файла, ничего не подсветится.
<Project>
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
</Project>
P.S.
Работа по написанию библиотеки, до версии 1.1.0 уже с имеющимися наработками заняла около 15 дней.
Некоторые задачи, как с "проверкой открытых портов" - я решал по несколько дней, пробуя и ища информацию.
Много времени уходит на документацию на русским и английском, на описание методов и классов через теги в самом коде, а так же на юнит-тесты.
Да, у меня есть не статические объекты и я не брезгаю тестами, хоть их и очень не люблю тратить на них время как и все разработчики.
Все эти заморочки с тестами и документацией реализуются специально, по принципу - как бы я сам хотел видеть стороннюю документацию, чтобы в ней разобраться максимально быстро.
Не факт, это что получается у меня хорошо, но все же.
Эта версия библиотеки не последняя.
В нее планируется еще включить пару утилит, которые мне самому пригодятся в работе.
В первую очередь я пишу эту библиотеку для самого себя, но открыт для помощи со стороны.
Всем спасибо!