Разработка WebSocket-приложений с использованием Go: однокомнатный чат
Разработка WebSocket-приложений с использованием Go: однокомнатный чат

фон

Всем привет, меня зовут официальный аккаунт "Офлайн-игры" для Автор «вечеринок» разработал «Коллекцию настольных онлайн-игр», представляющую собой веб-страницу, на которой вы можете легко играть в «Арендодатели», «Нарды» и т. д. онлайн со своими друзьями. Основная технология — WebSocket, быстрый фокус наHullQinДавайте учиться вместе!

Я еще не выучил Go, что мне прочитать в первую очередь?

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

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

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

Кроме того, вам также необходимо понимать использование распространенных пакетов, включая fmt и net/http.

Выбор технологии

Что вам следует делать, столкнувшись с языком и незнакомой структурой, с которой вы не знакомы?

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

image.png
image.png

Смотреть,Мы нашли этоgorilla/websocket,Звездное число существенно отличается от следующих. Теперь не о чем беспокоиться,Используйте его решительно.

Новый проект

При использовании GoLand есть два варианта создания нового проекта Go:

image.png
image.png

Мы можем выбрать первое.

Если у вас нет GoLand,Вы также можете создавать папки вручную,Создайте в нем новый файлgo.mod(Я использую последнюю стабильную версию1.18)

Язык кода:text
копировать
module echo

go 1.18

Установить зависимости

Язык кода:shell
копировать
go get github.com/gorilla/websocket

Скопировать код чата

Пучокgorilla/websocketчиновникdemoПросто скопируйте это,Давайте медленно проанализируем:

Вам нужны эти 4 файла:

  • main.go
  • hub.go
  • client.go
  • index.html

Первый шаг — посмотреть на основную функцию

Язык кода:go
копировать
func main() {
   flag.Parse()
   hub := newHub()
   go hub.run()
   http.HandleFunc("/", serveHome)
   http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
      serveWs(hub, w, r)
   })
   err := http.ListenAndServe(*addr, nil)
   if err != nil {
      log.Fatal("ListenAndServe: ", err)
   }
}

Уже представлено в предыдущей статьеflagиhttp.HandleFunc,Это точно так же, как и предыдущее.

Здесь также открывается горутина. Обратите внимание, что она прописана в основной функции, а не в http.HandleFunc. Поэтому независимо от того, сколько клиентов подключено, этот сервис открывает только одну горутину.newHub().run()。нас Следующий шагсмотретьnewHub(),в файлеhub.go.

Давайте посмотрим на две зарегистрированные функции обработки запросов:

  • serveHomeэтоHTTPСлужить,Верните html-файл инициатору запроса (браузеру).
  • против/wsмаршрутизация,будет вызванserveWs,нас下Следующий шагсмотретьserveWsчто сделал,в файле clent.go.

Второй шаг — заглянуть на Hub.go.

Определение хаба и определение функции newHub

Язык кода:go
копировать
type Hub struct {
   clients map[*Client]bool
   broadcast chan []byte
   register chan *Client
   unregister chan *Client
}

func newHub() *Hub {
   return &Hub{
      clients:    make(map[*Client]bool),
      register:   make(chan *Client),
      unregister: make(chan *Client),
      broadcast:  make(chan []byte),
   }
}

Вы можете видеть, что newHub просто создает новый пустой концентратор. И 1 Хаб содержит 4 вещи:

  • clients,Сохраняет карту, на которую ссылается каждый клиент (фактически значение этой карты не используется).,ключ — это ссылка на клиента,Можно рассматривать как набор на других языках).
  • register,Канал, используемый для регистрации клиентов. Всякий раз, когда клиент устанавливает соединение через веб-сокет,по реестру,Сохраните клиента в справочнике клиентов.
  • unregister,Канал, используемый для выхода клиента из системы. Всякий раз, когда клиент отключается от веб-сокета,путем отмены регистрации,Поместите ссылку на клиента из клиентов удалите.
  • broadcast,Канал, используемый для передачи трансляций. После сохранения сообщения в этот канал,Позже будут и другиеgoroutineТраверсclients,Отправьте сообщение всем клиентам.

Горутина запускается при запуске службы:hub.run()

Язык кода:go
копировать
func (h *Hub) run() {
   for {
      select {
      case client := <-h.register:
         h.clients[client] = true
      case client := <-h.unregister:
         if _, ok := h.clients[client]; ok {
            delete(h.clients, client)
            close(client.send)
         }
      case message := <-h.broadcast:
         for client := range h.clients {
            select {
            case client.send <- message:
            default:
               close(client.send)
               delete(h.clients, client)
            }
         }
      }
   }
}

бесконечный цикл:постоянно отchannelЧтение данных。читатьregister,Просто зарегистрируйте клиента。читатьunregister,Отключить клиент,Удалить ссылку。читатьbroadcast,就Траверсclients,Широковещательное сообщение(通过Пучокинформация写入каждый客户端изclient.sendchannelсередина,реализовать трансляцию),Именно на эту логику стоит обратить внимание дальше.

Следующий шаг,нассмотретьclient

Третий шаг — посмотреть client.go.

Определение клиента

Язык кода:go
копировать
type Client struct {
   hub *Hub
   conn *websocket.Conn
   send chan []byte
}
  • hub: каждыйClientКлиент сэкономилHubКавычки。(Хотя на данный момент существуют только1индивидуальныйhub,Но для масштабируемости,Лучше сохраните копию,Потому что в будущем будет много хабов,Мы представим его в следующей статье! )
  • conn: То есть подключитесь к веб-сокету клиента.,通过这индивидуальныйconnМожете взаимодействовать с клиентами(Отправляйте и получайте сообщения)。
  • send: 一индивидуальныйchannel,Уже видел на втором этапе,во время трансляции,就是Пучокинформация写入了каждыйClientизsend в канале. Прочитав сообщение из этого канала, отправьте сообщение клиенту.

Функция serWs, используемая основной функцией

Язык кода:go
копировать
func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) {
   conn, err := upgrader.Upgrade(w, r, nil)
   if err != nil {
      log.Println(err)
      return
   }
   client := &Client{hub: hub, conn: conn, send: make(chan []byte, 256)}
   client.hub.register <- client

   // Allow collection of memory referenced by the caller by doing all work in
   // new goroutines.
   go client.writePump()
   go client.readPump()
}

Зарегистрировался в хабе.

Затем были запущены 2 горутины: client.writePump()иclient.readPump(),Тогда логика функции заканчивается.

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

client.writePump

Язык кода:go
копировать
func (c *Client) writePump() {
   ticker := time.NewTicker(pingPeriod)
   defer func() {
      ticker.Stop()
      c.conn.Close()
   }()
   for {
      select {
      case message, ok := <-c.send:
         c.conn.SetWriteDeadline(time.Now().Add(writeWait))
         if !ok {
            c.conn.WriteMessage(websocket.CloseMessage, []byte{})
            return
         }
         w, err := c.conn.NextWriter(websocket.TextMessage)
         if err != nil {
            return
         }
         w.Write(message)
         if err := w.Close(); err != nil {
            return
         }
      case <-ticker.C:
         c.conn.SetWriteDeadline(time.Now().Add(writeWait))
         if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
            return
         }
      }
   }
}

Сначала запускается таймер пинга. Сообщения Ping будут отправляться клиенту через регулярные промежутки времени. Это требуется протоколом WebSocket.,ссылка《RFC6455》。你在浏览器上抓包смотреть不到这индивидуальныйPingинформация。Сюда,Неотвечающие соединения можно очистить.

Затем эта горутина объявляет логику отложенного выполнения: закрытие таймера и закрытие соединения.

самая важная часть,Эта горутина имеет бесконечный цикл: она непрерывно считывает данные из канала client.send. Пока Hub.broadcast отправляет ему сообщение,那么就由这индивидуальныйgoroutineсправиться。c.conn.NextWriterиw.Write(message)是真正из发информацияиз逻辑。

Кроме того, время от времени (интервал времени, установленный таймером) сервер отправляет браузеру пинг. Браузер автоматически ответит Pong (разработчикам клиентов не нужно обращать внимание, разработчики клиентов обычно являются разработчиками JS).

client.readPump

Язык кода:go
копировать
func (c *Client) readPump() {
   defer func() {
      c.hub.unregister <- c
      c.conn.Close()
   }()
   c.conn.SetReadLimit(maxMessageSize)
   c.conn.SetReadDeadline(time.Now().Add(pongWait))
   c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
   for {
      _, message, err := c.conn.ReadMessage()
      if err != nil {
         if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
            log.Printf("error: %v", err)
         }
         break
      }
      message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1))
      c.hub.broadcast <- message
   }
}

readPump就是读取информация,После получения сообщения клиента,Просто используйтеhub.broadcastТранслировать。

также,У этой горутины важная задача: после закрытия соединения,Ответственныйhub.unregisterиconn.Close

Подведите итоги! Самая важная картинка!

Чтобы вам было понятнее, я нарисовал такую ​​схему:

image.png
image.png

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

На диаграммах «Пользователь» и «Клиент» нарисованы только два, и их можно продолжать добавлять.

напиши в конце

Я Халл Кин,Официальный аккаунтОфлайн-игры для вечеринокиз作者(добро пожаловатьсосредоточиться най,交индивидуальный朋友)。Прежде чем пересылать эту статью, необходимо узнать автораHullQinАвторизовать。я独立Разработана «Коллекция настольных онлайн-игр».,Это веб-страница,Вы можете легко играть в «Арендодатели», «Нарды» и т. д. онлайн со своими друзьями.,Никаких комиссий, никакой рекламы。Также разработано《Dice Crush》УчастиеGame Jam 2022。Вы можете, если хотитесосредоточиться най噢~я有空了会分享做играиз相关技术,会在这индивидуальный专栏里分享:《Научите вас играть в маленькие игры》

boy illustration
Углубленный анализ переполнения памяти CUDA: OutOfMemoryError: CUDA не хватает памяти. Попыталась выделить 3,21 Ги Б (GPU 0; всего 8,00 Ги Б).
boy illustration
[Решено] ошибка установки conda. Среда решения: не удалось выполнить первоначальное зависание. Повторная попытка с помощью файла (графическое руководство).
boy illustration
Прочитайте нейросетевую модель Трансформера в одной статье
boy illustration
.ART Теплые зимние предложения уже открыты
boy illustration
Сравнительная таблица описания кодов ошибок Amap
boy illustration
Уведомление о последних правилах Points Mall в декабре 2022 года.
boy illustration
Даже новички могут быстро приступить к работе с легким сервером приложений.
boy illustration
Взгляд на RSAC 2024|Защита конфиденциальности в эпоху больших моделей
boy illustration
Вы используете ИИ каждый день и до сих пор не знаете, как ИИ дает обратную связь? Одна статья для понимания реализации в коде Python общих функций потерь генеративных моделей + анализ принципов расчета.
boy illustration
Используйте (внутренний) почтовый ящик для образовательных учреждений, чтобы использовать Microsoft Family Bucket (1T дискового пространства на одном диске и версию Office 365 для образовательных учреждений)
boy illustration
Руководство по началу работы с оперативным проектом (7) Практическое сочетание оперативного письма — оперативного письма на основе интеллектуальной системы вопросов и ответов службы поддержки клиентов
boy illustration
[docker] Версия сервера «Чтение 3» — создайте свою собственную программу чтения веб-текста
boy illustration
Обзор Cloud-init и этапы создания в рамках PVE
boy illustration
Корпоративные пользователи используют пакет регистрационных ресурсов для регистрации ICP для веб-сайта и активации оплаты WeChat H5 (с кодом платежного узла версии API V3)
boy illustration
Подробное объяснение таких показателей производительности с высоким уровнем параллелизма, как QPS, TPS, RT и пропускная способность.
boy illustration
Удачи в конкурсе Python Essay Challenge, станьте первым, кто испытает новую функцию сообщества [Запускать блоки кода онлайн] и выиграйте множество изысканных подарков!
boy illustration
[Техническая посадка травы] Кровавая рвота и отделка позволяют вам необычным образом ощипывать гусиные перья! Не распространяйте информацию! ! !
boy illustration
[Официальное ограниченное по времени мероприятие] Сейчас ноябрь, напишите и получите приз
boy illustration
Прочтите это в одной статье: Учебник для няни по созданию сервера Huanshou Parlu на базе CVM-сервера.
boy illustration
Cloud Native | Что такое CRD (настраиваемые определения ресурсов) в K8s?
boy illustration
Как использовать Cloudflare CDN для настройки узла (CF самостоятельно выбирает IP) Гонконг, Китай/Азия узел/сводка и рекомендации внутреннего высокоскоростного IP-сегмента
boy illustration
Дополнительные правила вознаграждения амбассадоров акции в марте 2023 г.
boy illustration
Можно ли открыть частный сервер Phantom Beast Palu одним щелчком мыши? Супер простой урок для начинающих! (Прилагается метод обновления сервера)
boy illustration
[Играйте с Phantom Beast Palu] Обновите игровой сервер Phantom Beast Pallu одним щелчком мыши
boy illustration
Maotouhu делится: последний доступный внутри страны адрес склада исходного образа Docker 2024 года (обновлено 1 декабря)
boy illustration
Кодирование Base64 в MultipartFile
boy illustration
5 точек расширения SpringBoot, супер практично!
boy illustration
Глубокое понимание сопоставления индексов Elasticsearch.
boy illustration
15 рекомендуемых платформ разработки с нулевым кодом корпоративного уровня. Всегда найдется та, которая вам понравится.
boy illustration
Аннотация EasyExcel позволяет экспортировать с сохранением двух десятичных знаков.