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}");
}
}
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/[email protected]/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>
</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
+
设备控制
已经可以用于:
IoT 系统
设备管理平台
本地控制软件
工业工具系统
下一篇预告
下一篇将进入 架构升级的重要一步:
MAUI 嵌入式 Web 架构实战(八)
插件化 API 架构:Controller 自动发现与模块化扩展
我们将实现:
插件加载
模块扩展
动态 API
模块管理
最终把系统升级为:
真正可扩展的本地 Web 平台
关联项目
FreeSql QQ群:4336577
BA & Blazor QQ群:795206915
Maui Blazor 中文社区 QQ群:645660665
知识共享许可协议
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名AlexChow(包含链接: https://github.com/densen2014 ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系 。
转载声明
本文来自博客园,作者:周创琳 AlexChow,转载请注明原文链接:https://chuna2.787528.xyz/densen2014/p/19674936
AlexChow
今日头条 | 博客园 | 知乎 | Gitee | GitHub


浙公网安备 33010602011771号