原文:使用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. HDU4009 Transfer water 【最小树形图】

    Transfer water Time Limit: 5000/3000 MS (Java/Others)    Memory Limit: 65768/65768 K (Java/Others) T ...

  2. 【HDU 4763】Theme Section(KMP)

    这题数据水的一B.直接暴力都能够过. 比赛的时候暴力过的.回头依照正法做了一发. 匹配的时候 失配函数 事实上就是前缀 后缀的匹配长度,之后就是乱搞了. KMP的题可能不会非常直接的出,可是KMP的思 ...

  3. 新辰:十种外链终极方法 让SEOer外链之路不再孤独!

    大家都知道,外链就是指从别的站点导入到自己站点的链接.导入链接对于新辰站点优化来说是很重要的一个过程.因此,新辰觉得.对于中小型站点来说.外链但是优化的重中之重! 由于也有了"外链专员&qu ...

  4. session timer(一)

    功能介绍 SIP并没有为所建立的会话定义存活机制. 虽然用户代理能够通过会话特定的机制推断会话是否超时,可是代理server却做不到这点. 如此一来.代理server有时会无法推断会话是否还是活动的. ...

  5. html5中的空格符

    html5中的空格符 1,Html中空格      不断行的空白(1个字符宽度)      半个空白(1个字符宽度)     一个空白(2个字符宽度)      窄空白(小于1个字符宽度) 2,Css ...

  6. Ubuntu 16.04下的LAMP环境配置

    在学习开发过程中,每当遇到新的问题时,通常都能在网上搜到解决的方法,但是网上的方法千千万,有些是已经过时了的,有些是跟自己开发环境不同的,总是不能第一时间能找到答案. 而当时遇到的问题可能在今后的开发 ...

  7. 如何在canvas中画出一个太极图

    先放一个效果图: 代码如下 <!DOCTYPE html> <html> <head> <meta charset="utf-8" /&g ...

  8. Jquery Validator 增加自定义验证方法

    $(document).ready(function () { jQuery.validator.addMethod("namerepeate", function(value, ...

  9. Resource Access Based on Multiple Credentials

    A collection of multiple user credentials each associated with one of multiple different users is ob ...

  10. C# 读取指定文件夹中的全部文件,并按规则生成SQL语句!

    本实例的目的在于: 1 了解怎样遍历指定文件夹中的全部文件 2 控制台怎样输入和输出数据 代码: using System; using System.IO; namespace ToSql{ cla ...