Вебсокеты. Некоторый Опыт Разработки И Эксплуатации. Изменение Клиента

Приветствую всех, кому интересен этот протокол, и заранее прошу прощения за излишне эмоциональную заметку.

Я работаю над этой темой эпизодически (по мере необходимости), но уже давно.

В связи с этим накопилась и сформировалась определенная практика проектирования и использования данной технологии.

Описан вариант реализации услуги.

здесь .

С тех пор много воды утекло.

Основные принципы остались прежними, но сам код приложения претерпел естественные изменения.

В некоторых местах были найдены и исправлены некритические ошибки, в других оптимизировано управление программными потоками, списками открытых соединений и т.д. Но помимо серверной части, как известно, есть еще и клиентская часть.

И здесь хотелось бы остановиться подробнее и описать, с чем мне пришлось столкнуться и на что можно было повлиять.

Конечно, при использовании JavaScript особо развлечься не получится, потому что все готово и закрыто, но кое-что о клиенте на Java рассказать можно.

Каким бы хорошим программистом ты ни был, всегда сложно разработать, придумать что-то свое, уникальное, а потом эти свои стихи отлаживать.

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

Критерии выбора готового модуля были просты.

Я хотел получить полный работающий Java-код с минимальными накладными расходами.

В качестве избранного я остановился на модуле, использующем библиотеки Apache. В частности, это:

  • apache-mime4j-core-0.7.2.jar;
  • httpclient-4.2.1.jar;
  • httpcore-4.2.1.jar;
  • httpmime-4.2.1.jar.
Что вы можете сказать об их использовании? Программное обеспечение Apache всегда славилось своей надежностью, сложностью и оптимальностью.

Клиент Android работал успешно.

Размер готового *.

apk файла оказался не критично большим.

Особых претензий к работе этих библиотек не было.

Но жизнь всегда умнее нас.

И время (а это период примерно в четыре-пять лет) вносит свои коррективы.

Приложение было написано, когда версия Android была 4.2 – 4.4. А потребность в новых решениях возникла уже в этом году, когда устройства с 10-й версией уже были в полном разгаре.

Разработка велась в свое время на Eclipse для Windows 7. Обновление Android SDK до необходимого уровня привело к тому, что небольшой жесткий диск SSD объемом 128 ГБ оказался заполнен.

Мне пришлось переключиться на Android Studio. Более того, пришлось изменить базовую операционную систему.

Я попробовал установить Ubuntu (точный номер версии не помню) и использовать в этой среде Studio. Но опять неудача, Andriod Studio упорно отказывалась устанавливаться.

Почему - я уже забыл.

В итоге по совету друзей я установил последнюю версию Linux-Mint, и, о чудо, инструментарий на ней заработал без нареканий.

Далее последовала собственно причина, по которой были описаны все эти детали, а именно рутина написания тестов.

Итак, что же ждало в этой нудной работе? Начнем с того, что Apache скопировал с официального сайта более актуальные версии вышеупомянутых библиотек.

Я добавил их в проект и.

И стали появляться ошибки компиляции.

Время прошло, интерфейсы классов изменились.

Вот и пришлось (из-за нехватки времени на изучение новых библиотек) вернуться к старым версиям.

Но… Но повторюсь, мы не ищем легких путей.

Я подумал, зачем мне все эти библиотеки? Тексты этих пакетов доступны.

А что, если взять и извлечь из них только нужные классы? Более того, при изучении текстов модуля работы с вебсокетами можно было увидеть всего два класса из этих библиотек.

Поэтому я создал новый проект и начал добавлять необходимые классы.

И в итоге оказалось, что для успешной компиляции необходимо извлечь 32 класса.

И все же да, проект сработал.

Все начало дышать.

Соединение со службой веб-сокетов прошло успешно.

И всё бы ничего, но я заметил следующее, непонятное для меня событие.

При закрытии соединения модуль, отвечающий за соединение, выдал исключение: java.io.EOFException в java.io.DataInputStream.readByte(DataInputStream.java:77) на com.example.wsci.HybiParser.start(HybiParser.java:112) в com.example.wsci.WebSocketClient$1.run(WebSocketClient.java:144) в java.lang.Thread.run(Thread.java:818) Я был в растерянности.

Следующее сбивало с толку.

Соединение с сервером прошло успешно.

Посылки пришли и ушли успешно.

Но почему именно закрытие вызвало исключение? Причём на сервере всё было нормально.

Очевидно, где-то в тексте клиентов была какая-то маленькая деталь, повлиявшая на закрытие.

Более того, в текстах была обнаружена такая особенность.

В соответствии с п.

7.1.1 документа Закрытие на стороне клиента заключается не просто в вызове метода close(), а в генерации и отправке пакета с кодом операции 8 (операция закрытия).

В этом случае сервер отправит собственный пакет закрытия, после получения которого клиент закроет соединение.

Но в нашем случае такой последовательности вызовов не наблюдалось.

Просто была вызвана функция close и всё.

В общем, было о чем подумать.

И чем больше я смотрел и изучал тексты этого модуля с помощью парсера пакетов, тем меньше он мне нравился, тем больше мне хотелось переписать их со своим видением того, как работает этот протокол.

В конце концов было принято решение совершить этот «трудовой подвиг».

Что вас собственно не устроило, что вызвало «гражданский протест» в этих модулях? Во-первых, организация взаимодействия модуля прямого подключения к серверу и парсера пакетов.

Оказалось, что модуль подключения взаимодействовал с сервером, генерировал парсер, которому в качестве параметра передавал ссылку на себя.

В результате парсеру были делегированы полномочия принимать решения о предстоящих сетевых событиях.

В связи с этим возник вопрос, хорошо ли это? Не лучше ли было бы, если бы модуль парсера выполнял соответствующую ему миссию, возвращал результат своей работы, но управляющее решение по событиям осуществлял бы объект, сгенерировавший парсер? В этом случае будет определена строгая иерархия взаимодействия между объектами.

(Здесь, конечно, можно поспорить, что лучше — иерархия или сеть, но тогда мы отойдём от темы.

) Второе, что заставило меня захотеть все переписать, — это структура парсера.

Этот объект (класс) должен выполнять две основные функции, а именно генерировать пакет данных для передачи на сервер и анализировать пакеты, полученные от сервера.

Так что именно эти две функции нас по большому счету не устроили.

И вот что.

Представим, что произошло сетевое событие и пришел некий пакет. Что сделал HybiParser в этом случае? Этот объект считывал первые два байта из входящего байтового потока сокета и определял свои последующие действия: разбор размера данных, масок и т.д. В результате это было реализовано в нескольких операциях чтения из входного потока сокета.

Более того, парсинг усложнялся этапами чтения, что еще больше запутывало алгоритм.

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

Похоже, еще один спорный аспект работы парсера — «вечный» цикл приема пакетов.

Цикл выполняется в отдельном потоке программы.

В какой-то момент сокет закрывается.

Что дальше, обычная обработка исключений? Или что делать? Нет, я не против механизма исключений, но просто было бы неплохо заранее предусмотреть это обстоятельство и реакцию на него.

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

В результате оценки всех этих нюансов были определены следующие требования к конструкции необходимых модулей:

  • Модули должны быть независимы от сторонних библиотек;
  • Модули должны быть простыми и легко интегрироваться в другие проекты;
  • Модули должны быть готовы к последующему функциональному расширению.

Ну и чтобы не быть обвиненными в излишней критике предыдущего готового решения, добавим к этому тот факт, что часть готовых и беспроблемных функций можно было легко перенести в новую реализацию.

Ну вот и все, и как говорили на XXII съезде КПСС: «Наши цели ясны, наши задачи определены.

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

Итак, предлагаемая реализация содержит всего четыре модуля (в соответствии с вышеизложенными требованиями в проект не были включены библиотеки Apache или отдельные классы из них):

  • Модуль глобальных констант протокола WebSocket, уровень 07;
  • Вспомогательный класс исключений;
  • Вебсокет-клиент;
  • Модуль для анализа пакетов протокола WebSocket уровня 07.
В первых двух модулях реализация тривиальна, рассматривать там нечего.

Клиентский модуль реализует контроль соединения с сервером, и здесь хотелось бы остановиться на следующих моментах.

Функция открытия соединения имеет цикл заголовков, поступающих с сервера.

Здесь фактически реализован парсинг ключа «Sec-WebSocket-Accept», причем в нашем случае парсинг осуществляется без использования библиотек Apache. Далее следует обратить внимание на функции управления пакетным шлейфом.

Реализация тривиальна, через объект синхронизации.

Следующий момент, требующий уважения, касается функции цикла.

Цикл не является «вечным», а завершается по условию проверки объекта синхронизации.

В цикле пришедший пакет считывается за одну операцию.

Пакет анализируется соответствующим объектом анализа.

Далее принимается управляющее решение относительно произошедшего сетевого события.

В завершение описания отметим функцию безопасного завершения соединения.

Это делается следующим образом.

На сервер отправляется пакет завершения соединения и генерируется переменная класса Runnable, которая затем помещается в обработчик очереди выполнения через функцию postDelayed, одним из параметров которой является задержка ответа в миллисекундах.

В переменной Runnable функция run содержит последовательность вызовов для завершения потока программы и закрытия соединения с сервером.

Если ответный пакет на закрытие приходит раньше, данная позиция в списке обработки удаляется.

Класс, реализующий парсинг пакетов WebSocket, содержит два метода, требующих внимания: сам парсинг и формирование пакета для передачи с использованием соответствующих параметров.

При парсинге все флаги и данные из полученного пакета сохраняются в переменных открытого класса.

Почему публичное? Да, для простоты, чтобы не создавать для них дополнительных функций get/set. Ну вот и все, дорогой читатель.

Архив с проектом для Android Studio прикрепил .

Я не буду предъявлять никаких претензий на использование этих текстов в ваших проектах.

Конструктивная критика принимается.

Отвечайте на вопросы, когда это возможно.

Теги: #программирование #мессенджеры #вебсокеты

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.