前面讲到了:
一个月玩转MQTT(篇二:部署阿里云服务器和EMQX)-CSDN博客
一个月玩转MQTT(篇四:移远EC200U模块MQTT连接测试)-CSDN博客
现在我讲一下,如何在云服务器上,生成一个自己的web网页,然后web网页可以订阅和发布消息。
背景:
1、服务器还是之前提到的阿里云ECS服务器,搭载的是Ubuntu 22.04 64位 UEFI版
2、web页面,采用ASP.NET跨平台开发。我的开发环境是visual studio 2022
由于我一直是C#系开发者,一直对ASP.NET熟悉,所以驾轻就熟的使用这门语言。当然你们也可以用自己熟悉的语言,比如node.js + Vue 的方案等等。
好吧,那我们就开始吧!
第一步:Windows 11 + VS 2022 开发准备
1. 确保 VS 2022 安装了 .NET 8 开发工具
打开 VS 2022 → 点击「工具」→「获取工具和功能」;在「工作负载」里勾选:
✅ ASP.NET 和 Web 开发(必选);
点击「修改」,安装完成后重启 VS。

2. 创建 ASP.NET Core MVC 项目
打开 VS 2022 → 「创建新项目」→ 选择「ASP.NET Core Web 应用程序(MVC)」
框架选择:.NET 8 (长期支持) → 取消勾选「启用 Docker」 → 创建

3、安装 MQTTnet 依赖(对接 EMQX)
- 右键项目 →「管理 NuGet 程序包」→ 「浏览」;
- 搜索并安装以下包(版本选最新稳定版)
MQTTnet 4.3.1207(核心 MQTT 客户端)MQTTnet.Extensions.ManagedClient 4.3.1207(自动重连的托管客户端,适合后台运行)Microsoft.AspNetCore.SignalR(实时推送数据到 Web 前端,可选但推荐)

上图中的5.1.0.1559,我后边都统一改成了4.3.1207。

第二步: 实现 MQTT 核心功能(订阅传感器消息 + 存储数据)
我们公司长沙湾流智能科技有限公司,是专门做倾角传感器的,可以检测物体倾斜,比如货柜,铁塔,这些传感器通过4G MQTT与服务器进行连接通信。那么接下来我就以倾角传感器来举例进行说明。
我们计划按下述步骤实现:
- 后台服务(asp.net)持续连接 EMQX,订阅传感器主题;
- 接收倾角传感器的 roll/pitch (X、Y)角度数据,存储到内存(这里先不使用数据库);
- 通过 SignalR 实时推送到 Web 前端。
步骤 1:创建传感器数据模型
在项目中新建「Models」文件夹→新建类SensorData.cs:
cs
// 传感器数据模型(对应roll/pitch)
namespace SensorMqttDashboard.Models
{
public class SensorData
{
// 传感器ID(区分5个传感器,如Sensor_1、Sensor_2)
public string SensorId { get; set; } = string.Empty;
// 横滚角
public double Roll { get; set; }
// 俯仰角
public double Pitch { get; set; }
// 数据更新时间
public DateTime UpdateTime { get; set; } = DateTime.Now;
}
}
步骤 2:创建 SignalR 集线器(实时推送数据)
新建「Hubs」文件夹→新建类SensorHub.cs:
cs
using Microsoft.AspNetCore.SignalR;
using SensorMqttDashboard.Models;
namespace SensorMqttDashboard.Hubs
{
// SignalR集线器,用于向前端推送传感器数据
public class SensorHub : Hub
{
// 前端调用此方法获取所有传感器数据(可选)
public async Task SendSensorData(SensorData data)
{
// 推送给所有连接的客户端
await Clients.All.SendAsync("ReceiveSensorData", data);
}
}
}
步骤 3:创建 MQTT 后台服务(核心)
新建「Services」文件夹→新建类MqttBackgroundService.cs:
cs
using Microsoft.AspNetCore.SignalR;
using MQTTnet;
using MQTTnet.Client;
using MQTTnet.Extensions.ManagedClient;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using SensorMqttDashboard.Hubs;
using SensorMqttDashboard.Models;
using System.Text;
using System.Text.Json;
namespace SensorMqttDashboard.Services
{
// 后台MQTT服务,持续运行并订阅传感器消息
public class MqttBackgroundService : BackgroundService
{
// 存储所有传感器数据(内存中,重启会丢失,新手先这样)
public static Dictionary<string, SensorData> AllSensorData = new Dictionary<string, SensorData>();
private readonly IManagedMqttClient _mqttClient;
private readonly ILogger<MqttBackgroundService> _logger;
private readonly IHubContext<SensorHub> _hubContext;
// 阿里云EMQX配置(替换成你的服务器信息)
private readonly string _mqttServer = "你的阿里云服务器公网IP"; // 例如 120.78.xxx.xxx
private readonly int _mqttPort = 1883; // EMQX默认MQTT端口
private readonly string _mqttUsername = "mqtt_user"; // 步骤1.3创建的用户名
private readonly string _mqttPassword = "mqtt_pass123"; // 步骤1.3创建的密码
private readonly string _sensorTopic = "sensor/inclination/#"; // 订阅所有传感器主题(#是通配符)
public MqttBackgroundService(ILogger<MqttBackgroundService> logger, IHubContext<SensorHub> hubContext)
{
_logger = logger;
_hubContext = hubContext;
// 创建托管MQTT客户端(自动重连,断网后会自动重试)
var factory = new MqttFactory();
_mqttClient = factory.CreateManagedMqttClient();
}
// 服务启动时执行:连接EMQX + 订阅主题
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
// 配置MQTT客户端选项
var mqttOptions = new ManagedMqttClientOptionsBuilder()
.WithAutoReconnectDelay(TimeSpan.FromSeconds(5)) // 重连间隔5秒
.WithClientOptions(new MqttClientOptionsBuilder()
.WithTcpServer(_mqttServer, _mqttPort) // 阿里云EMQX地址+端口
.WithClientId($"AspNetClient_{Guid.NewGuid()}") // 唯一客户端ID
.WithCredentials(_mqttUsername, _mqttPassword) // 认证信息
.WithCleanSession() // 清理会话
.Build())
.Build();
// 注册消息接收事件(核心:收到传感器消息后的处理)
_mqttClient.ApplicationMessageReceivedAsync += async e =>
{
try
{
// 1. 解析MQTT消息
var topic = e.ApplicationMessage.Topic; // 传感器主题,如 sensor/inclination/Sensor_1
var payload = System.Text.Encoding.UTF8.GetString(e.ApplicationMessage.Payload); // 消息内容,如 {"SensorId":"Sensor_1","Roll":12.3,"Pitch":4.5}
_logger.LogInformation("收到传感器消息:Topic={Topic}, Payload={Payload}", topic, payload);
// 2. 反序列化为SensorData对象
var sensorData = JsonSerializer.Deserialize<SensorData>(payload);
if (sensorData == null || string.IsNullOrEmpty(sensorData.SensorId))
{
_logger.LogWarning("消息格式错误:{Payload}", payload);
return;
}
// 3. 更新内存中的传感器数据
if (AllSensorData.ContainsKey(sensorData.SensorId))
{
AllSensorData[sensorData.SensorId] = sensorData;
}
else
{
AllSensorData.Add(sensorData.SensorId, sensorData);
}
// 4. 通过SignalR推送到Web前端
await _hubContext.Clients.All.SendAsync("ReceiveSensorData", sensorData);
}
catch (Exception ex)
{
_logger.LogError(ex, "处理MQTT消息失败");
}
};
// 连接EMQX并订阅主题
await _mqttClient.StartAsync(mqttOptions);
await _mqttClient.SubscribeAsync(new List<MqttTopicFilter>{
new MqttTopicFilterBuilder()
.WithTopic(_sensorTopic)
.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce)
.Build()
});
_logger.LogInformation("MQTT客户端已连接到 {Server}:{Port},订阅主题:{Topic}", _mqttServer, _mqttPort, _sensorTopic);
// 保持服务运行,直到停止
await Task.Delay(Timeout.Infinite, stoppingToken);
}
// 服务停止时断开MQTT连接
public override async Task StopAsync(CancellationToken cancellationToken)
{
await _mqttClient.StopAsync();
_logger.LogInformation("MQTT客户端已断开连接");
await base.StopAsync(cancellationToken);
}
// 可选:发布消息到EMQX(比如前端手动发送指令给传感器)
public async Task PublishMessage(string topic, string payload)
{
var message = new MqttApplicationMessageBuilder()
.WithTopic(topic)
.WithPayload(Encoding.UTF8.GetBytes(payload)) // 显式转字节数组,避免编码问题
.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce)
.WithRetainFlag(false)
.Build();
// 托管客户端用EnqueueAsync替代PublishAsync
await _mqttClient.EnqueueAsync(message);
}
}
}
步骤 4:注册服务和中间件(Program.cs)
替换Program.cs的全部代码:
cs
using SensorMqttDashboard.Hubs;
using SensorMqttDashboard.Services;
var builder = WebApplication.CreateBuilder(args);
// 添加MVC控制器和视图支持
builder.Services.AddControllersWithViews();
// 注册SignalR
builder.Services.AddSignalR();
// 注册MQTT后台服务
builder.Services.AddHostedService<MqttBackgroundService>();
var app = builder.Build();
// 配置HTTP请求管道
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
// 配置SignalR路由
app.MapHub<SensorHub>("/sensorHub");
// 配置MVC路由
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
第三步:Web 前端展示传感器数据(实时更新)
我们修改默认的 Home 视图,展示 5 个传感器的 roll/pitch 值,通过 SignalR 实时刷新。
步骤 1:修改 Home 控制器(HomeController.cs)
打开「Controllers/HomeController.cs」,添加获取所有传感器数据的方法:
cs
using Microsoft.AspNetCore.Mvc;
using SensorMqttDashboard.Models;
using SensorMqttDashboard.Services;
namespace SensorMqttDashboard.Controllers
{
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
// 首页:展示传感器数据
public IActionResult Index()
{
// 将内存中的传感器数据传递到视图
ViewBag.SensorDataList = MqttBackgroundService.AllSensorData.Values.ToList();
return View();
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View();
}
}
}
步骤 2:修改首页视图(Index.cshtml)
打开「Views/Home/Index.cshtml」,替换全部代码
cs
@{
ViewData["Title"] = "倾角传感器监控面板";
}
<!-- 传感器数据展示面板 -->
<div class="container mt-5">
<h1 class="text-center mb-4" style="font-size: 2.5rem; font-weight: bold;">倾角传感器实时数据</h1>
<h4 class="text-center mb-2" style="font-size: 1rem; color: #666;">长沙湾流智能科技有限公司</h4>
<h4 class="text-center mb-4" style="font-size: 1rem; color: #666;">抖音号:铁甲前沿</h4>
<!-- 5个传感器卡片(动态生成) -->
<div class="row g-4" id="sensorCards">
@foreach (var sensor in ViewBag.SensorDataList)
{
<div class="col-md-6 col-lg-4">
<div class="card shadow-sm h-100">
<div class="card-header bg-primary text-white">
<h5 class="card-title mb-0">传感器 @sensor.SensorId</h5>
</div>
<div class="card-body">
<p class="card-text"><strong>横滚角 (Roll):</strong> <span id="roll_@sensor.SensorId">@sensor.Roll</span> °</p>
<p class="card-text"><strong>俯仰角 (Pitch):</strong> <span id="pitch_@sensor.SensorId">@sensor.Pitch</span> °</p>
<p class="card-text text-muted"><small>最后更新:@sensor.UpdateTime.ToString("yyyy-MM-dd HH:mm:ss")</small></p>
</div>
</div>
</div>
}
<!-- 如果暂无数据,显示提示 -->
@if (ViewBag.SensorDataList.Count == 0)
{
<div class="col-12 text-center text-muted">
<h4>暂无传感器数据,请等待设备上报...</h4>
</div>
}
</div>
</div>
<!-- 引入SignalR客户端 + jQuery(简化DOM操作) -->
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
<script src="~/lib/signalr/dist/browser/signalr.js"></script>
<script>
// 初始化SignalR连接
const connection = new signalR.HubConnectionBuilder()
.withUrl("/sensorHub") // 对应Program.cs中的SignalR路由
.withAutomaticReconnect() // 自动重连
.build();
// 接收传感器数据的回调函数
connection.on("ReceiveSensorData", function (sensorData) {
console.log("收到实时数据:", sensorData);
// 检查卡片是否存在,不存在则创建
const cardId = `card_${sensorData.sensorId}`;
if ($(`#${cardId}`).length === 0) {
// 创建新的传感器卡片
const newCard = `
<div class="col-md-6 col-lg-4" id="${cardId}">
<div class="card shadow-sm h-100">
<div class="card-header bg-primary text-white">
<h5 class="card-title mb-0">传感器 ${sensorData.sensorId}</h5>
</div>
<div class="card-body">
<p class="card-text"><strong>横滚角 (Roll):</strong> <span id="roll_${sensorData.sensorId}">${sensorData.roll}</span> °</p>
<p class="card-text"><strong>俯仰角 (Pitch):</strong> <span id="pitch_${sensorData.sensorId}">${sensorData.pitch}</span> °</p>
<p class="card-text text-muted"><small>最后更新:${new Date(sensorData.updateTime).toLocaleString()}</small></p>
</div>
</div>
</div>
`;
// 清空"暂无数据"提示,添加新卡片
$("#sensorCards").empty().append(newCard);
} else {
// 更新已有卡片的数据
$(`#roll_${sensorData.sensorId}`).text(sensorData.roll);
$(`#pitch_${sensorData.sensorId}`).text(sensorData.pitch);
$(`#${cardId} .text-muted small`).text(`最后更新:${new Date(sensorData.updateTime).toLocaleString()}`);
}
});
// 启动SignalR连接
connection.start().catch(function (err) {
return console.error(err.toString());
});
</script>
第四步:测试运行
程序架构:
运行效果:

好啦,
现在能正常看到 Web 界面啦。
显示 "暂无传感器数据,请等待设备上报",这说明核心的 MVC 项目、视图、MQTT 后台服务都已经正常运行了!
现在只需要完成「模拟传感器发消息」和「验证数据实时显示」这最后两步,就能看到传感器的 roll/pitch 值了。
后边继续讲,如何将刚才这个ASP.NET项目发布到阿里云的 Ubuntu 服务器上。