当前位置: 首页 > news >正文

MAUI 嵌入式 Web 架构实战(七) 构建设备实时通信与控制系统

MAUI 嵌入式 Web 架构实战(七)

PicoServer + WebSocket

构建设备实时通信与控制系统

源码地址
https://github.com/densen2014/MauiPicoAdmin


一、为什么需要 WebSocket

在前面的文章中,我们已经实现了完整架构:

Web Admin UI↓
PicoServer REST API↓
MAUI Service↓
SQLite / Device

Web API 适合:

CRUD
请求响应

但对于 实时系统,REST API 有明显局限:

例如:

场景 REST API 问题
设备状态变化 需要不断轮询
实时日志 延迟高
设备控制 交互慢

例如浏览器:

每秒请求一次
/api/device/status

这叫:

Polling(轮询)

问题:

服务器压力大
延迟高
体验差

解决方案是:

WebSocket


二、什么是 WebSocket

WebSocket 是一种 长连接通信协议

通信模式:

浏览器⇅
WebSocket⇅
Server

特点:

特性 说明
双向通信 Client / Server 都能发送
长连接 不需要重复建立连接
实时性 毫秒级

因此非常适合:

设备控制
实时日志
消息推送
IoT系统

三、系统架构升级

加入 WebSocket 后架构变成:

           Web Admin│┌──────┴───────┐│              │REST API        WebSocket│              │▼              ▼PicoServer│▼Service│▼Device / DB

REST API:

CRUD

WebSocket:

实时通信
设备控制

四、代码实现 WebSocket 服务器

在 PicoServer 中增加:

WebSocketManager

创建:

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);

现在浏览器可以连接:

ws://localhost:8090/ws

六、前端连接 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");}

发送消息:

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);}
}

实现:

设备 → Server → Web Admin

实时更新。

完整前端代码

<!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 class="container"><h1 class="mt-4">WebSocket</h1><div class="mt-4"><button onclick="send()" class="btn btn-primary">发送消息</button><button onclick="start()" class="btn btn-success">控制设备(start)</button><button onclick="stop()" class="btn btn-danger">控制设备(stop)</button><button onclick="startSSE()" class="btn btn-success">HTTP消息推送(SSE Demo)</button></div><p id="result"></p><script>const ws = new WebSocket("ws://127.0.0.1:8090/ws");ws.onopen = () => {document.getElementById("result").innerHTML += "WebSocket Connected" + "<br>";};ws.onmessage = (e) => {document.getElementById("result").innerHTML += "Message:" + e.data + "<br>";try {let msg = JSON.parse(e.data);if (msg.type === "device_status") {document.getElementById("result").innerHTML += "Status: " + JSON.stringify(msg, null, 2) + "<br>";}} catch {}};ws.onclose = () => {document.getElementById("result").innerHTML += "Disconnected" + "<br>";};function send() {ws.send("hello device");}function start() {const msg = {type: "device_control",device: "printer",cmd: "start"};ws.send(JSON.stringify(msg));}function stop() {const msg = {type: "device_control",device: "printer",cmd: "stop"};ws.send(JSON.stringify(msg));}function startSSE() {const source = new EventSource("http://127.0.0.1:8090/iot/notify");source.onmessage = function (event) {document.getElementById("result").innerHTML += event.data + "<br>";};source.onerror = function () {source.close();};}</script>
</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();}}

运行截图

image


十、实时日志系统

例如设备日志:

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
+
设备控制

已经可以用于:

IoT 系统
设备管理平台
本地控制软件
工业工具系统

下一篇预告

下一篇将进入 架构升级的重要一步

MAUI 嵌入式 Web 架构实战(八)

插件化架构与模块系统

我们将实现:

插件加载
模块扩展
动态 API
模块管理

最终把系统升级为:

真正可扩展的本地 Web 平台

http://www.jsqmd.com/news/460077/

相关文章:

  • 高效数据标注:从工具选择到实操技巧
  • Windows Sandbox开发环境配置全攻略:从零到高效隔离开发
  • 2026年高校AIGC检测政策收紧,毕业论文降AI的合规底线在哪
  • 20260310 之所思 - 人生如梦
  • 工业机器人核心指令实战解析:从基础运动到流程控制
  • Windows环境下的TensorRT安装与常见问题解决指南
  • GitLab离线部署实战:从零搭建内网代码仓库
  • 企业微信消息推送API实战:5步搞定可信IP与域名配置(含Python代码示例)
  • LevOJ.sln - 算法设计课程
  • 第一次用嘎嘎降AI?从注册到拿结果完整教程保姆级讲解
  • 基于MATLAB的旋转变压器软件解码算法:从信号仿真到角度提取
  • 边界点与聚点:高等数学中的微妙差异与实例解析
  • 2026年内蒙古抖音代运营公司5强推荐名单及联系方式公开 - 精选优质企业推荐榜
  • 论文AI率从90%降到10%以下怎么弄?这套组合拳照着做就行
  • Minecraft《龙之冒险:新征程2.0》整合包Docker容器化部署指南 | 高可用Linux服务器搭建
  • speech_campplus_sv_zh-cn_16k-common 报错 AttributeError: ‘SpeakerVerificationPipeline’ object has no a
  • Swagger-php避坑指南:从注释写法到YAML生成的完整流程解析
  • 将盾 CDN:AI 驱动的智能安全防御体系
  • 【深度解析】数字孪生技术赋能产业园区智能化升级(40页PPT)
  • ZYNQ实战:AXI4-Stream FIFO跨时钟域传输的5个关键配置(附DAC接口代码)
  • pdf.js 实现移动端双指缩放:不修改源码的优雅集成方案
  • 2026年陕西抖音短视频代运营5强推荐名单及联系方式公开 - 精选优质企业推荐榜
  • 51单片机 vs STM32:从入门到进阶,如何根据项目需求选择最适合的单片机?
  • ComfyUI v0.3.14生产环境部署实战:如何用systemd管理AI服务并解决_lzma模块缺失问题
  • 250MHz高速数据采集:基于FMC接口的ADS42LB69与ZYNQ zcu102硬件设计要点解析
  • 设计师必备:用PS快速制作SVG图标并上传阿里iconfont的完整流程
  • 【矩阵论】5. 线性空间与线性变换——从生成子空间到数据降维的桥梁
  • 5分钟搞定!用Nginx+RTMP搭建个人直播服务器(含摄像头推流实战)
  • Unity打包后逻辑失效?从Editor文件夹迁移脚本的避坑指南
  • VSCode插件实战:一键生成Python项目的UML类图