Источник: Nuances of Programming
Протокол WebSocket предоставляет двунаправленный (сервер и клиент могут обмениваться сообщениями) и полнодуплексный (сервер или клиент могут отправлять сообщения одновременно) канал связи, подходящий для сценариев реального времени, таких как приложения-чаты и т. д. Подключенные пользователи чата (клиенты) могут отправлять сообщения в приложение (на сервер WebSocket) и обмениваться ими друг с другом — аналогично тому, что происходит в одноранговой сети.
В этой статье мы научимся создавать простое приложение-чат с помощью WebSocket и Go. При создании приложения также будет использован Redis (подробнее об этом далее).
Вы освоите:
- Azure Cache for Redis (управляемое в облаке предложение Redis).
Почему Redis?
Посмотрим, как создаётся приложение-чат. При подключении пользователя первым делом внутри приложения (на сервере WebSocket) создаётся соответствующее соединение WebSocket, которое связано с отдельным экземпляром приложения. Благодаря этому соединению WebSocket пользователи чата имеют возможность отправлять друг другу сообщения. Мы можем осуществить (горизонтальное) масштабирование нашего приложения (например, для охвата большой базы пользователей), запустив несколько экземпляров. Теперь каждого нового пользователя можно подключить к новому экземпляру. Таким образом, у нас есть сценарий, в котором разные пользователи (вместе с соответствующими подключениями WebSocket) связаны с разными экземплярами, но не имеют возможности обмениваться сообщениями друг с другом. А это неприемлемо даже для нашего простенького приложения-чата. 😉
Redis — это универсальное хранилище данных в формате «ключ — значение», которое поддерживает самые разные структуры данных с широкой функциональностью (List, Set, Sorted Set, Hash и другие). Одной из функциональных возможностей является также PubSub, с помощью которой издатели могут отправлять сообщения на канал(ы) Redis, а подписчики могут прослушивать сообщения на этом(их) канале(ах) абсолютно независимо, будучи не связанными друг с другом. Этим можно воспользоваться для решения нашей проблемы. Вместо того чтобы зависеть только от подключений WebSocket, мы можем использовать Redis channel, на который можно подписать любое приложение-чат. Так сообщения, отправляемые на соединение WebSocket, теперь могут передаваться по каналу Redis, что обеспечивает их получение всеми экземплярами приложения (и связанными с ними пользователями чата).
Подробнее поговорим об этом далее, когда перейдём к коду. Он доступен на Github.
Обратите внимание, что вместо обычногоWebSocketможно также использовать технологии типаAzure SignalR, которые позволяют приложениям отправлять обновления контента подключенным клиентам, например одностраничному веб-сайту или мобильному приложению.В результате клиенты обновляются без необходимости опрашивать сервер или отправлять новые HTTP-запросы на обновления.
Дальше для развертывания этого решения на Azure вам потребуется учётная запись в Microsoft Azure. Если у вас её ещё нет, сейчас можно получить её бесплатно!
Принцип работы приложения-чата
А теперь быстренько разберём код. Вот структура приложения:
В main.go регистрируем наш обработчик WebSocket и запускаем веб-сервер. Здесь надо использовать обычный пакет net/http:
WebSocket обрабатывает пользователей чата (которые являются не кем иным, как клиентами WebSocket) и запускает новый чат.
ChatSession (часть chat/chat-session.go) представляет пользователя и соответствующее ему соединение WebSocket (на стороне сервера).
Когда чат начинает работу, он запускает goroutine для приёма сообщений от пользователя, только что присоединившегося к чату. Это делается с помощью вызова ReadMessage() (из websocket.Conn) в цикле for. Выполнение горутины завершается (exit), если пользователь отсоединяется (закрывается соединение WebSocket), или выключается приложение (например, нажатием ctrl+c). Для каждого пользователя создаётся отдельная горутина под его сообщения в чате.
Когда сообщение от пользователя получено (через соединение WebSocket), оно пересылается другим пользователям с помощью функции SendToChannel, которая является частью chat/redis.go. Она передаёт сообщение на канал Redis pubsub.
Важная роль в этой задаче отводится sub (подписчику). В отличие от случая, когда для каждого подключённого пользователя чата выделялась отдельная горутина, мы используем единую горутину (в рамках приложения), с тем чтобы и подписываться на канал Redis, и получать сообщения, и пересылать их всем пользователям через соответствующее их соединение WebSocket.
Подписка завершается, когда экземпляр приложения выключается. А это, в свою очередь, останавливает цикл канала for-range, и выполнение горутины завершается.
Функция startSubscriber вызывается из функции init() в redis.go. Функция init()запускается при подключении к Redis, а в случае сбоя подключения приложение завершает работу.
Теперь настроим экземпляр Redis, к которому можно подключить внутреннюю часть нашего приложения-чата. Давайте создадим сервер Redis в облаке!
Настройка Azure Redis Cache
Azure Redis Cache предоставляет доступ к защищённому выделенному кэшу Redis, который размещён в Azure и доступен любому приложению как в платформе Azure, так и за её пределами.
Для достижения наших целей мы будем настраивать Azure Redis Cache с уровнем Basic, который предусматривает кэш одного узла и идеально подходит для разработки/тестирования и некритичных рабочих нагрузок. А кроме базового, можно выбрать уровень Standard или Premium с дополнительным набором различных функциональных возможностей, в том числе постоянным хранением данных, кластеризацией, георепликацией и другими.
Для установки будем использовать Azure CLI. Если вам привычнее работать в браузере, можно также воспользоваться облачной оболочкой Azure Cloud Shell.
А быстро настроить экземпляр Azure Redis Cache можно командой az redis create. Например, вот так:
az redis create --location westus2 --name chat-redis --resource-group
chat-app-group --sku Basic --vm-size c0
Загляните в пошаговое руководство по созданию Azure Cache для Redis.
По завершении вам понадобится информация для подключения к экземпляру Azure Redis Cache, т.е. узел, порт и клавиши быстрого доступа. Получаем эту информацию также через CLI. Например, вот так:
Загляните в пошаговое руководство по получению имени узла, портов и ключей для Azure Cache для Redis”.
Вот и всё…
…поболтаем?
Для простоты приложение будет доступно в виде докерного образа.
Первым делом зададим несколько переменных окружения:
Приложение использует статический порт 8080 внутренне (для веб-сервера). Мы используем внешний порт, указываемый в строке с EXT_PORT, и сопоставляем его с портом 8080 внутри нашего контейнера (используя -p $EXT_PORT:8080).
Запускаем докерный контейнер:
docker run --name $NAME -e REDIS_HOST=$REDIS_HOST -e REDIS_PASSWORD=$REDIS_PASSWORD -p $EXT_PORT:8080 abhirockzz/redis-chat-go
Теперь можно присоединиться к чату! Вы можете использовать любой клиент WebSocket. Я предпочитаю использовать wscat в терминале и/или расширение WebSocket для Chrome в браузере.
Открываем два отдельных терминала, чтобы с помощью wscat сымитировать действия двух разных пользователей:
Вот как может выглядеть чат с участием пользователей foo и bar:
foo подключается первым и получает приветственное сообщение: «Добро пожаловать, foo!». После foo к чату присоединяется и bar, получающий аналогичное приветственное сообщение: «Добро пожаловать, bar!». Причём foo получил уведомление о том, что bar подключился к чату. foo и bar обменялись несколькими сообщениями, прежде чем bar покинул чат (foo получил уведомление и об этом тоже).
Чтобы потренироваться, вы можете запустить свой экземпляр приложения-чата. Разверните ещё один докерный контейнер с другим значением для внешнего порта EXT_PORT и названием чата. Например, такой:
Теперь подключаемся через порт 9091 (или выбранный вами порт), чтобы сымитировать действия другого пользователя:
//пользователь "pi"
wscat -c ws://localhost:9091/chat/pi
foo всё ещё активен в чате, поэтому он получит уведомление о прибытии нового участника pi, с которым они теперь могут обменяться любезностями.
Подтверждение от Redis
Давайте получим подтверждение, заглянув в структуры данных Redis. Для этого можно воспользоваться redis-cli. При работе с Azure Redis Cache я бы рекомендовал очень полезную веб-консоль для Redis.
У нас есть SET (с названием chat-users), в котором хранятся активные пользователи:
SMEMBERS chat-users
Теперь вы должны увидеть такой результат:
1) "foo"
2) "bar"
Это означает, что пользователи foo и bar сейчас подключены к приложению-чату и имеют активное подключение WebSocket.
А что с каналом PubSub?
PUBUSB CHANNELS
Так как для всех пользователей у нас один канал, вы должны получить такой результат от сервера Redis:
1) "chat"
Вот и всё.
Читайте также:
Читайте нас в телеграмме и vk
Перевод статьи Abhishek Gupta: Let’s learn how to to build a chat application with Redis, WebSocket and Go