Всем привет, меня зовут официальный аккаунт "Офлайн-игры" для Автор «вечеринок» разработал «Коллекцию настольных онлайн-игр», представляющую собой веб-страницу, на которой вы можете легко играть в «Арендодатели», «Нарды» и т. д. онлайн со своими друзьями. Основная технология — WebSocket, быстрый фокус наHullQinДавайте учиться вместе!
Рекомендуется потратить день на ознакомление с принципами и базовым синтаксисом Go. Подойдет любой учебник, только известный.
По крайней мере, вам нужно понимать: различные типы данных, как писать поток управления (for, if и т. д.), понимать каналы и горутины и как блокировать.
Обязательно попробуйте написать горутину и направить свои усилия на понимание основного синтаксиса.
Кроме того, вам также необходимо понимать использование распространенных пакетов, включая fmt и net/http.
Что вам следует делать, столкнувшись с языком и незнакомой структурой, с которой вы не знакомы?
Позвольте мне рассказать вам небольшую хитрость. Выполните поиск прямо на Github и посмотрите репозиторий с наибольшим количеством звезд. Вот и все~.
Смотреть,Мы нашли этоgorilla/websocket
,Звездное число существенно отличается от следующих. Теперь не о чем беспокоиться,Используйте его решительно.
При использовании GoLand есть два варианта создания нового проекта Go:
Мы можем выбрать первое.
Если у вас нет GoLand,Вы также можете создавать папки вручную,Создайте в нем новый файлgo.mod
(Я использую последнюю стабильную версию1.18)
module echo
go 1.18
go get github.com/gorilla/websocket
Пучокgorilla/websocket
чиновникdemoПросто скопируйте это,Давайте медленно проанализируем:
Вам нужны эти 4 файла:
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.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
,Отправьте сообщение всем клиентам.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.send
channelсередина,реализовать трансляцию),Именно на эту логику стоит обратить внимание дальше.
Следующий шаг,нассмотретьclient
。
type Client struct {
hub *Hub
conn *websocket.Conn
send chan []byte
}
hub
: каждыйClientКлиент сэкономилHub
Кавычки。(Хотя на данный момент существуют только1индивидуальныйhub,Но для масштабируемости,Лучше сохраните копию,Потому что в будущем будет много хабов,Мы представим его в следующей статье! )conn
: То есть подключитесь к веб-сокету клиента.,通过这индивидуальныйconn
Можете взаимодействовать с клиентами(Отправляйте и получайте сообщения)。send
: 一индивидуальныйchannel,Уже видел на втором этапе,во время трансляции,就是Пучокинформация写入了каждыйClientизsend
в канале. Прочитав сообщение из этого канала, отправьте сообщение клиенту.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()
,Тогда логика функции заканчивается.
Эти две горутины используются для обработки сообщений записи и чтения сообщений соответственно.
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).
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
。
Чтобы вам было понятнее, я нарисовал такую схему:
Среди них цветной прямоугольник представляет горутину, а цветные линии представляют каждый канал (от A до B, данные записываются горутиной A, а данные считываются горутиной B).
На диаграммах «Пользователь» и «Клиент» нарисованы только два, и их можно продолжать добавлять.
Я Халл Кин,Официальный аккаунтОфлайн-игры для вечеринокиз作者(добро пожаловатьсосредоточиться най,交индивидуальный朋友)。Прежде чем пересылать эту статью, необходимо узнать автораHullQinАвторизовать。я独立Разработана «Коллекция настольных онлайн-игр».,Это веб-страница,Вы можете легко играть в «Арендодатели», «Нарды» и т. д. онлайн со своими друзьями.,Никаких комиссий, никакой рекламы。Также разработано《Dice Crush》УчастиеGame Jam 2022。Вы можете, если хотитесосредоточиться най噢~я有空了会分享做играиз相关技术,会在这индивидуальный专栏里分享:《Научите вас играть в маленькие игры》。