如何使用 Go 实现 Server-Sent Events(SSE)

1,413 阅读4分钟

How to implement Server-Sent Events in Go

原文链接:packagemain.tech/p/implement…

原文作者:Julien Singler

译者:菜小鸟魔王

01 Introduction

服务器推送技术(Server-Sent Events, SSE)是一种高效能的解决方案,它支持服务器向客户端进行实时且单向的数据传输。本文将带领读者深入了解如何在 Go 语言中应用 SSE,我们将讨论其诸多优点、适用场景,并通过具体案例展示其实现过程。

02 什么是 Server-Sent Events 技术?

SSE 技术允许服务器通过持续的 HTTP 连接向客户端推送数据。

与双向通信的 WebSockets 相比,SSE 只支持单向数据流,因此它在实现上更为简洁,特别适用于那些只需服务器向客户端实时发送数据更新,而不需要客户端回传信息的场景。

构建一个采用 SSE 的 Web 应用并不复杂。需要在服务器上编写一些代码来向前端发送事件流,而客户端在接收事件的处理逻辑上,与使用 websockets 时非常相似。不过,由于这是一种单向连接,所以并不支持从客户端向服务器发送事件。

03 SSE 的优点

  • 简洁:SSE 在实现上比 WebSockets 更为简单。

  • 原生浏览器兼容:SSE 无需额外配置,即可在多数现代浏览器上直接使用。

  • 自动重连功能:当连接意外断开时,客户端能够自动进行重连尝试。

  • 高效:SSE 通过单一 HTTP 连接进行数据传输,有效减少了服务器和客户端的资源消耗。

04 用 GO 实现 SSE

让我们用 Go 创建一个简单的 SSE 服务器:

package main

import (
    "fmt"
    "net/http"
    "time"
)

func sseHandler(w http.ResponseWriter, r *http.Request) {
    // Set http headers required for SSE
    w.Header().Set("Content-Type", "text/event-stream")
    w.Header().Set("Cache-Control", "no-cache")
    w.Header().Set("Connection", "keep-alive")

    // You may need this locally for CORS requests
    w.Header().Set("Access-Control-Allow-Origin", "*")

    // Create a channel for client disconnection
    clientGone := r.Context().Done()

    rc := http.NewResponseController(w)
    t := time.NewTicker(time.Second)
    defer t.Stop()
    for {
        select {
        case <-clientGone:
            fmt.Println("Client disconnected")
            return
        case <-t.C:
            // Send an event to the client
            // Here we send only the "data" field, but there are few others
            _, err := fmt.Fprintf(w, "data: The time is %s\n\n", time.Now().Format(time.UnixDate))
            if err != nil {
                return
            }
            err = rc.Flush()
            if err != nil {
                return
            }
        }
    }
}

func main() {
    http.HandleFunc("/events", sseHandler)
    fmt.Println("server is running on :8080")
    if err := http.ListenAndServe(":8080", nil); err != nil {
        fmt.Println(err.Error())
    }
}

05 事件流格式

  • 每个事件都是以一对换行符结尾的文本块形式发送的,即以 “\n\n” 结尾。

  • 负责发送事件的服务器端脚本需要使用 MIME 类型 “text/event-stream” 来响应。

  • 为了调试方便,我们可以添加注释。在一行的开头使用冒号(:)就是添加注释,这样的代码行会被忽略。

接收到的每个消息都包含以下字段组合,每个字段占据一行:

  • event - 用来标识所描述事件类型的字符串。

  • data - 消息的数据字段。

  • id - 用于设置 EventSource 对象的最后一个事件 ID 值的事件 ID。

  • retry - 重连的时间间隔。

在前端,需要使用一个名为 EventSource 的对象:


<!doctype html>
<html>
    <body>
        <ul id="list"></ul>
    </body>

    <script type="text/javascript">
        const eventSrc = new EventSource("http://127.0.0.1:8080/events");

        const list = document.getElementById("list");

        eventSrc.onmessage = (event) => {
            const li = document.createElement("li");
            li.textContent = `message: ${event.data}`;

            list.appendChild(li);
        };
    </script>
</html>

欲知更多详情,请点击此处。以下是浏览器控制台中的显示内容:

06 实现 SSE 的关键组件

  • 头部设置:需要设定特定的头部信息以建立 SSE 连接。

  • 事件循环:服务器将不断地向客户端推送事件。

  • 数据即时发送:利用 http.Flusher 接口来保证数据能够被即时发送。

  • 客户端断开处理:以恰当的方式处理客户端的断开连接。

07 Golang 中 SSE 的实践指导原则

  • 错误处理:为连接问题实现健壮的错误处理机制。

  • 事件格式化:使用结构化格式(例如 JSON)来定义事件。

  • 重连策略:客户端在连接断开时应该有一个合理的重连策略,比如使用指数退避算法来避免频繁的重连尝试。

  • 负载均衡:在服务器端,如果应用程序需要处理大量并发连接,应该使用负载均衡器来分配流量。

08 SSE 的使用场景

  1. 实时仪表板
  2. 体育赛事实时得分
  3. 社交媒体动态
  4. 股票市场行情显示器
  5. 长时间运行任务的进度指示器

09 Conclusion

Server-Sent Events(简称 SSE)为 Golang 提供了一种高效且简单的方法来实现实时服务器到客户端的通信。利用 SSE,开发人员可以轻松构建出响应速度快且动态的 Web 应用,同时还能保持最小的开销和复杂度。

在构建基于 SSE 的应用程序时,请务必考虑到可扩展性、错误处理以及客户端的实现,以确保实时通信系统的健壮性和效率。