@@ -2,7 +2,7 @@ | |||||
<package > | <package > | ||||
<metadata> | <metadata> | ||||
<id>MQTTnet</id> | <id>MQTTnet</id> | ||||
<version>2.5.2-rc1</version> | |||||
<version>2.5.2</version> | |||||
<authors>Christian Kratky</authors> | <authors>Christian Kratky</authors> | ||||
<owners>Christian Kratky</owners> | <owners>Christian Kratky</owners> | ||||
<licenseUrl>https://github.com/chkr1011/MQTTnet/blob/master/LICENSE</licenseUrl> | <licenseUrl>https://github.com/chkr1011/MQTTnet/blob/master/LICENSE</licenseUrl> | ||||
@@ -10,13 +10,7 @@ | |||||
<iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl> | <iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl> | ||||
<requireLicenseAcceptance>false</requireLicenseAcceptance> | <requireLicenseAcceptance>false</requireLicenseAcceptance> | ||||
<description>MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker).</description> | <description>MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker).</description> | ||||
<releaseNotes>* [Core] Fixed library reference issues for .NET 4.6 and netstandard 2.0 (Thanks to @JanEggers). | |||||
* [Core] Several COM exceptions are now wrapped properly resulting in less warnings in the trace. | |||||
* [Core] Removed application message payload from trace to reduce trace size and increase performance. | |||||
* [Client] Fixed WebSocket sub protocol negotiation for ASP.NET Core 2 servers (Thanks to @JanEggers). | |||||
* [Client] Fixed broken connection after 30 seconds then using WebSocket protocol (Thanks to @ChristianRiedl). | |||||
* [Server] Client connections are now closed when the server is stopped (Thanks to @zhudanfei). | |||||
* [Server] Published messages from the server are now retained (if set) (Thanks to @ChristianRiedl). BREAKING CHANGE! | |||||
<releaseNotes>* [Core] Refactored trace messages. | |||||
</releaseNotes> | </releaseNotes> | ||||
<copyright>Copyright Christian Kratky 2016-2017</copyright> | <copyright>Copyright Christian Kratky 2016-2017</copyright> | ||||
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags> | <tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags> | ||||
@@ -32,11 +32,15 @@ namespace MQTTnet.Core.Adapter | |||||
public async Task ConnectAsync(TimeSpan timeout) | public async Task ConnectAsync(TimeSpan timeout) | ||||
{ | { | ||||
_logger.LogInformation("Connecting [Timeout={0}]", timeout); | |||||
await ExecuteAndWrapExceptionAsync(() => _channel.ConnectAsync().TimeoutAfter(timeout)); | await ExecuteAndWrapExceptionAsync(() => _channel.ConnectAsync().TimeoutAfter(timeout)); | ||||
} | } | ||||
public async Task DisconnectAsync(TimeSpan timeout) | public async Task DisconnectAsync(TimeSpan timeout) | ||||
{ | { | ||||
_logger.LogInformation("Disconnecting [Timeout={0}]", timeout); | |||||
await ExecuteAndWrapExceptionAsync(() => _channel.DisconnectAsync().TimeoutAfter(timeout)); | await ExecuteAndWrapExceptionAsync(() => _channel.DisconnectAsync().TimeoutAfter(timeout)); | ||||
} | } | ||||
@@ -10,7 +10,7 @@ namespace MQTTnet.Core.Packets | |||||
public override string ToString() | public override string ToString() | ||||
{ | { | ||||
return nameof(MqttConnAckPacket) + ": [ConnectReturnCode=" + ConnectReturnCode + "] [IsSessionPresent=" + IsSessionPresent + "]"; | |||||
return "ConnAck: [ConnectReturnCode=" + ConnectReturnCode + "] [IsSessionPresent=" + IsSessionPresent + "]"; | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -20,7 +20,7 @@ namespace MQTTnet.Core.Packets | |||||
public override string ToString() | public override string ToString() | ||||
{ | { | ||||
return nameof(MqttConnectPacket) + ": [ClientId=" + ClientId + "] [Username=" + Username + "] [Password=" + Password + "] [KeepAlivePeriod=" + KeepAlivePeriod + "] [CleanSession=" + CleanSession + "]"; | |||||
return "Connect: [ClientId=" + ClientId + "] [Username=" + Username + "] [Password=" + Password + "] [KeepAlivePeriod=" + KeepAlivePeriod + "] [CleanSession=" + CleanSession + "]"; | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -2,5 +2,9 @@ | |||||
{ | { | ||||
public sealed class MqttDisconnectPacket : MqttBasePacket | public sealed class MqttDisconnectPacket : MqttBasePacket | ||||
{ | { | ||||
public override string ToString() | |||||
{ | |||||
return "Disconnect"; | |||||
} | |||||
} | } | ||||
} | } |
@@ -4,7 +4,7 @@ | |||||
{ | { | ||||
public override string ToString() | public override string ToString() | ||||
{ | { | ||||
return nameof(MqttPingReqPacket); | |||||
return "PingReq"; | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -4,7 +4,7 @@ | |||||
{ | { | ||||
public override string ToString() | public override string ToString() | ||||
{ | { | ||||
return nameof(MqttPingRespPacket); | |||||
return "PingResp"; | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -2,5 +2,9 @@ | |||||
{ | { | ||||
public sealed class MqttPubAckPacket : MqttBasePublishPacket | public sealed class MqttPubAckPacket : MqttBasePublishPacket | ||||
{ | { | ||||
public override string ToString() | |||||
{ | |||||
return "PubAck"; | |||||
} | |||||
} | } | ||||
} | } |
@@ -2,5 +2,9 @@ | |||||
{ | { | ||||
public sealed class MqttPubCompPacket : MqttBasePublishPacket | public sealed class MqttPubCompPacket : MqttBasePublishPacket | ||||
{ | { | ||||
public override string ToString() | |||||
{ | |||||
return "PubComp"; | |||||
} | |||||
} | } | ||||
} | } |
@@ -2,5 +2,9 @@ | |||||
{ | { | ||||
public sealed class MqttPubRecPacket : MqttBasePublishPacket | public sealed class MqttPubRecPacket : MqttBasePublishPacket | ||||
{ | { | ||||
public override string ToString() | |||||
{ | |||||
return "PubRec"; | |||||
} | |||||
} | } | ||||
} | } |
@@ -2,5 +2,9 @@ | |||||
{ | { | ||||
public sealed class MqttPubRelPacket : MqttBasePublishPacket | public sealed class MqttPubRelPacket : MqttBasePublishPacket | ||||
{ | { | ||||
public override string ToString() | |||||
{ | |||||
return "PubRel"; | |||||
} | |||||
} | } | ||||
} | } |
@@ -16,9 +16,8 @@ namespace MQTTnet.Core.Packets | |||||
public override string ToString() | public override string ToString() | ||||
{ | { | ||||
return nameof(MqttPublishPacket) + | |||||
": [Topic=" + Topic + "]" + | |||||
" [PayloadLength=" + Payload?.Length + "]" + | |||||
return "Publish: [Topic=" + Topic + "]" + | |||||
" [Payload.Length=" + Payload?.Length + "]" + | |||||
" [QoSLevel=" + QualityOfServiceLevel + "]" + | " [QoSLevel=" + QualityOfServiceLevel + "]" + | ||||
" [Dup=" + Dup + "]" + | " [Dup=" + Dup + "]" + | ||||
" [Retain=" + Retain + "]" + | " [Retain=" + Retain + "]" + | ||||
@@ -13,7 +13,7 @@ namespace MQTTnet.Core.Packets | |||||
public override string ToString() | public override string ToString() | ||||
{ | { | ||||
var subscribeReturnCodesText = string.Join(",", SubscribeReturnCodes.Select(f => f.ToString())); | var subscribeReturnCodesText = string.Join(",", SubscribeReturnCodes.Select(f => f.ToString())); | ||||
return nameof(MqttSubAckPacket) + ": [PacketIdentifier=" + PacketIdentifier + "] [SubscribeReturnCodes=" + subscribeReturnCodesText + "]"; | |||||
return "SubAck: [PacketIdentifier=" + PacketIdentifier + "] [SubscribeReturnCodes=" + subscribeReturnCodesText + "]"; | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -12,7 +12,7 @@ namespace MQTTnet.Core.Packets | |||||
public override string ToString() | public override string ToString() | ||||
{ | { | ||||
var topicFiltersText = string.Join(",", TopicFilters.Select(f => f.Topic + "@" + f.QualityOfServiceLevel)); | var topicFiltersText = string.Join(",", TopicFilters.Select(f => f.Topic + "@" + f.QualityOfServiceLevel)); | ||||
return nameof(MqttSubscribePacket) + ": [PacketIdentifier=" + PacketIdentifier + "] [TopicFilters=" + topicFiltersText + "]"; | |||||
return "Subscribe: [PacketIdentifier=" + PacketIdentifier + "] [TopicFilters=" + topicFiltersText + "]"; | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -3,5 +3,10 @@ | |||||
public sealed class MqttUnsubAckPacket : MqttBasePacket, IMqttPacketWithIdentifier | public sealed class MqttUnsubAckPacket : MqttBasePacket, IMqttPacketWithIdentifier | ||||
{ | { | ||||
public ushort PacketIdentifier { get; set; } | public ushort PacketIdentifier { get; set; } | ||||
public override string ToString() | |||||
{ | |||||
return "UnsubAck: [PacketIdentifier=" + PacketIdentifier + "]"; | |||||
} | |||||
} | } | ||||
} | } |
@@ -7,5 +7,11 @@ namespace MQTTnet.Core.Packets | |||||
public ushort PacketIdentifier { get; set; } | public ushort PacketIdentifier { get; set; } | ||||
public IList<string> TopicFilters { get; set; } = new List<string>(); | public IList<string> TopicFilters { get; set; } = new List<string>(); | ||||
public override string ToString() | |||||
{ | |||||
var topicFiltersText = string.Join(",", TopicFilters); | |||||
return "Subscribe: [PacketIdentifier=" + PacketIdentifier + "] [TopicFilters=" + topicFiltersText + "]"; | |||||
} | |||||
} | } | ||||
} | } |
@@ -57,42 +57,7 @@ namespace MQTTnet.Core.Server | |||||
await _gate.WaitAsync().ConfigureAwait(false); | await _gate.WaitAsync().ConfigureAwait(false); | ||||
try | try | ||||
{ | { | ||||
var saveIsRequired = false; | |||||
if (applicationMessage.Payload?.Any() == false) | |||||
{ | |||||
saveIsRequired = _retainedMessages.Remove(applicationMessage.Topic); | |||||
_logger.LogInformation("Client '{0}' cleared retained message for topic '{1}'.", clientId, applicationMessage.Topic); | |||||
} | |||||
else | |||||
{ | |||||
if (!_retainedMessages.ContainsKey(applicationMessage.Topic)) | |||||
{ | |||||
_retainedMessages[applicationMessage.Topic] = applicationMessage; | |||||
saveIsRequired = true; | |||||
} | |||||
else | |||||
{ | |||||
var existingMessage = _retainedMessages[applicationMessage.Topic]; | |||||
if (existingMessage.QualityOfServiceLevel != applicationMessage.QualityOfServiceLevel || !existingMessage.Payload.SequenceEqual(applicationMessage.Payload ?? new byte[0])) | |||||
{ | |||||
_retainedMessages[applicationMessage.Topic] = applicationMessage; | |||||
saveIsRequired = true; | |||||
} | |||||
} | |||||
_logger.LogInformation("Client '{0}' set retained message for topic '{1}'.", clientId, applicationMessage.Topic); | |||||
} | |||||
if (!saveIsRequired) | |||||
{ | |||||
_logger.LogTrace("Skipped saving retained messages because no changes were detected."); | |||||
} | |||||
if (saveIsRequired && _options.Storage != null) | |||||
{ | |||||
await _options.Storage.SaveRetainedMessagesAsync(_retainedMessages.Values.ToList()); | |||||
} | |||||
await HandleMessageInternalAsync(clientId, applicationMessage); | |||||
} | } | ||||
catch (Exception exception) | catch (Exception exception) | ||||
{ | { | ||||
@@ -137,5 +102,45 @@ namespace MQTTnet.Core.Server | |||||
return retainedMessages; | return retainedMessages; | ||||
} | } | ||||
private async Task HandleMessageInternalAsync(string clientId, MqttApplicationMessage applicationMessage) | |||||
{ | |||||
var saveIsRequired = false; | |||||
if (applicationMessage.Payload?.Any() == false) | |||||
{ | |||||
saveIsRequired = _retainedMessages.Remove(applicationMessage.Topic); | |||||
_logger.LogInformation("Client '{0}' cleared retained message for topic '{1}'.", clientId, applicationMessage.Topic); | |||||
} | |||||
else | |||||
{ | |||||
if (!_retainedMessages.ContainsKey(applicationMessage.Topic)) | |||||
{ | |||||
_retainedMessages[applicationMessage.Topic] = applicationMessage; | |||||
saveIsRequired = true; | |||||
} | |||||
else | |||||
{ | |||||
var existingMessage = _retainedMessages[applicationMessage.Topic]; | |||||
if (existingMessage.QualityOfServiceLevel != applicationMessage.QualityOfServiceLevel || !existingMessage.Payload.SequenceEqual(applicationMessage.Payload ?? new byte[0])) | |||||
{ | |||||
_retainedMessages[applicationMessage.Topic] = applicationMessage; | |||||
saveIsRequired = true; | |||||
} | |||||
} | |||||
_logger.LogInformation("Client '{0}' set retained message for topic '{1}'.", clientId, applicationMessage.Topic); | |||||
} | |||||
if (!saveIsRequired) | |||||
{ | |||||
_logger.LogTrace("Skipped saving retained messages because no changes were detected."); | |||||
} | |||||
if (saveIsRequired && _options.Storage != null) | |||||
{ | |||||
await _options.Storage.SaveRetainedMessagesAsync(_retainedMessages.Values.ToList()); | |||||
} | |||||
} | |||||
} | } | ||||
} | } |
@@ -1,4 +1,5 @@ | |||||
using System; | using System; | ||||
using System.Collections.Concurrent; | |||||
using System.Text; | using System.Text; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
using Windows.Security.Cryptography.Certificates; | using Windows.Security.Cryptography.Certificates; | ||||
@@ -18,6 +19,8 @@ namespace MQTTnet.TestApp.UniversalWindows | |||||
{ | { | ||||
public sealed partial class MainPage | public sealed partial class MainPage | ||||
{ | { | ||||
private readonly ConcurrentQueue<MqttNetTraceMessage> _traceMessages = new ConcurrentQueue<MqttNetTraceMessage>(); | |||||
private IMqttClient _mqttClient; | private IMqttClient _mqttClient; | ||||
private IMqttServer _mqttServer; | private IMqttServer _mqttServer; | ||||
@@ -30,15 +33,32 @@ namespace MQTTnet.TestApp.UniversalWindows | |||||
private async void OnTraceMessagePublished(object sender, MqttNetTraceMessagePublishedEventArgs e) | private async void OnTraceMessagePublished(object sender, MqttNetTraceMessagePublishedEventArgs e) | ||||
{ | { | ||||
var text = $"[{e.TraceMessage.Timestamp:yyyy-MM-dd HH:mm:ss.fff}] [{e.TraceMessage.Level}] [{e.TraceMessage.Source}] [{e.TraceMessage.ThreadId}] [{e.TraceMessage.Message}]{Environment.NewLine}"; | |||||
if (e.TraceMessage.Exception != null) | |||||
_traceMessages.Enqueue(e.TraceMessage); | |||||
while (_traceMessages.Count > 100) | |||||
{ | |||||
_traceMessages.TryDequeue(out _); | |||||
} | |||||
var logText = new StringBuilder(); | |||||
foreach (var traceMessage in _traceMessages) | |||||
{ | { | ||||
text += $"{e.TraceMessage.Exception}{Environment.NewLine}"; | |||||
logText.AppendFormat( | |||||
"[{0:yyyy-MM-dd HH:mm:ss.fff}] [{1}] [{2}] [{3}] [{4}]{5}", traceMessage.Timestamp, | |||||
traceMessage.Level, | |||||
traceMessage.Source, | |||||
traceMessage.ThreadId, | |||||
traceMessage.Message, | |||||
Environment.NewLine); | |||||
if (traceMessage.Exception != null) | |||||
{ | |||||
logText.AppendLine(traceMessage.Exception.ToString()); | |||||
} | |||||
} | } | ||||
await Trace.Dispatcher.RunAsync(CoreDispatcherPriority.Low, () => | await Trace.Dispatcher.RunAsync(CoreDispatcherPriority.Low, () => | ||||
{ | { | ||||
Trace.Text += text; | |||||
Trace.Text = logText.ToString(); | |||||
}); | }); | ||||
} | } | ||||