воскресенье, 13 марта 2016 г.

Один кошелёк для 30+ криптовалют

Давным-давно я увлекался bitcoin, потом забыл на пару лет, а вот теперь вспомнил. Форки bitcoin и просто альтернативные криптовалюты подросли с тех пор. Кое-кто уже перевалил за миллиард по рыночной капитализации. И подумалось мне, а не запастись ли сотней-другой монеток нескольких перспективных форков? Вдруг подорожают?
Сказано-сделано. Читаем о криптовалютах, выбираем "инвестиционный инструмент". Выбрали, покупаем на бирже. И выводим с биржи от греха подальше. А то мало ли что...
Сохраняем монеты на своей машине в локальных кошельках, чтобы не рисковать внезапным отказом какого-нибудь стороннего сервиса. Бекапим, все как положено... 
И вот тут первая проблема: много разных кошельков. Каждый требует установки, синхронизации, обновления, бекапа... Нет, если бы речь шла о миллионах - не жалко. Но пока наши монетки стоят недорого и возиться со всем этим как-то не хочется. И если бы просто сбекапить кошельки и забыть... Но нет, хочется иногда и поторговать, монеты нужно заводить на биржу, выводить. 

Итак, ищем "мультикошелёк"
Такой есть. Замечательное приложение Coinomi позволит убрать с вашего мобильного десяток разнокалиберных кошельков, заменив их одним удобным и приятным интерфейсом. Все монеты хранятся в одном кошельке. Это позволяет их бекапить и восстанавливать одним движением, что не может не радовать. 
Это возможно благодаря тому, что криптография всех поддерживаемых валют работает одинаково. Есть один root - ключ, от которого можно произвести любое количество "ветвей"-кошельков. Каждый адрес для получения средств каждой монеты может быть заново получен из корневого ключа. Сам же корневой ключ может быть представлен как последовательность из нескольких слов. Такой бекап можно в конце концов просто запомнить. 
Попробовав Coinomi на Android вы, несомненно захотите держать такой же кошелёк на вашем ноутбуке. Бекапим кошелёк на одном устройстве и восстанавливаем на другом. Теперь ваши монеты доступны или "на ходу" с мобильного или в спокойной "десктопной" обстановке. И тут разочарование... Нет десктопной версии Coinomi. Обещали ещё год назад, но увы... 
Но мы же программисты! А лучший кошелёк - тот в котором ты на 100% уверен. А значит тот, что написан (в нашем случае портирован из открытых исходников) "вот этими руками" :)

четверг, 18 июня 2015 г.

Git: делаем удалённый репозиторий

Ещё одно простое решение для простой задачи.
Немного предистории. Когда-то давно я задумался о том как хранить свои исходники в репозитории на своём vds. Тогда я пользовался Subversion и не считал ssh-туннели для доступа к репозиторию избыточной сложностью. Теперь я постарел, стал ленивым и полюбил git.
Что может быть проще, чем git init в корне проекта? И уже можно пользоваться всеми преимуществами контроля версий без всякого сервера.
Следующий шаг: проект уже вырос и паранойя выросла вместе с ним. Все разработчики делятся не два вида: одни ещё ни разу не теряли исходники а другие уже делают git push в конце каждого рабочего дня :)

среда, 27 мая 2015 г.

Https в Android: делаем правильно

To что я опишу ниже - не открытие и не тайна. Это есть даже в официальной документации. Проблема в том, что большинство программистов под Android (и я в том числе) пришли из других, более старых платформ, где проблемы с ssl решались иначе или не возникали вообще.
Например, если сертификат вашего сервера почему-то не в порядке (например он самоподписной), то решение отключить проверку сертификата напрашивается само собой. Такое себе "быстрое решение". В результате вроде бы https и все круто, но на публичном wifi ваш клиент рискует пообщаться по "защищенному" каналу с мошенником. Или, к примеру, вы подключаетесь по ip к своему серверу, а в сертификате прописан домен. Отключаем проверку домена? Давайте не будем спешить. Есть решение которое в "особых" случаях не только не снижает безопасность соединения вашего приложения с сервером но и повышает её до воистину параноидального уровня.

среда, 21 января 2015 г.

Самый "легкий" Java - сервер

Когда я начинал работать в Java EE, серверная инфраструктура казалась мне загадочным капризным монстром. И это при том, что в программирование я пришёл из админов. Залил ear в Resin, проверь, поднялся ли? Успешно ли распаковался? Однажды мы с админами потратили полночи чтобы понять, почему после успешного вроде бы деплоя на запросы отвечает по-прежнему старая копия приложения. При таких танцах с бубном переход на GlassFish казался хорошим решением, а отказ от ejb - ещё лучшим. Кто-то из моих коллег радовался могуществу Spring, кто-то искал что-то попроще... Я писал сервлеты под tomcat, а когда делал что-то совсем маленькое - просто встраивал jetty.
Чем проще, тем лучше. Понятно, что есть нетривиальные требования, есть сложные задачи. Но признаемся себе, сколько простых ненагруженных сервисов вы реализовали используя слишком мощные и сложные инструменты? "Я привык к этому фреймфорку и не хочу терять скилл" - слышу я обычно. "Ты уйдёшь и тот, кто будет поддерживать этот монстрокод, проклянёт тебя" - думаю я в ответ.
Может я и не убедил вас писать простые вещи простыми средствами, но посмотрите все-таки как можно сделать серверное java-приложение вообще без сервера. Это, в конце концов, интересно.

четверг, 6 февраля 2014 г.

Netty: делаем лёгкий сервер с блэкджеком и аннотациями

Допустим вам нужно обрабатывать http-запросы в своём приложении... Пишем на servlet-ах! Spring!! ЕщёКакойТоФреймворк!!!
A теперь нам нужно слушать websocket... Выбор сужается? А завтра потребуется добавить поддержку SMPP или какого-нибудь ещё "необычного" протокола? Рано или поздно вам прийдётся создать консольное java-приложение и начать изучать "встраиваемые" сервера. Встроить можно много чего, но что если фантазия разработчиков "с той стороны internet-а" родит совсем уже неведомый протокол? И тут мы вспомним о Netty. На его основе можно реализовать практически что угодно, при чём такая универсальность не пойдёт в ущерб ни производительности ни простоте. Чтобы подтвердить свою мысль я ниже сделаю свой "крошечный" http-сервер, в который можно будет добавлять "сервлетообразные" обработчики, "навешивая" их на url с помощью аннотаций.

пятница, 24 января 2014 г.

Handler - маленький помошник Android разработчика

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

В кратце android.os.Handler это абстракция позволяющая "выполнять" в указаной очередности другие абстракции - события. А что же такое событие? На реализации событиями являются android.os.Message и обычные Runnable.

Немного по делу. Знакомо CalledFromWrongThreadException? Если да, то runOnUiThread не будет открытием, иначе все впереди:).  Рано или поздно у каждого из нас возникает необходимость обновить views после получения данных которое чаще всего выполняется в другом(не главном) потоке. "AsyncTask!" - скажите вы, "Отлично!" - отвечу я. Что общего между runOnUiThread и AsyncTask? Версия выхода не считается;)
Давайте посмотрим на реализацию в фреймфорке.

Случай №1:

    public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }

Случай №2. Реализация вызова onPostExecute в AsyncTask:

   private Result postResult(Result result) {
        Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult<Result>(this, result));
        message.sendToTarget();
        return result;
    }
    private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);
        } else {
            onPostExecute(result);
        }
    }
    private static class InternalHandler extends Handler {
        public void handleMessage(Message msg) {
            AsyncTaskResult result = (AsyncTaskResult) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT:
                    result.mTask.finish(result.mData[0]);
                    break;
                .........................
            }
        }
    }

четверг, 16 января 2014 г.

Делаем алгоритм шардинга. Два решения: простое и красивое

Надеюсь все знают что такое шардинг. Ну а если вы всего лишь "слышали об этом где-то", то вы по-своему счастливый человек. Шардинг данных это решение "последнего выхода", когда никакие другие оптимизации системы хранения данных вроде индексации, денормализации и кластеризации уже не помогают. При шардинге вы берёте свою таблицу (или коллекцию для noSQL) размером в десять миллионов записей и разрезаете её, к примеру, на 10 таблиц по миллиону. Эти таблицы могут лежать в разных базах (нодах) на разных серверах. Так мы получаем то, за что разработчики высоконагруженных проектов так любят шардинг: бесконечное горизонтальное масштабирование. Чтобы определить для каждой записи ноду в которую её нужно положить или где её следует потом искать мы должны реализовать алгоритм определения ноды исходя из ключа и общего числа нод. Для записей определённой структуры с заранее известным "ключевым" полем это сделать несложно. Если вы режете таблицу пользователей с инкрементным id в качестве ключа, то можно просто определить диапазоны id  для каждой ноды. То же самое при разбиении набора записей с датой в качестве ключа. Но возьмём тяжёлый случай: ключ имеет произвольную структуру. Это не последовательное число и не дата. Просто строка. Как гарантированно отнести такой ключ к определённой ноде?
Ниже я предложу два решения: первое я сочинил сам, второе "подсмотрел" в исходниках jedis - популярного "драйвера" для подключения java-приложений к noSQL хранилищу Redis. 

понедельник, 30 декабря 2013 г.

Java EE: Делаем логи ещё удобнее

Где-то месяц назад я описывал простую систему логирования для web-приложений, которая позволяла, кроме всего прочего, группировать данные в логах в контексте одного запроса. Фича весьма удобная и я с удовольствием использую её в своих проектах. Но, как водится, в процессе эксплуатации вылезли и недоработки. Этот прост о том, как развивалась система, и о том, как она теперь работает.

Что было не так со старой системой?
Да, она группировала вывод в контексте одного запроса к серверу. Вернее в контексте одного потока, запущенного servlet-контейнером для обработки этого запроса. Если servlet не порождал новых потоков, то всё было нормально, но вот если новые потоки создавались, мы тут получали проблему. Логгер писал их отдельно, а значит, вперемешку с остальными потоками. Мелочь, а неприятно.

пятница, 15 ноября 2013 г.

Включаем multitouch и "natural scroll" для touchpad-а Dell Inspiron n5110

Сегодня переехал на "новый" Dell. Поставил свой любимый дистрибутив Elementary OS, с привычным удовольствием настроил софт, разложил всё по местам... И тут - разочарование: прокрутка двумя пальцами, к которой уже привык на предыдущем ноуте не работает. То, что система не предлагала эту опцию навело меня на мысль, что мой touchpad не определился как multitouch-устройство. А ведь это не так! Несколько минут поиска по форумам дали достаточно простое решение:
Скачиваем ALPS dlkm driver
Распаковываем его. Каталог psmouse-alps-1.3 с правами root копируем в /usr/src
Выполняем 
sudo dkms add /usr/src/psmouse-alps-1.3
sudo dkms autoinstall

sudo rmmod psmouse
sudo modprobe psmouse
После этого multitouch определяется корректно и мы можем включить нашу любимую опцию.
Если этого не случилось, попробуйте подменить 
/usr/src/psmouse-alps-1.3/src/alps.c исходниками взятыми отсюда и повторить процедуру. 
Собственно вся технология взята тут, судя по коментариям она отлично работает на большинстве linux-дистрибутивов.

Попутно нашёл ещё один полезный фокус для любителей этого няшного дистрибутива. Если вам нравится "natural scroll" в стиле Mac, в файле /usr/share/X11/xorg.conf.d/50-synaptics.conf добавляем строки:
Option "VertScrollDelta" "-111" 
Option "HorizScrollDelta" "-111"
так чтобы секция выглядела следующим образом:
Section “InputClass”
Identifier “touchpad catchall”
Driver “synaptics”
MatchIsTouchpad “on”
Option “VertScrollDelta” “-111”
Option “HorizScrollDelta” “-111”
MatchDevicePath “/dev/input/event*”
EndSection

среда, 13 ноября 2013 г.

"Вечный Service" в Android

Когда мы делаем приложения, мы руководствуемся самыми гумаными соображениями. Мы хотим облегчить жизнь нашим клиентам, помочь им, защитить, вооружить против любых проблем этого ужасного мира. Но клиенты почему-то не хотят покоряться нашему мудрому руководству. Закрывают наше приложения, останавливают его, находят в списке "запущенных процессов" через настройки и опять-таки останавливают. Ну, как дети малые, ей-богу ;) Если вы в своей всеобъемлющей мудрости хотите спасти своих клиентов от их самих, вам наверняка прийдётся защититься от их жалких попыток упавлять своим смартфоном. Как же это сделать? Перенести логику в Service, чтобы закрытие приложения не останавливало его работу? Недостаточно. Сделать в нём вечный цикл, вызвать startService() в onDestroy() - тоже. В конце концов эти неразумные существа могут перезагрузить смартфон и мы утратим над ними власть не сможем помогать им. Можно, конечно ловить событие загрузки (и ещё стопицот других системных событий) и поднимать наш сервис. Однако система на то и система, чтобы жить своими, непредсказуемыми событиями, а значит никакой гарантии, что эти события произойдут когда нам нужно, увы, нет. Как же нам быть? Ответ очевиден:

среда, 6 ноября 2013 г.

UDP в Android: как приложения ищут друг друга в сети?

В большинстве случаев говря о передаче данных по сети мы имеем в виду TCP. Для большинства сетевых задач этот протокол лучший, но он вообще-то совсем не единственный. И есть вещи, которые с его помощью делать не удобно. Например: мы знаем порт, но не знаем IP-адреса получателя и нам нужно его найти. Опрашивать все адреса локальной сети по очереди? Жуть... Тут надо бы послать широковещательное сообщение, а для этого мы уже используем UDP. Этот протокол достаточно интересен. Например, мы можем слать сообщения и слушать на одном и том же проту одновременно. UDP при отправке широковещательных сообщений не создаёт соединения в привычном нам смысле. Мы не знаем, доставлено ли получателю наше UDP сообщение. Впрочем нам это и не нужно. Передавать данные мы будем уже по TCP. Итак как же экземплярам нашего приложения искать друг друга?

среда, 30 октября 2013 г.

Java EE: Удобные логи - это просто

- Привет, тут у меня клиент, он не видит в архиве своих операций. Глянь плиз, что у него не так? Только быстрее, он VIP, он злой и висит на линии...

Если вы пишите серверные приложения, то такие просьбы девочек из саппорта вам знакомы. И знакомо то чувство отвращения, злости и отчаяния с которым вы бросаете свой незаконченный рефакторинг в самом неподходящем месте, чтобы залезть в консоль и смотреть логи.
А логи, как на зло пишутся по старинке, потому что разбираться с log4j некогда, да и неохота... Вот если бы сливать логи в базу, да запилить админку... Но когда этим заниматься? 100500 задач в разработке а тут ещё эти логи.
Как же быть? Выделить день-другой и переписать логирование на какой-нибудь фреймворк? Это можно. Но мы ведь программисты, давайте сначала оценим задачу. Итак чего бы нам хотелось?
  • Логи не должны писаться вперемешку. Все записи в контексте одного запроса должны в логах находиться в одном месте, "пакетом".
  • Все записи одного уровня должны маркироваться общим тегом
  • Вместе с пакетом записей должна быть возможность вывести в лог произвольные данные (например id клиента и т.п.), относящиеся к этому запросу. 
И всё? И для этих трёх простых вещей тащить в свой проект ещё один фреймврок? Нет уж, давайте напишим несколько строк кода и покончим с этим ;)

вторник, 29 октября 2013 г.

Настраиваем связку Tomcat+Nginx

Это сугубо практичный пост: просто набор команд для настройки "маленького сервера с блекджеком и танцовщицами" для хостинга java-приложений. Настраивал сегодня wds на Ubuntu 12.04 и, наверное, в десятый раз вспоминал всё это. Теперь всё будет в одном месте.

1. Java
sudo add-apt-repository ppa:webupd8team/java
sudo apt-get update
sudo apt-get install oracle-java6-installer


в конец ~/.bashrc добавляем 
export JAVA_HOME=/usr/lib/jvm/java-6-oracle 

2. Tomcat
sudo apt-get install tomcat7

Tomcat будет стартовать при старте системы, приложения будут разворачиваться на порту 8080 (каждое в своём соответствующем контексте) если .war залить в  /var/lib/tomcat7/webapps/.
Так уже можно работать, но для клиентов нужно спрятать tomcat за прокси, чтобы иметь возможность фровардить порты и прятать контексты за доменными именами. 

3. Nginx
wget http://nginx.org/keys/nginx_signing.key
sudo apt-key add nginx_signing.key

В конец файла /etc/apt/sources.list добавляем строки:
deb http://nginx.org/packages/ubuntu/ codename nginx 
deb-src http://nginx.org/packages/ubuntu/ codename nginx

где codename - имя дистрибутива (посмотрите его в строчках выше или получите из сat /etc/*-release) 

sudo apt-get update
sudo apt-get install nginx

Это почти всё. Осталось только настроить проксирование запросов. Допустим, мы пишем конфиг для нашего хоста newgoogle.com, приложение для которого мы в tomcat-e развернули на контексте /ng:
sudo cp /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/newgoogle.com.conf
sudo vim /etc/nginx/conf.d/newgoogle.com.conf
меняем server_name на newgoogle.com и меняем location-секцию на: 
    location / {
        proxy_pass        http://localhost:8080/ng/;
        proxy_set_header  X-Real-IP  $remote_addr;
    }

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

среда, 9 октября 2013 г.

Авторизация в web-приложениях. Заменяем Spring Security на 100 строк своего кода :)

Есть несколько принципов разработки приложений, вроде бы верных и абстрактно полезных. Эти принципы настолько укоренились в сознании современного программиста, что порой заменяют ему здравый смысл, лишают его способности, которая так ценна в его профессии: способности думать. Вот к примеру:
"Нельзя делать свои велосипеды! Надо использовать проверенные и отлаженные библиотеки!"
Вроде бы верно. Но если посмотреть чуть глубже... Почему так? Адепты "проверенных библиотек" с горящими глазами скажут что-то вроде: "Свои велосипеды писать дольше, их надо отлаживать и их труднее сопровождать". Для "сферического проекта в вакууме" это верно. Но давайте рассмотрим задачу реальную. Я постараюсь поставить условия задачи, которые чаще всего встречались в моей, уже почти 10-летней практике. И покажу решение, которое писать быстрее, проще и легче сопровождать, хотя оно и является классическим "велосипедом" :)

пятница, 4 октября 2013 г.

Создаём свои всплыващие сообщения в Android

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

среда, 2 октября 2013 г.

Сериализация и десериализация объектов

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

  • "...мы планируем обслуживать 10 миллионов клиентов к концу первого года." - Пишем с оглядкой на масштабирование.
  • "...нужно быстро внедрять 100500 похожих фич." - Думаем над системой плагинов.
  • И т.п. 
Однако есть проблемы, которые вроде как и не заметны поначалу. Но потом, спустя год активной доработки проекта мы вдруг увязаем в них как в болоте.  Одну такую я встретил недавно и цепочка моих рассуждений при её решении показалась мне достаточно интересной, чтобы оформить её тут. В конце этой цепочки - довольно элегантное на мой взгляд решение, которое позволит реализовать специфическую сериализацию объектов в вашем проекте. Это не открытие, подобный подход используется, например, в Parcelable в Android. Но паттерн, до которого дошёл "от проблемы" а не путём изучения научных трудов, запоминается намного лучше.
И, кстати, если вы пришли сюда скопировать исходничек не вникая в текст, лучше почитайте другие посты. Тут много букв, картинок и вообще, скукотища ;)

пятница, 7 июня 2013 г.

Делаем собственный компонент для Android-приложений

Если вы уже не в первый раз делаете приложения под Android, то наверняка вас часто посещает чувство дежавю: решение большинства задач вы уже где-то видели, когда-то что-то делали, а порой вам даже удаётся найти старый код и "вклеить" его в новый проект. Если же вы работаете в команде, то "помню я это где-то делал" уже не работает. Нужно систематизировать наработки, держать актуальный репозиторий библиотек. А когда своих и сторонних библиотек становится много, то поневоле начинаешь перебирать их, оставляя те, что лучше и избавляясь от остальных. Как же понять, что лучше? И как сделать такой компонент, который навсегда займёт почётное место в репозитории вашей команды?
Ниже я предложу своё видение критериев оценки библиотек и в качестве бонуса: код весьма полезного и удобного компонента для асинхронной загрузки, кеширования и отображения картинок из сети.

пятница, 26 апреля 2013 г.

Обучаем Tesseract

Tesseract - свободная платформа для оптического распознавания текста, исходники которой Google подарил сообществу в 2006 году. Если вы пишите софт для распознавания текста, то вам наверняка приходилось обращаться к услугам этой мощной библиотеки. И если она не справилась с вашим текстом, то выход у вас остаётся один - научить её. Процесс этот достаточно сложный и изобилует не очевидными а порой и прям-таки магическими действиями. Оригинальное описание есть тут. Мне понадобился почти целый день на постижение всей его глубины, поэтому тут я хочу сохранить, надеюсь, более понятный его вариант. Так чтобы помочь себе и другим пройти этот путь в следующий раз быстрее.

понедельник, 15 апреля 2013 г.

Шифрование с открытым ключом в java

Давным-давно я как-то описывал несколько примеров использования различных алгоритмов шифрования в java. Решил вот продолжить полезную тему и описать простой пример реализации шифрования алгоритмом RSA.
В отличие от описанных ранее, этот алгоритм асимметричный: т.е. данные шифруются одним ключом (публичным) а расшифровываются другим (приватным).
Есть много преимуществ такого алгоритма перед симметричными, и одно из главных - нем не нужен защищённый канал для передачи секретного ключа. Публичный ключ из своей пары мы можем разместить где угодно ни о чём не беспокоясь: этим ключом расшифровать адресованные нам сообщения в принципе невозможно. И наоборот, наш "собеседник" также свободно распространяет свой публичный ключ и мы можем передавать ему свои сообщения не опасаясь, что их прочитает кто-нибудь посторонний.
Ключи для обмена данными обычно генерируются один раз. После генерации их можно сохранить в файлы. При установке соединения с кем-то мы сохраняем его публичный ключ и передаём ему свой. Если получено шифрованное сообщение, мы восстанавливаем свой приватный ключ и с его помощью выполняем расшифровку.

пятница, 1 февраля 2013 г.

Создание websocket-приложения на Tomcat 7

Ранее я писал о своём опыте использования технологии websocket в связывании серверного java-приложения и android-клиента. Я предлагал использовать jetty 8 и делал обычное консольное java-приложение. Но если у нас websocket является частью web-приложения с сервлетами и т.п., то такой подход уже не годится. Да и зачем тянуть в свой проект jetty, если наш старый добрый Tomcat с версии 7.0.27 уже поддерживает websocket самостоятельно?
Сегодня мы сделаем websocket-приложение с помощью одного только Tomcat и javascript на клиенте.

WebSocketServlet
Ключевым механизмом реализации websocket в Tomcat является класс org.apache.catalina.websocket.WebSocketServlet. Он расширяет обычный javax.servlet.http.HttpServlet а значит мы можем "мапить" его на url как это делаем с остальными своими сервлетами. Собственно сама наша реализация крайне проста:

import org.apache.catalina.websocket.StreamInbound;
import org.apache.catalina.websocket.WebSocketServlet;
 
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
 
@WebServlet(name = "WsServlet", urlPatterns = {"/ws"})
public class WsServlet extends WebSocketServlet {
    @Override
    protected StreamInbound createWebSocketInbound(String s, HttpServletRequest httpServletRequest) {
        return new WsConnection();
    }
}

Мы тут реализуем метод, который возвращает объект, отвечающий за обслуживание websocket-соединения. Сам объект WsConnection выглядит так:

import org.apache.catalina.websocket.MessageInbound;
import org.apache.catalina.websocket.WsOutbound;
 
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.util.concurrent.ArrayBlockingQueue;
 
public class WsConnection extends MessageInbound {
 
    public static ArrayBlockingQueue<WsOutbound> connections = new ArrayBlockingQueue<WsOutbound>(100);
    private WsOutbound outbound;
 
    @Override
    protected void onBinaryMessage(ByteBuffer byteBuffer) throws IOException {
    }
 
    @Override
    protected void onTextMessage(CharBuffer charBuffer) throws IOException {
        broadcast(charBuffer.toString());
    }
 
    @Override
    protected void onOpen(WsOutbound outbound) {
        this.outbound = outbound;
        connections.add(outbound);
    }
 
    @Override
    protected void onClose(int status) {
        connections.remove(this.outbound);
    }
 
    private void broadcast(String message) {
        for (WsOutbound connection : connections) {
            try {
                CharBuffer buffer = CharBuffer.wrap(message);
                connection.writeTextMessage(buffer);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

Тут мы расширяем MessageInbound, что позволяет нам переопределяя соответствующие методы обрабатывать события установления и обрыва соединения, получения сообщения и т.п. При подключении нового клиента мы добавляем его в синхронизированную очередь, при отключении - удаляем. При получении сообщения просто рассылаем его всем подключенным клиентам. Такой вот чатик :)
На клиентской стороне можно сделать много красивостей, но мы обойдёмся минимальным количеством кода. Только чтобы убедиться в работоспособности сервера.

<html>
  <head>
    <title></title>
      <script type="text/javascript">
          var socket = new WebSocket("ws://mydomain.com:8080/ws");
 
          socket.onopen = function() {
              alert("Соединение установлено.");
          };
 
          socket.onclose = function(event) {
              if (event.wasClean) {
                  alert('Соединение закрыто');
              } else {
                  alert('Обрыв соединения');
              }
              alert('Код: ' + event.code + ' причина: ' + event.reason);
          };
          socket.onmessage = function(event) {
              var logarea = document.getElementById("log");
              logarea.value = event.data+"n"+logarea.value;
          };
          socket.onerror = function(error) {
              alert("Ошибка " + error.message);
          };
 
          function send() {
              var s = document.getElementById("in").value;
              socket.send(s);
          }
 
      </script>
  </head>
  <body>
    <input type="text" id="in" /><input type="button" onclick="send()" value="send" />
    <br/>
    <textarea id="log" rows="8" cols="20">