Golang 搭建 WebSocket 应用(二) - 基本群聊 demo

简介: Golang 搭建 WebSocket 应用(二) - 基本群聊 demo

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站

上一篇文章中,我们已经了解了 gorilla/websocket 的一些基本概念和简单的用法。

接下来,我们通过一个再复杂一点的例子来了解它的实际用法。

功能

这个例子来自源码里面的 examples/chat,它包含了以下功能:

  1. 用户访问群聊页面的时候,可以发送消息给所有其他在聊天室内的用户(也就是同样打开群聊页面的用户)
  2. 所有的用户发送的消息,群聊中的所有用户都能收到(包括自己)

其基本效果如下:

为了更好地理解 gorilla/websocket 的使用方式,下文在讲解的时候会去掉一些出于健壮性考虑而写的代码。

基本架构

这个 demo 的基本组件如下图:

  1. Client:也就是连接到了服务端的客户端,可以有多个
  2. Hub:所有的客户端会保存到 Hub 中,同时所有的消息也会经过 Hub 来进行广播(也就是将消息发给所有连接到 Hub 的客户端)

工作原理

Hub

Hub 的源码如下:

type Hub struct {
    // 保存所有客户端
  clients map[*Client]bool
    // 需要广播的消息
  broadcast chan []byte
    // 等待连接的客户端
  register chan *Client
    // 等待断开的客户端
  unregister chan *Client
}

Hub 的核心方法如下:

func (h *Hub) run() {
  for {
    select {
    case client := <-h.register:
            // 从等待连接的客户端 chan 取一项,设置到 clients 中
      h.clients[client] = true
    case client := <-h.unregister:
            // 断开连接:
            // 1. 从 clients 移除
            // 2. 关闭发送消息的 chan
      if _, ok := h.clients[client]; ok {
        delete(h.clients, client)
        close(client.send)
      }
    case message := <-h.broadcast:
            // 发送广播消息给每一个客户端
      for client := range h.clients {
        select {
                    // 成功写入消息到客户端的 send 通道
        case client.send <- message:
        default:
                    // 发送失败则剔除这个客户端
          close(client.send)
          delete(h.clients, client)
        }
      }
    }
  }
}

这个例子中使用了 chan 来做同步,这可以提高 Hub 的并发处理速度,因为不需要等待 Hubrun 方法中其他 chan 的处理。

简单来说,Hub 做了如下操作:

  1. 维护所有的客户端连接:客户端连接、断开连接等
  2. 发送广播消息

Client

Client 的源码如下:

type Client struct {
    // Hub 单例
  hub *Hub
    // 底层的 websocket 连接
  conn *websocket.Conn
    // 等待发送给客户端的消息
  send chan []byte
}

它包含了如下字段:

  1. Hub 单例(我们的 demo 中只有一个聊天室)
  2. conn 底层的 WebSocket 连接
  3. send 通道,这里保存了等待发送给这个客户端的数据

Client 中,是通过 readPump 这个方法来从客户端接收消息的:

func (c *Client) readPump() {
  defer func() {
        // 连接断开、出错等:
        // 会关闭连接,从 hub 移除这个连接
    c.hub.unregister <- c
    c.conn.Close()
  }()
  // ... 
  for {
        // 接收消息
    _, message, err := c.conn.ReadMessage()
    if err != nil {
      // ... 错误处理
      break
    }
        // 消息处理,最终放入 broadcast,准备发给所有其他在线的客户端
    message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1))
    c.hub.broadcast <- message
  }
}

readPump 方法做的事情很简单,它就是接收消息,然后通过 Hubbroadcast 来发给所有在线的客户端。

而发送消息会稍微复杂一点,我们来看看 writePump 的源码:

func (c *Client) writePump() {
  defer func() {
        // 连接断开、出错:关闭 WebSocket 连接
    c.conn.Close()
  }()
  for {
    select {
    case message, ok := <-c.send:
            // 控制写超时时间
      c.conn.SetWriteDeadline(time.Now().Add(writeWait))
      if !ok {
        // 连接已经被 hub 关闭了
        c.conn.WriteMessage(websocket.CloseMessage, []byte{})
        return
      }

            // 获取用以发送消息的 Writer
      w, err := c.conn.NextWriter(websocket.TextMessage)
      if err != nil {
        return
      }
            // 发送消息
      w.Write(message)

      n := len(c.send)
      for i := 0; i < n; i++ {
        w.Write(newline)
                // 将接收到的信息发送出去
        w.Write(<-c.send)
      }

            // 调用 Close 的时候,消息会被发送出去
      if err := w.Close(); err != nil {
        return
      }
    }
  }
}

虽然比读操作复杂了一点,但是也还是很好理解,它做的东西也不多:

  1. 获取用以发送消息的 Writer
  2. 获取从 hub 中接收到的其他客户端的消息,发送给当前这个客户端

具体是如何工作起来的?

  1. main 函数中创建 hub 实例
  2. 通过下面这个 serveWs 来将建立 WebSocket 连接:
func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) {
    // 将 HTTP 连接转换为 WebSocket 连接
  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)}
    // 注册到 hub
  client.hub.register <- client

  // 发送数据到客户端的协程
  go client.writePump()
    // 从客户端接收数据的协程
  go client.readPump()
}

serveWs 中,我们在跟客户端建立起连接后,创建了两个协程,一个是从客户端接收数据的,另一个是发送消息到客户端的。

这个 demo 的作用

这个 demo 是一个比较简单的 demo,不过也包含了我们构建 WebSocket 应用的一些关键处理逻辑,比如:

  • 使用 Hub 来维持一个低层次的连接信息
  • Client 中区分读和写的协程
  • 以及一些边界情况的处理:比如连接断开、超时等

在后续的文章中,我们会基于这些已有知识去构建一个更加完善的 WebSocket 应用,今天就到此为止了。


目录
相关文章
|
1月前
|
前端开发 JavaScript UED
探索Python Django中的WebSocket集成:为前后端分离应用添加实时通信功能
通过在Django项目中集成Channels和WebSocket,我们能够为前后端分离的应用添加实时通信功能,实现诸如在线聊天、实时数据更新等交互式场景。这不仅增强了应用的功能性,也提升了用户体验。随着实时Web应用的日益普及,掌握Django Channels和WebSocket的集成将为开发者开启新的可能性,推动Web应用的发展迈向更高层次的实时性和交互性。
75 1
|
22天前
|
JavaScript 前端开发 测试技术
前端全栈之路Deno篇(五):如何快速创建 WebSocket 服务端应用 + 客户端应用 - 可能是2025最佳的Websocket全栈实时应用框架
本文介绍了如何使用Deno 2.0快速构建WebSocket全栈应用,包括服务端和客户端的创建。通过一个简单的代码示例,展示了Deno在WebSocket实现中的便捷与强大,无需额外依赖,即可轻松搭建具备基本功能的WebSocket应用。Deno 2.0被认为是最佳的WebSocket全栈应用JS运行时,适合全栈开发者学习和使用。
|
20天前
|
Kubernetes Cloud Native JavaScript
为使用WebSocket构建的双向通信应用带来基于服务网格的全链路灰度
介绍如何使用为基于WebSocket的云原生应用构建全链路灰度方案。
|
2月前
|
算法 安全 测试技术
golang 栈数据结构的实现和应用
本文详细介绍了“栈”这一数据结构的特点,并用Golang实现栈。栈是一种FILO(First In Last Out,即先进后出或后进先出)的数据结构。文章展示了如何用slice和链表来实现栈,并通过golang benchmark测试了二者的性能差异。此外,还提供了几个使用栈结构解决的实际算法问题示例,如有效的括号匹配等。
golang 栈数据结构的实现和应用
|
2月前
|
JavaScript 前端开发 UED
WebSocket在Python Web开发中的革新应用:解锁实时通信的新可能
在快速发展的Web应用领域中,实时通信已成为许多现代应用不可或缺的功能。传统的HTTP请求/响应模式在处理实时数据时显得力不从心,而WebSocket技术的出现,为Python Web开发带来了革命性的变化,它允许服务器与客户端之间建立持久的连接,从而实现了数据的即时传输与交换。本文将通过问题解答的形式,深入探讨WebSocket在Python Web开发中的革新应用及其实现方法。
44 3
|
1月前
|
中间件 Go 数据处理
应用golang的管道-过滤器架构风格
【10月更文挑战第1天】本文介绍了一种面向数据流的软件架构设计模式——管道-过滤器(Pipe and Filter),并通过Go语言的Gin框架实现了一个Web应用示例。该模式通过将数据处理流程分解为一系列独立的组件(过滤器),并利用管道连接这些组件,实现了模块化、可扩展性和高效的分布式处理。文中详细讲解了Gin框架的基本使用、中间件的应用以及性能优化方法,展示了如何构建高性能的Web服务。
65 0
|
1月前
|
消息中间件 网络协议 安全
C# 一分钟浅谈:WebSocket 协议应用
【10月更文挑战第6天】在过去的一年中,我参与了一个基于 WebSocket 的实时通信系统项目,该项目不仅提升了工作效率,还改善了用户体验。本文将分享在 C# 中应用 WebSocket 协议的经验和心得,包括基础概念、C# 实现示例、常见问题及解决方案等内容,希望能为广大开发者提供参考。
99 0
|
2月前
|
存储 监控 Go
面向OpenTelemetry的Golang应用无侵入插桩技术
文章主要讲述了阿里云 ARMS 团队与程序语言与编译器团队合作研发的面向OpenTelemetry的Golang应用无侵入插桩技术解决方案,旨在解决Golang应用监控的挑战。
|
3月前
|
Go 开发者
|
3月前
|
人工智能 安全 Go
Golang 搭建 WebSocket 应用(八) - 完整代码
Golang 搭建 WebSocket 应用(八) - 完整代码
40 0