Полный код этого раздела:GitHub
Эта статья является четвертой из серии статей о создании чат-приложений с помощью ReactJS и Go. Часть 3 вы можете найти здесь -Интерфейсная реализация
Этот раздел в основном реализует функцию обработки нескольких клиентских сообщений и трансляции полученного сообщения каждому подключенному клиенту. К концу этой части серии мы:
- Реализован механизм объединения для эффективного отслеживания количества соединений в службе WebSocket.
- Возможность широковещательной рассылки любого полученного сообщения всем соединениям в пуле соединений.
- Возможность уведомлять существующих клиентов, когда другой клиент подключается или отключается.
К концу этой части курса наше приложение будет выглядеть так:
Разделить код веб-сокета
Теперь, когда необходимая базовая работа проделана, мы можем продолжить улучшать кодовую базу. Некоторые приложения можно разделить на подпакеты для упрощения разработки.
Теперь, в идеале, вашmain.go
Файл должен быть просто точкой входа для приложения Go, он должен быть довольно маленьким и может вызывать другие пакеты в проекте.
Примечание — мы будем ссылаться на неофициальный стандартный макет структуры проекта Go —golang-standards/project-layout
Давайте создадим файл в каталоге бэкэнд-проекта с именемpkg/
новый каталог. Тем временем мы собираемся создать еще одинwebsocket/
каталог, который будет содержатьwebsocket.go
документ.
Мы поставим текущийmain.go
Большая часть кода на основе WebSocket, используемого в файле, была перемещена в этот новыйwebsocket.go
в файле.
Примечание. Следует отметить, что при копировании функций вам нужно делать первую букву каждой функции заглавной, мы хотим, чтобы эти функции можно было экспортировать в остальную часть проекта.
package websocket
import (
"fmt"
"io"
"log"
"net/http"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool { return true },
}
func Upgrade(w http.ResponseWriter, r *http.Request) (*websocket.Conn, error) {
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println(err)
return ws, err
}
return ws, nil
}
func Reader(conn *websocket.Conn) {
for {
messageType, p, err := conn.ReadMessage()
if err != nil {
log.Println(err)
return
}
fmt.Println(string(p))
if err := conn.WriteMessage(messageType, p); err != nil {
log.Println(err)
return
}
}
}
func Writer(conn *websocket.Conn) {
for {
fmt.Println("Sending")
messageType, r, err := conn.NextReader()
if err != nil {
fmt.Println(err)
return
}
w, err := conn.NextWriter(messageType)
if err != nil {
fmt.Println(err)
return
}
if _, err := io.Copy(w, r); err != nil {
fmt.Println(err)
return
}
if err := w.Close(); err != nil {
fmt.Println(err)
return
}
}
}
Этот новый был созданwebsocket
пакет, то мы хотим обновитьmain.go
файл для вызова этого пакета. Сначала вам нужно добавить новый импорт в список импорта в верхней части файла, затем вы можете сделать это с помощьюwebsocket.
для вызова функций в этом пакете. нравится:
package main
import (
"fmt"
"net/http"
"realtime-chat-go-react/backend/pkg/websocket"
)
func serveWs(pool *websocket.Pool, w http.ResponseWriter, r *http.Request) {
fmt.Println("WebSocket Endpoint Hit")
conn, err := websocket.Upgrade(w, r)
if err != nil {
fmt.Fprintf(w, "%+v\n", err)
}
client := &websocket.Client{
Conn: conn,
Pool: pool,
}
pool.Register <- client
client.Read()
}
func setupRoutes() {
pool := websocket.NewPool()
go pool.Start()
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
serveWs(pool, w, r)
})
}
func main() {
fmt.Println("Distributed Chat App v0.01")
setupRoutes()
http.ListenAndServe(":8080", nil)
}
После этих изменений мы должны проверить, не нарушают ли они существующую функциональность. Попробуйте снова запустить бэкенд и интерфейс, чтобы убедиться, что вы все еще можете отправлять и получать сообщения:
$ cd backend/
$ go run main.go
В случае успеха мы можем перейти к расширению кодовой базы для обработки нескольких клиентов.
К настоящему времени структура каталогов должна выглядеть так:
- backend/
- - pkg/
- - - websocket/
- - - - websocket.go
- - main.go
- - go.mod
- - go.sum
- frontend/
- ...
Работа с несколькими клиентами
Теперь, когда основы выполнены, мы можем перейти к улучшению серверной части и реализации возможности обработки нескольких клиентов.
Для этого нам нужно подумать о том, как обрабатывать подключение к службе WebSocket. Всякий раз, когда устанавливаются новые соединения, мы должны добавлять их в существующий пул соединений и следить за тем, чтобы каждый раз при отправке сообщения все в этом пуле получали это сообщение.
Использование каналов
Нам необходимо разработать систему с большим количеством одновременных подключений. Запускается новый на время соединенияgoroutine
для обработки каждого соединения. Это означает, что мы должны заботиться об этихgoroutine
связь между ними и обеспечить потокобезопасность.
при дальнейшей реализацииPool
структура, мы должны рассмотреть возможность использованияsync.Mutex
блокировать другиеgoroutine
при доступе/изменении данных, или мы также можем использоватьchannels
.
Для этого проекта я думаю, что лучше всего использоватьchannels
и безопасным образом между несколькими одновременнымиgoroutine
общаться в.
Примечание. Если вы хотите узнать больше о
channels
, и другие мои статьи можно найти здесь:Go Channels Tutorial
client.go
Сначала мы создаемclient.go
новый файл, он будет существовать вpkg/websocket
каталог, в файле будет определен файл со следующим содержимымClient
Структура:
- ID: Уникальная идентифицируемая строка для определенного соединения.
-
Conn:направление
websocket.Conn
указатель -
Pool:направление
Pool
указатель
также необходимо определитьRead()
метод, который всегда будет слушать этоClient
Новое сообщение, отправленное через соединение через веб-сокет.
Если будут получены новые сообщения, он передаст эти сообщения в пулBroadcast
канал, который затем рассылает полученные сообщения каждому клиенту в пуле.
package websocket
import (
"fmt"
"log"
"github.com/gorilla/websocket"
)
type Client struct {
ID string
Conn *websocket.Conn
Pool *Pool
}
type Message struct {
Type int `json:"type"`
Body string `json:"body"`
}
func (c *Client) Read() {
defer func() {
c.Pool.Unregister <- c
c.Conn.Close()
}()
for {
messageType, p, err := c.Conn.ReadMessage()
if err != nil {
log.Println(err)
return
}
message := Message{Type: messageType, Body: string(p)}
c.Pool.Broadcast <- message
fmt.Printf("Message Received: %+v\n", message)
}
}
Отлично, мы определили клиент в коде, давайте перейдем к реализации пула.
Структура бассейна
мы вpkg/websocket
Создайте новый файл в каталогеpool.go
.
Сначала определитеPool
struct, которая будет содержать все необходимое для параллельного общенияchannels
, и клиентmap
.
package websocket
import "fmt"
type Pool struct {
Register chan *Client
Unregister chan *Client
Clients map[*Client]bool
Broadcast chan Message
}
func NewPool() *Pool {
return &Pool{
Register: make(chan *Client),
Unregister: make(chan *Client),
Clients: make(map[*Client]bool),
Broadcast: make(chan Message),
}
}
Нам нужно убедиться, что только одна точка в приложении может записывать в соединение WebSocket, иначе мы столкнемся с проблемами параллельной записи. Итак, определитеStart()
метод, который всегда будет слушатьPool
содержимое каналов, а затем, если он получит что-то, отправленное на один из каналов, он будет действовать соответствующим образом.
-
Register- при подключении нового клиента,
Register channel
будет отправлено всем клиентам в этом пулеNew User Joined...
- Unregister- Выйдите из системы, уведомите пул, когда клиент отключится.
- Clients- Булева карта клиентов. Логическое значение можно использовать для определения активности/бездействия клиента.
- Broadcast- Канал, который при доставке сообщений перебирает всех клиентов в пуле и отправляет сообщения через сокет.
Код:
func (pool *Pool) Start() {
for {
select {
case client := <-pool.Register:
pool.Clients[client] = true
fmt.Println("Size of Connection Pool: ", len(pool.Clients))
for client, _ := range pool.Clients {
fmt.Println(client)
client.Conn.WriteJSON(Message{Type: 1, Body: "New User Joined..."})
}
break
case client := <-pool.Unregister:
delete(pool.Clients, client)
fmt.Println("Size of Connection Pool: ", len(pool.Clients))
for client, _ := range pool.Clients {
client.Conn.WriteJSON(Message{Type: 1, Body: "User Disconnected..."})
}
break
case message := <-pool.Broadcast:
fmt.Println("Sending message to all clients in Pool")
for client, _ := range pool.Clients {
if err := client.Conn.WriteJSON(message); err != nil {
fmt.Println(err)
return
}
}
}
}
}
websocket.go
Отлично, давай поправляйсяwebsocket.go
файл с небольшими изменениями и удалить некоторые функции и методы, которые больше не нужны:
package websocket
import (
"log"
"net/http"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool { return true },
}
func Upgrade(w http.ResponseWriter, r *http.Request) (*websocket.Conn, error) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println(err)
return nil, err
}
return conn, nil
}
Обновить main.go
Наконец, нам нужно обновитьmain.go
файл, создайте новый при каждом подключенииClient
и использоватьPool
Зарегистрируйте клиент:
package main
import (
"fmt"
"net/http"
"github.com/TutorialEdge/realtime-chat-go-react/pkg/websocket"
)
func serveWs(pool *websocket.Pool, w http.ResponseWriter, r *http.Request) {
fmt.Println("WebSocket Endpoint Hit")
conn, err := websocket.Upgrade(w, r)
if err != nil {
fmt.Fprintf(w, "%+v\n", err)
}
client := &websocket.Client{
Conn: conn,
Pool: pool,
}
pool.Register <- client
client.Read()
}
func setupRoutes() {
pool := websocket.NewPool()
go pool.Start()
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
serveWs(pool, w, r)
})
}
func main() {
fmt.Println("Distributed Chat App v0.01")
setupRoutes()
http.ListenAndServe(":8080", nil)
}
контрольная работа
Теперь, когда все необходимые изменения внесены, мы должны проверить, что было сделано, и убедиться, что все работает так, как ожидалось.
Запустите ваше серверное приложение:
$ go run main.go
Distributed Chat App v0.01
Если вы открываете в нескольких браузерахhttp://localhost:3000, вы можете видеть, что они автоматически подключаются к серверной службе WebSocket, и теперь мы можем отправлять и получать сообщения от других клиентов в том же пуле!
Суммировать
В этом разделе нам удалось реализовать способ обработки нескольких клиентов и передачи сообщения всем, кто подключен к пулу соединений.
Теперь это начинает становиться интересным. Мы можем добавить новые функции, такие как настраиваемые сообщения, в следующем разделе.
Следующий раздел: Часть 5 -Оптимизируйте интерфейс
оригинал:учебник edge.net/projects/eat…
автор:Elliot ForbesПереводчик:щелк-щелкВычитка:polaris1119
Эта статья написанаGCTTоригинальная компиляция,Перейти на китайский языкЧесть запуска