[Перевод] Создание системы чата с помощью Go и ReactJS (4)

задняя часть Go React.js

Полный код этого раздела: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.

Сначала определитеPoolstruct, которая будет содержать все необходимое для параллельного общения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оригинальная компиляция,Перейти на китайский языкЧесть запуска