20_HTML5 SSE --[HTML5 API 学习之旅]

发布于:2025-02-11 ⋅ 阅读:(87) ⋅ 点赞:(0)

HTML5 Server-Sent Events (SSE) 是一种技术,它允许服务器向浏览器推送更新。与传统的轮询不同,SSE提供了真正的单向实时通信通道:服务器可以主动发送数据到客户端,而不需要客户端发起请求。这对于实现实时更新的应用非常有用,比如股票行情、社交网络更新、聊天应用等。

SSE 的特点

  • 单向通信:服务器到客户端的通信是单向的,客户端不能通过同一个连接向服务器发送信息。
  • 自动重连:如果连接断开,浏览器会自动尝试重新建立连接。
  • 简单性:相比WebSocket,SSE更简单,因为它基于HTTP协议,并且不需要额外的握手过程。
  • 事件驱动:服务器可以通过发送特定格式的消息来触发客户端上的事件处理函数。

SSE 的基本语法

服务器端

服务器需要设置正确的HTTP响应头,并使用特定的格式发送消息给客户端:

Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive

data: This is a message\n\n

每个消息以data:开头,后面跟着消息内容,最后用两个换行符(\n\n)结束。此外,还可以发送其他类型的指令,如event:指定事件类型或id:设置事件ID。

客户端

在客户端,使用JavaScript中的EventSource对象来接收来自服务器的消息:

if(typeof(EventSource)!=="undefined") {
    var source = new EventSource("/events");
    source.onmessage = function(event) {
        console.log("New message:", event.data);
    };
    source.onerror = function(event) {
        console.error("Error occurred:", event);
    };
} else {
    console.log("Your browser does not support server-sent events.");
}

示例:

下面是五个使用HTML5 Server-Sent Events (SSE) 从Gin框架中获取信息的完整示例,每个示例展示了不同的应用场景。这些示例包括了从简单的计数器更新到更复杂的实时通知系统和动态数据推送。

示例1: 简单计数器更新

服务器端(Go + Gin)代码:

文件:main.go

package main

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

	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()

	// 加载HTML模板文件夹中的所有HTML文件,以便可以在响应中使用这些模板。
	// 这里假设HTML文件位于项目根目录下的"templates"文件夹中。
	r.LoadHTMLGlob("templates/*")

	// 定义根路径"/"的GET请求处理器。
	// 当用户访问根URL时,将渲染并返回"index.html"模板。
	r.GET("/", func(c *gin.Context) {
		c.HTML(http.StatusOK, "index.html", nil)
	})

	// 定义处理SSE事件的GET请求处理器,路径为"/events"。
	r.GET("/events", func(c *gin.Context) {
		// 设置HTTP响应头以支持SSE。
		// Content-Type设置为"text/event-stream"表示这是一个SSE流。
		// Cache-Control设置为"no-cache"防止浏览器缓存数据。
		// Connection设置为"keep-alive"保持连接打开。
		c.Header("Content-Type", "text/event-stream")
		c.Header("Cache-Control", "no-cache")
		c.Header("Connection", "keep-alive")

		// 检查ResponseWriter是否实现了Flusher接口,确保可以实时发送数据。
		flusher, ok := c.Writer.(http.Flusher)
		if !ok {
			http.Error(c.Writer, "Streaming unsupported!", http.StatusInternalServerError)
			return
		}

		// 开始一个循环来模拟服务器向客户端发送20条消息。
		for i := 0; i < 20; i++ { // 发送20条消息
			// 构建要发送的消息,格式为"data: [message]\n\n"。
			message := fmt.Sprintf("data: %d\n\n", i)
			c.Writer.WriteString(message)

			// 调用Flush()方法立即将缓冲区的数据发送给客户端。
			flusher.Flush()

			// 每次发送消息后暂停1秒,模拟延迟。
			time.Sleep(time.Second)

			// 如果客户端断开了连接(例如关闭了页面),则退出循环。
			if c.Writer.Size() == 0 {
				break
			}
		}
	})

	// 启动HTTP服务器,监听8080端口。
	// Gin框架默认在0.0.0.0上监听,意味着可以从任何网络接口访问该服务。
	r.Run(":8080") // 监听并在 0.0.0.0:8080 上启动服务
}
客户端(HTML + JavaScript)代码:

文件:templates/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <!-- 设置文档的字符编码为UTF-8 -->
    <title>SSE Counter</title>
    <!-- 设置页面标题 -->
</head>
<body>
    <h1>Counter:</h1>
    <!-- 显示计数器标题 -->
    <p id="counter">0</p>
    <!-- 显示计数器数值,初始值设置为0 -->

    <script type="text/javascript">
        // 检查浏览器是否支持Server-Sent Events (SSE)
        if(typeof(EventSource)!=="undefined") {
            // 创建一个新的EventSource对象,连接到服务器端点"/events"
            var source = new EventSource("/events");

            // 当从服务器接收到消息时触发此函数
            source.onmessage = function(event) {
                // 更新页面中ID为"counter"的<p>元素的内容为接收到的消息数据
                document.getElementById("counter").innerHTML = event.data;
            };

            // 如果发生错误(例如连接断开),触发此函数
            source.onerror = function(event) {
                console.error("Error occurred:", event);
                // 可选:关闭事件源以防止进一步尝试重新连接
                // source.close();
            };
        } else {
            // 如果浏览器不支持SSE,则显示提示信息
            document.getElementById("counter").innerHTML = "Sorry, your browser does not support server-sent events.";
        }
    </script>
    <!-- 该脚本块用于与服务器建立SSE连接,并根据接收的数据更新页面上的计数器 -->
</body>
</html>

在这里插入图片描述

示例2: 实时通知系统

服务器端(Go + Gin)代码:

文件:main.go

package main

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

	"github.com/gin-gonic/gin"
)

// 定义一个互斥锁,用于在广播通知时同步对clients的访问。
var mu sync.Mutex

// clients是一个保存所有客户端连接的映射,键是客户端的通信信道,值是一个布尔值表示状态。
var clients = make(map[chan string]bool)

// broadcastNotification向所有连接的客户端广播通知消息。
func broadcastNotification(msg string) {
	// 加锁以确保并发安全。
	mu.Lock()
	defer mu.Unlock()
	// 遍历所有客户端,向它们发送消息。
	for client := range clients {
		client <- msg
	}
}

// handleEvents处理客户端的事件流请求,保持连接开放并实时发送事件数据。
func handleEvents(c *gin.Context) {
	// 设置响应头,指明这是一个事件流。
	c.Header("Content-Type", "text/event-stream")
	c.Header("Cache-Control", "no-cache")
	c.Header("Connection", "keep-alive")

	// 创建一个信道,用于向该客户端发送消息。
	clientChan := make(chan string)
	defer close(clientChan)

	// 加锁以确保并发安全。
	mu.Lock()
	clients[clientChan] = true
	mu.Unlock()

	// 检查是否支持Flush操作。
	flusher, ok := c.Writer.(http.Flusher)
	if !ok {
		http.Error(c.Writer, "Streaming unsupported!", http.StatusInternalServerError)
		return
	}

	// 启动一个goroutine监听clientChan,并向客户端发送数据。
	go func() {
		for msg := range clientChan {
			c.Writer.WriteString(fmt.Sprintf("data: %s\n\n", msg))
			flusher.Flush()
		}
	}()

	// 保持连接开放,定期检查客户端是否还在线。
	for {
		time.Sleep(time.Second)
		if _, err := c.Writer.Write([]byte{}); err != nil {
			delete(clients, clientChan)
			break
		}
	}
}

// main是程序的入口点,初始化Gin框架并设置路由。
func main() {
	r := gin.Default()
	r.LoadHTMLGlob("templates/*")

	// 定义根路径"/"的GET请求处理器。
	// 当用户访问根URL时,将渲染并返回"index.html"模板。
	r.GET("/", func(c *gin.Context) {
		c.HTML(http.StatusOK, "index.html", nil)
	})

	// 定义"/events"的GET请求处理器,用于处理事件流。
	r.GET("/events", handleEvents)

	// 启动一个goroutine定期生成通知消息并广播。
	go func() {
		for i := 0; ; i++ {
			time.Sleep(3 * time.Second)
			broadcastNotification(fmt.Sprintf("New Notification %d", i))
		}
	}()

	// 启动HTTP服务器,监听8080端口。
	r.Run(":8080")
}

客户端(HTML + JavaScript)代码:

文件:templates/index.html

<!DOCTYPE html>
<html lang="en">
<head>
<!-- 定义文档的字符编码 -->
<meta charset="UTF-8">
<!-- 设置网页的标题 -->
<title>Real-time Notifications</title>
</head>
<body>
<!-- 显示通知列表的标题 -->
<h1>Notifications:</h1>
<!-- 动态添加通知的容器 -->
<ul id="notifications"></ul>

<script type="text/javascript">
// 检查浏览器是否支持EventSource接口
if(typeof(EventSource)!=="undefined") {
	// 创建EventSource对象,连接到服务器的事件流
	var source = new EventSource("/events");
	// 监听来自服务器的消息事件
	source.onmessage = function(event) {
		// 创建一个新的<li>元素来显示通知
		var li = document.createElement("li");
		// 设置<li>元素的文本内容为接收到的消息数据
		li.textContent = event.data;
		// 将新的通知添加到通知列表中
		document.getElementById("notifications").appendChild(li);
	};
} else {
	// 如果浏览器不支持EventSource,显示提示信息
	document.write("Sorry, your browser does not support server-sent events.");
}
</script>
</body>
</html>

在这里插入图片描述

示例3: 动态天气更新

服务器端(Go + Gin)代码:

文件:main.go

package main

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

	"github.com/gin-gonic/gin"
)

// getWeatherData 获取天气数据。在实际应用中,这个函数应该与天气服务API交互以获取实时数据。
// 返回值: 一个描述天气的字符串。
func getWeatherData() string {
	// 这里应该有一个函数来获取实际的天气数据
	return "Sunny with a chance of meatballs."
}

// main 是程序的入口点。它设置了一个Gin路由器,配置了两个GET请求的处理器,并启动了服务器。
func main() {
	r := gin.Default()
	r.LoadHTMLGlob("templates/*")

	// 定义根路径"/"的GET请求处理器。
	// 当用户访问根URL时,将渲染并返回"index.html"模板。
	r.GET("/", func(c *gin.Context) {
		c.HTML(http.StatusOK, "index.html", nil)
	})

	// 定义"/weather"路径的GET请求处理器。
	// 该处理器通过Server-Sent Events (SSE)每5分钟向客户端发送一次天气更新。
	r.GET("/weather", func(c *gin.Context) {
		c.Header("Content-Type", "text/event-stream")
		c.Header("Cache-Control", "no-cache")
		c.Header("Connection", "keep-alive")

		flusher, ok := c.Writer.(http.Flusher)
		if !ok {
			http.Error(c.Writer, "Streaming unsupported!", http.StatusInternalServerError)
			return
		}

		for {
			data := getWeatherData()
			c.Writer.WriteString(fmt.Sprintf("data: %s\n\n", data))
			flusher.Flush()

			time.Sleep(5 * time.Minute) // 每五分钟更新一次

			if c.Writer.Size() == 0 {
				break
			}
		}
	})

	// 启动HTTP服务器,监听8080端口。
	r.Run(":8080")
}

客户端(HTML + JavaScript)代码:

文件:templates/index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Dynamic Weather Updates</title>
</head>
<body>
<h1>Weather:</h1>
<p id="weather">Loading...</p>

<script type="text/javascript">
if(typeof(EventSource)!=="undefined") {
	var source = new EventSource("/weather");
	source.onmessage = function(event) {
		document.getElementById("weather").innerHTML = event.data;
	};
} else {
	document.getElementById("weather").innerHTML = "Your browser does not support server-sent events.";
}
</script>
</body>
</html>

在这里插入图片描述

示例4: 聊天室消息推送

服务器端(Go + Gin)代码:

文件:main.go

package main

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

	"github.com/gin-gonic/gin"
)

// messages 存储聊天消息。
var messages []string

// mu 用于保护 messages 切片的并发访问安全。
var mu sync.Mutex

// addMessage 向消息列表中添加新消息。
// 该函数通过 mu 锁确保并发安全。
func addMessage(msg string) {
	mu.Lock()
	defer mu.Unlock()
	messages = append(messages, msg)
}

func main() {
	r := gin.Default()
	r.LoadHTMLGlob("templates/*")

	// 定义根路径"/"的GET请求处理器。
	// 当用户访问根URL时,将渲染并返回"index.html"模板。
	r.GET("/", func(c *gin.Context) {
		c.HTML(http.StatusOK, "index.html", nil)
	})

	// 处理"/chat"的GET请求,以Server-Sent Events(SSE)形式发送聊天消息。
	// 该处理器持续运行,每秒向客户端推送一次消息,直到连接关闭。
	r.GET("/chat", func(c *gin.Context) {
		c.Header("Content-Type", "text/event-stream")
		c.Header("Cache-Control", "no-cache")
		c.Header("Connection", "keep-alive")

		flusher, ok := c.Writer.(http.Flusher)
		if !ok {
			http.Error(c.Writer, "Streaming unsupported!", http.StatusInternalServerError)
			return
		}

		for {
			mu.Lock()
			msgs := messages
			mu.Unlock()

			for _, msg := range msgs {
				c.Writer.WriteString(fmt.Sprintf("data: %s\n\n", msg))
				flusher.Flush()
			}

			time.Sleep(time.Second)

			if c.Writer.Size() == 0 {
				break
			}
		}
	})

	// 处理"/send"的POST请求,用于接收客户端发送的新消息。
	// 从请求中提取"message"字段,并将其添加到消息列表中。
	r.POST("/send", func(c *gin.Context) {
		msg := c.PostForm("message")
		addMessage(msg)
	})

	// 启动HTTP服务器,监听端口为8080。
	r.Run(":8080")
}

客户端(HTML + JavaScript)代码:

文件:templates/index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Chat Room</title>
</head>
<body>
<h1>Chat Room:</h1>
<ul id="messages"></ul>
<form id="chat-form">
	<input type="text" name="message" placeholder="Type a message..." required />
	<button type="submit">Send</button>
</form>

<script type="text/javascript">
// 检查浏览器是否支持EventSource
if(typeof(EventSource)!=="undefined") {
    // 创建EventSource实例,建立到服务器的连接
    var source = new EventSource("/chat");
    
    // 监听服务器发送的消息事件
    source.onmessage = function(event) {
        // 创建一个新的li元素
        var li = document.createElement("li");
        // 将服务器发送的数据设置为li元素的文本内容
        li.textContent = event.data;
        // 将li元素添加到页面上的messages元素中
        document.getElementById("messages").appendChild(li);
    };

    // 监听表单的提交事件
    document.getElementById("chat-form").addEventListener("submit", function(event) {
        // 阻止默认的表单提交行为
        event.preventDefault();
        // 创建FormData实例,用于处理表单数据
        var formData = new FormData(event.target);
        
        // 使用fetch API发送POST请求到服务器
        fetch('/send', {
            method: 'POST',
            body: formData,
        }).then(response => {
            // 如果服务器响应成功,重置表单
            if (response.ok) {
                event.target.reset();
            }
        });
    });
} else {
    // 如果浏览器不支持EventSource,显示提示信息
    document.write("Sorry, your browser does not support server-sent events.");
}
</script>
</body>
</html>

在这里插入图片描述

示例5: 实时股票价格更新

服务器端(Go + Gin)代码:

文件:main.go

package main

import (
	"fmt"
	"math/rand"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
)

// getRandomStockPrice 生成一个随机的股票价格。
// 假设股票价格在100到200之间。
func getRandomStockPrice() float64 {
	return rand.Float64()*100 + 100
}

func main() {
	r := gin.Default()
	r.LoadHTMLGlob("templates/*")

	// 定义根路径"/"的GET请求处理器。
	// 当用户访问根URL时,将渲染并返回"index.html"模板。
	r.GET("/", func(c *gin.Context) {
		c.HTML(http.StatusOK, "index.html", nil)
	})

	// 处理"/stock"的GET请求,通过Server-Sent Events (SSE) 实时推送股票价格。
	r.GET("/stock", func(c *gin.Context) {
		c.Header("Content-Type", "text/event-stream")
		c.Header("Cache-Control", "no-cache")
		c.Header("Connection", "keep-alive")

		// 检查响应写入器是否支持flush操作。
		flusher, ok := c.Writer.(http.Flusher)
		if !ok {
			http.Error(c.Writer, "Streaming unsupported!", http.StatusInternalServerError)
			return
		}

		// 创建一个定时器,每10秒发送一次股票价格。
		ticker := time.NewTicker(10 * time.Second)
		defer ticker.Stop()

		for {
			select {
			case <-ticker.C:
				// 生成新的股票价格并发送到客户端。
				price := getRandomStockPrice()
				c.Writer.WriteString(fmt.Sprintf("data: %.2f\n\n", price))
				flusher.Flush()

				// 如果响应内容为空,则终止循环。
				if c.Writer.Size() == 0 {
					return
				}
			}
		}

	})

	// 启动HTTP服务器,监听端口8080。
	r.Run(":8080")
}

客户端(HTML + JavaScript)代码:

文件:templates/index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Live Stock Price</title>
</head>
<body>
<h1>Stock Price:</h1>
<p id="price">Loading...</p>

<script type="text/javascript">
if(typeof(EventSource)!=="undefined") {
	var source = new EventSource("/stock");
	source.onmessage = function(event) {
		document.getElementById("price").innerHTML = "$" + event.data;
	};
} else {
	document.getElementById("price").innerHTML = "Your browser does not support server-sent events.";
}
</script>
</body>
</html>

在这里插入图片描述

这些示例涵盖了从简单到复杂的不同SSE应用场景。你可以根据自己的需求调整和扩展这些例子,以适应特定的业务逻辑或功能要求。

测试代码下载

html5_api代码


网站公告

今日签到

点亮在社区的每一天
去签到