Maui Blazor 中文社区 QQ群:645660665

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 平台

posted @ 2026-03-10 23:02  AlexChow  阅读(10)  评论(0)    收藏  举报