第 11 章:使用 WebSockets

WebSockets 技术为云原生应用带来实时双向通信能力,极大提升了用户体验和系统交互性。本章将系统介绍 WebSockets 的原理、云原生适应性及第三方消息服务集成实践。

引言

过去,Web 应用需要手动刷新才能获取最新数据。如今,实时交互已成为网站标配,WebSockets 和服务器发送事件(SSE)技术让前端与后端实现高效的双向或单向通信,极大提升了用户体验和系统响应能力。

本章主要内容包括:

  • WebSockets 原理与云端适用性
  • 第三方消息服务集成 WebSockets 应用
  • 架构设计与代码实现

WebSockets 技术解析

WebSockets 允许浏览器与服务器之间建立持久的双向通信通道,适用于实时通知、在线聊天、游戏等场景。相比传统 HTTP 请求,WebSockets 通过连接升级实现协议转换,支持高效的数据交换。

WebSockets 工作原理

WebSockets 由客户端发起,通过 HTTP 连接升级为 WebSockets 协议。请求示例:

GET /game HTTP/1.1
Host: server.mygame.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: awesomegame
Sec-WebSocket-Version: 13
Origin: http://mygame.com

服务器响应:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: awesomegame

客户端可通过标准 JavaScript API 建立连接:

var socket = new WebSocket('ws://server.mygame.com');

随后可定义事件处理函数,实现消息收发。

WebSockets 与 SSE 对比

WebSockets 支持全双工通信,SSE 仅支持服务器向客户端单向推送。WebSockets 适合复杂交互场景,SSE 适合轻量级通知。两者在云原生架构下均需关注连接管理与扩展性。

WebSockets 服务器设计要点

设计 WebSockets 服务器需关注以下方面:

  • 支持连接升级与协议转换
  • 管理所有 WebSockets 连接映射
  • 支持广播与定向消息推送
  • 连接关闭时及时清理资源

WebSockets 的云原生适应性

WebSockets 连接具有有状态性,易受实例扩展影响。云原生应用强调无状态和可扩展性,需避免单实例连接孤岛问题。如下图所示:

WebSockets 和糟糕的水平扩展
WebSockets 和糟糕的水平扩展

为实现跨实例通信,建议将 WebSockets 组件抽离至后端消息服务,实现统一消息分发和连接管理:

可扩展的、适合云的 WebSockets 架构
可扩展的、适合云的 WebSockets 架构

集成第三方消息服务实现 WebSockets 应用

推荐使用 PubNub 等第三方消息服务,支持发布 - 订阅模式,简化消息分发与连接管理。PubNub 提供免费账户和丰富的功能,适合云原生场景。

前端集成 PubNub 示例

以下代码展示了如何在前端订阅和发布消息,实现实时聊天功能:

<!-- assets/templates/index.html JavaScript snippet -->
<script src="http://cdn.pubnub.com/pubnub-3.14.4.min.js"></script>
<script>
var source = "{{ .Email }}";
pubnub = PUBNUB({
    publish_key : '{{ .PubKey }}',
    subscribe_key : '{{ .SubKey }}'
});

console.log("Subscribing..");

pubnub.subscribe({
    channel : "hello_world",
    message : function (message, envelope, channelOrGroup, time, channel) {
        console.log(
            "Message Received." + "\n" +
            "Channel or Group: " + JSON.stringify(channelOrGroup) + "\n" +
            "Channel: " + JSON.stringify(channel) + "\n" +
            "Message: " + JSON.stringify(message) + "\n" +
            "Time: " + time + "\n" +
            "Raw Envelope: " + JSON.stringify(envelope)
        );
        var newDiv = document.createElement('div')
        newDiv.innerHTML = message
        var oldDiv = document.getElementById('chatLog')
        oldDiv.appendChild(newDiv)
    },
    connect: pub
});

function pub(txt) {
    console.log("About to publish: " + txt);
    pubnub.publish({
        channel : "hello_world",
        message : "[" + source + "]: " + txt,
        callback: function(m){ console.log(m);}
    })
}

function publish() {
    console.log("Going to publish on demand")
    txt = document.getElementById("theText").value;
    pub(txt)
}
</script>

该代码通过模板变量注入服务端信息,实现用户身份与消息服务的绑定。

服务端广播消息示例

服务端可通过 PubNub Go SDK 向所有客户端广播消息:

// filepath: server/broadcast_handler.go
package server

import (
    "fmt"
    "net/http"
    "github.com/pubnub/go/messaging"
    "github.com/unrolled/render"
)

func broadcastHandler(messagingConfig *pubsubConfig) http.HandlerFunc {
    formatter := render.New(render.Options{
        IndentJSON: true,
    })

    pubnub := messaging.NewPubnub(messagingConfig.PublishKey,
        messagingConfig.SubscribeKey, "", "", false, "")
    channel := "hello_world"

    return func(w http.ResponseWriter, r *http.Request) {
        successChannel := make(chan []byte)
        errorChannel := make(chan []byte)
        go pubnub.Publish(channel, "[SYSTEM] Broadcast from the server!",
            successChannel, errorChannel)

        select {
        case response := <-successChannel:
            fmt.Printf("pubnub publish response: %s\n", string(response))
        case err := <-errorChannel:
            fmt.Printf("pubnub publish error: %s\n", string(err))
        case <-messaging.Timeout():
            fmt.Println("pubnub publish() timeout")
        }
        formatter.JSON(w, http.StatusOK, nil)
    }
}

该处理器通过 goroutine 异步推送消息,并使用 select 语句处理结果。

运行与部署 WebSockets 应用

运行 WebSockets 示例需配置 Auth0 和 PubNub 账户,并在 local_config/env 文件中设置相关密钥。启动服务后,用户可通过 OAuth 登录并访问 /chat 页面,体验实时聊天功能。

如需服务端广播消息,可通过如下命令触发:

curl -X POST http://192.168.99.100/broadcast

浏览器端会收到系统广播消息,支持多用户、多标签页实时互动。

聊天会话示例
聊天会话示例

关于 JavaScript 框架

示例代码采用原生 JavaScript,便于理解消息服务集成原理。实际项目可结合 React、Vue 等现代前端框架,提升代码可维护性和用户体验。

总结

本章系统介绍了 WebSockets 技术原理、云原生架构适应性及第三方消息服务集成方法。通过 PubNub 等服务实现高可扩展性和实时交互能力,极大提升了云原生应用的用户体验。建议结合实际项目,深入理解消息服务与 WebSockets 的集成模式,为后续复杂系统开发打下坚实基础。

参考文献

文章导航

独立页面

这是书籍中的独立页面。

书籍首页

评论区