如何用Python通过SSE实现与HTML页面的互动询问?

摘要:**博客:使用 Python 通过 SSE 与 HTML 实现主动通讯** 在现代 Web 应用中,实时性和交互性成为了越来越重要的需求。服务器向客户端主动推送数据,而不是等待客户端发送请求,就是一种实现实时通讯的方式。Server-Sen
博客:使用 Python 通过 SSE 与 HTML 实现主动通讯 在现代 Web 应用中,实时性和交互性成为了越来越重要的需求。服务器向客户端主动推送数据,而不是等待客户端发送请求,就是一种实现实时通讯的方式。Server-Sent Events(SSE)正是一种用于实现这种服务器主动推送的技术。本文将介绍如何使用 Python 和 Flask 框架,通过 SSE 与 HTML 页面实现主动通讯,让前端实时接收服务器端的数据并进行展示。 1. 什么是 Server-Sent Events (SSE) Server-Sent Events(SSE)是 HTML5 规范的一部分,它允许服务器端通过单向的 HTTP 连接,向客户端(通常是浏览器)实时地发送数据。相比传统的轮询或长轮询方式,SSE 更加高效,因为它不需要频繁地建立和关闭连接,而是保持长连接,服务器可以在有新数据时立即发送给客户端。 2. 使用 Python 和 Flask 实现 SSE 服务器 首先,我们需要安装 Flask 框架,它是一个轻量级的 Python Web 框架,方便我们构建 Web 应用。 pip install flask 接下来,我们创建一个 Python 文件 sse_flask_demo.py,并实现 SSE 服务器端代码: # 导入所需的模块 import json import time import datetime from flask import Flask, request, Response, render_template app = Flask(__name__) # 解决跨域问题 @app.after_request def after_request(response): response.headers.add('Access-Control-Allow-Origin', '*') response.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS') response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization') response.headers.add('Access-Control-Allow-Credentials', 'true') return response # 获取当前时间,并转换为 JSON 格式 def get_time_json(): dt_ms = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f') return json.dumps({'time': dt_ms}, ensure_ascii=False) # 设置路由,返回 SSE 流 @app.route('/') def hello_world(): return render_template('sse.html') @app.route('/sse') def stream(): user_id = request.args.get('user_id') # 可选,用于区分不同用户的连接 print(user_id) def eventStream(): id = 0 while True: id += 1 time.sleep(1/50) # 50Hz,每秒发送约 50 条数据 event_name = 'time_reading' str_out = f'id: {id}\nevent: {event_name}\ndata: {get_time_json()}\n\n' print(str_out) # 在服务器端打印发送的数据 yield str_out return Response(eventStream(), mimetype="text/event-stream") if __name__ == '__main__': app.run(host='0.0.0.0', port=5678, debug=True) 在上述代码中,我们创建了一个 Flask 应用并定义了两个路由,/ 路由返回了一个 HTML 页面(稍后会讲解),/sse 路由则返回 SSE 流。在 stream() 函数中,我们使用一个无限循环来模拟不断向客户端发送数据,每次发送都包含一个唯一的 ID 和当前时间的 JSON 字符串。 3. 前端 HTML 页面 为了接收服务器端的 SSE 数据,我们需要在前端创建一个 HTML 页面。在 templates 文件夹下,创建一个名为 sse.html 的文件,并将以下代码复制进去: <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Server-Sent Events</title> </head> <body> <div id="content"> <h1>Server-Sent Events</h1> <p>time: <span id="time_show"></span></p> </div> </body> <script> function connectSSE() { if (window.EventSource) { let sse_url = 'http://localhost:5678/sse'; // 创建 EventSource 对象连接服务器 const source = new EventSource(sse_url); // 连接成功后会触发 open 事件 source.addEventListener('open', () => { console.log('Connected'); }, false); // 当接收到服务器端发送的数据时会触发 time_reading 事件 source.addEventListener('time_reading', function (e) { console.log("time_reading", e.data); // 把时间显示到页面上,这里需要解析一下 JSON document.getElementById("time_show").innerHTML = JSON.parse(e.data).time; }, false); // 服务器发送信息到客户端时,如果没有 event 字段,默认会触发 message 事件 source.addEventListener('message', e => { console.log(`data: ${e.data}`); }, false); // 连接异常时会触发 error 事件并自动重连 source.addEventListener('error', e => { if (e.target.readyState === EventSource.CLOSED) { console.log('Disconnected'); } else if (e.target.readyState === EventSource.CONNECTING) { console.log('Connecting...'); } }, false); } else { console.error('Your browser doesn\'t support SSE'); } } connectSSE(); </script> </html> 上述 HTML 页面中,我们使用 JavaScript 创建了一个 EventSource 对象,并连接到服务器的 /sse 路由。当接收到服务器端发送的 time_reading 事件时,我们解析 JSON 数据,并将时间显示在页面上。 4. 运行并测试 在完成上述代码编写后,我们可以运行 Python 服务器。在命令行中执行以下命令: python sse_flask_demo.py 服务器将会运行在 http://localhost:5678 上。 现在,打开浏览器并访问 http://localhost:5678/,你应该会 看到一个简单的页面显示 "Server-Sent Events" 以及时间的实时更新。 5. 结语 通过 Python 和 Flask 框架,我们成功地实现了使用 SSE 技术与 HTML 页面进行实时通讯的功能。这种方式可以在很多场景下派上用场,例如实时聊天、实时数据展示等。注意,在真实的应用中,我们可能会使用数据库或其他数据源提供实时数据,而不是简单地使用时间戳作为示例数据。 总结起来,SSE 技术为现代 Web 应用提供了一种高效、实时的服务器主动推送数据的方式,而 Python 和 Flask 框架的结合,让我们可以轻松实现这种功能,让 Web 应用变得更加动态、交互性更强。 6. 扩展esp32 以下代码提供esp32作为服务器的参考 esp32基于Arduino的代码 /* * @Author: Dapenson * @Date: 2023-07-26 13:58:08 * @LastEditors: Dapenson * @LastEditTime: 2023-07-26 13:58:19 * @FilePath: \SSE\esp32_demo.cpp */ #include <Arduino.h> #include <WiFi.h> #include <AsyncTCP.h> #include <ESPAsyncWebServer.h> #include <Arduino_JSON.h> #include "SPIFFS.h" const char *ssid = "REPLACE_WITH_YOUR_SSID"; const char *password = "REPLACE_WITH_YOUR_PASSWORD"; // 端口号 AsyncWebServer server(80); // 事件接口 AsyncEventSource events("/sse"); JSONVar readings; void initSPIFFS() { if (!SPIFFS.begin()) { Serial.println("An error has occurred while mounting SPIFFS"); } Serial.println("SPIFFS mounted successfully"); } void initWiFi() { WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); Serial.println(""); Serial.print("Connecting to WiFi..."); while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(1000); } Serial.println(""); Serial.println(WiFi.localIP()); } void setup() { Serial.begin(115200); initWiFi(); initSPIFFS(); server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { request->send(SPIFFS, "/index.html", "text/html"); }); server.serveStatic("/", SPIFFS, "/"); events.onConnect([](AsyncEventSourceClient *client) { if(client->lastId()){ Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId()); } client->send("hello!", NULL, millis(), 10000); }); server.addHandler(&events); server.on("/reset", HTTP_GET, [](AsyncWebServerRequest *request) { readings["time"] = String(millis()); events.send(JSON.stringify(readings).c_str(), "time_reading", millis()); request->send(200, "text/plain", "OK"); }); server.begin(); } void loop() { // 50Hz delay(1000 / 50); readings["time"] = String(millis()); events.send(JSON.stringify(readings).c_str(), "time_reading", millis()); }