原文:使用Surging Mqtt 开发基于WS的MqttClient客户端

最近一段时间由于要做一套智能设备系统,而有幸了解到Surging中的Mqtt broker,学习了很多东西本篇文章基于Surging开源的.netcore项目有兴趣的朋友可点击此处进行了解。话不多说我们来基于Surging 中的WS与MqttClient结合来开发服务端MqttClient的使用。

准备工作

开发环境:  Visual Studio 2017 15.9.5

.netCore版本:2.2.102(目前Surging已经升级至netcore 2.2版本)

surging项目下载地址  https://github.com/dotnetcore/surging

开始工作

接口部分

新建类库Surging.IModuleServices.MqttWithWS

添加引用Surging.Core.Protocol.WS

新建文件夹Model 并创建类MqttClientOption.cs 此类为读取surgingSettings.json中配置的MqttClient的相关参数MqttClientOption的代码如下

public class MqttClientOption
{
public string ClientID { get; set; }
public string MqttClientConnection { get; set; } = "";
public string MqttClientUserName { get; set; }
public string MqttClientPassword { get; set; }
public int Port { get; set; }
public int KeepAlivedTime { get; set; }
public List<string> Topics { get; set; } = new List<string>();
public bool CleanSession { get; set; }
}

surgingSettings.json的MqttClient配置代码参见底部Surging.MqttClientWithWsServices.Server中的配置

根据Surging作者创建的例子来创建一个接口IChatService.cs

using Surging.Core.CPlatform.Ioc;
using Surging.Core.CPlatform.Runtime.Client.Address.Resolvers.Implementation.Selectors.Implementation;
using Surging.Core.CPlatform.Runtime.Server.Implementation.ServiceDiscovery.Attributes;
using Surging.Core.CPlatform.Support.Attributes;
using Surging.Core.Protocol.WS.Attributes;
using Surging.IModuleServices.MqttWithWS.Models;
using System;
using System.Threading.Tasks; namespace Surging.IModuleServices.MqttWithWS
{
[ServiceBundle("Api/{Service}")]
[BehaviorContract(IgnoreExtensions = true)]
public interface IChatService: IServiceKey
{
[Command(ShuntStrategy = AddressSelectorMode.HashAlgorithm)]
Task RunMqttClient(MqttClientOption mqttClientOption);
}
}

结构如图所示

接口实现部分

新建类库Surging.Modules.WSWithMqtt

添加引用Surging.IModuleServices.MqttWithWS

添加依赖项:MQTTnet

由于要获取surgingSettings.json中的配置项,因此需要创建一个文件夹Configurations并在文件夹下创建SurgingMqttBuilderExtendsions.cs类代码如下:

using Autofac;
using Microsoft.Extensions.Configuration;
using Surging.Core.CPlatform;
using Surging.Core.ServiceHosting.Internal;
using Surging.IModuleServices.MqttWithWS;
using Surging.IModuleServices.MqttWithWS.Models;
using System;
using System.Collections.Generic;
using System.Text; namespace Surging.Modules.WSWithMqtt.Configurations
{
public static class SurgingMqttBuilderExtendsions
{
public static IServiceHostBuilder UseMqttClient(this IServiceHostBuilder builder)
{
builder.MapServices(collection =>
{
MqttClientOption mqttClientOption = new MqttClientOption();
var section = AppConfig.GetSection("MqttClient");
if (section.Exists())
mqttClientOption = section.Get<MqttClientOption>();
collection.Resolve<IChatService>().RunMqttClient(mqttClientOption);
});
return builder;
}
}
}

由于此服务基于WSServiceBase 因此我创建了MqttWSServieBase继承自WSServiceBase用于创建MqttClient客户端,代码如下:

using MQTTnet;
using MQTTnet.Client;
using Surging.Core.Protocol.WS;
using Surging.IModuleServices.MqttWithWS.Models;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks; namespace Surging.Modules.WSWithMqtt
{
public abstract class MqttWSServieBase : WSServiceBase
{
protected IMqttClientOptions _mqttClientOptions;
protected IMqttClient _mqttClient;
protected IEnumerable<TopicFilter> _topicFilters;
public Task RunMqttClientBase(MqttClientOption mqttClientOption)
{
var clientOptions = new MqttClientOptionsBuilder();
if (mqttClientOption != null)
{
clientOptions.WithClientId(mqttClientOption.ClientID + Guid.NewGuid().ToString("N"))
.WithCleanSession(mqttClientOption.CleanSession);
clientOptions.WithTcpServer(mqttClientOption.MqttClientConnection, mqttClientOption.Port);
if (!string.IsNullOrWhiteSpace(mqttClientOption.MqttClientUserName))
clientOptions.WithCredentials(mqttClientOption.MqttClientUserName, mqttClientOption.MqttClientPassword);
clientOptions.WithKeepAlivePeriod(TimeSpan.FromSeconds(mqttClientOption.KeepAlivedTime));
}
_mqttClientOptions = clientOptions.Build();
IList<TopicFilter> filters = new List<TopicFilter>();
if (mqttClientOption != null)
{
foreach (var item in mqttClientOption.Topics)
{
var topicFilerbuilder = new TopicFilterBuilder();
topicFilerbuilder.WithTopic(item);
filters.Add(topicFilerbuilder.Build());
}
}
_topicFilters = filters;
_mqttClient = new MqttFactory().CreateMqttClient();
return Task.CompletedTask;
}
}
}

接口继承类ChatService,此类用于连接WS,并通过此连接对Surging的 Mqtt Broker进行发布订阅。 代码如下:

using MQTTnet;
using MQTTnet.Client;
using Surging.IModuleServices.MqttWithWS;
using Surging.IModuleServices.MqttWithWS.Models;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
using WebSocketCore; namespace Surging.Modules.WSWithMqtt
{
public class ChatService : MqttWSServieBase, IChatService
{
private static readonly ConcurrentDictionary<string, string> _users = new ConcurrentDictionary<string, string>();
private static readonly ConcurrentDictionary<string, string> _clients = new ConcurrentDictionary<string, string>();
private string _name;
private string _to; protected override void OnMessage(MessageEventArgs e)
{
if (_clients.ContainsKey(ID))
{
Dictionary<string, object> model = new Dictionary<string, object>();
model.Add("name", _to);
model.Add("data", e.Data);
//var result = ServiceLocator.GetService<IServiceProxyProvider>()
// .Invoke<object>(model, "api/chat/SendMessage").Result; }
} protected override void OnOpen()
{
_name = Context.QueryString["name"];
_to = Context.QueryString["to"];
if (!string.IsNullOrEmpty(_name))
{
_clients[ID] = _name;
_users[_name] = ID;
}
}
public Task SendMessage(string name, string data)
{
if (_users.ContainsKey(name))
{
this.GetClient().SendTo($"hello,{name},{data}", _users[name]);
}
return Task.CompletedTask;
}
public async Task RunMqttClient(MqttClientOption mqttClientOption)
{
await base.RunMqttClientBase(mqttClientOption);
_mqttClient.ApplicationMessageReceived += _mqttClient_ApplicationMessageReceived;
_mqttClient.Connected += _mqttClient_Connected;
_mqttClient.Disconnected += _mqttClient_Disconnected;
await _mqttClient.ConnectAsync(_mqttClientOptions);
}
#region mqttClient
/// <summary>
/// 接收消息
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void _mqttClient_ApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs e)
{
if (e.ApplicationMessage != null)
{ }
}
/// <summary>
/// 断开连接
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void _mqttClient_Disconnected(object sender, MqttClientDisconnectedEventArgs e)
{
Console.WriteLine("mqtt客户端与服务端断开连接!");
await Task.Delay(TimeSpan.FromSeconds(5));
try
{
await _mqttClient.ConnectAsync(_mqttClientOptions);
}
catch
{
Console.WriteLine("mqtt客户端与服务端尝试连接失败!");
}
}
/// <summary>
/// 连接成功
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void _mqttClient_Connected(object sender, MqttClientConnectedEventArgs e)
{
_mqttClient.SubscribeAsync(_topicFilters);
}
#endregion
}
}

结构如图

创建控制台应用程序,用于项目启动

新建控制台程序Surging.MqttClientWithWsServices.Server将Surging.Services.Server中的配置文件cacheSettings.json、eventBusSettings.json、log4net.config、NLog.config、以及surgingSettings.json文件都拷贝到新建的这个控制台程序中。

添加依赖项:Autofac    Autofac.Extensions.DependencyInjection    System.Text.Encoding.CodePages   Microsoft.Extensions.Logging   Microsoft.Extensions.Logging.Console

添加引用

修改Program.cs

using Autofac;
using Microsoft.Extensions.Logging;
using Surging.Core.Caching.Configurations;
using Surging.Core.CPlatform;
using Surging.Core.CPlatform.Configurations;
using Surging.Core.CPlatform.Utilities;
using Surging.Core.Nlog;
using Surging.Core.ProxyGenerator;
using Surging.Core.ServiceHosting;
using Surging.Core.ServiceHosting.Internal.Implementation;
using Surging.Modules.WSWithMqtt.Configurations;
using System;
using System.Text; namespace Surging.MqttClientWithWsServices.Server
{
class Program
{
static void Main(string[] args)
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
var host = new ServiceHostBuilder()
.RegisterServices(builder =>
{
builder.AddMicroService(option =>
{
option.AddServiceRuntime()
.AddClientProxy()
.AddRelateService()
.AddConfigurationWatch();
builder.Register(p => new CPlatformContainer(ServiceLocator.Current));
});
})
.ConfigureLogging(logger =>
{
logger.AddConfiguration(
Surging.Core.CPlatform.AppConfig.GetSection("Logging"));
})
.UseNLog(LogLevel.Debug, "NLog.config")
.UseServer(options => { })
.UseConsoleLifetime()
.UseMqttClient()
.UseProxy()
.Configure(build =>
build.AddCacheFile("${cachepath}|cacheSettings.json", optional: false, reloadOnChange: true))
.Configure(build =>
build.AddCPlatformFile("${surgingpath}|surgingSettings.json", optional: false, reloadOnChange: true))
.UseStartup<Startup>()
.Build(); using (host.Run())
{
Console.WriteLine($"服务端启动成功,{DateTime.Now}。");
}
}
}
}

注意添加是需要加一段代码.UseMqttClient()用于启动MqttClient。

添加Startup.cs 此类与Surging.Services.Server中的Startup.cs一致。

using Autofac;
using Autofac.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Surging.Core.Caching.Configurations;
using Surging.Core.CPlatform.Utilities;
using System.IO;
namespace Surging.MqttClientWithWsServices.Server
{
public class Startup
{ public Startup(IConfigurationBuilder config)
{
config.SetBasePath(Directory.GetCurrentDirectory());
ConfigureCache(config);
} public IContainer ConfigureServices(ContainerBuilder builder)
{
var services = new ServiceCollection();
ConfigureLogging(services);
builder.Populate(services);
ServiceLocator.Current = builder.Build();
return ServiceLocator.Current;
} public void Configure(IContainer app)
{ } #region 私有方法
/// <summary>
/// 配置日志服务
/// </summary>
/// <param name="services"></param>
private void ConfigureLogging(IServiceCollection services)
{
services.AddLogging();
} /// <summary>
/// 配置缓存服务
/// </summary>
private void ConfigureCache(IConfigurationBuilder build)
{
build
.AddCacheFile("cacheSettings.json", optional: false);
}
#endregion
}
}

现在基本上已经完事了,下面再说下surgingSettings.json配置信息为了避免与Surging.Services.Server的端口重复因此配置如下:

{
"Surging": {
"Ip": "${Surging_Server_IP}",
"WatchInterval": 30,
"Port": "${Surging_Server_Port}|100",
"MappingIp": "${Mapping_ip}",
"MappingPort": "${Mapping_Port}",
"Token": "true",
"WanIp": "${Mapping_WanIp}",
"Libuv": true,
"MaxConcurrentRequests": 20,
"ExecutionTimeoutInMilliseconds": 30000,
"Protocol": "${Protocol}|None", //Http、Tcp、None
"RootPath": "${RootPath}|",
"Ports": {
"HttpPort": "${HttpPort}|2801",
"MQTTPort": "${MQTTPort}|971",
"WSPort": "${WSPort}|961"
},
"RequestCacheEnabled": false,
"Packages": [
{
"TypeName": "EnginePartModule",
"Using": "${UseEngineParts}|DotNettyModule;NLogModule;MessagePackModule;ServiceProxyModule;ConsulModule;EventBusRabbitMQModule;CachingModule;"
}
]
}, //如果引用多个同类型的组件,需要配置Packages,如果是自定义按需引用,无需配置Packages
"Consul": {
"ConnectionString": "${Register_Conn}|127.0.0.1:8500", // "127.0.0.1:8500",
"SessionTimeout": "${Register_SessionTimeout}|50",
"RoutePath": "${Register_RoutePath}",
"ReloadOnChange": true,
"EnableChildrenMonitor": false
},
"Swagger": {
"Version": "${SwaggerVersion}|V1", // "127.0.0.1:8500",
"Title": "${SwaggerTitle}|Surging Demo",
"Description": "${SwaggerDes}|surging demo",
"Contact": {
"Name": "API Support",
"Url": "https://github.com/dotnetcore/surging",
"Email": "fanliang1@hotmail.com"
},
"License": {
"Name": "MIT",
"Url": "https://github.com/dotnetcore/surging/blob/master/LICENSE"
}
},
"EventBus_Kafka": {
"Servers": "${EventBusConnection}|localhost:9092",
"MaxQueueBuffering": "${MaxQueueBuffering}|10",
"MaxSocketBlocking": "${MaxSocketBlocking}|10",
"EnableAutoCommit": "${EnableAutoCommit}|false",
"LogConnectionClose": "${LogConnectionClose}|false",
"OffsetReset": "${OffsetReset}|earliest",
"GroupID": "${EventBusGroupID}|surgingdemo"
},
"WebSocket": {
"WaitTime": 2,
"KeepClean": false,
"Behavior": {
"IgnoreExtensions": true,
"EmitOnPing": false
}
},
"EventBus": {
"EventBusConnection": "${EventBusConnection}|192.168.1.127",
"EventBusUserName": "${EventBusUserName}|guest",
"EventBusPassword": "${EventBusPassword}|guest",
"VirtualHost": "${VirtualHost}|/",
"MessageTTL": "${MessageTTL}|30000",
"RetryCount": "${RetryCount}|1",
"FailCount": "${FailCount}|3",
"prefetchCount": "${PrefetchCount}|0",
"BrokerName": "${BrokerName}|surging_demo",
"Port": "${EventBusPort}|32671"
},
"Zookeeper": {
"ConnectionString": "${Zookeeper_ConnectionString}|127.0.0.1:2181",
"SessionTimeout": 50,
"ReloadOnChange": true
},
"Logging": {
"Debug": {
"LogLevel": {
"Default": "Information"
}
},
"Console": {
"IncludeScopes": true,
"LogLevel": {
"Default": "${LogLevel}|Debug"
}
},
"LogLevel": {
"Default": "${LogLevel}|Debug"
}
},
"MqttClient": {
"ClientID": "${MqttClientID}|serverclientid",
"MqttClientConnection": "${MqttClientConnection}|127.0.0.1",
"MqttClientUserName": "admin",
"MqttClientPassword": "123456",
"Port": 97,
"KeepAlivedTime": 60,
"CleanSession": true,
"Topics": [ "test1","test2" ]
}
}

 总的Server代码如图所示:

此时代码开发阶段结束。我们可以设置多项目启动,启动项目前确定你的 Consul能够正常使用。本地启动Consul 可以通过控制台来做测试就OK

1、Surging.Services.Server 2、Surging.MqttClientWithWsServices.Server即(红框标注内容):

说到此处,想必大家都知道怎么使用SurgingWS、MqttClient 与MqttBroker进行连接了。写出这段代码主要是针对于Surging不了解,或者摄入不深的人,能直接快速的使用本代码让用户与设备间可以正常通讯。

注意如果使用Docker编排,或者Rancher编排Surging Broker 或者 WSMqttClient 时如果涉及到多个编排我们需要进行相应的逻辑判断。

近期由于个人需求,需要把设备在线状态通知给各个DBMqttClient端,用于保存现有设备状态。在不处理设备连接时,我们就可以知道设备是否在线,是否有异常。异常时长等。如有此需求可在下方留言。此代码近期可能会贡献给Surging,让Surging更加强大。写这篇文章呢,主要目的是再没有看懂作者代码的情况下,可以尽情使用MqttBroker的功能。次处只是引用了WS与MqttClient,其实可以以此为参考部署更多的MqttClient,比如数据保存与服务通讯分开等。在此感谢Surging作者在业余时间为我们做了这么好的开源项目,愿Surging越来越好。

使用Surging Mqtt 开发基于WS的MqttClient客户端的更多相关文章

  1. 开发基于CXF的 RESTful WebService web 项目 webservice发布

    配置步骤 开发基于CXF的 RESTful WebService 1.创建Web项目并导入CXF的jar 2.在Web.xml中配置 CXFServlet <servlet> <se ...

  2. Mqtt开发笔记:windows下C++ ActiveMQ客户端介绍、编译和使用

    前话   项目需求,需要使用到mqtt协议,之前编译QtMqtt库,不支持队列模式queue(点对点),只支持订阅/发布者模式.,所以使用C++ ActiveMQ实现.   MQTT协议 简介   M ...

  3. [Intel Edison开发板] 05、Edison开发基于MRAA实现IO控制,特别是UART通信

    一.前言 下面是本系列文章的前几篇: [Intel Edison开发板] 01.Edison开发板性能简述 [Intel Edison开发板] 02.Edison开发板入门 [Intel Edison ...

  4. {VS2010C#}{WinForm}{ActiveX}VS2010C#开发基于WinForm的ActiveX控件

    在VS2010中使用C#开发基于WinForm的ActiveX控件 常见的一些ActiveX大部分是使用VB.Delphi.C++开发,使用C#开发ActiveX要解决下面三个问题: 使.NET组件可 ...

  5. Form_Form Builder开发基于视图页面和自动代码生成包(案例)

     2014-01-06 Created By BaoXinjian

  6. 转】Mahout分步式程序开发 基于物品的协同过滤ItemCF

    原博文出自于: http://blog.fens.me/hadoop-mahout-mapreduce-itemcf/ 感谢! Posted: Oct 14, 2013 Tags: Hadoopite ...

  7. Masstransit开发基于消息传递的分布式应用

    使用Masstransit开发基于消息传递的分布式应用 Masstransit作为.Net平台下的一款优秀的开源产品却没有得到应有的关注,这段时间有机会阅读了Masstransit的源码,我觉得我有必 ...

  8. 用c++开发基于tcp协议的文件上传功能

    用c++开发基于tcp协议的文件上传功能 2005我正在一家游戏公司做程序员,当时一直在看<Windows网络编程> 这本书,把里面提到的每种IO模型都试了一次,强烈推荐学习网络编程的同学 ...

  9. 基于Hadoop开发网络云盘系统客户端界面设计初稿

    基于Hadoop开发网络云盘系统客户端界面设计初稿 前言: 本文是<基于Hadoop开发网络云盘系统架构设计方案>的第二篇,针对界面原型原本考虑有两个方案:1.类windows模式,文件夹 ...

随机推荐

  1. 知名游戏开发者称 C++ 是一种非常糟糕、可怕的语言(C++不是一门可怕的语言,可怕的是一群没有耐心的程序员来使用C++这门语言)

    抛出一个问题:C++ 真的很可怕吗? 2016 年底,C++ 之父 Bjarne Stroustrup 在一次采访中表示:”C++ 让编程专家很容易编写出复杂.高性能.低资源消耗的代码,但不足以成为广 ...

  2. EC2 开启 IPV6 访问 和 禁止重启后自动分配IP地址

    EC2 开启 IPV6 访问 和 禁止重启后自动分配IP地址进入 VPC 控制台,对当前 VPC 添加 IPV6 CIDR 块对该 VPC 的路由表进行修改,添加其它路由,第一个空填::/0,第二个空 ...

  3. .Net数据操作案例

    Interface using System.Collections.Generic; using Ddd.Core.Domain.Customers; namespace Ddd.Services. ...

  4. Android图像处理之冰冻效果

    原图                                                                          效果图 代码: package com.colo ...

  5. js中的this详解

    在web前端开发中,javascript中的this和其他的JAVA,C#等大型语言一样,是一个重要概念.但是要注意的是,在javascript中,由于 javascript的动态性,this的指向在 ...

  6. Kinect 开发 —— 硬件设备解剖

    Kinect for Xbox: 360 不支持“近景模式” 三只眼睛 —— 红外投影机,RGB摄像头,红外深度投影头  —— 色彩影像中的每个像素分别与深度影像中的一个像素对应 四只耳朵 —— L形 ...

  7. hbase xshell

    用Xshell登陆linux主机后,在hbase shell下死活不能使用backspace和delete删除误输的指令,只得不停退出,重登,仔细输..又错了,再退出,再登,仔细输...又错了...又 ...

  8. fetch 封装

    fetch.js var http = { get: function (url) { return new Promise((resolve, reject) => { fetch(url) ...

  9. docker 部署 jenkins server

    1. 拉取一个jenkins 镜像 docker pull jenkins 2. 创建与jenkins配置目录对应的,容器外的,文件目录,并修改相应的权限 mkdir /home/jenkins ch ...

  10. 【UWP通用应用开发】控件、应用栏

    控件的属性.事件与样式资源 怎样加入控件 加入控件的方式有多种,大家更喜欢以下哪一种呢? 1)使用诸如Blend for Visual Studio或Microsoft Visual Studio X ...