Dotnetty
Netty是顶顶大名的网络通讯开发库,使通讯开发人员再也不用费九牛二虎之力来处理socket通讯和一些常见应用层协议如HTTP、WEBSOCKET、MQTT等底层问题,而可以专心做应用层的开发工作。Dotnetty是Azure团队照着Netty开发的C#版本。
MQTT Client
基于 Netty/Dotnetty 提供的是MQTT 14种Packet和编码、解码的实现,但没有提供MQTT客户端或服务端的官方实现。我重点参考了https://github.com/jetlinks/netty-mqtt-client的java代码,其中包含了重连和重传机制,相较于其他netty的mqtt client更为成熟,但代码中出现的Future/Promise也着实不好对付。
JAVA到C#
Java中的Future相当于C#的Task,Promise相当于TaskCompletionSource,后者主要用于将多个步骤合并为一个任务,都完成后再返回。Mqtt协议里,这种多步一回合的情况是普遍的,包括:
- CONNECT——CONACT
- PUBLISH——PUBACK
- PUBLISH——PUBREC——PUBREL——PUBCOMP
- SUBSCRIBE——SUBACK
- UNSUBSCRIBE——UNSUBACK
例如,Client对外提供的 Publish 方法在QoS>0的情况下,并不是WriteAndFlush后就算完成了,还要等到PUBACK(QoS=1)甚至PUBREL(QoS=2)才能返回,这中间Mqtt Client和Mqtt Broker之间已经进行了好几次通讯,因此需要用Promise/TaskCompletionSource来将这几步通讯包裹成一个任务,执行完后Publish方法才返回。
代码结构
相关的代码已上传github,csdn下载,核心是MqttClient类,提供了Connect, Disconnect, Publish,Subscribe 和Unsubscribe 向Mqtt Broker发送消息的方法,以及On*** 负责接收来自Mqtt Broker消息的回调方法。MqttHandler作为管道里的最后一个Handler,负责接收来自Mqtt Broker的消息,转发给MqttClient。
在MqttClient基础上,可以方便的进行扩展,例如HeartbeatMqttClient就是在管道中加入了Idle和Ping两个Handler,实现定时发送PING包完成心跳功能。
测试一:订阅/发布相同主题
在云端服务器上运行mosquitto Mqtt Broker。通过如下代码,向服务器订阅test/1主题,然后再向test/1主题每秒发送 0-7,收到0-7,验证了Connect, Disconnect, Publish,Subscribe。
Mqtt.Client.MqttClient mqttClient = new Mqtt.Client.MqttClient();
var result = await mqttClient.ConnectAsync("127.0.0.1", 1883);
Console.WriteLine($"connect {result.Success}");
if (result.Success)
{
await mqttClient.SubscribeAsync("test/1", DotNetty.Codecs.Mqtt.Packets.QualityOfService.AtLeastOnce, (packet) =>
{
var pubpacket = (DotNetty.Codecs.Mqtt.Packets.PublishPacket)packet;
Console.WriteLine(pubpacket.Payload.GetString(0, pubpacket.Payload.WriterIndex, UTF8Encoding.UTF8));
});
await Task.Delay(1000);
for(int i = 0; i < 8; i++)
{
await mqttClient.PublishAsync("test/1", UTF8Encoding.UTF8.GetBytes(i.ToString()));
await Task.Delay(1000);
}
await Task.Delay(1000);
await mqttClient.DisconnectAsync();
}
测试二:心跳
Mqtt.Client.HeartbeatMqttClient mqttClient = new Mqtt.Client.HeartbeatMqttClient();
var result = await mqttClient.ConnectAsync("127.0.0.1", 1883);
Console.WriteLine($"connect {result.Success}");
if (result.Success)
{
await Task.Delay(100000);
await mqttClient.DisconnectAsync();
}