MAUI 嵌入式 Web 架构实战(七)
PicoServer + WebSocket
构建设备实时通信与控制系统
源码地址
https://github.com/densen2014/MauiPicoAdmin
一、为什么需要 WebSocket
在前面的文章中,我们已经实现了完整架构:- Web Admin UI
- ↓
- PicoServer REST API
- ↓
- MAUI Service
- ↓
- SQLite / Device
复制代码 Web API 适合:但对于 实时系统,REST API 有明显局限:
例如:
场景REST API 问题设备状态变化需要不断轮询实时日志延迟高设备控制交互慢例如浏览器:这叫:
Polling(轮询)
问题:解决方案是:
WebSocket
二、什么是 WebSocket
WebSocket 是一种 长连接通信协议。
通信模式:特点:
特性说明双向通信Client / Server 都能发送长连接不需要重复建立连接实时性毫秒级因此非常适合:三、系统架构升级
加入 WebSocket 后架构变成:- Web Admin
- │
- ┌──────┴───────┐
- │ │
- REST API WebSocket
- │ │
- ▼ ▼
- PicoServer
- │
- ▼
- Service
- │
- ▼
- Device / DB
复制代码 REST API:WebSocket:四、代码实现 WebSocket 服务器
在 PicoServer 中增加:创建:- Services/WebSocketManager.cs
复制代码 示例实现:- using PicoServer;
- public class WebSocketManager
- {
- private WebAPIServer? api;
- public void RegisterWebSocket(WebAPIServer api)
- {
- this.api = api;
- api.enableWebSocket = true;
- api.WsOnConnectionChanged = WsConnectChanged;
- api.WsOnMessage = OnMessageReceived;
- }
-
- public async Task OnMessageReceived(string clientId, string message, Func<string, Task> reply)
- {
- await reply("收到!");
- var clients = api!.WsGetOnlineClients();
- foreach (var client in clients)
- {
- await api.WsSendToClientAsync(client, $"{clientId}说:{message}");
- }
- }
- //相关方法
- //api.enableWebSocket = true; //启用WebSocket支持
- //api.WsOnConnectionChanged; // 事件:WebSocket客户端连接状态发生变化
- //api.WsOnMessage; //事件:收到WebSocket客户端发送来的消息
- //api.WsBroadcastAsync(); //对所有在线客户端广播消息
- //api.WsGetOnlineClients; //获取在线客户端列表
- //api.WsSendToClientAsync(client, message); //给指定客户端发送消息
- //api.WsEnableHeartbeat = true; //启用 WebSocket 服务端心跳检测,默认false
- //api.WsHeartbeatTimeout = 60; //设置 WebSocket 心跳时间,默认30秒
- //api.WsMaxConnections = 200; //设置 WebSocket 最大连接数,默认100
- //api.WsPingString = "hi"; //设置 WebSocket 的ping消息,默认"pong",不区分大小写
- public async Task WsConnectChanged(string clientId, bool connected)
- {
- await api!.WsBroadcastAsync($"{clientId} {connected}");
- }
- }
复制代码 这个组件实现了:五、注册 WebSocket 路由
在 ServerHost 中注册:- ws.RegisterWebSocket(api);
复制代码 现在浏览器可以连接:六、前端连接 WebSocket
在 Web Admin 中:- const ws = new WebSocket("ws://127.0.0.1:8090/ws");
- ws.onopen = () => {
- console.log("WebSocket Connected");
- };
- ws.onmessage = (event) => {
- console.log("Message:", event.data);
- };
- ws.onclose = () => {
- console.log("Disconnected");
- };
- function send() {
- ws.send("hello device");
- }
复制代码 发送消息:七、设备控制协议设计
实际系统中需要定义 通信协议。
推荐使用 JSON。
例如:
设备控制:- {
- "type": "device_control",
- "device": "printer",
- "cmd": "start"
- }
复制代码 设备状态:- {
- "type": "device_status",
- "device": "printer",
- "status": "running"
- }
复制代码 日志消息:- {
- "type": "log",
- "message": "device started"
- }
复制代码 八、Server 处理设备命令
解析 WebSocket 消息:- var cmd = JsonSerializer.Deserialize<WsCommand>(msg);
- switch(cmd.Type)
- {
- case "device_control":
- DeviceService.Execute(cmd.Device, cmd.Cmd);
- break;
- }
复制代码 示例:- DeviceService.Execute("printer","start");
复制代码 九、实时推送设备状态
当设备状态变化时:- await ws.Broadcast(JsonSerializer.Serialize(new
- {
- type = "device_status",
- device = "printer",
- status = "running"
- }));
复制代码 前端立即收到:- ws.onmessage = e => {
- let msg = JSON.parse(e.data);
- if(msg.type === "device_status")
- {
- updateUI(msg);
- }
- }
复制代码 实现:实时更新。
完整前端代码- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8">
- <title>WebSocket</title>
- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"
- rel="stylesheet">
- </head>
- <body >
- <h1 >WebSocket</h1>
-
- <button onclick="send()" >发送消息</button>
- <button onclick="start()" >控制设备(start)</button>
- <button onclick="stop()" >控制设备(stop)</button>
- <button onclick="startSSE()" >HTTP消息推送(SSE Demo)</button>
-
- <p id="result"></p>
-
- </body>
- </html>
复制代码 后端代码- public async Task OnMessageReceived(string clientId, string message, Func<string, Task> reply)
- {
- await reply("收到!");
- var clients = api!.WsGetOnlineClients();
- foreach (var client in clients)
- {
- await api.WsSendToClientAsync(client, $"{clientId}说:{message}");
- }
- try
- {
- var cmd = JsonSerializer.Deserialize<WsCommand>(message, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
- if (cmd != null)
- {
- switch (cmd.Type)
- {
- case "device_control":
- await Task.Delay(200);//模拟执行控制命令
- //DeviceService.Execute(cmd.Device, cmd.Cmd);
- await api.WsBroadcastAsync(JsonSerializer.Serialize(new
- {
- type = "device_status",
- device = "printer",
- status = cmd.Cmd == "start" ? "running" : "stop"
- }));
- break;
- }
- }
- }
- catch (JsonException)
- {
- // 处理 JSON 解析错误
- }
- }
- //SSE(Server - Sent Events)推送, 请注册路由 api.AddRoute("/iot/notify", HttpHelper.Notify, "GET");
- public static async Task Notify(HttpListenerRequest request, HttpListenerResponse response)
- {
- response.ContentType = "text/event-stream";
- response.Headers.Add("Cache-Control", "no-cache");
- response.SendChunked = true;
- try
- {
- for (int i = 0; i < 5; i++)
- {
- string msg = $"data: 消息推送 {i} 时间: {DateTime.Now}\n\n";
- byte[] buffer = System.Text.Encoding.UTF8.GetBytes(msg);
- await response.OutputStream.WriteAsync(buffer, 0, buffer.Length);
- await response.OutputStream.FlushAsync();
- await Task.Delay(1000);
- }
- }
- finally
- {
- //在使用 HttpListenerResponse 进行 SSE(Server - Sent Events)推送时,response.Close(); 并不是必须的,但推荐在推送结束后调用它,以确保资源释放和连接正确关闭。
- // 示例这里是推送结束后调用 response.Close();,确保响应流关闭
- // 如果是无限推送(如实时设备报警),不要关闭响应,直到客户端断开。
- response.Close();
- }
- }
复制代码 运行截图
十、实时日志系统
例如设备日志:- await ws.Broadcast(JsonSerializer.Serialize(new
- {
- type="log",
- message="print job started"
- }));
复制代码 前端:- if(msg.type==="log"){
- logPanel.append(msg.message)
- }
复制代码 效果:十一、完整实时架构
最终系统变成:- Web Admin UI
- / \
- REST API WebSocket
- │ │
- ▼ ▼
- PicoServer Core
- │
- ▼
- Services
- │
- ┌─────────┴─────────┐
- ▼ ▼
- SQLite Device
复制代码 系统能力升级为:十二、本篇总结
本篇为系统新增:
核心能力:- WebSocket 实时通信
- 设备控制协议实时日志推送状态同步
复制代码 系统能力升级为:- Web Admin
- +
- REST API
- +
- WebSocket
- +
- 设备控制
复制代码 已经可以用于:下一篇预告
下一篇将进入 架构升级的重要一步:
MAUI 嵌入式 Web 架构实战(八)
插件化架构与模块系统
我们将实现:最终把系统升级为:
真正可扩展的本地 Web 平台
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |