使用Go和React实现Websocket,实现Web apps实时通信

一个简单的教程,让你在Web apps中实现实时通信

照片由Manson YimUnsplash拍摄,作者修改

对于很多想在Web apps中实现实时通信的用户而言,这篇文章对你肯定大有裨益,它将引导你逐步了解如何使用Go和React实现WebSocket。

依赖项

所需的依赖项如下:

设置API服务器

首先,我们将使用echo包制作一个API服务器。

将以下代码添加到 main.go

package main

import (
	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
	"net/http"
)

func main() {
	e := echo.New()

	e.Use(middleware.Logger())
	e.Use(middleware.Recover())

	e.GET("/", func(c echo.Context) error {
		return c.String(http.StatusOK, "Hello, World!")
	})

	e.Logger.Fatal(e.Start(":8080"))
}

它将在http:// localhost:8080 Hello, World! 进行响应,如下所示:

http://localhost:8080

设置一个WebSocket

默认情况下,Go有一个专为WebSocket服务的软件包,但是目前,它涵盖的功能却没有人们口中所说的那么多:

此程序包目前缺乏WebSocket程序包中可替代的和更积极维护的的某些功能:

https://godoc.org/github.com/gorilla/websocket
https://godoc.org/nhooyr.io/websocket

因此,我们将使用gorilla / websocket包来设置WebSocket

——这是Go中实现广泛使用的WebSocket。

实现WebSocket的步骤如下:

  • 为WebSocket创建端点
  • 升级传入的HTTP连接
  • 侦听一个连接

为WebSocket创建一个端点

我们要做的第一件事是为Websocket创建一个端点。

让我们将WebSocket端点添加到 main.go ,如下所示:

func main() {
  	// ...
  	
  	e.GET("/ws", func(c echo.Context) error {
		return c.String(http.StatusOK, "Hi, WebSocket!")
	})
}

升级传入的HTTP连接

接下来,我们将升级传入的HTTP连接。

要做到这一点,我们需要创建一个结构: websocket.Upgrader 。此代码的结构包含WebSocket连接的信息,如下所示:

var upgrader = websocket.Upgrader {}
func main(){}

并使用以下代码在处理程序中升级HTTP连接:

func main() {
  e.GET("/ws", func(c echo.Context) error {
  	upgrader.CheckOrigin = func(r *http.Request) bool { return true }
    
	ws, err := upgrader.Upgrade(c.Response().Writer, c.Request(), nil)
	if !errors.Is(err, nil) {
		log.Println(err)
	}
	defer ws.Close()

	log.Println("Connected!")

	return nil
  })
}

侦听一个连接

接下来,我们将创建一个函数来侦听通过客户端发送的任何WebSocket连接。

在这种情况下,我们期望来自客户端应用程序的JSON数据,所以让我们创建一个名为 Message 的结构:

type Message struct {
   Message string `json:"message"`
}
func main() {}

并在处理程序中添加一些代码:

	e.GET("/ws", func(c echo.Context) error {
		upgrader.CheckOrigin = func(r *http.Request) bool { return true }

		ws, err := upgrader.Upgrade(c.Response().Writer, c.Request(), nil)
		if !errors.Is(err, nil) {
			log.Println(err)
		}
		defer ws.Close()

		log.Println("Connected!")

		for {
			var message Message
			err := ws.ReadJSON(&message)
			if !errors.Is(err, nil) {
				log.Printf("error occurred: %v", err)
				break
			}
			log.Println(message)

			// send message from server
			if err := ws.WriteJSON(message); !errors.Is(err, nil) {
				log.Printf("error occurred: %v", err)
			}
		}

		return nil
	})

定义 for 循环,该循环将侦听从客户端应用程序发送的任何消息。

读取JSON数据后,它将通过 ws.WriteJSON(message) 发送消息到客户端。

设置客户端应用程序

现在我们已经为WebSocket设置了一个端点,我们将创建一个客户端应用程序,方便通过WebSocket端点发送。

为了快速开始,我们将使用 create-react-app

npx create-react-app websocket-app --template typescript

安装后,通过运行以下命令来运行开发服务器:

yarn start

你将看到欢迎页面:


欢迎页面

连接到WebSocket端点

为了连接到我们的WebSocket端点,我们将创建一个WebSocket API实例在 ws://127.0.0.1:8080/ws 上进行连接,并尝试通过该连接发送消息。

src/App.tsx 进行一些修改,如下所示:

import React, { useCallback, useEffect, useState } from 'react';
import './App.css';

const socket = new WebSocket("ws://127.0.0.1:8080/ws");

function App() {
  const [message, setMessage] = useState('')
  const [inputValue, setInputValue] = useState('')

  useEffect(() => {
    socket.onopen = () => {
      setMessage('Connected')
    };

    socket.onmessage = (e) => {
      setMessage("Get message from server: " + e.data)
    };

    return () => {
      socket.close()
    }
  }, [])

  const handleClick = useCallback((e) => {
    e.preventDefault()

    socket.send(JSON.stringify({
      message: inputValue
    }))
  }, [inputValue])

  const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
    setInputValue(e.target.value)
  }, [])

  return (
    <div className="App">
      <input id="input" type="text" value={inputValue} onChange={handleChange} />
      <button onClick={handleClick}>Send</button>
      <pre>{message}</pre>
    </div>
  );
}

export default App;

安装好后,App组件将使用 socket.onopen 事件启动侦听器,WebSocket将在该事件中升级连接。

socket.messsage 事件监听来自服务器的消息。

现在我们准备对其进行测试,访问http:// localhost:3000,然后让我们在字段中输入一些值:

1_forbiiUX9O3xzPtz8f1pqw
检测连接

你可以看到连接成功运行。

但是,如果多个客户端同时发送消息怎么办?是否要在其他标签中同步呢?


检测连接 2

答案是否定的:因为每个客户端都有对应的连接。在 main.go 中,我们只向有一个关联连接的客户端发送消息。

在本文中,我们希望在整个应用程序中实现实时连接而无需重新加载。

为此,我们需要实现一个可以处理多个客户端并向其发送消息的实现。

处理多个客户的实现

首先,我们将创建 hub.go ,其中包含客户信息并向他们发送消息。我们将使用以下代码:

package main

import (
	"errors"
	"github.com/gorilla/websocket"
	"log"
)

type Hub struct {
	clients map[*websocket.Conn]bool
	broadcast chan Message
}

func NewHub() *Hub {
	return &Hub{
		clients:   make(map[*websocket.Conn]bool),
		broadcast: make(chan Message),
	}
}

func (h *Hub) run() {
	for {
		select {
		case message := <-h.broadcast:
			for client := range h.clients {
				if err := client.WriteJSON(message); !errors.Is(err, nil) {
					log.Printf("error occurred: %v", err)
				}
			}
		}
	}
}

并对 main.go 进行一些修改:


image
hub 结构存储客户端并通过通道发送消息。通道收到消息后,它将写回所有客户端。

现在,我们已经实现了一个侦听器,它将向所有选项卡发送消息。

让我们测试一下是否一切正常。


如你所见,一旦右侧的选项卡发送消息,左侧的选项卡将显示相同的消息。
现在,我们就已经实现了实时通信。

结论

我们已经介绍了如何使用Go和React实现WebSocket连接,从而在Web apps中实现实时通信。而WebSocket已经存在了一段时间,现在根据我可以使用的介绍,大多数现代浏览器都支持WebSocket 。

此外,GraphQL通过Subscriptions利用WebSocket协议,该操作可监视Graphql服务器发出的事件。

对于双向实时通信,WebSocket比旧的方式(例如HTTP轮询和SSE)更可靠、更高效。此外,借助上面已经看到的出色的库,你可以轻松地将WebSocket引入你的应用程序。

原文作者 Manato Kuroda
原文链接https://betterprogramming.pub/implementing-websocket-with-go-and-react-b3ee976770ab

推荐阅读
作者信息
AgoraTechnicalTeam
TA 暂未填写个人简介
文章
153
相关专栏
本专栏仅用于分享音视频相关的技术文章,与其他开发者和 Agora 研发团队交流、分享行业前沿技术、资讯。发帖前,请参考「社区发帖指南」,方便您更好的展示所发表的文章和内容。