Compare commits

..
Author SHA1 Message Date
Christian c0c5f20c5a Add samples. 2022-08-07 12:34:10 +02:00
84 changed files with 1213 additions and 2116 deletions
-2
View File
@@ -1,2 +0,0 @@
* [Core] Fixed dead lock and race conditions in new _AsyncLock_ implementation (#1542).
* [Client] Fixed connection freeze when using Xamarin etc.
+1 -1
View File
@@ -3,7 +3,7 @@ name: CI
on: [push, pull_request] on: [push, pull_request]
env: env:
VERSION: "4.1.2.${{github.run_number}}" VERSION: "4.1.0.${{github.run_number}}"
jobs: jobs:
build: build:
-2
View File
@@ -210,7 +210,6 @@
</Or> </Or>
</Entry.Match> </Entry.Match>
<Entry.SortBy> <Entry.SortBy>
<Access Order="Public Internal Protected Private" />
<Name /> <Name />
</Entry.SortBy> </Entry.SortBy>
</Entry> </Entry>
@@ -241,6 +240,5 @@ See the LICENSE file in the project root for more information.</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=PINGREQ/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=PINGREQ/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=PINGRESP/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Tnet/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Tnet/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=unsub/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> <s:Boolean x:Key="/Default/UserDictionary/Words/=unsub/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
+34 -1
View File
@@ -12,7 +12,7 @@
# MQTTnet # MQTTnet
MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker) and supports the MQTT protocol up to version 5. It is compatible with mostly any supported .NET Framework version and CPU architecture. MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker) and supports the MQTT protocol up to version 5.
## Features ## Features
@@ -50,6 +50,39 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov
* A custom message interceptor can be added which allows transforming or extending every received application message * A custom message interceptor can be added which allows transforming or extending every received application message
* Validate subscriptions and deny subscribing of certain topics depending on requesting clients * Validate subscriptions and deny subscribing of certain topics depending on requesting clients
## Supported frameworks
| Framwork | Version |
| ------------------ | ----------- |
|.NET | 5.0+ |
|.NET Framework | 4.5.2+ |
|.NET Standard | 1.3+ |
|.NET Core | 1.1+ |
|.NET Core App | 1.1+ |
| Mono | 5.2+ |
| UWP | 10.0.10240+ |
| Xamarin.Android | 7.5+ |
| Xamarin.iOS | 10.14+ |
| Blazor WebAssembly | 3.2.0+ |
## References
This library is used in the following projects:
* Azure Functions MQTT Bindings, <https://github.com/keesschollaart81/CaseOnline.Azure.WebJobs.Extensions.Mqtt>)
* HA4IoT (Open Source Home Automation system for .NET, <https://github.com/chkr1011/HA4IoT>)
* MQTT Client Rx (Wrapper for Reactive Extensions, <https://github.com/1iveowl/MQTTClient.rx>)
* MQTT Client Rx (Managed Client Wrapper for Reactive Extensions, <https://github.com/mmuecke/RxMQTTnet>)
* MQTT Tester (MQTT client test app for [Android](https://play.google.com/store/apps/details?id=com.liveowl.mqtttester) and [iOS](https://itunes.apple.com/us/app/mqtt-tester/id1278621826?mt=8))
* MQTTnet App (Cross platform client application for MQTT debugging, inspection etc., <https://github.com/chkr1011/MQTTnetApp>)
* Wirehome.Core (Open Source Home Automation system for .NET Core, <https://github.com/chkr1011/Wirehome.Core>)
* SparkplugNet (Sparkplug library for .Net, <https://github.com/SeppPenner/SparkplugNet>)
* Silverback (Framework to build event-driven applications - support for MQTT, Kafka & RabbitMQ) <https://github.com/BEagle1984/silverback>
Further projects using this project can be found under https://github.com/dotnet/MQTTnet/network/dependents.
If you use this library and want to see your project here please create a pull request.
## Code of Conduct ## Code of Conduct
This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community. This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community.
+11 -52
View File
@@ -10,7 +10,7 @@ using System.Security.Authentication;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Extensions.WebSocket4Net; using MQTTnet.Extensions.WebSocket4Net;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Samples.Helpers; using MQTTnet.Samples.Shared;
namespace MQTTnet.Samples.Client; namespace MQTTnet.Samples.Client;
@@ -134,17 +134,15 @@ public static class Client_Connection_Samples
} }
} }
public static async Task Connect_Client_Using_WebSocket4Net() public static async Task Connect_Client_Using_WebSockets()
{ {
/* /*
* This sample creates a simple MQTT client and connects to a public broker using a WebSocket connection. * This sample creates a simple MQTT client and connects to a public broker using a WebSocket connection.
* Instead of the .NET implementation of WebSockets the implementaion from WebSocket4Net is used. It provides more
* encryption algorithms and supports more platforms.
* *
* This is a modified version of the sample _Connect_Client_! See other sample for more details. * This is a modified version of the sample _Connect_Client_! See other sample for more details.
*/ */
var mqttFactory = new MqttFactory().UseWebSocket4Net(); var mqttFactory = new MqttFactory();
using (var mqttClient = mqttFactory.CreateMqttClient()) using (var mqttClient = mqttFactory.CreateMqttClient())
{ {
@@ -158,15 +156,17 @@ public static class Client_Connection_Samples
} }
} }
public static async Task Connect_Client_Using_WebSockets() public static async Task Connect_Client_Using_WebSocket4Net()
{ {
/* /*
* This sample creates a simple MQTT client and connects to a public broker using a WebSocket connection. * This sample creates a simple MQTT client and connects to a public broker using a WebSocket connection.
* Instead of the .NET implementation of WebSockets the implementaion from WebSocket4Net is used. It provides more
* encryption algorithms and supports more platforms.
* *
* This is a modified version of the sample _Connect_Client_! See other sample for more details. * This is a modified version of the sample _Connect_Client_! See other sample for more details.
*/ */
var mqttFactory = new MqttFactory(); var mqttFactory = new MqttFactory().UseWebSocket4Net();
using (var mqttClient = mqttFactory.CreateMqttClient()) using (var mqttClient = mqttFactory.CreateMqttClient())
{ {
@@ -214,49 +214,6 @@ public static class Client_Connection_Samples
} }
} }
public static async Task Disconnect_Clean()
{
/*
* This sample disconnects from the server with sending a DISCONNECT packet.
* This way of disconnecting is treated as a clean disconnect which will not
* trigger sending the last will etc.
*/
var mqttFactory = new MqttFactory();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
// Calling _DisconnectAsync_ will send a DISCONNECT packet before closing the connection.
// Using a reason code requires MQTT version 5.0.0!
await mqttClient.DisconnectAsync(MqttClientDisconnectReason.ImplementationSpecificError);
}
}
public static async Task Disconnect_Non_Clean()
{
/*
* This sample disconnects from the server without sending a DISCONNECT packet.
* This way of disconnecting is treated as a non clean disconnect which will
* trigger sending the last will etc.
*/
var mqttFactory = new MqttFactory();
var mqttClient = mqttFactory.CreateMqttClient();
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
// Calling _Dispose_ or use of a _using_ statement will close the transport connection
// without sending a DISCONNECT packet to the server.
mqttClient.Dispose();
}
public static async Task Inspect_Certificate_Validation_Errors() public static async Task Inspect_Certificate_Validation_Errors()
{ {
/* /*
@@ -364,12 +321,14 @@ public static class Client_Connection_Samples
{ {
try try
{ {
// This code will also do the very first connect! So no call to _ConnectAsync_ is required in the first place. // This code will also do the very first connect! So no call to _ConnectAsync_ is required
if (!await mqttClient.TryPingAsync()) // in the first place.
if (!mqttClient.IsConnected)
{ {
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
// Subscribe to topics when session is clean etc. // Subscribe to topics when session is clean etc.
Console.WriteLine("The MQTT client is connected."); Console.WriteLine("The MQTT client is connected.");
} }
} }
-47
View File
@@ -40,53 +40,6 @@ public static class Client_Publish_Samples
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None); await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
await mqttClient.DisconnectAsync();
Console.WriteLine("MQTT application message is published.");
}
}
public static async Task Publish_Multiple_Application_Messages()
{
/*
* This sample pushes multiple simple application message including a topic and a payload.
*
* See sample _Publish_Application_Message_ for more details.
*/
var mqttFactory = new MqttFactory();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("broker.hivemq.com")
.Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
var applicationMessage = new MqttApplicationMessageBuilder()
.WithTopic("samples/temperature/living_room")
.WithPayload("19.5")
.Build();
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
applicationMessage = new MqttApplicationMessageBuilder()
.WithTopic("samples/temperature/living_room")
.WithPayload("20.0")
.Build();
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
applicationMessage = new MqttApplicationMessageBuilder()
.WithTopic("samples/temperature/living_room")
.WithPayload("21.0")
.Build();
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
await mqttClient.DisconnectAsync();
Console.WriteLine("MQTT application message is published."); Console.WriteLine("MQTT application message is published.");
} }
} }
+9 -104
View File
@@ -7,9 +7,7 @@
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Packets; using MQTTnet.Samples.Shared;
using MQTTnet.Protocol;
using MQTTnet.Samples.Helpers;
namespace MQTTnet.Samples.Client; namespace MQTTnet.Samples.Client;
@@ -25,7 +23,9 @@ public static class Client_Subscribe_Samples
using (var mqttClient = mqttFactory.CreateMqttClient()) using (var mqttClient = mqttFactory.CreateMqttClient())
{ {
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build(); var mqttClientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("broker.hivemq.com")
.Build();
// Setup message handling before connecting so that queued messages // Setup message handling before connecting so that queued messages
// are also handled properly. When there is no event handler attached all // are also handled properly. When there is no event handler attached all
@@ -41,11 +41,7 @@ public static class Client_Subscribe_Samples
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder() var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder()
.WithTopicFilter( .WithTopicFilter(f => { f.WithTopic("mqttnet/samples/topic/2"); })
f =>
{
f.WithTopic("mqttnet/samples/topic/2");
})
.Build(); .Build();
await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None); await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
@@ -57,95 +53,6 @@ public static class Client_Subscribe_Samples
} }
} }
public static async Task Send_Responses()
{
/*
* This sample subscribes to a topic and sends a response to the broker. This requires at least QoS level 1 to work!
*/
var mqttFactory = new MqttFactory();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
mqttClient.ApplicationMessageReceivedAsync += delegate(MqttApplicationMessageReceivedEventArgs args)
{
// Do some work with the message...
// Now respond to the broker with a reason code other than success.
args.ReasonCode = MqttApplicationMessageReceivedReasonCode.ImplementationSpecificError;
args.ResponseReasonString = "That did not work!";
// User properties require MQTT v5!
args.ResponseUserProperties.Add(new MqttUserProperty("My", "Data"));
// Now the broker will resend the message again.
return Task.CompletedTask;
};
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder()
.WithTopicFilter(
f =>
{
f.WithTopic("mqttnet/samples/topic/1");
})
.Build();
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
Console.WriteLine("MQTT client subscribed to topic.");
// The response contains additional data sent by the server after subscribing.
response.DumpToConsole();
}
}
public static async Task Subscribe_Multiple_Topics()
{
/*
* This sample subscribes to several topics in a single request.
*/
var mqttFactory = new MqttFactory();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
// Create the subscribe options including several topics with different options.
// It is also possible to all of these topics using a dedicated call of _SubscribeAsync_ per topic.
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder()
.WithTopicFilter(
f =>
{
f.WithTopic("mqttnet/samples/topic/1");
})
.WithTopicFilter(
f =>
{
f.WithTopic("mqttnet/samples/topic/2").WithNoLocal();
})
.WithTopicFilter(
f =>
{
f.WithTopic("mqttnet/samples/topic/3").WithRetainHandling(MqttRetainHandling.SendAtSubscribe);
})
.Build();
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
Console.WriteLine("MQTT client subscribed to topics.");
// The response contains additional data sent by the server after subscribing.
response.DumpToConsole();
}
}
public static async Task Subscribe_Topic() public static async Task Subscribe_Topic()
{ {
/* /*
@@ -156,16 +63,14 @@ public static class Client_Subscribe_Samples
using (var mqttClient = mqttFactory.CreateMqttClient()) using (var mqttClient = mqttFactory.CreateMqttClient())
{ {
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build(); var mqttClientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("broker.hivemq.com")
.Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder() var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder()
.WithTopicFilter( .WithTopicFilter(f => { f.WithTopic("mqttnet/samples/topic/1"); })
f =>
{
f.WithTopic("mqttnet/samples/topic/1");
})
.Build(); .Build();
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None); var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
+25 -11
View File
@@ -8,6 +8,7 @@
using System.Text; using System.Text;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
using MQTTnet.Samples.Shared;
namespace MQTTnet.Samples.Diagnostics; namespace MQTTnet.Samples.Diagnostics;
@@ -22,9 +23,7 @@ public static class Logger_Samples
var mqttFactory = new MqttFactory(new MyLogger()); var mqttFactory = new MqttFactory(new MyLogger());
var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder() var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
.WithTcpServer("broker.hivemq.com")
.Build();
using (var mqttClient = mqttFactory.CreateMqttClient()) using (var mqttClient = mqttFactory.CreateMqttClient())
{ {
@@ -32,13 +31,31 @@ public static class Logger_Samples
Console.WriteLine("MQTT client is connected."); Console.WriteLine("MQTT client is connected.");
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder() var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder().Build();
.Build();
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None); await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
} }
} }
public static async Task Log_To_Console()
{
/*
* This sample covers the creation of a custom logger which is used to log to the console.
*/
var mqttFactory = new MqttFactory(new ConsoleLogger());
var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
Console.WriteLine("MQTT client is connected. Press any key to exit.");
Console.ReadKey(true);
}
}
public static async Task Use_Event_Logger() public static async Task Use_Event_Logger()
{ {
/* /*
@@ -64,9 +81,7 @@ public static class Logger_Samples
var mqttFactory = new MqttFactory(mqttEventLogger); var mqttFactory = new MqttFactory(mqttEventLogger);
var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder() var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
.WithTcpServer("broker.hivemq.com")
.Build();
using (var mqttClient = mqttFactory.CreateMqttClient()) using (var mqttClient = mqttFactory.CreateMqttClient())
{ {
@@ -74,8 +89,7 @@ public static class Logger_Samples
Console.WriteLine("MQTT client is connected."); Console.WriteLine("MQTT client is connected.");
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder() var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder().Build();
.Build();
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None); await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
} }
@@ -83,7 +97,7 @@ public static class Logger_Samples
sealed class MyLogger : IMqttNetLogger sealed class MyLogger : IMqttNetLogger
{ {
public bool IsEnabled { get; set; } = true; public bool IsEnabled { get; } = true;
public void Publish(MqttNetLogLevel logLevel, string source, string message, object[] parameters, Exception exception) public void Publish(MqttNetLogLevel logLevel, string source, string message, object[] parameters, Exception exception)
{ {
+1
View File
@@ -8,6 +8,7 @@ Console.WriteLine("Welcome to MQTTnet samples!");
Console.WriteLine(); Console.WriteLine();
var sampleClasses = Assembly.GetExecutingAssembly().GetExportedTypes().OrderBy(c => c.Name).ToList(); var sampleClasses = Assembly.GetExecutingAssembly().GetExportedTypes().OrderBy(c => c.Name).ToList();
sampleClasses.RemoveAll(c => c.Namespace.Contains(".Shared"));
var index = 0; var index = 0;
foreach (var sampleClass in sampleClasses) foreach (var sampleClass in sampleClasses)
+1 -47
View File
@@ -6,8 +6,8 @@
// ReSharper disable UnusedMember.Global // ReSharper disable UnusedMember.Global
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
using MQTTnet.Diagnostics;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Samples.Shared;
using MQTTnet.Server; using MQTTnet.Server;
namespace MQTTnet.Samples.Server; namespace MQTTnet.Samples.Server;
@@ -170,50 +170,4 @@ public static class Server_Simple_Samples
await server.StartAsync(); await server.StartAsync();
return server; return server;
} }
class ConsoleLogger : IMqttNetLogger
{
readonly object _consoleSyncRoot = new();
public bool IsEnabled => true;
public void Publish(MqttNetLogLevel logLevel, string source, string message, object[]? parameters, Exception? exception)
{
var foregroundColor = ConsoleColor.White;
switch (logLevel)
{
case MqttNetLogLevel.Verbose:
foregroundColor = ConsoleColor.White;
break;
case MqttNetLogLevel.Info:
foregroundColor = ConsoleColor.Green;
break;
case MqttNetLogLevel.Warning:
foregroundColor = ConsoleColor.DarkYellow;
break;
case MqttNetLogLevel.Error:
foregroundColor = ConsoleColor.Red;
break;
}
if (parameters?.Length > 0)
{
message = string.Format(message, parameters);
}
lock (_consoleSyncRoot)
{
Console.ForegroundColor = foregroundColor;
Console.WriteLine(message);
if (exception != null)
{
Console.WriteLine(exception);
}
}
}
}
} }
+53
View File
@@ -0,0 +1,53 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using MQTTnet.Diagnostics;
namespace MQTTnet.Samples.Shared;
public sealed class ConsoleLogger : IMqttNetLogger
{
readonly object _consoleSyncRoot = new();
public bool IsEnabled => true;
public void Publish(MqttNetLogLevel logLevel, string source, string message, object[]? parameters, Exception? exception)
{
var foregroundColor = ConsoleColor.White;
switch (logLevel)
{
case MqttNetLogLevel.Verbose:
foregroundColor = ConsoleColor.White;
break;
case MqttNetLogLevel.Info:
foregroundColor = ConsoleColor.Green;
break;
case MqttNetLogLevel.Warning:
foregroundColor = ConsoleColor.DarkYellow;
break;
case MqttNetLogLevel.Error:
foregroundColor = ConsoleColor.Red;
break;
}
if (parameters?.Length > 0)
{
message = string.Format(message, parameters);
}
lock (_consoleSyncRoot)
{
Console.ForegroundColor = foregroundColor;
Console.WriteLine(message);
if (exception != null)
{
Console.WriteLine(exception);
}
}
}
}
@@ -4,7 +4,7 @@
using System.Text.Json; using System.Text.Json;
namespace MQTTnet.Samples.Helpers; namespace MQTTnet.Samples.Shared;
internal static class ObjectExtensions internal static class ObjectExtensions
{ {
@@ -171,7 +171,7 @@ namespace MQTTnet.AspNetCore
{ {
var formatter = PacketFormatterAdapter; var formatter = PacketFormatterAdapter;
using (await _writerLock.EnterAsync(cancellationToken).ConfigureAwait(false)) using (await _writerLock.WaitAsync(cancellationToken).ConfigureAwait(false))
{ {
var buffer = formatter.Encode(packet); var buffer = formatter.Encode(packet);
var msg = buffer.Join().AsMemory(); var msg = buffer.Join().AsMemory();
@@ -1,66 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using MQTTnet.Internal;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
public class AsyncLockBenchmark : BaseBenchmark
{
[Benchmark]
public async Task Synchronize_100_Tasks()
{
const int tasksCount = 100;
var tasks = new Task[tasksCount];
var asyncLock = new AsyncLock();
var globalI = 0;
for (var i = 0; i < tasksCount; i++)
{
tasks[i] = Task.Run(
async () =>
{
using (await asyncLock.EnterAsync().ConfigureAwait(false))
{
var localI = globalI;
await Task.Delay(5).ConfigureAwait(false); // Increase the chance for wrong data.
localI++;
globalI = localI;
}
});
}
await Task.WhenAll(tasks).ConfigureAwait(false);
if (globalI != tasksCount)
{
throw new Exception($"Code is broken ({globalI})!");
}
}
[Benchmark]
public async Task Wait_100_000_Times()
{
var asyncLock = new AsyncLock();
using (var cancellationToken = new CancellationTokenSource())
{
for (var i = 0; i < 100000; i++)
{
using (await asyncLock.EnterAsync(cancellationToken.Token).ConfigureAwait(false))
{
}
}
}
}
}
}
@@ -12,10 +12,14 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.2" /> <PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'net5.0'">
<PackageReference Include="System.IO.Pipelines" Version="4.7.2" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net5.0'"> <ItemGroup Condition="'$(TargetFramework)' == 'net5.0'">
<PackageReference Include="System.IO.Pipelines" Version="5.0.0" /> <PackageReference Include="System.IO.Pipelines" Version="5.0.0" />
</ItemGroup> </ItemGroup>
@@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using MQTTnet.Channel; using MQTTnet.Channel;
using MQTTnet.Implementations; using MQTTnet.Implementations;
@@ -71,8 +70,8 @@ namespace MQTTnet.Benchmarks
while (read < expected) while (read < expected)
{ {
var readResult = await _clientChannel.ReadAsync(new byte[size], 0, size, CancellationToken.None).ConfigureAwait(false); var readresult = await _clientChannel.ReadAsync(new byte[size], 0, size, CancellationToken.None).ConfigureAwait(false);
read += readResult; read += readresult;
} }
} }
@@ -80,11 +79,9 @@ namespace MQTTnet.Benchmarks
{ {
await Task.Yield(); await Task.Yield();
var buffer = new ArraySegment<byte>(new byte[size]);
for (var i = 0; i < iterations; i++) for (var i = 0; i < iterations; i++)
{ {
await _serverChannel.WriteAsync(buffer, true, CancellationToken.None).ConfigureAwait(false); await _serverChannel.WriteAsync(new byte[size], 0, size, CancellationToken.None).ConfigureAwait(false);
} }
} }
} }
-4
View File
@@ -29,7 +29,6 @@ namespace MQTTnet.Benchmarks
Console.WriteLine("c = SubscribeBenchmark"); Console.WriteLine("c = SubscribeBenchmark");
Console.WriteLine("d = UnsubscribeBenchmark"); Console.WriteLine("d = UnsubscribeBenchmark");
Console.WriteLine("e = MessageDeliveryBenchmark"); Console.WriteLine("e = MessageDeliveryBenchmark");
Console.WriteLine("f = AsyncLockBenchmark");
var pressedKey = Console.ReadKey(true); var pressedKey = Console.ReadKey(true);
switch (pressedKey.KeyChar) switch (pressedKey.KeyChar)
@@ -76,9 +75,6 @@ namespace MQTTnet.Benchmarks
case 'e': case 'e':
BenchmarkRunner.Run<MessageDeliveryBenchmark>(); BenchmarkRunner.Run<MessageDeliveryBenchmark>();
break; break;
case 'f':
BenchmarkRunner.Run<AsyncLockBenchmark>();
break;
} }
Console.ReadLine(); Console.ReadLine();
@@ -104,7 +104,7 @@ namespace MQTTnet.Benchmarks
return Task.FromResult(count); return Task.FromResult(count);
} }
public Task WriteAsync(ArraySegment<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken) public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{ {
throw new NotSupportedException(); throw new NotSupportedException();
} }
@@ -167,7 +167,7 @@ namespace MQTTnet.Extensions.ManagedClient
try try
{ {
using (await _messageQueueLock.EnterAsync().ConfigureAwait(false)) using (await _messageQueueLock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
{ {
if (_messageQueue.Count >= Options.MaxPendingMessages) if (_messageQueue.Count >= Options.MaxPendingMessages)
{ {
@@ -296,7 +296,7 @@ namespace MQTTnet.Extensions.ManagedClient
_subscriptionsQueuedSignal.Release(); _subscriptionsQueuedSignal.Release();
return CompletedTask.Instance; return Task.FromResult(0);
} }
public Task UnsubscribeAsync(ICollection<string> topics) public Task UnsubscribeAsync(ICollection<string> topics)
@@ -319,7 +319,7 @@ namespace MQTTnet.Extensions.ManagedClient
_subscriptionsQueuedSignal.Release(); _subscriptionsQueuedSignal.Release();
return CompletedTask.Instance; return Task.FromResult(0);
} }
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
@@ -693,7 +693,7 @@ namespace MQTTnet.Extensions.ManagedClient
{ {
await InternalClient.PublishAsync(message.ApplicationMessage).ConfigureAwait(false); await InternalClient.PublishAsync(message.ApplicationMessage).ConfigureAwait(false);
using (await _messageQueueLock.EnterAsync().ConfigureAwait(false)) //lock to avoid conflict with this.PublishAsync using (await _messageQueueLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) //lock to avoid conflict with this.PublishAsync
{ {
// While publishing this message, this.PublishAsync could have booted this // While publishing this message, this.PublishAsync could have booted this
// message off the queue to make room for another (when using a cap // message off the queue to make room for another (when using a cap
@@ -724,7 +724,7 @@ namespace MQTTnet.Extensions.ManagedClient
//contradict the expected behavior of QoS 1 and 2, that's also true //contradict the expected behavior of QoS 1 and 2, that's also true
//for the usage of a message queue cap, so it's still consistent //for the usage of a message queue cap, so it's still consistent
//with prior behavior in that way. //with prior behavior in that way.
using (await _messageQueueLock.EnterAsync().ConfigureAwait(false)) //lock to avoid conflict with this.PublishAsync using (await _messageQueueLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) //lock to avoid conflict with this.PublishAsync
{ {
_messageQueue.RemoveFirst(i => i.Id.Equals(message.Id)); _messageQueue.RemoveFirst(i => i.Id.Equals(message.Id));
@@ -34,7 +34,7 @@ namespace MQTTnet.Extensions.ManagedClient
{ {
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
using (await _messagesLock.EnterAsync().ConfigureAwait(false)) using (await _messagesLock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
{ {
_messages.Add(applicationMessage); _messages.Add(applicationMessage);
await SaveAsync().ConfigureAwait(false); await SaveAsync().ConfigureAwait(false);
@@ -45,7 +45,7 @@ namespace MQTTnet.Extensions.ManagedClient
{ {
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
using (await _messagesLock.EnterAsync().ConfigureAwait(false)) using (await _messagesLock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
{ {
var index = _messages.IndexOf(applicationMessage); var index = _messages.IndexOf(applicationMessage);
if (index == -1) if (index == -1)
@@ -9,7 +9,7 @@ using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Internal; using MQTTnet.Implementations;
namespace MQTTnet.Extensions.Rpc namespace MQTTnet.Extensions.Rpc
{ {
@@ -116,7 +116,7 @@ namespace MQTTnet.Extensions.Rpc
{ {
if (!_waitingCalls.TryRemove(eventArgs.ApplicationMessage.Topic, out var awaitable)) if (!_waitingCalls.TryRemove(eventArgs.ApplicationMessage.Topic, out var awaitable))
{ {
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
} }
#if NET452 #if NET452
@@ -128,7 +128,7 @@ namespace MQTTnet.Extensions.Rpc
// Set this message to handled to that other code can avoid execution etc. // Set this message to handled to that other code can avoid execution etc.
eventArgs.IsHandled = true; eventArgs.IsHandled = true;
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
} }
public void Dispose() public void Dispose()
@@ -14,7 +14,6 @@ using System.Threading.Tasks;
using MQTTnet.Channel; using MQTTnet.Channel;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
using MQTTnet.Internal;
using SuperSocket.ClientEngine; using SuperSocket.ClientEngine;
using WebSocket4Net; using WebSocket4Net;
@@ -125,7 +124,7 @@ namespace MQTTnet.Extensions.WebSocket4Net
_webSocket.Close(); _webSocket.Close();
} }
return CompletedTask.Instance; return Task.FromResult(0);
} }
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
@@ -155,10 +154,10 @@ namespace MQTTnet.Extensions.WebSocket4Net
return Task.FromResult(readBytes); return Task.FromResult(readBytes);
} }
public Task WriteAsync(ArraySegment<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken) public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{ {
_webSocket.Send(buffer.Array, buffer.Offset, buffer.Count); _webSocket.Send(buffer, offset, count);
return CompletedTask.Instance; return Task.FromResult(0);
} }
public void Dispose() public void Dispose()
-28
View File
@@ -1,28 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Internal;
namespace MQTTnet.TestApp
{
public sealed class AsyncLockTest
{
public async Task Run()
{
var asyncLock = new AsyncLock();
using (var cancellationToken = new CancellationTokenSource())
{
for (var i = 0; i < 100000; i++)
{
using (await asyncLock.EnterAsync(cancellationToken.Token).ConfigureAwait(false))
{
}
}
}
}
}
}
+2 -2
View File
@@ -7,7 +7,7 @@ using System;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
using MQTTnet.Internal; using MQTTnet.Implementations;
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.TestApp namespace MQTTnet.TestApp
@@ -40,7 +40,7 @@ namespace MQTTnet.TestApp
Console.WriteLine($"+ Retain = {e.ApplicationMessage.Retain}"); Console.WriteLine($"+ Retain = {e.ApplicationMessage.Retain}");
Console.WriteLine(); Console.WriteLine();
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
client.ConnectedAsync += async e => client.ConnectedAsync += async e =>
+3 -3
View File
@@ -9,7 +9,7 @@ using Newtonsoft.Json;
using System.Collections.Generic; using System.Collections.Generic;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Extensions.ManagedClient; using MQTTnet.Extensions.ManagedClient;
using MQTTnet.Internal; using MQTTnet.Implementations;
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.TestApp namespace MQTTnet.TestApp
@@ -42,7 +42,7 @@ namespace MQTTnet.TestApp
managedClient.ApplicationMessageReceivedAsync += e => managedClient.ApplicationMessageReceivedAsync += e =>
{ {
Console.WriteLine(">> RECEIVED: " + e.ApplicationMessage.Topic); Console.WriteLine(">> RECEIVED: " + e.ApplicationMessage.Topic);
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await managedClient.StartAsync(options); await managedClient.StartAsync(options);
@@ -85,7 +85,7 @@ namespace MQTTnet.TestApp
public Task SaveQueuedMessagesAsync(IList<ManagedMqttApplicationMessage> messages) public Task SaveQueuedMessagesAsync(IList<ManagedMqttApplicationMessage> messages)
{ {
File.WriteAllText(Filename, JsonConvert.SerializeObject(messages)); File.WriteAllText(Filename, JsonConvert.SerializeObject(messages));
return CompletedTask.Instance; return Task.FromResult(0);
} }
public Task<IList<ManagedMqttApplicationMessage>> LoadQueuedMessagesAsync() public Task<IList<ManagedMqttApplicationMessage>> LoadQueuedMessagesAsync()
@@ -5,7 +5,6 @@ using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Linq; using System.Linq;
using MQTTnet.Internal;
namespace MQTTnet.TestApp namespace MQTTnet.TestApp
{ {
@@ -125,7 +124,7 @@ namespace MQTTnet.TestApp
{ {
_cancellationTokenSource.Cancel(); _cancellationTokenSource.Cancel();
} }
return CompletedTask.Instance; return Implementations.PlatformAbstractionLayer.CompletedTask;
} }
public async Task Cleanup() public async Task Cleanup()
-5
View File
@@ -28,7 +28,6 @@ namespace MQTTnet.TestApp
Console.WriteLine("c = Start QoS 0 benchmark"); Console.WriteLine("c = Start QoS 0 benchmark");
Console.WriteLine("d = Start server with logging"); Console.WriteLine("d = Start server with logging");
Console.WriteLine("e = Start Message Throughput Test"); Console.WriteLine("e = Start Message Throughput Test");
Console.WriteLine("f = Start AsyncLock Test");
var pressedKey = Console.ReadKey(true); var pressedKey = Console.ReadKey(true);
if (pressedKey.KeyChar == '1') if (pressedKey.KeyChar == '1')
@@ -89,10 +88,6 @@ namespace MQTTnet.TestApp
{ {
Task.Run(new MessageThroughputTest().Run); Task.Run(new MessageThroughputTest().Run);
} }
else if (pressedKey.KeyChar == 'f')
{
Task.Run(new AsyncLockTest().Run);
}
Thread.Sleep(Timeout.Infinite); Thread.Sleep(Timeout.Infinite);
} }
+2 -2
View File
@@ -8,7 +8,7 @@ using System.Security.Authentication;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Internal; using MQTTnet.Implementations;
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.TestApp namespace MQTTnet.TestApp
@@ -144,7 +144,7 @@ namespace MQTTnet.TestApp
client.ApplicationMessageReceivedAsync += e => client.ApplicationMessageReceivedAsync += e =>
{ {
receivedMessage = e.ApplicationMessage; receivedMessage = e.ApplicationMessage;
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await client.ConnectAsync(options); await client.ConnectAsync(options);
+11 -9
View File
@@ -4,11 +4,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
using MQTTnet.Internal; using MQTTnet.Extensions.ManagedClient;
using MQTTnet.Implementations;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Server; using MQTTnet.Server;
using Newtonsoft.Json; using Newtonsoft.Json;
@@ -67,13 +69,13 @@ namespace MQTTnet.TestApp
} }
File.WriteAllText(Filename, JsonConvert.SerializeObject(e.StoredRetainedMessages)); File.WriteAllText(Filename, JsonConvert.SerializeObject(e.StoredRetainedMessages));
return CompletedTask.Instance; return Task.FromResult(0);
}; };
mqttServer.RetainedMessagesClearedAsync += e => mqttServer.RetainedMessagesClearedAsync += e =>
{ {
File.Delete(Filename); File.Delete(Filename);
return CompletedTask.Instance; return Task.FromResult(0);
}; };
mqttServer.LoadingRetainedMessageAsync += e => mqttServer.LoadingRetainedMessageAsync += e =>
@@ -91,7 +93,7 @@ namespace MQTTnet.TestApp
e.LoadedRetainedMessages = retainedMessages; e.LoadedRetainedMessages = retainedMessages;
return CompletedTask.Instance; return Task.FromResult(0);
}; };
mqttServer.InterceptingPublishAsync += e => mqttServer.InterceptingPublishAsync += e =>
@@ -109,7 +111,7 @@ namespace MQTTnet.TestApp
e.CloseConnection = true; e.CloseConnection = true;
} }
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
mqttServer.ValidatingConnectionAsync += e => mqttServer.ValidatingConnectionAsync += e =>
@@ -122,7 +124,7 @@ namespace MQTTnet.TestApp
} }
} }
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
mqttServer.InterceptingSubscriptionAsync += e => mqttServer.InterceptingSubscriptionAsync += e =>
@@ -138,7 +140,7 @@ namespace MQTTnet.TestApp
e.CloseConnection = true; e.CloseConnection = true;
} }
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
mqttServer.InterceptingPublishAsync += e => mqttServer.InterceptingPublishAsync += e =>
@@ -147,7 +149,7 @@ namespace MQTTnet.TestApp
$"'{e.ClientId}' reported '{e.ApplicationMessage.Topic}' > '{Encoding.UTF8.GetString(e.ApplicationMessage.Payload ?? new byte[0])}'", $"'{e.ClientId}' reported '{e.ApplicationMessage.Topic}' > '{Encoding.UTF8.GetString(e.ApplicationMessage.Payload ?? new byte[0])}'",
ConsoleColor.Magenta); ConsoleColor.Magenta);
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
//options.ApplicationMessageInterceptor = c => //options.ApplicationMessageInterceptor = c =>
@@ -175,7 +177,7 @@ namespace MQTTnet.TestApp
mqttServer.ClientConnectedAsync += e => mqttServer.ClientConnectedAsync += e =>
{ {
Console.Write("Client disconnected event fired."); Console.Write("Client disconnected event fired.");
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await mqttServer.StartAsync(); await mqttServer.StartAsync();
@@ -11,10 +11,12 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
using MQTTnet.Extensions.ManagedClient; using MQTTnet.Extensions.ManagedClient;
using MQTTnet.Implementations;
using MQTTnet.Internal; using MQTTnet.Internal;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Server; using MQTTnet.Server;
using MQTTnet.Tests.Mockups; using MQTTnet.Tests.Mockups;
using MqttClient = MQTTnet.Client.MqttClient;
namespace MQTTnet.Tests.Client namespace MQTTnet.Tests.Client
{ {
@@ -37,7 +39,7 @@ namespace MQTTnet.Tests.Client
managedClient.ConnectingFailedAsync += args => managedClient.ConnectingFailedAsync += args =>
{ {
connectingFailedEventArgs = args; connectingFailedEventArgs = args;
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await managedClient.StartAsync( await managedClient.StartAsync(
@@ -115,7 +117,7 @@ namespace MQTTnet.Tests.Client
receivingClient.ApplicationMessageReceivedAsync += e => receivingClient.ApplicationMessageReceivedAsync += e =>
{ {
Interlocked.Increment(ref receivedMessagesCount); Interlocked.Increment(ref receivedMessagesCount);
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await receivingClient.SubscribeAsync("My/last/will"); await receivingClient.SubscribeAsync("My/last/will");
@@ -279,7 +281,7 @@ namespace MQTTnet.Tests.Client
}); });
// Wait a bit for the retained message to be available // Wait a bit for the retained message to be available
await LongTestDelay(); await Task.Delay(1000);
await sendingClient.DisconnectAsync(); await sendingClient.DisconnectAsync();
@@ -288,29 +290,28 @@ namespace MQTTnet.Tests.Client
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort); var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort);
var receivedManagedMessages = new List<MqttApplicationMessage>(); var receivedManagedMessages = new List<MqttApplicationMessage>();
var managedClient = new ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger());
var managedClient = testEnvironment.Factory.CreateManagedMqttClient(testEnvironment.CreateClient());
managedClient.ApplicationMessageReceivedAsync += e => managedClient.ApplicationMessageReceivedAsync += e =>
{ {
receivedManagedMessages.Add(e.ApplicationMessage); receivedManagedMessages.Add(e.ApplicationMessage);
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await managedClient.SubscribeAsync("topic"); await managedClient.SubscribeAsync("topic");
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(1)).Build()); await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(1)).Build());
await LongTestDelay(); await Task.Delay(1000);
Assert.AreEqual(1, receivedManagedMessages.Count); Assert.AreEqual(1, receivedManagedMessages.Count);
await managedClient.StopAsync(); await managedClient.StopAsync();
await LongTestDelay(); await Task.Delay(500);
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(1)).Build()); await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(1)).Build());
await LongTestDelay(); await Task.Delay(1000);
// After reconnect and then some delay, the retained message must not be received, // After reconnect and then some delay, the retained message must not be received,
// showing that the subscriptions were cleared // showing that the subscriptions were cleared
@@ -318,9 +319,7 @@ namespace MQTTnet.Tests.Client
// Make sure that it gets received after subscribing again. // Make sure that it gets received after subscribing again.
await managedClient.SubscribeAsync("topic"); await managedClient.SubscribeAsync("topic");
await Task.Delay(500);
await LongTestDelay();
Assert.AreEqual(2, receivedManagedMessages.Count); Assert.AreEqual(2, receivedManagedMessages.Count);
} }
} }
@@ -429,7 +428,7 @@ namespace MQTTnet.Tests.Client
managedClient.ConnectedAsync += e => managedClient.ConnectedAsync += e =>
{ {
connected.TrySetResult(true); connected.TrySetResult(true);
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
return connected.Task; return connected.Task;
@@ -458,7 +457,7 @@ namespace MQTTnet.Tests.Client
result.TrySetResult(receivedMessages); result.TrySetResult(receivedMessages);
} }
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
return result.Task; return result.Task;
@@ -487,7 +486,7 @@ namespace MQTTnet.Tests.Client
public Task SaveQueuedMessagesAsync(IList<ManagedMqttApplicationMessage> messages) public Task SaveQueuedMessagesAsync(IList<ManagedMqttApplicationMessage> messages)
{ {
_messages = messages; _messages = messages;
return CompletedTask.Instance; return Task.FromResult(0);
} }
} }
} }
+36 -41
View File
@@ -14,17 +14,15 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Internal; using MQTTnet.Implementations;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Tests.Mockups; using MQTTnet.Tests.Mockups;
// ReSharper disable InconsistentNaming
namespace MQTTnet.Tests.Client namespace MQTTnet.Tests.Client
{ {
[TestClass] [TestClass]
public sealed class MqttClient_Tests : BaseTestClass public sealed class Client_Tests : BaseTestClass
{ {
[DataTestMethod] [DataTestMethod]
[DataRow(MqttQualityOfServiceLevel.ExactlyOnce)] [DataRow(MqttQualityOfServiceLevel.ExactlyOnce)]
@@ -58,7 +56,7 @@ namespace MQTTnet.Tests.Client
} }
_ = InvokeInternal(); _ = InvokeInternal();
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
var publishes = Task.WhenAll(publisher.PublishStringAsync("a", null, qos), publisher.PublishStringAsync("b", null, qos)); var publishes = Task.WhenAll(publisher.PublishStringAsync("a", null, qos), publisher.PublishStringAsync("b", null, qos));
@@ -109,7 +107,7 @@ namespace MQTTnet.Tests.Client
client.DisconnectedAsync += args => client.DisconnectedAsync += args =>
{ {
disconnectHandlerCalled = true; disconnectHandlerCalled = true;
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build()); await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build());
@@ -123,7 +121,7 @@ namespace MQTTnet.Tests.Client
//Assert.IsInstanceOfType(exception.InnerException, typeof(SocketException)); //Assert.IsInstanceOfType(exception.InnerException, typeof(SocketException));
} }
await LongTestDelay(); // disconnected handler is called async await Task.Delay(100); // disconnected handler is called async
Assert.IsTrue(disconnectHandlerCalled); Assert.IsTrue(disconnectHandlerCalled);
} }
} }
@@ -138,7 +136,7 @@ namespace MQTTnet.Tests.Client
client.DisconnectedAsync += e => client.DisconnectedAsync += e =>
{ {
ex = e.Exception; ex = e.Exception;
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
try try
@@ -147,7 +145,6 @@ namespace MQTTnet.Tests.Client
} }
catch catch
{ {
// Ignore errors.
} }
await Task.Delay(500); await Task.Delay(500);
@@ -170,7 +167,7 @@ namespace MQTTnet.Tests.Client
server.InterceptingPublishAsync += c => server.InterceptingPublishAsync += c =>
{ {
i++; i++;
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await client.SendAsync( await client.SendAsync(
@@ -211,7 +208,7 @@ namespace MQTTnet.Tests.Client
client.DisconnectedAsync += e => client.DisconnectedAsync += e =>
{ {
handlerFired = true; handlerFired = true;
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await server.StopAsync(); await server.StopAsync();
@@ -242,10 +239,10 @@ namespace MQTTnet.Tests.Client
for (var i = 0; i < 98; i++) for (var i = 0; i < 98; i++)
{ {
Assert.IsFalse(clients[i].TryPingAsync().GetAwaiter().GetResult(), $"clients[{i}] is not connected"); Assert.IsFalse(clients[i].IsConnected, $"clients[{i}] is not connected");
} }
Assert.IsTrue(clients[99].TryPingAsync().GetAwaiter().GetResult()); Assert.IsTrue(clients[99].IsConnected);
Assert.AreEqual(1, clientStatus.Count); Assert.AreEqual(1, clientStatus.Count);
Assert.AreEqual(1, sessionStatus.Count); Assert.AreEqual(1, sessionStatus.Count);
@@ -255,7 +252,7 @@ namespace MQTTnet.Tests.Client
receiveClient.ApplicationMessageReceivedAsync += e => receiveClient.ApplicationMessageReceivedAsync += e =>
{ {
receivedPayload = e.ApplicationMessage.ConvertPayloadToString(); receivedPayload = e.ApplicationMessage.ConvertPayloadToString();
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await receiveClient.SubscribeAsync("x"); await receiveClient.SubscribeAsync("x");
@@ -313,7 +310,7 @@ namespace MQTTnet.Tests.Client
receiver.ApplicationMessageReceivedAsync += e => receiver.ApplicationMessageReceivedAsync += e =>
{ {
receivedMessage = e.ApplicationMessage; receivedMessage = e.ApplicationMessage;
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await sender.PublishAsync(message.Build(), CancellationToken.None); await sender.PublishAsync(message.Build(), CancellationToken.None);
@@ -335,7 +332,7 @@ namespace MQTTnet.Tests.Client
var client = await testEnvironment.ConnectClient(); var client = await testEnvironment.ConnectClient();
Assert.IsTrue(client.TryPingAsync().GetAwaiter().GetResult()); Assert.IsTrue(client.IsConnected);
} }
} }
@@ -346,11 +343,11 @@ namespace MQTTnet.Tests.Client
{ {
await testEnvironment.StartServer(); await testEnvironment.StartServer();
var client = await testEnvironment.ConnectClient(); var client = await testEnvironment.ConnectClient();
Assert.IsTrue(client.TryPingAsync().GetAwaiter().GetResult()); Assert.IsTrue(client.IsConnected);
await client.DisconnectAsync(); await client.DisconnectAsync();
Assert.IsFalse(client.TryPingAsync().GetAwaiter().GetResult()); Assert.IsFalse(client.IsConnected);
} }
} }
@@ -456,7 +453,7 @@ namespace MQTTnet.Tests.Client
receivedValues.Add(value); receivedValues.Add(value);
} }
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
} }
client1.ApplicationMessageReceivedAsync += Handler1; client1.ApplicationMessageReceivedAsync += Handler1;
@@ -534,7 +531,7 @@ namespace MQTTnet.Tests.Client
client2.ApplicationMessageReceivedAsync += e => client2.ApplicationMessageReceivedAsync += e =>
{ {
client2TopicResults.Add(Encoding.UTF8.GetString(e.ApplicationMessage.Payload)); client2TopicResults.Add(Encoding.UTF8.GetString(e.ApplicationMessage.Payload));
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await client2.SubscribeAsync(client2Topic); await client2.SubscribeAsync(client2Topic);
@@ -569,7 +566,7 @@ namespace MQTTnet.Tests.Client
receivedMessages.Add(e.ApplicationMessage); receivedMessages.Add(e.ApplicationMessage);
} }
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await client1.SubscribeAsync("a"); await client1.SubscribeAsync("a");
@@ -594,17 +591,17 @@ namespace MQTTnet.Tests.Client
var client = await testEnvironment.ConnectClient(); var client = await testEnvironment.ConnectClient();
await Task.Delay(500); await Task.Delay(500);
Assert.IsTrue(await client.TryPingAsync()); Assert.IsTrue(client.IsConnected);
await server.StopAsync(); await server.StopAsync();
await Task.Delay(500); await Task.Delay(500);
Assert.IsFalse(await client.TryPingAsync()); Assert.IsFalse(client.IsConnected);
await server.StartAsync(); await server.StartAsync();
await Task.Delay(500); await Task.Delay(500);
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build()); await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build());
Assert.IsTrue(await client.TryPingAsync()); Assert.IsTrue(client.IsConnected);
} }
} }
@@ -640,7 +637,6 @@ namespace MQTTnet.Tests.Client
} }
catch catch
{ {
// Ignore errors.
} }
SpinWait.SpinUntil(() => tries >= maxTries, 10000); SpinWait.SpinUntil(() => tries >= maxTries, 10000);
@@ -660,11 +656,11 @@ namespace MQTTnet.Tests.Client
var client = await testEnvironment.ConnectClient(); var client = await testEnvironment.ConnectClient();
await Task.Delay(500); await Task.Delay(500);
Assert.IsTrue(client.TryPingAsync().GetAwaiter().GetResult()); Assert.IsTrue(client.IsConnected);
await server.StopAsync(); await server.StopAsync();
await Task.Delay(500); await Task.Delay(500);
Assert.IsFalse(client.TryPingAsync().GetAwaiter().GetResult()); Assert.IsFalse(client.IsConnected);
for (var i = 0; i < 5; i++) for (var i = 0; i < 5; i++)
{ {
@@ -675,7 +671,6 @@ namespace MQTTnet.Tests.Client
} }
catch catch
{ {
// Ignore errors.
} }
} }
@@ -683,7 +678,7 @@ namespace MQTTnet.Tests.Client
await Task.Delay(500); await Task.Delay(500);
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build()); await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build());
await client.PingAsync(); Assert.IsTrue(client.IsConnected);
} }
} }
@@ -728,7 +723,7 @@ namespace MQTTnet.Tests.Client
replies.Add(eventArgs.ApplicationMessage.Topic); replies.Add(eventArgs.ApplicationMessage.Topic);
} }
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
} }
client2.ApplicationMessageReceivedAsync += Handler2; client2.ApplicationMessageReceivedAsync += Handler2;
@@ -766,7 +761,7 @@ namespace MQTTnet.Tests.Client
replyReceived = true; replyReceived = true;
} }
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
client2.ApplicationMessageReceivedAsync += async e => client2.ApplicationMessageReceivedAsync += async e =>
@@ -817,7 +812,7 @@ namespace MQTTnet.Tests.Client
replyReceived = true; replyReceived = true;
} }
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await client.PublishStringAsync("request", null, MqttQualityOfServiceLevel.AtLeastOnce); await client.PublishStringAsync("request", null, MqttQualityOfServiceLevel.AtLeastOnce);
@@ -833,14 +828,14 @@ namespace MQTTnet.Tests.Client
{ {
using (var testEnvironment = new TestEnvironment(TestContext)) using (var testEnvironment = new TestEnvironment(TestContext))
{ {
await testEnvironment.StartServer(); var server = await testEnvironment.StartServer();
var client = await testEnvironment.ConnectClient(); var client = await testEnvironment.ConnectClient();
Assert.IsTrue(client.TryPingAsync().GetAwaiter().GetResult()); Assert.IsTrue(client.IsConnected);
client.DisconnectedAsync += e => client.DisconnectedAsync += e =>
{ {
Assert.IsTrue(e.ClientWasConnected); Assert.IsTrue(e.ClientWasConnected);
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await client.DisconnectAsync(); await client.DisconnectAsync();
@@ -856,11 +851,11 @@ namespace MQTTnet.Tests.Client
var server = await testEnvironment.StartServer(); var server = await testEnvironment.StartServer();
var client = await testEnvironment.ConnectClient(); var client = await testEnvironment.ConnectClient();
Assert.IsTrue(client.TryPingAsync().GetAwaiter().GetResult()); Assert.IsTrue(client.IsConnected);
client.DisconnectedAsync += e => client.DisconnectedAsync += e =>
{ {
Assert.IsTrue(e.ClientWasConnected); Assert.IsTrue(e.ClientWasConnected);
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await server.StopAsync(); await server.StopAsync();
@@ -897,7 +892,7 @@ namespace MQTTnet.Tests.Client
receivedMessages.Add(e.ApplicationMessage); receivedMessages.Add(e.ApplicationMessage);
} }
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort).Build()); await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort).Build());
@@ -922,14 +917,14 @@ namespace MQTTnet.Tests.Client
client1.DisconnectedAsync += c => client1.DisconnectedAsync += c =>
{ {
disconnectedFired = true; disconnectedFired = true;
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
var messageReceived = false; var messageReceived = false;
client1.ApplicationMessageReceivedAsync += e => client1.ApplicationMessageReceivedAsync += e =>
{ {
messageReceived = true; messageReceived = true;
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await client1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("topic1").WithExactlyOnceQoS().Build()); await client1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("topic1").WithExactlyOnceQoS().Build());
@@ -946,7 +941,7 @@ namespace MQTTnet.Tests.Client
await Task.Delay(500); await Task.Delay(500);
Assert.IsTrue(messageReceived); Assert.IsTrue(messageReceived);
Assert.IsTrue(client1.TryPingAsync().GetAwaiter().GetResult()); Assert.IsTrue(client1.IsConnected);
Assert.IsFalse(disconnectedFired); Assert.IsFalse(disconnectedFired);
} }
} }
@@ -7,7 +7,7 @@ using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Internal; using MQTTnet.Implementations;
namespace MQTTnet.Tests.Diagnostics namespace MQTTnet.Tests.Diagnostics
{ {
@@ -33,7 +33,7 @@ namespace MQTTnet.Tests.Diagnostics
mqttClient.InspectPackage += eventArgs => mqttClient.InspectPackage += eventArgs =>
{ {
packets.Add(eventArgs.Direction + ":" + Convert.ToBase64String(eventArgs.Buffer)); packets.Add(eventArgs.Direction + ":" + Convert.ToBase64String(eventArgs.Buffer));
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
@@ -8,7 +8,7 @@ using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
using MQTTnet.Extensions.ManagedClient; using MQTTnet.Extensions.ManagedClient;
using MQTTnet.Internal; using MQTTnet.Implementations;
using MQTTnet.Server; using MQTTnet.Server;
namespace MQTTnet.Tests.Factory namespace MQTTnet.Tests.Factory
@@ -56,7 +56,7 @@ namespace MQTTnet.Tests.Factory
managedClient.ConnectingFailedAsync += e => managedClient.ConnectingFailedAsync += e =>
{ {
tcs.TrySetResult(null); tcs.TrySetResult(null);
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await Task.WhenAny(Task.Delay(managedClient.Options.ClientOptions.Timeout), tcs.Task); await Task.WhenAny(Task.Delay(managedClient.Options.ClientOptions.Timeout), tcs.Task);
@@ -6,6 +6,7 @@ using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Implementations;
using MQTTnet.Internal; using MQTTnet.Internal;
namespace MQTTnet.Tests.Internal namespace MQTTnet.Tests.Internal
@@ -15,28 +16,6 @@ namespace MQTTnet.Tests.Internal
{ {
int _testEventAsyncCount; int _testEventAsyncCount;
[TestMethod]
public async Task Attach_and_Detach_Sync_And_Async_EventHandler()
{
var testClass = new TestClass();
testClass.TestEventAsync += OnTestEventAsync;
testClass.TestEvent += OnTestEvent;
await testClass.FireEventAsync(EventArgs.Empty);
Assert.AreEqual(2, _testEventAsyncCount);
testClass.TestEvent -= OnTestEvent;
await testClass.FireEventAsync(EventArgs.Empty);
Assert.AreEqual(3, _testEventAsyncCount);
testClass.TestEventAsync -= OnTestEventAsync;
await testClass.FireEventAsync(EventArgs.Empty);
Assert.AreEqual(3, _testEventAsyncCount);
}
[TestMethod] [TestMethod]
public async Task Attach_EventHandler() public async Task Attach_EventHandler()
{ {
@@ -49,19 +28,6 @@ namespace MQTTnet.Tests.Internal
Assert.AreEqual(1, _testEventAsyncCount); Assert.AreEqual(1, _testEventAsyncCount);
} }
[TestMethod]
public async Task Attach_Sync_And_Async_EventHandler()
{
var testClass = new TestClass();
testClass.TestEventAsync += OnTestEventAsync;
testClass.TestEvent += OnTestEvent;
await testClass.FireEventAsync(EventArgs.Empty);
Assert.AreEqual(2, _testEventAsyncCount);
}
[TestMethod] [TestMethod]
public async Task Detach_EventHandler() public async Task Detach_EventHandler()
{ {
@@ -75,62 +41,22 @@ namespace MQTTnet.Tests.Internal
Assert.AreEqual(0, _testEventAsyncCount); Assert.AreEqual(0, _testEventAsyncCount);
} }
[TestMethod]
public void Has_Handlers()
{
var testClass = new TestClass();
testClass.TestEventAsync += OnTestEventAsync;
Assert.AreEqual(true, testClass.HasTestHandlers);
}
[TestMethod]
public void No_Handlers()
{
var testClass = new TestClass();
Assert.AreEqual(false, testClass.HasTestHandlers);
}
[TestMethod]
public void Remove_Handlers()
{
var testClass = new TestClass();
testClass.TestEventAsync += OnTestEventAsync;
testClass.TestEventAsync -= OnTestEventAsync;
Assert.AreEqual(false, testClass.HasTestHandlers);
}
void OnTestEvent(EventArgs arg)
{
Interlocked.Increment(ref _testEventAsyncCount);
}
Task OnTestEventAsync(EventArgs arg) Task OnTestEventAsync(EventArgs arg)
{ {
Interlocked.Increment(ref _testEventAsyncCount); Interlocked.Increment(ref _testEventAsyncCount);
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
} }
sealed class TestClass sealed class TestClass
{ {
readonly AsyncEvent<EventArgs> _testEvent = new AsyncEvent<EventArgs>(); readonly AsyncEvent<EventArgs> _testEvent = new AsyncEvent<EventArgs>();
public event Action<EventArgs> TestEvent
{
add => _testEvent.AddHandler(value);
remove => _testEvent.RemoveHandler(value);
}
public event Func<EventArgs, Task> TestEventAsync public event Func<EventArgs, Task> TestEventAsync
{ {
add => _testEvent.AddHandler(value); add => _testEvent.AddHandler(value);
remove => _testEvent.RemoveHandler(value); remove => _testEvent.RemoveHandler(value);
} }
public bool HasTestHandlers => _testEvent.HasHandlers;
public Task FireEventAsync(EventArgs eventArgs) public Task FireEventAsync(EventArgs eventArgs)
{ {
return _testEvent.InvokeAsync(eventArgs); return _testEvent.InvokeAsync(eventArgs);
+44 -233
View File
@@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; using System;
using System.Diagnostics;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -12,186 +11,8 @@ using MQTTnet.Internal;
namespace MQTTnet.Tests.Internal namespace MQTTnet.Tests.Internal
{ {
[TestClass] [TestClass]
public sealed class AsyncLock_Tests public class AsyncLock_Tests
{ {
[TestMethod]
public async Task Cancellation_Of_Awaiter()
{
var @lock = new AsyncLock();
// This call will not yet "release" the lock due to missing _using_.
var releaser = await @lock.EnterAsync().ConfigureAwait(false);
var counter = 0;
Debug.WriteLine("Prepared locked lock.");
_ = Task.Run(
async () =>
{
// SHOULD GET TIMEOUT!
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(1)))
{
using (await @lock.EnterAsync(timeout.Token))
{
Debug.WriteLine("Task 1 incremented");
counter++;
}
}
});
_ = Task.Run(
async () =>
{
// SHOULD GET TIMEOUT!
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2)))
{
using (await @lock.EnterAsync(timeout.Token))
{
Debug.WriteLine("Task 2 incremented");
counter++;
}
}
});
_ = Task.Run(
async () =>
{
// SHOULD GET TIMEOUT!
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(3)))
{
using (await @lock.EnterAsync(timeout.Token))
{
Debug.WriteLine("Task 3 incremented");
counter++;
}
}
});
_ = Task.Run(
async () =>
{
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(4)))
{
using (await @lock.EnterAsync(timeout.Token))
{
Debug.WriteLine("Task 4 incremented");
counter++;
}
}
});
_ = Task.Run(
async () =>
{
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
{
using (await @lock.EnterAsync(timeout.Token))
{
Debug.WriteLine("Task 5 incremented");
counter++;
}
}
});
_ = Task.Run(
async () =>
{
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(6)))
{
using (await @lock.EnterAsync(timeout.Token))
{
Debug.WriteLine("Task 6 incremented");
counter++;
}
}
});
Debug.WriteLine("Delay before release...");
await Task.Delay(TimeSpan.FromSeconds(3.1));
releaser.Dispose();
Debug.WriteLine("Wait for all tasks...");
await Task.Delay(TimeSpan.FromSeconds(6.1));
Assert.AreEqual(3, counter);
}
[TestMethod]
public void Lock_Parallel_Tasks()
{
const int taskCount = 50;
var @lock = new AsyncLock();
var tasks = new Task[taskCount];
var globalI = 0;
for (var i = 0; i < taskCount; i++)
{
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
tasks[i] = Task.Run(
async () =>
{
using (await @lock.EnterAsync())
{
var localI = globalI;
await Task.Delay(5); // Increase the chance for wrong data.
localI++;
globalI = localI;
}
});
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
}
Task.WaitAll(tasks);
Assert.AreEqual(taskCount, globalI);
}
[TestMethod]
public void Lock_10_Parallel_Tasks_With_Dispose_Doesnt_Lockup()
{
const int ThreadsCount = 10;
var threads = new Task[ThreadsCount];
var @lock = new AsyncLock();
var globalI = 0;
for (var i = 0; i < ThreadsCount; i++)
{
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
threads[i] = Task.Run(
async () =>
{
using (await @lock.EnterAsync())
{
var localI = globalI;
await Task.Delay(10); // Increase the chance for wrong data.
localI++;
globalI = localI;
}
})
.ContinueWith(
x =>
{
if (globalI == 5)
{
@lock.Dispose();
@lock = new AsyncLock();
}
if (x.Exception != null)
{
Debug.WriteLine(x.Exception.GetBaseException().GetType().Name);
}
});
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
}
Task.WaitAll(threads);
// Expect only 6 because the others are failing due to disposal (if (globalI == 5)).
Assert.AreEqual(6, globalI);
}
[TestMethod] [TestMethod]
public async Task Lock_Serial_Calls() public async Task Lock_Serial_Calls()
{ {
@@ -200,7 +21,7 @@ namespace MQTTnet.Tests.Internal
var @lock = new AsyncLock(); var @lock = new AsyncLock();
for (var i = 0; i < 100; i++) for (var i = 0; i < 100; i++)
{ {
using (await @lock.EnterAsync().ConfigureAwait(false)) using (await @lock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
{ {
sum++; sum++;
} }
@@ -216,76 +37,66 @@ namespace MQTTnet.Tests.Internal
var @lock = new AsyncLock(); var @lock = new AsyncLock();
// This call will never "release" the lock due to missing _using_. // This call will never "release" the lock due to missing _using_.
await @lock.EnterAsync().ConfigureAwait(false); await @lock.WaitAsync(CancellationToken.None).ConfigureAwait(false);
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3))) using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3)))
{ {
await @lock.EnterAsync(cts.Token).ConfigureAwait(false); await @lock.WaitAsync(cts.Token).ConfigureAwait(false);
} }
} }
[TestMethod] //[TestMethod]
public async Task Test_Cancellation_With_Later_Access() //public async Task Test_Cancellation_With_Later_Access()
{ //{
var asyncLock = new AsyncLock(); // var @lock = new AsyncLock();
var releaser = await asyncLock.EnterAsync().ConfigureAwait(false); // var releaser = await @lock.WaitAsync().ConfigureAwait(false);
try // try
{ // {
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(3))) // using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3)))
{ // {
await asyncLock.EnterAsync(timeout.Token).ConfigureAwait(false); // await @lock.WaitAsync(cts.Token).ConfigureAwait(false);
} // }
// }
// catch (OperationCanceledException)
// {
// }
Assert.Fail("Exception should be thrown!"); // releaser.Dispose();
}
catch (OperationCanceledException)
{
}
releaser.Dispose(); // using (await @lock.WaitAsync().ConfigureAwait(false))
// {
using (await asyncLock.EnterAsync(CancellationToken.None).ConfigureAwait(false)) // // When the method finished, the thread got access.
{ // }
// When the method finished, the thread got access. //}
}
}
[TestMethod] [TestMethod]
public async Task Use_After_Cancellation() public void Lock_10_Parallel_Tasks()
{ {
const int ThreadsCount = 10;
var threads = new Task[ThreadsCount];
var @lock = new AsyncLock(); var @lock = new AsyncLock();
var globalI = 0;
// This call will not yet "release" the lock due to missing _using_. for (var i = 0; i < ThreadsCount; i++)
var releaser = await @lock.EnterAsync().ConfigureAwait(false);
try
{ {
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3))) #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
threads[i] = Task.Run(async () =>
{ {
await @lock.EnterAsync(cts.Token).ConfigureAwait(false); using (var releaser = await @lock.WaitAsync(CancellationToken.None))
} {
} var localI = globalI;
catch (OperationCanceledException) await Task.Delay(10); // Increase the chance for wrong data.
{ localI++;
// Expected exception! globalI = localI;
}
});
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
} }
releaser.Dispose(); Task.WaitAll(threads);
Assert.AreEqual(ThreadsCount, globalI);
// Regular usage after cancellation.
using (await @lock.EnterAsync().ConfigureAwait(false))
{
}
using (await @lock.EnterAsync().ConfigureAwait(false))
{
}
using (await @lock.EnterAsync().ConfigureAwait(false))
{
}
} }
} }
} }
+1 -1
View File
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net452;net461;net48;netcoreapp3.1;net6.0</TargetFrameworks> <TargetFrameworks>net452;net461;netcoreapp3.1;net6.0</TargetFrameworks>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<LangVersion>7.3</LangVersion> <LangVersion>7.3</LangVersion>
<EnableNETAnalyzers>false</EnableNETAnalyzers> <EnableNETAnalyzers>false</EnableNETAnalyzers>
+4 -3
View File
@@ -8,9 +8,10 @@ using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Internal; using MQTTnet.Implementations;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests.MQTTv5 namespace MQTTnet.Tests.MQTTv5
{ {
@@ -42,7 +43,7 @@ namespace MQTTnet.Tests.MQTTv5
client.ApplicationMessageReceivedAsync += e => client.ApplicationMessageReceivedAsync += e =>
{ {
receivedMessage = e.ApplicationMessage; receivedMessage = e.ApplicationMessage;
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await client.PublishAsync(new MqttApplicationMessageBuilder() await client.PublishAsync(new MqttApplicationMessageBuilder()
@@ -244,7 +245,7 @@ namespace MQTTnet.Tests.MQTTv5
receiver.ApplicationMessageReceivedAsync += e => receiver.ApplicationMessageReceivedAsync += e =>
{ {
receivedMessage = e.ApplicationMessage; receivedMessage = e.ApplicationMessage;
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
var sender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500)); var sender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500));
+4 -46
View File
@@ -8,7 +8,7 @@ using MQTTnet.Formatter;
using MQTTnet.Tests.Mockups; using MQTTnet.Tests.Mockups;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Internal; using MQTTnet.Implementations;
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.Tests.MQTTv5 namespace MQTTnet.Tests.MQTTv5
@@ -31,7 +31,7 @@ namespace MQTTnet.Tests.MQTTv5
c1.ApplicationMessageReceivedAsync += e => c1.ApplicationMessageReceivedAsync += e =>
{ {
Interlocked.Increment(ref receivedMessagesCount); Interlocked.Increment(ref receivedMessagesCount);
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build()); await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
@@ -171,53 +171,11 @@ namespace MQTTnet.Tests.MQTTv5
// Session should be present // Session should be present
Assert.IsTrue(!result3.IsSessionPresent, "Session is present when it should not"); Assert.IsTrue(!result3.IsSessionPresent, "Session is present when it should not");
} }
} }
[TestMethod] MqttClientOptions CreateClientOptions(TestEnvironment testEnvironment, string clientId, bool cleanSession, uint sessionExpiryInterval)
public async Task Disconnect_with_Reason()
{
using (var testEnvironment = CreateTestEnvironment())
{
var disconnectReason = MqttClientDisconnectReason.UnspecifiedError;
string testClientId = null;
await testEnvironment.StartServer();
testEnvironment.Server.ClientConnectedAsync += e =>
{
testClientId = e.ClientId;
return CompletedTask.Instance;
};
var client = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500));
client.DisconnectedAsync += e =>
{
disconnectReason = e.Reason;
return CompletedTask.Instance;
};
await LongTestDelay();
// Test client should be connected now
Assert.IsTrue(testClientId != null);
// Have the server disconnect the client with AdministrativeAction reason
await testEnvironment.Server.DisconnectClientAsync(testClientId, MqttDisconnectReasonCode.AdministrativeAction);
await LongTestDelay();
// The reason should be returned to the client in the DISCONNECT packet
Assert.AreEqual(MqttClientDisconnectReason.AdministrativeAction, disconnectReason);
}
}
static MqttClientOptions CreateClientOptions(TestEnvironment testEnvironment, string clientId, bool cleanSession, uint sessionExpiryInterval)
{ {
return testEnvironment.Factory.CreateClientOptionsBuilder() return testEnvironment.Factory.CreateClientOptionsBuilder()
.WithProtocolVersion(MqttProtocolVersion.V500) .WithProtocolVersion(MqttProtocolVersion.V500)
@@ -2,13 +2,11 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.IO; using System.IO;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Channel; using MQTTnet.Channel;
using MQTTnet.Internal;
namespace MQTTnet.Tests.Mockups namespace MQTTnet.Tests.Mockups
{ {
@@ -34,12 +32,12 @@ namespace MQTTnet.Tests.Mockups
public Task ConnectAsync(CancellationToken cancellationToken) public Task ConnectAsync(CancellationToken cancellationToken)
{ {
return CompletedTask.Instance; return Task.FromResult(0);
} }
public Task DisconnectAsync(CancellationToken cancellationToken) public Task DisconnectAsync(CancellationToken cancellationToken)
{ {
return CompletedTask.Instance; return Task.FromResult(0);
} }
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
@@ -47,9 +45,9 @@ namespace MQTTnet.Tests.Mockups
return _stream.ReadAsync(buffer, offset, count, cancellationToken); return _stream.ReadAsync(buffer, offset, count, cancellationToken);
} }
public Task WriteAsync(ArraySegment<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken) public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{ {
return _stream.WriteAsync(buffer.Array, buffer.Offset, buffer.Count, cancellationToken); return _stream.WriteAsync(buffer, offset, count, cancellationToken);
} }
public void Dispose() public void Dispose()
@@ -7,7 +7,7 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Internal; using MQTTnet.Implementations;
namespace MQTTnet.Tests.Mockups namespace MQTTnet.Tests.Mockups
{ {
@@ -46,7 +46,7 @@ namespace MQTTnet.Tests.Mockups
_receivedEventArgs.Add(eventArgs); _receivedEventArgs.Add(eventArgs);
} }
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
} }
} }
} }
@@ -13,7 +13,7 @@ using MQTTnet.Client;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
using MQTTnet.Extensions.Rpc; using MQTTnet.Extensions.Rpc;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Internal; using MQTTnet.Implementations;
using MQTTnet.LowLevelClient; using MQTTnet.LowLevelClient;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Server; using MQTTnet.Server;
@@ -220,7 +220,7 @@ namespace MQTTnet.Tests.Mockups
} }
} }
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
return client; return client;
@@ -261,7 +261,7 @@ namespace MQTTnet.Tests.Mockups
} }
} }
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
return Server; return Server;
+1 -2
View File
@@ -13,7 +13,6 @@ using MQTTnet.Extensions.Rpc;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Implementations; using MQTTnet.Implementations;
using MQTTnet.Internal;
namespace MQTTnet.Tests namespace MQTTnet.Tests
{ {
@@ -190,7 +189,7 @@ namespace MQTTnet.Tests
responseSender.ApplicationMessageReceivedAsync += async e => responseSender.ApplicationMessageReceivedAsync += async e =>
{ {
Assert.IsNull(e.ApplicationMessage.ResponseTopic); Assert.IsNull(e.ApplicationMessage.ResponseTopic);
await CompletedTask.Instance; await PlatformAbstractionLayer.CompletedTask;
}; };
var requestSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500)); var requestSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500));
+2 -2
View File
@@ -8,7 +8,7 @@ using System.Diagnostics;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Internal; using MQTTnet.Implementations;
using MQTTnet.Tests.Mockups; using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests namespace MQTTnet.Tests
@@ -33,7 +33,7 @@ namespace MQTTnet.Tests
receiverClient.ApplicationMessageReceivedAsync += e => receiverClient.ApplicationMessageReceivedAsync += e =>
{ {
response?.TrySetResult(e.ApplicationMessage.ConvertPayloadToString()); response?.TrySetResult(e.ApplicationMessage.ConvertPayloadToString());
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await receiverClient.SubscribeAsync("#"); await receiverClient.SubscribeAsync("#");
@@ -7,7 +7,7 @@ using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Internal; using MQTTnet.Implementations;
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.Tests.Server namespace MQTTnet.Tests.Server
@@ -47,20 +47,20 @@ namespace MQTTnet.Tests.Server
e.ReasonCode = MqttConnectReasonCode.Success; e.ReasonCode = MqttConnectReasonCode.Success;
} }
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
testEnvironment.Server.ClientConnectedAsync += args => testEnvironment.Server.ClientConnectedAsync += args =>
{ {
serverConnectedClientId = args.ClientId; serverConnectedClientId = args.ClientId;
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
testEnvironment.Server.ClientDisconnectedAsync += args => testEnvironment.Server.ClientDisconnectedAsync += args =>
{ {
serverDisconnectedClientId = args.ClientId; serverDisconnectedClientId = args.ClientId;
disconnectedMre.Set(); disconnectedMre.Set();
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
// Arrange client // Arrange client
@@ -68,7 +68,7 @@ namespace MQTTnet.Tests.Server
client.ConnectedAsync += args => client.ConnectedAsync += args =>
{ {
clientAssignedClientId = args.ConnectResult.AssignedClientIdentifier; clientAssignedClientId = args.ConnectResult.AssignedClientIdentifier;
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
// Act // Act
+8 -8
View File
@@ -7,7 +7,7 @@ using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Internal; using MQTTnet.Implementations;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Server; using MQTTnet.Server;
@@ -27,7 +27,7 @@ namespace MQTTnet.Tests.Server
server.ClientConnectedAsync += e => server.ClientConnectedAsync += e =>
{ {
eventArgs = e; eventArgs = e;
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await testEnvironment.ConnectClient(o => o.WithCredentials("TheUser")); await testEnvironment.ConnectClient(o => o.WithCredentials("TheUser"));
@@ -54,7 +54,7 @@ namespace MQTTnet.Tests.Server
server.ClientDisconnectedAsync += e => server.ClientDisconnectedAsync += e =>
{ {
eventArgs = e; eventArgs = e;
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
var client = await testEnvironment.ConnectClient(o => o.WithCredentials("TheUser")); var client = await testEnvironment.ConnectClient(o => o.WithCredentials("TheUser"));
@@ -81,7 +81,7 @@ namespace MQTTnet.Tests.Server
server.ClientSubscribedTopicAsync += e => server.ClientSubscribedTopicAsync += e =>
{ {
eventArgs = e; eventArgs = e;
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
var client = await testEnvironment.ConnectClient(); var client = await testEnvironment.ConnectClient();
@@ -108,7 +108,7 @@ namespace MQTTnet.Tests.Server
server.ClientUnsubscribedTopicAsync += e => server.ClientUnsubscribedTopicAsync += e =>
{ {
eventArgs = e; eventArgs = e;
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
var client = await testEnvironment.ConnectClient(); var client = await testEnvironment.ConnectClient();
@@ -134,7 +134,7 @@ namespace MQTTnet.Tests.Server
server.InterceptingPublishAsync += e => server.InterceptingPublishAsync += e =>
{ {
eventArgs = e; eventArgs = e;
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
var client = await testEnvironment.ConnectClient(); var client = await testEnvironment.ConnectClient();
@@ -161,7 +161,7 @@ namespace MQTTnet.Tests.Server
server.StartedAsync += e => server.StartedAsync += e =>
{ {
eventArgs = e; eventArgs = e;
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await server.StartAsync(); await server.StartAsync();
@@ -183,7 +183,7 @@ namespace MQTTnet.Tests.Server
server.StoppedAsync += e => server.StoppedAsync += e =>
{ {
eventArgs = e; eventArgs = e;
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await server.StopAsync(); await server.StopAsync();
+30 -84
View File
@@ -11,7 +11,8 @@ using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Adapter; using MQTTnet.Adapter;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Internal; using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Server; using MQTTnet.Server;
@@ -98,7 +99,7 @@ namespace MQTTnet.Tests.Server
server.ValidatingConnectionAsync += e => server.ValidatingConnectionAsync += e =>
{ {
e.ReasonCode = MqttConnectReasonCode.NotAuthorized; e.ReasonCode = MqttConnectReasonCode.NotAuthorized;
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
var connectingFailedException = await Assert.ThrowsExceptionAsync<MqttConnectingFailedException>(() => testEnvironment.ConnectClient()); var connectingFailedException = await Assert.ThrowsExceptionAsync<MqttConnectingFailedException>(() => testEnvironment.ConnectClient());
@@ -121,7 +122,7 @@ namespace MQTTnet.Tests.Server
e.Response.ReasonCode = MqttSubscribeReasonCode.UnspecifiedError; e.Response.ReasonCode = MqttSubscribeReasonCode.UnspecifiedError;
} }
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
// Prepare some retained messages. // Prepare some retained messages.
@@ -145,7 +146,7 @@ namespace MQTTnet.Tests.Server
buffer.Append(e.ApplicationMessage.Topic); buffer.Append(e.ApplicationMessage.Topic);
} }
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await client2.SubscribeAsync("y"); await client2.SubscribeAsync("y");
@@ -171,13 +172,13 @@ namespace MQTTnet.Tests.Server
server.ClientConnectedAsync += e => server.ClientConnectedAsync += e =>
{ {
Interlocked.Increment(ref clientConnectedCalled); Interlocked.Increment(ref clientConnectedCalled);
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
server.ClientDisconnectedAsync += e => server.ClientDisconnectedAsync += e =>
{ {
Interlocked.Increment(ref clientDisconnectedCalled); Interlocked.Increment(ref clientDisconnectedCalled);
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder()); var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder());
@@ -224,7 +225,7 @@ namespace MQTTnet.Tests.Server
// Clear retained message. // Clear retained message.
await client.PublishAsync( await client.PublishAsync(
new MqttApplicationMessageBuilder().WithTopic("r" + i2) new MqttApplicationMessageBuilder().WithTopic("r" + i2)
.WithPayload(EmptyBuffer.Array) .WithPayload(PlatformAbstractionLayer.EmptyByteArray)
.WithRetainFlag() .WithRetainFlag()
.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce) .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)
.Build()); .Build());
@@ -275,7 +276,7 @@ namespace MQTTnet.Tests.Server
{ {
e.ApplicationMessage = new MqttApplicationMessage { Topic = "new_topic" }; e.ApplicationMessage = new MqttApplicationMessage { Topic = "new_topic" };
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
string receivedTopic = null; string receivedTopic = null;
@@ -285,7 +286,7 @@ namespace MQTTnet.Tests.Server
c1.ApplicationMessageReceivedAsync += a => c1.ApplicationMessageReceivedAsync += a =>
{ {
receivedTopic = a.ApplicationMessage.Topic; receivedTopic = a.ApplicationMessage.Topic;
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("original_topic").Build()); await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("original_topic").Build());
@@ -304,7 +305,7 @@ namespace MQTTnet.Tests.Server
server.InterceptingPublishAsync += e => server.InterceptingPublishAsync += e =>
{ {
e.ApplicationMessage.Payload = Encoding.ASCII.GetBytes("extended"); e.ApplicationMessage.Payload = Encoding.ASCII.GetBytes("extended");
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
var c1 = await testEnvironment.ConnectClient(); var c1 = await testEnvironment.ConnectClient();
@@ -315,7 +316,7 @@ namespace MQTTnet.Tests.Server
c2.ApplicationMessageReceivedAsync += e => c2.ApplicationMessageReceivedAsync += e =>
{ {
isIntercepted = string.Compare("extended", Encoding.UTF8.GetString(e.ApplicationMessage.Payload), StringComparison.Ordinal) == 0; isIntercepted = string.Compare("extended", Encoding.UTF8.GetString(e.ApplicationMessage.Payload), StringComparison.Ordinal) == 0;
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("test").Build()); await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("test").Build());
@@ -338,7 +339,7 @@ namespace MQTTnet.Tests.Server
server.ApplicationMessageNotConsumedAsync += e => server.ApplicationMessageNotConsumedAsync += e =>
{ {
undeliverd = e.ApplicationMessage.Topic; undeliverd = e.ApplicationMessage.Topic;
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
var client = await testEnvironment.ConnectClient(); var client = await testEnvironment.ConnectClient();
@@ -375,7 +376,7 @@ namespace MQTTnet.Tests.Server
receivedMessages.Add(e.ApplicationMessage); receivedMessages.Add(e.ApplicationMessage);
} }
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await Task.Delay(500); await Task.Delay(500);
@@ -399,7 +400,7 @@ namespace MQTTnet.Tests.Server
s.RetainedMessageChangedAsync += e => s.RetainedMessageChangedAsync += e =>
{ {
savedRetainedMessages = e.StoredRetainedMessages; savedRetainedMessages = e.StoredRetainedMessages;
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
var c1 = await testEnvironment.ConnectClient(); var c1 = await testEnvironment.ConnectClient();
@@ -439,7 +440,7 @@ namespace MQTTnet.Tests.Server
c1.ApplicationMessageReceivedAsync += e => c1.ApplicationMessageReceivedAsync += e =>
{ {
receivedTopic = e.ApplicationMessage.Topic; receivedTopic = e.ApplicationMessage.Topic;
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await c1.SubscribeAsync("#"); await c1.SubscribeAsync("#");
@@ -486,7 +487,7 @@ namespace MQTTnet.Tests.Server
client.ApplicationMessageReceivedAsync += e => client.ApplicationMessageReceivedAsync += e =>
{ {
Interlocked.Increment(ref receivedMessagesCount); Interlocked.Increment(ref receivedMessagesCount);
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
var message = new MqttApplicationMessageBuilder().WithTopic("a").WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce).Build(); var message = new MqttApplicationMessageBuilder().WithTopic("a").WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce).Build();
@@ -520,13 +521,13 @@ namespace MQTTnet.Tests.Server
c2.ApplicationMessageReceivedAsync += e => c2.ApplicationMessageReceivedAsync += e =>
{ {
Interlocked.Increment(ref receivedMessagesCount); Interlocked.Increment(ref receivedMessagesCount);
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
c3.ApplicationMessageReceivedAsync += e => c3.ApplicationMessageReceivedAsync += e =>
{ {
Interlocked.Increment(ref receivedMessagesCount); Interlocked.Increment(ref receivedMessagesCount);
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await c2.SubscribeAsync(new MqttTopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }).ConfigureAwait(false); await c2.SubscribeAsync(new MqttTopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }).ConfigureAwait(false);
@@ -580,7 +581,7 @@ namespace MQTTnet.Tests.Server
events.Add("c"); events.Add("c");
} }
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
server.ClientDisconnectedAsync += e => server.ClientDisconnectedAsync += e =>
@@ -590,7 +591,7 @@ namespace MQTTnet.Tests.Server
events.Add("d"); events.Add("d");
} }
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
var clientOptionsBuilder = new MqttClientOptionsBuilder().WithClientId(Guid.NewGuid().ToString()); var clientOptionsBuilder = new MqttClientOptionsBuilder().WithClientId(Guid.NewGuid().ToString());
@@ -620,7 +621,7 @@ namespace MQTTnet.Tests.Server
events.Add("r"); events.Add("r");
} }
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await c2.SubscribeAsync("topic"); await c2.SubscribeAsync("topic");
@@ -668,7 +669,7 @@ namespace MQTTnet.Tests.Server
server.ValidatingConnectionAsync += e => server.ValidatingConnectionAsync += e =>
{ {
ConnectionValidationHandler(e); ConnectionValidationHandler(e);
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
var events = new List<string>(); var events = new List<string>();
@@ -680,7 +681,7 @@ namespace MQTTnet.Tests.Server
events.Add("c"); events.Add("c");
} }
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
server.ClientDisconnectedAsync += e => server.ClientDisconnectedAsync += e =>
@@ -690,7 +691,7 @@ namespace MQTTnet.Tests.Server
events.Add("d"); events.Add("d");
} }
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
var clientOptions = new MqttClientOptionsBuilder().WithClientId("same_id"); var clientOptions = new MqttClientOptionsBuilder().WithClientId("same_id");
@@ -705,7 +706,7 @@ namespace MQTTnet.Tests.Server
events.Add("x"); events.Add("x");
} }
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
c1.ApplicationMessageReceivedAsync += e => c1.ApplicationMessageReceivedAsync += e =>
@@ -715,7 +716,7 @@ namespace MQTTnet.Tests.Server
events.Add("r"); events.Add("r");
} }
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
c1.SubscribeAsync("topic").Wait(); c1.SubscribeAsync("topic").Wait();
@@ -784,7 +785,7 @@ namespace MQTTnet.Tests.Server
client1.ApplicationMessageReceivedAsync += e => client1.ApplicationMessageReceivedAsync += e =>
{ {
receivedBody = e.ApplicationMessage.Payload; receivedBody = e.ApplicationMessage.Payload;
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await client1.SubscribeAsync("string"); await client1.SubscribeAsync("string");
@@ -821,7 +822,7 @@ namespace MQTTnet.Tests.Server
receivedMessages.Add(e.ApplicationMessage); receivedMessages.Add(e.ApplicationMessage);
} }
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await Task.Delay(500); await Task.Delay(500);
@@ -849,7 +850,7 @@ namespace MQTTnet.Tests.Server
c1.DisconnectedAsync += e => c1.DisconnectedAsync += e =>
{ {
disconnectCalled++; disconnectCalled++;
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await Task.Delay(100); await Task.Delay(100);
@@ -935,61 +936,6 @@ namespace MQTTnet.Tests.Server
} }
} }
[TestMethod]
public async Task Disconnect_Client_with_Reason()
{
using (var testEnvironment = CreateTestEnvironment())
{
var disconnectPacketReceived = false;
string testClientId = null;
await testEnvironment.StartServer();
testEnvironment.Server.ClientConnectedAsync += e =>
{
testClientId = e.ClientId;
return CompletedTask.Instance;
};
var client = testEnvironment.CreateClient();
client.InspectPackage += e =>
{
if (e.Buffer.Length > 0)
{
if (e.Buffer[0] == (byte)MqttControlPacketType.Disconnect << 4)
{
disconnectPacketReceived = true;
}
}
return CompletedTask.Instance;
};
await client.ConnectAsync(
new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort)
.WithProtocolVersion(MQTTnet.Formatter.MqttProtocolVersion.V311)
.Build()
);
await LongTestDelay();
// Test client should be connected now
Assert.IsTrue(testClientId != null);
// Have the server disconnect the client with SessionTakenOver reason; no DISCONNECT should be sent to MQTT 3.1.1 clients
await testEnvironment.Server.DisconnectClientAsync(testClientId, Protocol.MqttDisconnectReasonCode.SessionTakenOver);
await LongTestDelay();
// MQTT 3.1.1 client should not receive a disconnect packet
Assert.IsFalse(disconnectPacketReceived, "Disconnect packet received when none is expected");
}
}
void ConnectionValidationHandler(ValidatingConnectionEventArgs eventArgs) void ConnectionValidationHandler(ValidatingConnectionEventArgs eventArgs)
{ {
if (_connected.ContainsKey(eventArgs.ClientId)) if (_connected.ContainsKey(eventArgs.ClientId))
+4 -4
View File
@@ -3,7 +3,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Internal; using MQTTnet.Implementations;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
@@ -26,7 +26,7 @@ namespace MQTTnet.Tests.Server
receiverClient.ApplicationMessageReceivedAsync += e => receiverClient.ApplicationMessageReceivedAsync += e =>
{ {
Interlocked.Increment(ref receivedMessages); Interlocked.Increment(ref receivedMessages);
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await receiverClient.SubscribeAsync("t/+"); await receiverClient.SubscribeAsync("t/+");
@@ -74,7 +74,7 @@ namespace MQTTnet.Tests.Server
server.InterceptingPublishAsync += e => server.InterceptingPublishAsync += e =>
{ {
Interlocked.Increment(ref receivedMessages); Interlocked.Increment(ref receivedMessages);
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
for (var i = 0; i < 100; i++) for (var i = 0; i < 100; i++)
@@ -137,7 +137,7 @@ namespace MQTTnet.Tests.Server
server.InterceptingPublishAsync += e => server.InterceptingPublishAsync += e =>
{ {
Interlocked.Increment(ref receivedMessages); Interlocked.Increment(ref receivedMessages);
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
for (var i = 0; i < 100; i++) for (var i = 0; i < 100; i++)
+4 -4
View File
@@ -6,7 +6,7 @@ using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Internal; using MQTTnet.Implementations;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Server; using MQTTnet.Server;
@@ -26,7 +26,7 @@ namespace MQTTnet.Tests.Server
server.ClientAcknowledgedPublishPacketAsync += args => server.ClientAcknowledgedPublishPacketAsync += args =>
{ {
eventArgs = args; eventArgs = args;
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
var client1 = await testEnvironment.ConnectClient(); var client1 = await testEnvironment.ConnectClient();
@@ -53,7 +53,7 @@ namespace MQTTnet.Tests.Server
server.ClientAcknowledgedPublishPacketAsync += args => server.ClientAcknowledgedPublishPacketAsync += args =>
{ {
eventArgs = args; eventArgs = args;
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
var client1 = await testEnvironment.ConnectClient(); var client1 = await testEnvironment.ConnectClient();
@@ -88,7 +88,7 @@ namespace MQTTnet.Tests.Server
eventArgs.Add(args); eventArgs.Add(args);
} }
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
var client1 = await testEnvironment.ConnectClient(); var client1 = await testEnvironment.ConnectClient();
@@ -8,7 +8,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Adapter; using MQTTnet.Adapter;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
using MQTTnet.Internal; using MQTTnet.Implementations;
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.Tests.Server namespace MQTTnet.Tests.Server
@@ -33,7 +33,7 @@ namespace MQTTnet.Tests.Server
publishedApplicationMessages.Add(eventArgs.ApplicationMessage); publishedApplicationMessages.Add(eventArgs.ApplicationMessage);
} }
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
testEnvironment.Server.ValidatingConnectionAsync += eventArgs => testEnvironment.Server.ValidatingConnectionAsync += eventArgs =>
@@ -47,7 +47,7 @@ namespace MQTTnet.Tests.Server
eventArgs.ReasonCode = MqttConnectReasonCode.NotAuthorized; eventArgs.ReasonCode = MqttConnectReasonCode.NotAuthorized;
} }
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
using (var validClient = testEnvironment.CreateClient()) using (var validClient = testEnvironment.CreateClient())
@@ -157,7 +157,7 @@ namespace MQTTnet.Tests.Server
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword; e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
} }
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
var client = testEnvironment.CreateClient(); var client = testEnvironment.CreateClient();
@@ -8,7 +8,6 @@ using MQTTnet.Adapter;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Implementations; using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.Tests.Server namespace MQTTnet.Tests.Server
@@ -29,7 +28,7 @@ namespace MQTTnet.Tests.Server
{ {
e.ReasonCode = MqttConnectReasonCode.ServerMoved; e.ReasonCode = MqttConnectReasonCode.ServerMoved;
e.ServerReference = "new_server"; e.ServerReference = "new_server";
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
try try
+59 -35
View File
@@ -10,8 +10,9 @@ using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Internal; using MQTTnet.Implementations;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Server;
using MQTTnet.Tests.Mockups; using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests.Server namespace MQTTnet.Tests.Server
@@ -28,7 +29,7 @@ namespace MQTTnet.Tests.Server
await testEnvironment.StartServer(o => o.WithPersistentSessions()); await testEnvironment.StartServer(o => o.WithPersistentSessions());
const string clientId = "Client1"; const string ClientId = "Client1";
// Create client with clean session and long session expiry interval // Create client with clean session and long session expiry interval
@@ -37,7 +38,7 @@ namespace MQTTnet.Tests.Server
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort) .WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
.WithSessionExpiryInterval(9999) // not relevant for v311 but testing impact .WithSessionExpiryInterval(9999) // not relevant for v311 but testing impact
.WithCleanSession() // start and end with clean session .WithCleanSession() // start and end with clean session
.WithClientId(clientId) .WithClientId(ClientId)
.Build()); .Build());
// Disconnect; empty session should be removed from server // Disconnect; empty session should be removed from server
@@ -56,7 +57,7 @@ namespace MQTTnet.Tests.Server
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort) .WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
.WithSessionExpiryInterval(9999) // not relevant for v311 but testing impact .WithSessionExpiryInterval(9999) // not relevant for v311 but testing impact
.WithCleanSession(false) // see if there is a session .WithCleanSession(false) // see if there is a session
.WithClientId(clientId) .WithClientId(ClientId)
.Build(); .Build();
@@ -82,7 +83,7 @@ namespace MQTTnet.Tests.Server
server.SessionDeletedAsync += e => server.SessionDeletedAsync += e =>
{ {
deletedEventFired = true; deletedEventFired = true;
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
var client = await testEnvironment.ConnectClient(); var client = await testEnvironment.ConnectClient();
@@ -110,7 +111,7 @@ namespace MQTTnet.Tests.Server
e.SessionItems["can_subscribe_x"] = true; e.SessionItems["can_subscribe_x"] = true;
e.SessionItems["default_payload"] = "Hello World"; e.SessionItems["default_payload"] = "Hello World";
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await testEnvironment.ConnectClient(); await testEnvironment.ConnectClient();
@@ -122,12 +123,8 @@ namespace MQTTnet.Tests.Server
} }
} }
#if !NET452
[TestMethod] [TestMethod]
[DataRow(MqttProtocolVersion.V311)] public async Task Handle_Parallel_Connection_Attempts()
[DataRow(MqttProtocolVersion.V500)]
public async Task Handle_Parallel_Connection_Attempts(MqttProtocolVersion protocolVersion)
{ {
using (var testEnvironment = CreateTestEnvironment()) using (var testEnvironment = CreateTestEnvironment())
{ {
@@ -135,7 +132,7 @@ namespace MQTTnet.Tests.Server
await testEnvironment.StartServer(); await testEnvironment.StartServer();
var options = new MqttClientOptionsBuilder().WithClientId("1").WithTimeout(TimeSpan.FromSeconds(1)).WithProtocolVersion(protocolVersion); var options = new MqttClientOptionsBuilder().WithClientId("1").WithKeepAlivePeriod(TimeSpan.FromSeconds(5));
var hasReceive = false; var hasReceive = false;
@@ -149,15 +146,12 @@ namespace MQTTnet.Tests.Server
// Try to connect 50 clients at the same time. // Try to connect 50 clients at the same time.
var clients = await Task.WhenAll(Enumerable.Range(0, 50).Select(i => ConnectAndSubscribe(testEnvironment, options, OnReceive))); var clients = await Task.WhenAll(Enumerable.Range(0, 50).Select(i => ConnectAndSubscribe(testEnvironment, options, OnReceive)));
var connectedClients = clients.Where(c => c?.IsConnected ?? false).ToList();
await Task.Delay(5000);
var connectedClients = clients.Where(c => c?.TryPingAsync().GetAwaiter().GetResult() == true).ToList();
await LongTestDelay();
Assert.AreEqual(1, connectedClients.Count); Assert.AreEqual(1, connectedClients.Count);
await Task.Delay(5000);
var option2 = new MqttClientOptionsBuilder().WithClientId("2").WithKeepAlivePeriod(TimeSpan.FromSeconds(10)); var option2 = new MqttClientOptionsBuilder().WithClientId("2").WithKeepAlivePeriod(TimeSpan.FromSeconds(10));
var sendClient = await testEnvironment.ConnectClient(option2); var sendClient = await testEnvironment.ConnectClient(option2);
await sendClient.PublishStringAsync("aaa", "1"); await sendClient.PublishStringAsync("aaa", "1");
@@ -167,7 +161,25 @@ namespace MQTTnet.Tests.Server
Assert.AreEqual(true, hasReceive); Assert.AreEqual(true, hasReceive);
} }
} }
#endif
[TestMethod]
public async Task Manage_Session_MaxParallel()
{
using (var testEnvironment = CreateTestEnvironment())
{
testEnvironment.IgnoreClientLogErrors = true;
var serverOptions = new MqttServerOptionsBuilder();
await testEnvironment.StartServer(serverOptions);
var options = new MqttClientOptionsBuilder().WithClientId("1");
var clients = await Task.WhenAll(Enumerable.Range(0, 10).Select(i => TryConnect(testEnvironment, options)));
var connectedClients = clients.Where(c => c?.IsConnected ?? false).ToList();
Assert.AreEqual(1, connectedClients.Count);
}
}
[DataTestMethod] [DataTestMethod]
[DataRow(MqttQualityOfServiceLevel.ExactlyOnce)] [DataRow(MqttQualityOfServiceLevel.ExactlyOnce)]
@@ -188,7 +200,7 @@ namespace MQTTnet.Tests.Server
c.AutoAcknowledge = false; c.AutoAcknowledge = false;
++count; ++count;
Console.WriteLine("process"); Console.WriteLine("process");
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await subscriber.SubscribeAsync("#", qos); await subscriber.SubscribeAsync("#", qos);
@@ -225,7 +237,7 @@ namespace MQTTnet.Tests.Server
client1.DisconnectedAsync += c => client1.DisconnectedAsync += c =>
{ {
disconnectReason = c.Reason; disconnectReason = c.Reason;
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
var client2 = await testEnvironment.ConnectClient(options); var client2 = await testEnvironment.ConnectClient(options);
@@ -251,7 +263,7 @@ namespace MQTTnet.Tests.Server
e.SessionItems["can_subscribe_x"] = true; e.SessionItems["can_subscribe_x"] = true;
e.SessionItems["default_payload"] = "Hello World"; e.SessionItems["default_payload"] = "Hello World";
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
server.InterceptingSubscriptionAsync += e => server.InterceptingSubscriptionAsync += e =>
@@ -264,13 +276,13 @@ namespace MQTTnet.Tests.Server
} }
} }
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
server.InterceptingPublishAsync += e => server.InterceptingPublishAsync += e =>
{ {
e.ApplicationMessage.Payload = Encoding.UTF8.GetBytes(e.SessionItems["default_payload"] as string ?? string.Empty); e.ApplicationMessage.Payload = Encoding.UTF8.GetBytes(e.SessionItems["default_payload"] as string ?? string.Empty);
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
string receivedPayload = null; string receivedPayload = null;
@@ -279,7 +291,7 @@ namespace MQTTnet.Tests.Server
client.ApplicationMessageReceivedAsync += e => client.ApplicationMessageReceivedAsync += e =>
{ {
receivedPayload = e.ApplicationMessage.ConvertPayloadToString(); receivedPayload = e.ApplicationMessage.ConvertPayloadToString();
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
var subscribeResult = await client.SubscribeAsync("x"); var subscribeResult = await client.SubscribeAsync("x");
@@ -329,7 +341,7 @@ namespace MQTTnet.Tests.Server
c1.ApplicationMessageReceivedAsync += e => c1.ApplicationMessageReceivedAsync += e =>
{ {
Interlocked.Increment(ref receivedMessagesCount); Interlocked.Increment(ref receivedMessagesCount);
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build()); await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
@@ -337,10 +349,10 @@ namespace MQTTnet.Tests.Server
// C2 has the last will defined. // C2 has the last will defined.
var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will").WithClientId("WillOwner"); var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will").WithClientId("WillOwner");
await testEnvironment.ConnectClient(clientOptions); var c2 = await testEnvironment.ConnectClient(clientOptions);
// C3 will do the connection takeover. // C3 will do the connection takeover.
await testEnvironment.ConnectClient(clientOptions); var c3 = await testEnvironment.ConnectClient(clientOptions);
await Task.Delay(1000); await Task.Delay(1000);
@@ -348,29 +360,41 @@ namespace MQTTnet.Tests.Server
} }
} }
static async Task<IMqttClient> ConnectAndSubscribe(TestEnvironment testEnvironment, MqttClientOptionsBuilder options, Action onReceive) async Task<IMqttClient> ConnectAndSubscribe(TestEnvironment testEnvironment, MqttClientOptionsBuilder options, Action onReceive)
{ {
try try
{ {
var client = await testEnvironment.ConnectClient(options).ConfigureAwait(false); var sendClient = await testEnvironment.ConnectClient(options);
client.ApplicationMessageReceivedAsync += e => sendClient.ApplicationMessageReceivedAsync += e =>
{ {
onReceive(); onReceive();
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5))) using (var subscribeTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
{ {
await client.SubscribeAsync("aaa", MqttQualityOfServiceLevel.AtMostOnce, timeout.Token).ConfigureAwait(false); await sendClient.SubscribeAsync("aaa", MqttQualityOfServiceLevel.AtMostOnce, subscribeTimeout.Token).ConfigureAwait(false);
} }
return client; return sendClient;
} }
catch (Exception) catch (Exception)
{ {
return null; return null;
} }
} }
async Task<IMqttClient> TryConnect(TestEnvironment testEnvironment, MqttClientOptionsBuilder options)
{
try
{
return await testEnvironment.ConnectClient(options);
}
catch
{
return null;
}
}
} }
} }
+12 -12
View File
@@ -9,7 +9,7 @@ using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Internal; using MQTTnet.Implementations;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Server; using MQTTnet.Server;
@@ -33,7 +33,7 @@ namespace MQTTnet.Tests.Server
e.Response.ReasonCode = MqttSubscribeReasonCode.TopicFilterInvalid; e.Response.ReasonCode = MqttSubscribeReasonCode.TopicFilterInvalid;
} }
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
var client = await testEnvironment.ConnectClient(); var client = await testEnvironment.ConnectClient();
@@ -56,7 +56,7 @@ namespace MQTTnet.Tests.Server
server.ClientSubscribedTopicAsync += e => server.ClientSubscribedTopicAsync += e =>
{ {
server.InjectApplicationMessage(new InjectedMqttApplicationMessage(new MqttApplicationMessageBuilder().WithTopic("test_topic").Build())); server.InjectApplicationMessage(new InjectedMqttApplicationMessage(new MqttApplicationMessageBuilder().WithTopic("test_topic").Build()));
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
var client = await testEnvironment.ConnectClient(); var client = await testEnvironment.ConnectClient();
@@ -81,7 +81,7 @@ namespace MQTTnet.Tests.Server
{ {
// Set the topic to "a" regards what the client wants to subscribe. // Set the topic to "a" regards what the client wants to subscribe.
e.TopicFilter.Topic = "a"; e.TopicFilter.Topic = "a";
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
var topicAReceived = false; var topicAReceived = false;
@@ -99,7 +99,7 @@ namespace MQTTnet.Tests.Server
topicBReceived = true; topicBReceived = true;
} }
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await client.SubscribeAsync("b"); await client.SubscribeAsync("b");
@@ -146,7 +146,7 @@ namespace MQTTnet.Tests.Server
c1.ApplicationMessageReceivedAsync += e => c1.ApplicationMessageReceivedAsync += e =>
{ {
Interlocked.Increment(ref receivedMessagesCount); Interlocked.Increment(ref receivedMessagesCount);
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
for (var i = 0; i < 500; i++) for (var i = 0; i < 500; i++)
@@ -189,7 +189,7 @@ namespace MQTTnet.Tests.Server
c1.ApplicationMessageReceivedAsync += e => c1.ApplicationMessageReceivedAsync += e =>
{ {
Interlocked.Increment(ref receivedMessagesCount); Interlocked.Increment(ref receivedMessagesCount);
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
var optionsBuilder = new MqttClientSubscribeOptionsBuilder(); var optionsBuilder = new MqttClientSubscribeOptionsBuilder();
@@ -229,7 +229,7 @@ namespace MQTTnet.Tests.Server
c1.ApplicationMessageReceivedAsync += e => c1.ApplicationMessageReceivedAsync += e =>
{ {
Interlocked.Increment(ref receivedMessagesCount); Interlocked.Increment(ref receivedMessagesCount);
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await c1.SubscribeAsync(new MqttClientSubscribeOptionsBuilder().WithTopicFilter("a").Build()); await c1.SubscribeAsync(new MqttClientSubscribeOptionsBuilder().WithTopicFilter("a").Build());
@@ -267,7 +267,7 @@ namespace MQTTnet.Tests.Server
c1.ApplicationMessageReceivedAsync += e => c1.ApplicationMessageReceivedAsync += e =>
{ {
Interlocked.Increment(ref receivedMessagesCount); Interlocked.Increment(ref receivedMessagesCount);
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await c1.SubscribeAsync(new MqttClientSubscribeOptionsBuilder().WithTopicFilter("a").WithTopicFilter("b").WithTopicFilter("c").Build()); await c1.SubscribeAsync(new MqttClientSubscribeOptionsBuilder().WithTopicFilter("a").WithTopicFilter("b").WithTopicFilter("c").Build());
@@ -301,7 +301,7 @@ namespace MQTTnet.Tests.Server
c1.ApplicationMessageReceivedAsync += e => c1.ApplicationMessageReceivedAsync += e =>
{ {
Interlocked.Increment(ref receivedMessagesCount); Interlocked.Increment(ref receivedMessagesCount);
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
var c2 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("c2")); var c2 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("c2"));
@@ -316,7 +316,7 @@ namespace MQTTnet.Tests.Server
server.ClientSubscribedTopicAsync += e => server.ClientSubscribedTopicAsync += e =>
{ {
subscribeEventCalled = e.TopicFilter.Topic == "a" && e.ClientId == c1.Options.ClientId; subscribeEventCalled = e.TopicFilter.Topic == "a" && e.ClientId == c1.Options.ClientId;
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await c1.SubscribeAsync(new MqttTopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }); await c1.SubscribeAsync(new MqttTopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce });
@@ -331,7 +331,7 @@ namespace MQTTnet.Tests.Server
server.ClientUnsubscribedTopicAsync += e => server.ClientUnsubscribedTopicAsync += e =>
{ {
unsubscribeEventCalled = e.TopicFilter == "a" && e.ClientId == c1.Options.ClientId; unsubscribeEventCalled = e.TopicFilter == "a" && e.ClientId == c1.Options.ClientId;
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await c1.UnsubscribeAsync("a"); await c1.UnsubscribeAsync("a");
@@ -8,7 +8,7 @@ using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Internal; using MQTTnet.Implementations;
namespace MQTTnet.Tests.Server namespace MQTTnet.Tests.Server
{ {
@@ -50,7 +50,7 @@ namespace MQTTnet.Tests.Server
receivedTopics.Add(e.ApplicationMessage.Topic); receivedTopics.Add(e.ApplicationMessage.Topic);
} }
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await c1.SubscribeAsync("#"); await c1.SubscribeAsync("#");
@@ -9,7 +9,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Implementations; using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Tests.Mockups; using MQTTnet.Tests.Mockups;
@@ -49,7 +48,7 @@ namespace MQTTnet.Tests.Server
receiver.ApplicationMessageReceivedAsync += e => receiver.ApplicationMessageReceivedAsync += e =>
{ {
receivedMessage = e.ApplicationMessage; receivedMessage = e.ApplicationMessage;
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await sender.PublishAsync(message.Build(), CancellationToken.None); await sender.PublishAsync(message.Build(), CancellationToken.None);
+3 -3
View File
@@ -2,7 +2,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Internal; using MQTTnet.Implementations;
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.Tests.Server namespace MQTTnet.Tests.Server
@@ -26,7 +26,7 @@ namespace MQTTnet.Tests.Server
c1.ApplicationMessageReceivedAsync += e => c1.ApplicationMessageReceivedAsync += e =>
{ {
Interlocked.Increment(ref receivedMessagesCount); Interlocked.Increment(ref receivedMessagesCount);
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build()); await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
@@ -53,7 +53,7 @@ namespace MQTTnet.Tests.Server
c1.ApplicationMessageReceivedAsync += e => c1.ApplicationMessageReceivedAsync += e =>
{ {
Interlocked.Increment(ref receivedMessagesCount); Interlocked.Increment(ref receivedMessagesCount);
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
}; };
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build()); await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
+6 -28
View File
@@ -73,26 +73,7 @@ namespace MQTTnet.Adapter
try try
{ {
/* await _channel.ConnectAsync(cancellationToken).ConfigureAwait(false);
* We have to implement a small workaround here to support connecting in Xamarin
* with a disabled WiFi network. If the WiFi is disabled the connect method will
* block forever. Even a cancellation token is not supported properly.
*/
var connectTask = _channel.ConnectAsync(cancellationToken);
var timeout = new TaskCompletionSource<object>();
using (cancellationToken.Register(() => timeout.TrySetResult(null)))
{
await Task.WhenAny(connectTask, timeout.Task).ConfigureAwait(false);
if (timeout.Task.IsCompleted && !connectTask.IsCompleted)
{
throw new OperationCanceledException("MQTT connect cancelled.", cancellationToken);
}
// Make sure that the exception from the connect task gets thrown.
await connectTask.ConfigureAwait(false);
}
} }
catch (Exception exception) catch (Exception exception)
{ {
@@ -195,7 +176,7 @@ namespace MQTTnet.Adapter
// This lock makes sure that multiple threads can send packets at the same time. // This lock makes sure that multiple threads can send packets at the same time.
// This is required when a disconnect is sent from another thread while the // This is required when a disconnect is sent from another thread while the
// worker thread is still sending publish packets etc. // worker thread is still sending publish packets etc.
using (await _syncRoot.EnterAsync(cancellationToken).ConfigureAwait(false)) using (await _syncRoot.WaitAsync(cancellationToken).ConfigureAwait(false))
{ {
// Check for cancellation here again because "WaitAsync" might take some time. // Check for cancellation here again because "WaitAsync" might take some time.
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
@@ -207,14 +188,11 @@ namespace MQTTnet.Adapter
_logger.Verbose("TX ({0} bytes) >>> {1}", packetBuffer.Length, packet); _logger.Verbose("TX ({0} bytes) >>> {1}", packetBuffer.Length, packet);
await _channel.WriteAsync(packetBuffer.Packet.Array, packetBuffer.Packet.Offset, packetBuffer.Packet.Count, cancellationToken).ConfigureAwait(false);
if (packetBuffer.Payload.Count > 0) if (packetBuffer.Payload.Count > 0)
{ {
await _channel.WriteAsync(packetBuffer.Packet, false, cancellationToken).ConfigureAwait(false); await _channel.WriteAsync(packetBuffer.Payload.Array, packetBuffer.Payload.Offset, packetBuffer.Payload.Count, cancellationToken).ConfigureAwait(false);
await _channel.WriteAsync(packetBuffer.Payload, true, cancellationToken).ConfigureAwait(false);
}
else
{
await _channel.WriteAsync(packetBuffer.Packet, true, cancellationToken).ConfigureAwait(false);
} }
Interlocked.Add(ref _bytesReceived, packetBuffer.Length); Interlocked.Add(ref _bytesReceived, packetBuffer.Length);
@@ -363,7 +341,7 @@ namespace MQTTnet.Adapter
var fixedHeader = readFixedHeaderResult.FixedHeader; var fixedHeader = readFixedHeaderResult.FixedHeader;
if (fixedHeader.RemainingLength == 0) if (fixedHeader.RemainingLength == 0)
{ {
return new ReceivedMqttPacket(fixedHeader.Flags, EmptyBuffer.ArraySegment, 2); return new ReceivedMqttPacket(fixedHeader.Flags, PlatformAbstractionLayer.EmptyByteArraySegment, 2);
} }
var bodyLength = fixedHeader.RemainingLength; var bodyLength = fixedHeader.RemainingLength;
+1 -5
View File
@@ -12,17 +12,13 @@ namespace MQTTnet.Channel
public interface IMqttChannel : IDisposable public interface IMqttChannel : IDisposable
{ {
string Endpoint { get; } string Endpoint { get; }
bool IsSecureConnection { get; } bool IsSecureConnection { get; }
X509Certificate2 ClientCertificate { get; } X509Certificate2 ClientCertificate { get; }
Task ConnectAsync(CancellationToken cancellationToken); Task ConnectAsync(CancellationToken cancellationToken);
Task DisconnectAsync(CancellationToken cancellationToken); Task DisconnectAsync(CancellationToken cancellationToken);
Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken); Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken);
Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken);
Task WriteAsync(ArraySegment<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken);
} }
} }
+4 -4
View File
@@ -7,14 +7,14 @@ namespace MQTTnet.Client
{ {
public interface IMqttClient : IDisposable public interface IMqttClient : IDisposable
{ {
event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync;
event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync; event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync;
event Func<MqttClientConnectingEventArgs, Task> ConnectingAsync; event Func<MqttClientConnectingEventArgs, Task> ConnectingAsync;
event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync; event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync;
event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync;
event Func<InspectMqttPacketEventArgs, Task> InspectPackage; event Func<InspectMqttPacketEventArgs, Task> InspectPackage;
bool IsConnected { get; } bool IsConnected { get; }
@@ -27,12 +27,12 @@ namespace MQTTnet.Client
Task PingAsync(CancellationToken cancellationToken = default); Task PingAsync(CancellationToken cancellationToken = default);
Task<MqttClientPublishResult> PublishAsync(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken = default);
Task SendExtendedAuthenticationExchangeDataAsync(MqttExtendedAuthenticationExchangeData data, CancellationToken cancellationToken = default); Task SendExtendedAuthenticationExchangeDataAsync(MqttExtendedAuthenticationExchangeData data, CancellationToken cancellationToken = default);
Task<MqttClientSubscribeResult> SubscribeAsync(MqttClientSubscribeOptions options, CancellationToken cancellationToken = default); Task<MqttClientSubscribeResult> SubscribeAsync(MqttClientSubscribeOptions options, CancellationToken cancellationToken = default);
Task<MqttClientUnsubscribeResult> UnsubscribeAsync(MqttClientUnsubscribeOptions options, CancellationToken cancellationToken = default); Task<MqttClientUnsubscribeResult> UnsubscribeAsync(MqttClientUnsubscribeOptions options, CancellationToken cancellationToken = default);
Task<MqttClientPublishResult> PublishAsync(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken = default);
} }
} }
+383 -413
View File
@@ -2,57 +2,59 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Adapter; using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Internal; using MQTTnet.Internal;
using MQTTnet.PacketDispatcher; using MQTTnet.PacketDispatcher;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using System;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Diagnostics;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
namespace MQTTnet.Client namespace MQTTnet.Client
{ {
public sealed class MqttClient : Disposable, IMqttClient public sealed class MqttClient : Disposable, IMqttClient
{ {
readonly IMqttClientAdapterFactory _adapterFactory; readonly MqttPacketFactories _packetFactories = new MqttPacketFactories();
readonly MqttApplicationMessageFactory _applicationMessageFactory = new MqttApplicationMessageFactory();
readonly AsyncEvent<MqttApplicationMessageReceivedEventArgs> _applicationMessageReceivedEvent = new AsyncEvent<MqttApplicationMessageReceivedEventArgs>();
readonly MqttClientPublishResultFactory _clientPublishResultFactory = new MqttClientPublishResultFactory(); readonly MqttClientPublishResultFactory _clientPublishResultFactory = new MqttClientPublishResultFactory();
readonly MqttClientSubscribeResultFactory _clientSubscribeResultFactory = new MqttClientSubscribeResultFactory(); readonly MqttClientSubscribeResultFactory _clientSubscribeResultFactory = new MqttClientSubscribeResultFactory();
readonly MqttClientUnsubscribeResultFactory _clientUnsubscribeResultFactory = new MqttClientUnsubscribeResultFactory(); readonly MqttClientUnsubscribeResultFactory _clientUnsubscribeResultFactory = new MqttClientUnsubscribeResultFactory();
readonly MqttApplicationMessageFactory _applicationMessageFactory = new MqttApplicationMessageFactory();
readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher();
readonly object _disconnectLock = new object();
readonly AsyncEvent<MqttClientConnectedEventArgs> _connectedEvent = new AsyncEvent<MqttClientConnectedEventArgs>(); readonly AsyncEvent<MqttClientConnectedEventArgs> _connectedEvent = new AsyncEvent<MqttClientConnectedEventArgs>();
readonly AsyncEvent<MqttClientConnectingEventArgs> _connectingEvent = new AsyncEvent<MqttClientConnectingEventArgs>(); readonly AsyncEvent<MqttClientConnectingEventArgs> _connectingEvent = new AsyncEvent<MqttClientConnectingEventArgs>();
readonly AsyncEvent<MqttClientDisconnectedEventArgs> _disconnectedEvent = new AsyncEvent<MqttClientDisconnectedEventArgs>(); readonly AsyncEvent<MqttClientDisconnectedEventArgs> _disconnectedEvent = new AsyncEvent<MqttClientDisconnectedEventArgs>();
readonly object _disconnectLock = new object(); readonly AsyncEvent<MqttApplicationMessageReceivedEventArgs> _applicationMessageReceivedEvent = new AsyncEvent<MqttApplicationMessageReceivedEventArgs>();
readonly AsyncEvent<InspectMqttPacketEventArgs> _inspectPacketEvent = new AsyncEvent<InspectMqttPacketEventArgs>(); readonly AsyncEvent<InspectMqttPacketEventArgs> _inspectPacketEvent = new AsyncEvent<InspectMqttPacketEventArgs>();
readonly MqttNetSourceLogger _logger;
readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher();
readonly MqttPacketFactories _packetFactories = new MqttPacketFactories();
readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider(); readonly IMqttClientAdapterFactory _adapterFactory;
readonly IMqttNetLogger _rootLogger; readonly IMqttNetLogger _rootLogger;
readonly MqttNetSourceLogger _logger;
IMqttChannelAdapter _adapter;
CancellationTokenSource _backgroundCancellationTokenSource; CancellationTokenSource _backgroundCancellationTokenSource;
Task _packetReceiverTask;
Task _keepAlivePacketsSenderTask;
Task _publishPacketReceiverTask;
AsyncQueue<MqttPublishPacket> _publishPacketReceiverQueue;
IMqttChannelAdapter _adapter;
bool _cleanDisconnectInitiated; bool _cleanDisconnectInitiated;
volatile int _connectionStatus; volatile int _connectionStatus;
MqttClientDisconnectReason _disconnectReason; MqttClientDisconnectReason _disconnectReason;
string _disconnectReasonString;
Task _keepAlivePacketsSenderTask;
DateTime _lastPacketSentTimestamp; DateTime _lastPacketSentTimestamp;
Task _packetReceiverTask; string _disconnectReasonString;
AsyncQueue<MqttPublishPacket> _publishPacketReceiverQueue;
Task _publishPacketReceiverTask;
public MqttClient(IMqttClientAdapterFactory channelFactory, IMqttNetLogger logger) public MqttClient(IMqttClientAdapterFactory channelFactory, IMqttNetLogger logger)
{ {
@@ -61,12 +63,6 @@ namespace MQTTnet.Client
_logger = logger.WithSource(nameof(MqttClient)); _logger = logger.WithSource(nameof(MqttClient));
} }
public event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync
{
add => _applicationMessageReceivedEvent.AddHandler(value);
remove => _applicationMessageReceivedEvent.RemoveHandler(value);
}
public event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync public event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync
{ {
add => _connectedEvent.AddHandler(value); add => _connectedEvent.AddHandler(value);
@@ -85,6 +81,12 @@ namespace MQTTnet.Client
remove => _disconnectedEvent.RemoveHandler(value); remove => _disconnectedEvent.RemoveHandler(value);
} }
public event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync
{
add => _applicationMessageReceivedEvent.AddHandler(value);
remove => _applicationMessageReceivedEvent.RemoveHandler(value);
}
public event Func<InspectMqttPacketEventArgs, Task> InspectPackage public event Func<InspectMqttPacketEventArgs, Task> InspectPackage
{ {
add => _inspectPacketEvent.AddHandler(value); add => _inspectPacketEvent.AddHandler(value);
@@ -97,15 +99,8 @@ namespace MQTTnet.Client
public async Task<MqttClientConnectResult> ConnectAsync(MqttClientOptions options, CancellationToken cancellationToken = default) public async Task<MqttClientConnectResult> ConnectAsync(MqttClientOptions options, CancellationToken cancellationToken = default)
{ {
if (options == null) if (options == null) throw new ArgumentNullException(nameof(options));
{ if (options.ChannelOptions == null) throw new ArgumentException("ChannelOptions are not set.");
throw new ArgumentNullException(nameof(options));
}
if (options.ChannelOptions == null)
{
throw new ArgumentException("ChannelOptions are not set.");
}
ThrowIfConnected("It is not allowed to connect with a server after the connection is established."); ThrowIfConnected("It is not allowed to connect with a server after the connection is established.");
@@ -191,10 +186,7 @@ namespace MQTTnet.Client
public async Task DisconnectAsync(MqttClientDisconnectOptions options, CancellationToken cancellationToken = default) public async Task DisconnectAsync(MqttClientDisconnectOptions options, CancellationToken cancellationToken = default)
{ {
if (options is null) if (options is null) throw new ArgumentNullException(nameof(options));
{
throw new ArgumentNullException(nameof(options));
}
ThrowIfDisposed(); ThrowIfDisposed();
@@ -221,19 +213,63 @@ namespace MQTTnet.Client
} }
} }
public async Task PingAsync(CancellationToken cancellationToken = default) public Task PingAsync(CancellationToken cancellationToken = default)
{ {
if (cancellationToken.CanBeCanceled) return SendAndReceiveAsync<MqttPingRespPacket>(MqttPingReqPacket.Instance, cancellationToken);
}
public Task SendExtendedAuthenticationExchangeDataAsync(MqttExtendedAuthenticationExchangeData data, CancellationToken cancellationToken = default)
{
if (data == null) throw new ArgumentNullException(nameof(data));
ThrowIfDisposed();
ThrowIfNotConnected();
var authPacket = new MqttAuthPacket
{ {
await SendAndReceiveAsync<MqttPingRespPacket>(MqttPingReqPacket.Instance, cancellationToken).ConfigureAwait(false); // This must always be equal to the value from the CONNECT packet. So we use it here to ensure that.
} AuthenticationMethod = Options.AuthenticationMethod,
else AuthenticationData = data.AuthenticationData,
ReasonString = data.ReasonString,
UserProperties = data.UserProperties
};
return SendAsync(authPacket, cancellationToken);
}
public async Task<MqttClientSubscribeResult> SubscribeAsync(MqttClientSubscribeOptions options, CancellationToken cancellationToken = default)
{
if (options == null) throw new ArgumentNullException(nameof(options));
foreach (var topicFilter in options.TopicFilters)
{ {
using (var timeout = new CancellationTokenSource(Options.Timeout)) MqttTopicValidator.ThrowIfInvalidSubscribe(topicFilter.Topic);
{
await SendAndReceiveAsync<MqttPingRespPacket>(MqttPingReqPacket.Instance, timeout.Token).ConfigureAwait(false);
}
} }
ThrowIfDisposed();
ThrowIfNotConnected();
var subscribePacket = _packetFactories.Subscribe.Create(options);
subscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
var subAckPacket = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket, cancellationToken).ConfigureAwait(false);
return _clientSubscribeResultFactory.Create(subscribePacket, subAckPacket);
}
public async Task<MqttClientUnsubscribeResult> UnsubscribeAsync(MqttClientUnsubscribeOptions options, CancellationToken cancellationToken = default)
{
if (options == null) throw new ArgumentNullException(nameof(options));
ThrowIfDisposed();
ThrowIfNotConnected();
var unsubscribePacket = _packetFactories.Unsubscribe.Create(options);
unsubscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
var unsubAckPacket = await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket, cancellationToken).ConfigureAwait(false);
return _clientUnsubscribeResultFactory.Create(unsubscribePacket, unsubAckPacket);
} }
public Task<MqttClientPublishResult> PublishAsync(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken = default) public Task<MqttClientPublishResult> PublishAsync(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken = default)
@@ -268,67 +304,22 @@ namespace MQTTnet.Client
} }
} }
public Task SendExtendedAuthenticationExchangeDataAsync(MqttExtendedAuthenticationExchangeData data, CancellationToken cancellationToken = default) void Cleanup()
{ {
if (data == null) try
{ {
throw new ArgumentNullException(nameof(data)); _backgroundCancellationTokenSource?.Cancel(false);
} }
finally
ThrowIfDisposed();
ThrowIfNotConnected();
var authPacket = new MqttAuthPacket
{ {
// This must always be equal to the value from the CONNECT packet. So we use it here to ensure that. _backgroundCancellationTokenSource?.Dispose();
AuthenticationMethod = Options.AuthenticationMethod, _backgroundCancellationTokenSource = null;
AuthenticationData = data.AuthenticationData,
ReasonString = data.ReasonString,
UserProperties = data.UserProperties
};
return SendAsync(authPacket, cancellationToken); _publishPacketReceiverQueue?.Dispose();
} _publishPacketReceiverQueue = null;
public async Task<MqttClientSubscribeResult> SubscribeAsync(MqttClientSubscribeOptions options, CancellationToken cancellationToken = default) _adapter?.Dispose();
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
} }
foreach (var topicFilter in options.TopicFilters)
{
MqttTopicValidator.ThrowIfInvalidSubscribe(topicFilter.Topic);
}
ThrowIfDisposed();
ThrowIfNotConnected();
var subscribePacket = _packetFactories.Subscribe.Create(options);
subscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
var subAckPacket = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket, cancellationToken).ConfigureAwait(false);
return _clientSubscribeResultFactory.Create(subscribePacket, subAckPacket);
}
public async Task<MqttClientUnsubscribeResult> UnsubscribeAsync(MqttClientUnsubscribeOptions options, CancellationToken cancellationToken = default)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
ThrowIfDisposed();
ThrowIfNotConnected();
var unsubscribePacket = _packetFactories.Unsubscribe.Create(options);
unsubscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
var unsubAckPacket = await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket, cancellationToken).ConfigureAwait(false);
return _clientUnsubscribeResultFactory.Create(unsubscribePacket, unsubAckPacket);
} }
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
@@ -341,36 +332,6 @@ namespace MQTTnet.Client
base.Dispose(disposing); base.Dispose(disposing);
} }
Task AcknowledgeReceivedPublishPacket(MqttApplicationMessageReceivedEventArgs eventArgs, CancellationToken cancellationToken)
{
if (eventArgs.PublishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
{
// no response required
}
else if (eventArgs.PublishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)
{
if (!eventArgs.ProcessingFailed)
{
var pubAckPacket = _packetFactories.PubAck.Create(eventArgs);
return SendAsync(pubAckPacket, cancellationToken);
}
}
else if (eventArgs.PublishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
{
if (!eventArgs.ProcessingFailed)
{
var pubRecPacket = _packetFactories.PubRec.Create(eventArgs);
return SendAsync(pubRecPacket, cancellationToken);
}
}
else
{
throw new MqttProtocolViolationException("Received a not supported QoS level.");
}
return CompletedTask.Instance;
}
async Task<MqttClientConnectResult> AuthenticateAsync(MqttClientOptions options, CancellationToken cancellationToken) async Task<MqttClientConnectResult> AuthenticateAsync(MqttClientOptions options, CancellationToken cancellationToken)
{ {
MqttClientConnectResult result; MqttClientConnectResult result;
@@ -399,27 +360,32 @@ namespace MQTTnet.Client
return result; return result;
} }
void Cleanup() void ThrowIfNotConnected()
{ {
try if (!IsConnected)
{ {
_backgroundCancellationTokenSource?.Cancel(false); throw new MqttCommunicationException("The client is not connected.");
}
finally
{
_backgroundCancellationTokenSource?.Dispose();
_backgroundCancellationTokenSource = null;
_publishPacketReceiverQueue?.Dispose();
_publishPacketReceiverQueue = null;
_adapter?.Dispose();
} }
} }
MqttClientConnectionStatus CompareExchangeConnectionStatus(MqttClientConnectionStatus value, MqttClientConnectionStatus comparand) void ThrowIfConnected(string message)
{ {
return (MqttClientConnectionStatus)Interlocked.CompareExchange(ref _connectionStatus, (int)value, (int)comparand); if (IsConnected)
{
throw new MqttProtocolViolationException(message);
}
}
Task DisconnectInternalAsync(Task sender, Exception exception, MqttClientConnectResult connectResult)
{
var clientWasConnected = IsConnected;
if (!DisconnectIsPendingOrFinished())
{
return DisconnectCoreAsync(sender, exception, connectResult, clientWasConnected);
}
return PlatformAbstractionLayer.CompletedTask;
} }
async Task DisconnectCoreAsync(Task sender, Exception exception, MqttClientConnectResult connectResult, bool clientWasConnected) async Task DisconnectCoreAsync(Task sender, Exception exception, MqttClientConnectResult connectResult, bool clientWasConnected)
@@ -480,166 +446,28 @@ namespace MQTTnet.Client
} }
} }
Task DisconnectInternalAsync(Task sender, Exception exception, MqttClientConnectResult connectResult) void TryInitiateDisconnect()
{ {
var clientWasConnected = IsConnected; lock (_disconnectLock)
if (!DisconnectIsPendingOrFinished())
{
return DisconnectCoreAsync(sender, exception, connectResult, clientWasConnected);
}
return CompletedTask.Instance;
}
bool DisconnectIsPendingOrFinished()
{
var connectionStatus = (MqttClientConnectionStatus)_connectionStatus;
do
{
switch (connectionStatus)
{
case MqttClientConnectionStatus.Disconnected:
case MqttClientConnectionStatus.Disconnecting:
return true;
case MqttClientConnectionStatus.Connected:
case MqttClientConnectionStatus.Connecting:
// This will compare the _connectionStatus to old value and set it to "MqttClientConnectionStatus.Disconnecting" afterwards.
// So the first caller will get a "false" and all subsequent ones will get "true".
var curStatus = CompareExchangeConnectionStatus(MqttClientConnectionStatus.Disconnecting, connectionStatus);
if (curStatus == connectionStatus)
{
return false;
}
connectionStatus = curStatus;
break;
}
} while (true);
}
void EnqueueReceivedPublishPacket(MqttPublishPacket publishPacket)
{
try
{
_publishPacketReceiverQueue.Enqueue(publishPacket);
}
catch (Exception exception)
{
_logger.Error(exception, "Error while queueing application message.");
}
}
async Task<MqttApplicationMessageReceivedEventArgs> HandleReceivedApplicationMessageAsync(MqttPublishPacket publishPacket)
{
var applicationMessage = _applicationMessageFactory.Create(publishPacket);
var eventArgs = new MqttApplicationMessageReceivedEventArgs(Options.ClientId, applicationMessage, publishPacket, AcknowledgeReceivedPublishPacket);
await _applicationMessageReceivedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
return eventArgs;
}
Task ProcessReceivedAuthPacket(MqttAuthPacket authPacket)
{
var extendedAuthenticationExchangeHandler = Options.ExtendedAuthenticationExchangeHandler;
if (extendedAuthenticationExchangeHandler != null)
{
return extendedAuthenticationExchangeHandler.HandleRequestAsync(new MqttExtendedAuthenticationExchangeContext(authPacket, this));
}
return CompletedTask.Instance;
}
Task ProcessReceivedDisconnectPacket(MqttDisconnectPacket disconnectPacket)
{
_disconnectReason = (MqttClientDisconnectReason)disconnectPacket.ReasonCode;
_disconnectReasonString = disconnectPacket.ReasonString;
// Also dispatch disconnect to waiting threads to generate a proper exception.
_packetDispatcher.FailAll(new MqttUnexpectedDisconnectReceivedException(disconnectPacket));
return DisconnectInternalAsync(_packetReceiverTask, null, null);
}
async Task ProcessReceivedPublishPackets(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{ {
try try
{ {
var publishPacketDequeueResult = await _publishPacketReceiverQueue.TryDequeueAsync(cancellationToken).ConfigureAwait(false); _backgroundCancellationTokenSource?.Cancel(false);
if (!publishPacketDequeueResult.IsSuccess)
{
return;
}
var publishPacket = publishPacketDequeueResult.Item;
var eventArgs = await HandleReceivedApplicationMessageAsync(publishPacket).ConfigureAwait(false);
if (eventArgs.AutoAcknowledge)
{
await eventArgs.AcknowledgeAsync(cancellationToken).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
} }
catch (Exception exception) catch (Exception exception)
{ {
_logger.Error(exception, "Error while handling application message."); _logger.Warning(exception, "Error while initiating disconnect.");
} }
} }
} }
Task ProcessReceivedPubRecPacket(MqttPubRecPacket pubRecPacket, CancellationToken cancellationToken) Task SendAsync(MqttPacket packet, CancellationToken cancellationToken)
{ {
if (!_packetDispatcher.TryDispatch(pubRecPacket)) cancellationToken.ThrowIfCancellationRequested();
{
// The packet is unknown. Probably due to a restart of the client.
// So wen send this to the server to trigger a full resend of the message.
var pubRelPacket = _packetFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.PacketIdentifierNotFound);
return SendAsync(pubRelPacket, cancellationToken);
}
return CompletedTask.Instance; _lastPacketSentTimestamp = DateTime.UtcNow;
}
Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken) return _adapter.SendPacketAsync(packet, cancellationToken);
{
var pubCompPacket = _packetFactories.PubComp.Create(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success);
return SendAsync(pubCompPacket, cancellationToken);
}
async Task<MqttClientPublishResult> PublishAtLeastOnceAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
{
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
var pubAckPacket = await SendAndReceiveAsync<MqttPubAckPacket>(publishPacket, cancellationToken).ConfigureAwait(false);
return _clientPublishResultFactory.Create(pubAckPacket);
}
async Task<MqttClientPublishResult> PublishAtMostOnce(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
{
// No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier]
await SendAsync(publishPacket, cancellationToken).ConfigureAwait(false);
return _clientPublishResultFactory.Create(null);
}
async Task<MqttClientPublishResult> PublishExactlyOnceAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
{
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
var pubRecPacket = await SendAndReceiveAsync<MqttPubRecPacket>(publishPacket, cancellationToken).ConfigureAwait(false);
var pubRelPacket = _packetFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success);
var pubCompPacket = await SendAndReceiveAsync<MqttPubCompPacket>(pubRelPacket, cancellationToken).ConfigureAwait(false);
return _clientPublishResultFactory.Create(pubRecPacket, pubCompPacket);
} }
async Task<TResponsePacket> SendAndReceiveAsync<TResponsePacket>(MqttPacket requestPacket, CancellationToken cancellationToken) where TResponsePacket : MqttPacket async Task<TResponsePacket> SendAndReceiveAsync<TResponsePacket>(MqttPacket requestPacket, CancellationToken cancellationToken) where TResponsePacket : MqttPacket
@@ -680,43 +508,120 @@ namespace MQTTnet.Client
} }
} }
Task SendAsync(MqttPacket packet, CancellationToken cancellationToken) async Task TrySendKeepAliveMessagesAsync(CancellationToken cancellationToken)
{ {
cancellationToken.ThrowIfCancellationRequested(); try
_lastPacketSentTimestamp = DateTime.UtcNow;
return _adapter.SendPacketAsync(packet, cancellationToken);
}
void ThrowIfConnected(string message)
{
if (IsConnected)
{ {
throw new MqttProtocolViolationException(message); _logger.Verbose("Start sending keep alive packets.");
var keepAlivePeriod = Options.KeepAlivePeriod;
while (!cancellationToken.IsCancellationRequested)
{
// Values described here: [MQTT-3.1.2-24].
var timeWithoutPacketSent = DateTime.UtcNow - _lastPacketSentTimestamp;
if (timeWithoutPacketSent > keepAlivePeriod)
{
await PingAsync(cancellationToken).ConfigureAwait(false);
}
// Wait a fixed time in all cases. Calculation of the remaining time is complicated
// due to some edge cases and was buggy in the past. Now we wait several ms because the
// min keep alive value is one second so that the server will wait 1.5 seconds for a PING
// packet.
await Task.Delay(250, cancellationToken).ConfigureAwait(false);
}
}
catch (Exception exception)
{
if (_cleanDisconnectInitiated)
{
return;
}
if (exception is OperationCanceledException)
{
return;
}
else if (exception is MqttCommunicationException)
{
_logger.Warning(exception, "Communication error while sending/receiving keep alive packets.");
}
else
{
_logger.Error(exception, "Error exception while sending/receiving keep alive packets.");
}
await DisconnectInternalAsync(_keepAlivePacketsSenderTask, exception, null).ConfigureAwait(false);
}
finally
{
_logger.Verbose("Stopped sending keep alive packets.");
} }
} }
void ThrowIfNotConnected() async Task TryReceivePacketsAsync(CancellationToken cancellationToken)
{ {
if (!IsConnected) try
{ {
throw new MqttCommunicationException("The client is not connected."); _logger.Verbose("Start receiving packets.");
}
}
void TryInitiateDisconnect() while (!cancellationToken.IsCancellationRequested)
{ {
lock (_disconnectLock) MqttPacket packet;
var packetTask = _adapter.ReceivePacketAsync(cancellationToken);
if (packetTask.IsCompleted)
{
packet = packetTask.Result;
}
else
{
packet = await packetTask.ConfigureAwait(false);
}
if (cancellationToken.IsCancellationRequested)
{
return;
}
if (packet == null)
{
await DisconnectInternalAsync(_packetReceiverTask, null, null).ConfigureAwait(false);
return;
}
await TryProcessReceivedPacketAsync(packet, cancellationToken).ConfigureAwait(false);
}
}
catch (Exception exception)
{ {
try if (_cleanDisconnectInitiated)
{ {
_backgroundCancellationTokenSource?.Cancel(false); return;
} }
catch (Exception exception)
if (exception is OperationCanceledException)
{ {
_logger.Warning(exception, "Error while initiating disconnect.");
} }
else if (exception is MqttCommunicationException)
{
_logger.Warning(exception, "Communication error while receiving packets.");
}
else
{
_logger.Error(exception, "Error while receiving packets.");
}
_packetDispatcher.FailAll(exception);
await DisconnectInternalAsync(_packetReceiverTask, exception, null).ConfigureAwait(false);
}
finally
{
_logger.Verbose("Stopped receiving packets.");
} }
} }
@@ -785,125 +690,158 @@ namespace MQTTnet.Client
} }
} }
async Task TryReceivePacketsAsync(CancellationToken cancellationToken) void EnqueueReceivedPublishPacket(MqttPublishPacket publishPacket)
{ {
try try
{ {
_logger.Verbose("Start receiving packets."); _publishPacketReceiverQueue.Enqueue(publishPacket);
while (!cancellationToken.IsCancellationRequested)
{
MqttPacket packet;
var packetTask = _adapter.ReceivePacketAsync(cancellationToken);
if (packetTask.IsCompleted)
{
packet = packetTask.Result;
}
else
{
packet = await packetTask.ConfigureAwait(false);
}
if (cancellationToken.IsCancellationRequested)
{
return;
}
if (packet == null)
{
await DisconnectInternalAsync(_packetReceiverTask, null, null).ConfigureAwait(false);
return;
}
await TryProcessReceivedPacketAsync(packet, cancellationToken).ConfigureAwait(false);
}
} }
catch (Exception exception) catch (Exception exception)
{ {
if (_cleanDisconnectInitiated) _logger.Error(exception, "Error while queueing application message.");
{
return;
}
if (exception is OperationCanceledException)
{
}
else if (exception is MqttCommunicationException)
{
_logger.Warning(exception, "Communication error while receiving packets.");
}
else
{
_logger.Error(exception, "Error while receiving packets.");
}
_packetDispatcher.FailAll(exception);
await DisconnectInternalAsync(_packetReceiverTask, exception, null).ConfigureAwait(false);
}
finally
{
_logger.Verbose("Stopped receiving packets.");
} }
} }
async Task TrySendKeepAliveMessagesAsync(CancellationToken cancellationToken) async Task ProcessReceivedPublishPackets(CancellationToken cancellationToken)
{ {
try while (!cancellationToken.IsCancellationRequested)
{ {
_logger.Verbose("Start sending keep alive packets."); try
var keepAlivePeriod = Options.KeepAlivePeriod;
while (!cancellationToken.IsCancellationRequested)
{ {
// Values described here: [MQTT-3.1.2-24]. var publishPacketDequeueResult = await _publishPacketReceiverQueue.TryDequeueAsync(cancellationToken).ConfigureAwait(false);
var timeWithoutPacketSent = DateTime.UtcNow - _lastPacketSentTimestamp; if (!publishPacketDequeueResult.IsSuccess)
if (timeWithoutPacketSent > keepAlivePeriod)
{ {
using (var timeoutCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) return;
{
timeoutCancellationTokenSource.CancelAfter(Options.Timeout);
await PingAsync(timeoutCancellationTokenSource.Token).ConfigureAwait(false);
}
} }
// Wait a fixed time in all cases. Calculation of the remaining time is complicated var publishPacket = publishPacketDequeueResult.Item;
// due to some edge cases and was buggy in the past. Now we wait several ms because the var eventArgs = await HandleReceivedApplicationMessageAsync(publishPacket).ConfigureAwait(false);
// min keep alive value is one second so that the server will wait 1.5 seconds for a PING
// packet.
await Task.Delay(250, cancellationToken).ConfigureAwait(false);
}
}
catch (Exception exception)
{
if (_cleanDisconnectInitiated)
{
return;
}
if (exception is OperationCanceledException) if (eventArgs.AutoAcknowledge)
{ {
return; await eventArgs.AcknowledgeAsync(cancellationToken).ConfigureAwait(false);
}
} }
else if (exception is MqttCommunicationException) catch (OperationCanceledException)
{ {
_logger.Warning(exception, "Communication error while sending/receiving keep alive packets.");
} }
else catch (Exception exception)
{ {
_logger.Error(exception, "Error exception while sending/receiving keep alive packets."); _logger.Error(exception, "Error while handling application message.");
} }
}
}
await DisconnectInternalAsync(_keepAlivePacketsSenderTask, exception, null).ConfigureAwait(false); Task AcknowledgeReceivedPublishPacket(MqttApplicationMessageReceivedEventArgs eventArgs, CancellationToken cancellationToken)
} {
finally if (eventArgs.PublishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
{ {
_logger.Verbose("Stopped sending keep alive packets."); // no response required
} }
else if (eventArgs.PublishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)
{
if (!eventArgs.ProcessingFailed)
{
var pubAckPacket = _packetFactories.PubAck.Create(eventArgs);
return SendAsync(pubAckPacket, cancellationToken);
}
}
else if (eventArgs.PublishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
{
if (!eventArgs.ProcessingFailed)
{
var pubRecPacket = _packetFactories.PubRec.Create(eventArgs);
return SendAsync(pubRecPacket, cancellationToken);
}
}
else
{
throw new MqttProtocolViolationException("Received a not supported QoS level.");
}
return PlatformAbstractionLayer.CompletedTask;
}
Task ProcessReceivedPubRecPacket(MqttPubRecPacket pubRecPacket, CancellationToken cancellationToken)
{
if (!_packetDispatcher.TryDispatch(pubRecPacket))
{
// The packet is unknown. Probably due to a restart of the client.
// So wen send this to the server to trigger a full resend of the message.
var pubRelPacket = _packetFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.PacketIdentifierNotFound);
return SendAsync(pubRelPacket, cancellationToken);
}
return PlatformAbstractionLayer.CompletedTask;
}
Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
{
var pubCompPacket = _packetFactories.PubComp.Create(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success);
return SendAsync(pubCompPacket, cancellationToken);
}
Task ProcessReceivedDisconnectPacket(MqttDisconnectPacket disconnectPacket)
{
_disconnectReason = (MqttClientDisconnectReason) disconnectPacket.ReasonCode;
_disconnectReasonString = disconnectPacket.ReasonString;
// Also dispatch disconnect to waiting threads to generate a proper exception.
_packetDispatcher.FailAll(new MqttUnexpectedDisconnectReceivedException(disconnectPacket));
return DisconnectInternalAsync(_packetReceiverTask, null, null);
}
Task ProcessReceivedAuthPacket(MqttAuthPacket authPacket)
{
var extendedAuthenticationExchangeHandler = Options.ExtendedAuthenticationExchangeHandler;
if (extendedAuthenticationExchangeHandler != null)
{
return extendedAuthenticationExchangeHandler.HandleRequestAsync(new MqttExtendedAuthenticationExchangeContext(authPacket, this));
}
return PlatformAbstractionLayer.CompletedTask;
}
async Task<MqttClientPublishResult> PublishAtMostOnce(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
{
// No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier]
await SendAsync(publishPacket, cancellationToken)
.ConfigureAwait(false);
return _clientPublishResultFactory.Create(null);
}
async Task<MqttClientPublishResult> PublishAtLeastOnceAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
{
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
var pubAckPacket = await SendAndReceiveAsync<MqttPubAckPacket>(publishPacket, cancellationToken).ConfigureAwait(false);
return _clientPublishResultFactory.Create(pubAckPacket);
}
async Task<MqttClientPublishResult> PublishExactlyOnceAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
{
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
var pubRecPacket = await SendAndReceiveAsync<MqttPubRecPacket>(publishPacket, cancellationToken).ConfigureAwait(false);
var pubRelPacket = _packetFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success);
var pubCompPacket = await SendAndReceiveAsync<MqttPubCompPacket>(pubRelPacket, cancellationToken).ConfigureAwait(false);
return _clientPublishResultFactory.Create(pubRecPacket, pubCompPacket);
}
async Task<MqttApplicationMessageReceivedEventArgs> HandleReceivedApplicationMessageAsync(MqttPublishPacket publishPacket)
{
var applicationMessage = _applicationMessageFactory.Create(publishPacket);
var eventArgs = new MqttApplicationMessageReceivedEventArgs(Options.ClientId, applicationMessage, publishPacket, AcknowledgeReceivedPublishPacket);
await _applicationMessageReceivedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
return eventArgs;
} }
async Task WaitForTaskAsync(Task task, Task sender) async Task WaitForTaskAsync(Task task, Task sender)
@@ -936,5 +874,37 @@ namespace MQTTnet.Client
{ {
} }
} }
bool DisconnectIsPendingOrFinished()
{
var connectionStatus = (MqttClientConnectionStatus)_connectionStatus;
do
{
switch (connectionStatus)
{
case MqttClientConnectionStatus.Disconnected:
case MqttClientConnectionStatus.Disconnecting:
return true;
case MqttClientConnectionStatus.Connected:
case MqttClientConnectionStatus.Connecting:
// This will compare the _connectionStatus to old value and set it to "MqttClientConnectionStatus.Disconnecting" afterwards.
// So the first caller will get a "false" and all subsequent ones will get "true".
var curStatus = CompareExchangeConnectionStatus(MqttClientConnectionStatus.Disconnecting, connectionStatus);
if (curStatus == connectionStatus)
{
return false;
}
connectionStatus = curStatus;
break;
}
} while (true);
}
MqttClientConnectionStatus CompareExchangeConnectionStatus(MqttClientConnectionStatus value, MqttClientConnectionStatus comparand)
{
return (MqttClientConnectionStatus)Interlocked.CompareExchange(ref _connectionStatus, (int)value, (int)comparand);
}
} }
} }
@@ -14,26 +14,6 @@ namespace MQTTnet.Client
{ {
public static class MqttClientExtensions public static class MqttClientExtensions
{ {
public static async Task<bool> TryPingAsync(this IMqttClient client, CancellationToken cancellationToken = default)
{
if (client == null)
{
throw new ArgumentNullException(nameof(client));
}
try
{
await client.PingAsync(cancellationToken).ConfigureAwait(false);
return true;
}
catch
{
// Ignore errors.
}
return false;
}
public static Task DisconnectAsync(this IMqttClient client, MqttClientDisconnectReason reason = MqttClientDisconnectReason.NormalDisconnection, string reasonString = null) public static Task DisconnectAsync(this IMqttClient client, MqttClientDisconnectReason reason = MqttClientDisconnectReason.NormalDisconnection, string reasonString = null)
{ {
if (client == null) if (client == null)
@@ -24,58 +24,55 @@ namespace MQTTnet.Client
{ {
ClientId = clientId; ClientId = clientId;
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage)); ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
PublishPacket = publishPacket ?? throw new ArgumentNullException(nameof(publishPacket)); PublishPacket = publishPacket;
PacketIdentifier = publishPacket.PacketIdentifier;
_acknowledgeHandler = acknowledgeHandler; _acknowledgeHandler = acknowledgeHandler;
} }
public MqttApplicationMessage ApplicationMessage { get; } internal MqttPublishPacket PublishPacket { get; set; }
/// <summary> /// <summary>
/// Gets or sets whether the library should send MQTT ACK packets automatically if required. /// Gets the client identifier.
/// </summary> /// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
public bool AutoAcknowledge { get; set; } = true;
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary> /// </summary>
public string ClientId { get; } public string ClientId { get; }
/// <summary> public MqttApplicationMessage ApplicationMessage { get; }
/// Gets or sets whether this message was handled.
/// This value can be used in user code for custom control flow.
/// </summary>
public bool IsHandled { get; set; }
/// <summary>
/// Gets the identifier of the MQTT packet
/// </summary>
public ushort PacketIdentifier => PublishPacket.PacketIdentifier;
/// <summary>
/// Indicates if the processing of this PUBLISH packet has failed.
/// If the processing has failed the client will not send an ACK packet etc.
/// </summary>
public bool ProcessingFailed { get; set; } public bool ProcessingFailed { get; set; }
/// <summary> /// <summary>
/// Gets or sets the reason code which will be sent to the server. /// Gets or sets the reason code which will be sent to the server.
/// </summary> /// </summary>
public MqttApplicationMessageReceivedReasonCode ReasonCode { get; set; } = MqttApplicationMessageReceivedReasonCode.Success; public MqttApplicationMessageReceivedReasonCode ReasonCode { get; set; } = MqttApplicationMessageReceivedReasonCode.Success;
/// <summary> /// <summary>
/// Gets or sets the reason string which will be sent to the server in the ACK packet. /// Gets or sets the user properties which will be sent to the server in the ACK packet etc.
/// </summary>
public List<MqttUserProperty> ResponseUserProperties { get; } = new List<MqttUserProperty>();
/// <summary>
/// Gets or sets the reason string which will be sent to the server in the ACK packet.
/// </summary> /// </summary>
public string ResponseReasonString { get; set; } public string ResponseReasonString { get; set; }
/// <summary> /// <summary>
/// Gets or sets the user properties which will be sent to the server in the ACK packet etc. /// Gets or sets whether this message was handled.
/// This value can be used in user code for custom control flow.
/// </summary> /// </summary>
public List<MqttUserProperty> ResponseUserProperties { get; } = new List<MqttUserProperty>(); public bool IsHandled { get; set; }
/// <summary>
/// Gets or sets whether the library should send MQTT ACK packets automatically if required.
/// </summary>
public bool AutoAcknowledge { get; set; } = true;
public object Tag { get; set; } public object Tag { get; set; }
internal MqttPublishPacket PublishPacket { get; set; } /// <summary>
/// Gets the indentifier of MQTT packet
/// </summary>
public ushort PacketIdentifier { get; }
public Task AcknowledgeAsync(CancellationToken cancellationToken) public Task AcknowledgeAsync(CancellationToken cancellationToken)
{ {
+4 -4
View File
@@ -6,13 +6,13 @@ using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text; using System.Text;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
using MQTTnet.Internal; using MQTTnet.Implementations;
namespace MQTTnet.Formatter namespace MQTTnet.Formatter
{ {
public sealed class MqttBufferReader public sealed class MqttBufferReader
{ {
byte[] _buffer = EmptyBuffer.Array; byte[] _buffer = PlatformAbstractionLayer.EmptyByteArray;
int _initialPosition; int _initialPosition;
int _length; int _length;
int _position; int _position;
@@ -29,7 +29,7 @@ namespace MQTTnet.Formatter
if (length == 0) if (length == 0)
{ {
return EmptyBuffer.Array; return PlatformAbstractionLayer.EmptyByteArray;
} }
ValidateReceiveBuffer(length); ValidateReceiveBuffer(length);
@@ -66,7 +66,7 @@ namespace MQTTnet.Formatter
if (bufferLength == 0) if (bufferLength == 0)
{ {
return EmptyBuffer.Array; return PlatformAbstractionLayer.EmptyByteArray;
} }
var buffer = new byte[bufferLength]; var buffer = new byte[bufferLength];
+1 -2
View File
@@ -5,13 +5,12 @@
using System; using System;
using System.Linq; using System.Linq;
using MQTTnet.Implementations; using MQTTnet.Implementations;
using MQTTnet.Internal;
namespace MQTTnet.Formatter namespace MQTTnet.Formatter
{ {
public readonly struct MqttPacketBuffer public readonly struct MqttPacketBuffer
{ {
static readonly ArraySegment<byte> EmptyPayload = EmptyBuffer.ArraySegment; static readonly ArraySegment<byte> EmptyPayload = PlatformAbstractionLayer.EmptyByteArraySegment;
public MqttPacketBuffer(ArraySegment<byte> packet, ArraySegment<byte> payload) public MqttPacketBuffer(ArraySegment<byte> packet, ArraySegment<byte> payload)
{ {
@@ -15,20 +15,14 @@ namespace MQTTnet.Implementations
public sealed class CrossPlatformSocket : IDisposable public sealed class CrossPlatformSocket : IDisposable
{ {
readonly Socket _socket; readonly Socket _socket;
#if !NET5_0_OR_GREATER
readonly Action _socketDisposeAction; readonly Action _socketDisposeAction;
#endif
NetworkStream _networkStream; NetworkStream _networkStream;
public CrossPlatformSocket(AddressFamily addressFamily) public CrossPlatformSocket(AddressFamily addressFamily)
{ {
_socket = new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp); _socket = new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp);
#if !NET5_0_OR_GREATER
_socketDisposeAction = _socket.Dispose; _socketDisposeAction = _socket.Dispose;
#endif
} }
public CrossPlatformSocket() public CrossPlatformSocket()
@@ -37,76 +31,17 @@ namespace MQTTnet.Implementations
// will make use of dual mode in the .net framework. // will make use of dual mode in the .net framework.
_socket = new Socket(SocketType.Stream, ProtocolType.Tcp); _socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
#if !NET5_0_OR_GREATER
_socketDisposeAction = _socket.Dispose; _socketDisposeAction = _socket.Dispose;
#endif
} }
CrossPlatformSocket(Socket socket) public CrossPlatformSocket(Socket socket)
{ {
_socket = socket ?? throw new ArgumentNullException(nameof(socket)); _socket = socket ?? throw new ArgumentNullException(nameof(socket));
_networkStream = new NetworkStream(socket, true); _networkStream = new NetworkStream(socket, true);
#if !NET5_0_OR_GREATER
_socketDisposeAction = _socket.Dispose; _socketDisposeAction = _socket.Dispose;
#endif
} }
public bool DualMode
{
get => _socket.DualMode;
set => _socket.DualMode = value;
}
public bool IsConnected => _socket.Connected;
public bool KeepAlive
{
get => _socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive) as int? == 1;
set => _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, value ? 1 : 0);
}
public int TcpKeepAliveInterval
{
#if NETCOREAPP3_0_OR_GREATER
get => _socket.GetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval) as int? ?? 0;
set => _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, value);
#else
get { throw new NotSupportedException("TcpKeepAliveInterval requires at least net5.0."); }
set { throw new NotSupportedException("TcpKeepAliveInterval requires at least net5.0."); }
#endif
}
public int TcpKeepAliveRetryCount
{
#if NETCOREAPP3_0_OR_GREATER
get => _socket.GetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount) as int? ?? 0;
set => _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount, value);
#else
get { throw new NotSupportedException("TcpKeepAliveRetryCount requires at least net5.0."); }
set { throw new NotSupportedException("TcpKeepAliveRetryCount requires at least net5.0."); }
#endif
}
public int TcpKeepAliveTime
{
#if NETCOREAPP3_0_OR_GREATER
get => _socket.GetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime) as int? ?? 0;
set => _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, value);
#else
get { throw new NotSupportedException("TcpKeepAliveTime requires at least net5.0."); }
set { throw new NotSupportedException("TcpKeepAliveTime requires at least net5.0."); }
#endif
}
public LingerOption LingerState
{
get => _socket.LingerState;
set => _socket.LingerState = value;
}
public EndPoint LocalEndPoint => _socket.LocalEndPoint;
public bool NoDelay public bool NoDelay
{ {
// We cannot use the _NoDelay_ property from the socket because there is an issue in .NET 4.5.2, 4.6. // We cannot use the _NoDelay_ property from the socket because there is an issue in .NET 4.5.2, 4.6.
@@ -116,20 +51,24 @@ namespace MQTTnet.Implementations
set => _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, value ? 1 : 0); set => _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, value ? 1 : 0);
} }
public LingerOption LingerState
{
get => _socket.LingerState;
set => _socket.LingerState = value;
}
public bool DualMode
{
get => _socket.DualMode;
set => _socket.DualMode = value;
}
public int ReceiveBufferSize public int ReceiveBufferSize
{ {
get => _socket.ReceiveBufferSize; get => _socket.ReceiveBufferSize;
set => _socket.ReceiveBufferSize = value; set => _socket.ReceiveBufferSize = value;
} }
public EndPoint RemoteEndPoint => _socket.RemoteEndPoint;
public bool ReuseAddress
{
get => _socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress) as int? != 0;
set => _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, value ? 1 : 0);
}
public int SendBufferSize public int SendBufferSize
{ {
get => _socket.SendBufferSize; get => _socket.SendBufferSize;
@@ -142,6 +81,16 @@ namespace MQTTnet.Implementations
set => _socket.SendTimeout = value; set => _socket.SendTimeout = value;
} }
public EndPoint RemoteEndPoint => _socket.RemoteEndPoint;
public bool ReuseAddress
{
get => (int)_socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress) != 0;
set => _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, value ? 1 : 0);
}
public bool IsConnected => _socket.Connected;
public async Task<CrossPlatformSocket> AcceptAsync() public async Task<CrossPlatformSocket> AcceptAsync()
{ {
try try
@@ -160,6 +109,8 @@ namespace MQTTnet.Implementations
} }
} }
public EndPoint LocalEndPoint => _socket.LocalEndPoint;
public void Bind(EndPoint localEndPoint) public void Bind(EndPoint localEndPoint)
{ {
if (localEndPoint is null) if (localEndPoint is null)
@@ -170,6 +121,11 @@ namespace MQTTnet.Implementations
_socket.Bind(localEndPoint); _socket.Bind(localEndPoint);
} }
public void Listen(int connectionBacklog)
{
_socket.Listen(connectionBacklog);
}
public async Task ConnectAsync(string host, int port, CancellationToken cancellationToken) public async Task ConnectAsync(string host, int port, CancellationToken cancellationToken)
{ {
if (host is null) if (host is null)
@@ -181,17 +137,11 @@ namespace MQTTnet.Implementations
try try
{ {
#if NET5_0_OR_GREATER
if (_networkStream != null)
{
await _networkStream.DisposeAsync().ConfigureAwait(false);
}
await _socket.ConnectAsync(host, port, cancellationToken).ConfigureAwait(false);
#else
_networkStream?.Dispose(); _networkStream?.Dispose();
#if NET5_0_OR_GREATER
await _socket.ConnectAsync(host, port, cancellationToken).ConfigureAwait(false);
#else
// Workaround for: https://github.com/dotnet/corefx/issues/24430 // Workaround for: https://github.com/dotnet/corefx/issues/24430
using (cancellationToken.Register(_socketDisposeAction)) using (cancellationToken.Register(_socketDisposeAction))
{ {
@@ -225,26 +175,20 @@ namespace MQTTnet.Implementations
} }
} }
public void Dispose() public async Task SendAsync(ArraySegment<byte> buffer, SocketFlags socketFlags)
{ {
_networkStream?.Dispose(); try
_socket?.Dispose();
}
public NetworkStream GetStream()
{
var networkStream = _networkStream;
if (networkStream == null)
{ {
throw new IOException("The socket is not connected."); #if NET452 || NET461
await Task.Factory.FromAsync(SocketWrapper.BeginSend, _socket.EndSend, new SocketWrapper(_socket, buffer, socketFlags)).ConfigureAwait(false);
#else
await _socket.SendAsync(buffer, socketFlags).ConfigureAwait(false);
#endif
}
catch (ObjectDisposedException)
{
// This will happen when _socket.EndConnect gets called by Task library but the socket is already disposed.
} }
return networkStream;
}
public void Listen(int connectionBacklog)
{
_socket.Listen(connectionBacklog);
} }
public async Task<int> ReceiveAsync(ArraySegment<byte> buffer, SocketFlags socketFlags) public async Task<int> ReceiveAsync(ArraySegment<byte> buffer, SocketFlags socketFlags)
@@ -264,27 +208,28 @@ namespace MQTTnet.Implementations
} }
} }
public async Task SendAsync(ArraySegment<byte> buffer, SocketFlags socketFlags) public NetworkStream GetStream()
{ {
try var networkStream = _networkStream;
if (networkStream == null)
{ {
#if NET452 || NET461 throw new IOException("The socket is not connected.");
await Task.Factory.FromAsync(SocketWrapper.BeginSend, _socket.EndSend, new SocketWrapper(_socket, buffer, socketFlags)).ConfigureAwait(false);
#else
await _socket.SendAsync(buffer, socketFlags).ConfigureAwait(false);
#endif
}
catch (ObjectDisposedException)
{
// This will happen when _socket.EndConnect gets called by Task library but the socket is already disposed.
} }
return networkStream;
}
public void Dispose()
{
_networkStream?.Dispose();
_socket?.Dispose();
} }
#if NET452 || NET461 #if NET452 || NET461
sealed class SocketWrapper sealed class SocketWrapper
{ {
readonly ArraySegment<byte> _buffer;
readonly Socket _socket; readonly Socket _socket;
readonly ArraySegment<byte> _buffer;
readonly SocketFlags _socketFlags; readonly SocketFlags _socketFlags;
public SocketWrapper(Socket socket, ArraySegment<byte> buffer, SocketFlags socketFlags) public SocketWrapper(Socket socket, ArraySegment<byte> buffer, SocketFlags socketFlags)
@@ -294,28 +239,16 @@ namespace MQTTnet.Implementations
_socketFlags = socketFlags; _socketFlags = socketFlags;
} }
public static IAsyncResult BeginReceive(AsyncCallback callback, object state)
{
var socketWrapper = (SocketWrapper)state;
return socketWrapper._socket.BeginReceive(
socketWrapper._buffer.Array,
socketWrapper._buffer.Offset,
socketWrapper._buffer.Count,
socketWrapper._socketFlags,
callback,
state);
}
public static IAsyncResult BeginSend(AsyncCallback callback, object state) public static IAsyncResult BeginSend(AsyncCallback callback, object state)
{ {
var socketWrapper = (SocketWrapper)state; var socketWrapper = (SocketWrapper)state;
return socketWrapper._socket.BeginSend( return socketWrapper._socket.BeginSend(socketWrapper._buffer.Array, socketWrapper._buffer.Offset, socketWrapper._buffer.Count, socketWrapper._socketFlags, callback, state);
socketWrapper._buffer.Array, }
socketWrapper._buffer.Offset,
socketWrapper._buffer.Count, public static IAsyncResult BeginReceive(AsyncCallback callback, object state)
socketWrapper._socketFlags, {
callback, var socketWrapper = (SocketWrapper)state;
state); return socketWrapper._socket.BeginReceive(socketWrapper._buffer.Array, socketWrapper._buffer.Offset, socketWrapper._buffer.Count, socketWrapper._socketFlags, callback, state);
} }
} }
#endif #endif
@@ -108,12 +108,12 @@ namespace MQTTnet.Implementations
return _readStream.ReadAsync(buffer, offset, count, cancellationToken); return _readStream.ReadAsync(buffer, offset, count, cancellationToken);
} }
public Task WriteAsync(ArraySegment<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken) public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{ {
// In the write method only the internal buffer will be filled. So here is no // In the write method only the internal buffer will be filled. So here is no
// async/await required. The real network transmit is done when calling the // async/await required. The real network transmit is done when calling the
// Flush method. // Flush method.
_writeStream.Write(buffer.Array, buffer.Offset, buffer.Count); _writeStream.Write(buffer, offset, count);
return _writeStream.FlushAsync(cancellationToken); return _writeStream.FlushAsync(cancellationToken);
} }
@@ -14,7 +14,6 @@ using System.Runtime.ExceptionServices;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Internal;
namespace MQTTnet.Implementations namespace MQTTnet.Implementations
{ {
@@ -155,7 +154,7 @@ namespace MQTTnet.Implementations
public Task DisconnectAsync(CancellationToken cancellationToken) public Task DisconnectAsync(CancellationToken cancellationToken)
{ {
Dispose(); Dispose();
return CompletedTask.Instance; return Task.FromResult(0);
} }
public void Dispose() public void Dispose()
@@ -226,7 +225,7 @@ namespace MQTTnet.Implementations
} }
} }
public async Task WriteAsync(ArraySegment<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken) public async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
@@ -240,12 +239,12 @@ namespace MQTTnet.Implementations
} }
#if NETCOREAPP3_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER #if NETCOREAPP3_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
await stream.WriteAsync(buffer.AsMemory(), cancellationToken).ConfigureAwait(false); await stream.WriteAsync(buffer.AsMemory(offset, count), cancellationToken).ConfigureAwait(false);
#else #else
// Workaround for: https://github.com/dotnet/corefx/issues/24430 // Workaround for: https://github.com/dotnet/corefx/issues/24430
using (cancellationToken.Register(_disposeAction)) using (cancellationToken.Register(_disposeAction))
{ {
await stream.WriteAsync(buffer.Array, buffer.Offset, buffer.Count, cancellationToken).ConfigureAwait(false); await stream.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
} }
#endif #endif
} }
@@ -13,7 +13,6 @@ using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Internal;
namespace MQTTnet.Implementations namespace MQTTnet.Implementations
{ {
@@ -57,13 +56,13 @@ namespace MQTTnet.Implementations
RegisterListeners(options.TlsEndpointOptions, tlsCertificate, logger, _cancellationTokenSource.Token); RegisterListeners(options.TlsEndpointOptions, tlsCertificate, logger, _cancellationTokenSource.Token);
} }
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
} }
public Task StopAsync() public Task StopAsync()
{ {
Cleanup(); Cleanup();
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
} }
public void Dispose() public void Dispose()
@@ -125,7 +124,7 @@ namespace MQTTnet.Implementations
var clientHandler = ClientHandler; var clientHandler = ClientHandler;
if (clientHandler == null) if (clientHandler == null)
{ {
return CompletedTask.Instance; return Task.FromResult(0);
} }
return clientHandler(channelAdapter); return clientHandler(channelAdapter);
@@ -134,7 +134,7 @@ namespace MQTTnet.Implementations
continue; continue;
} }
_ = Task.Factory.StartNew(() => TryHandleClientConnectionAsync(clientSocket), cancellationToken, TaskCreationOptions.PreferFairness, TaskScheduler.Default).ConfigureAwait(false); Task.Run(() => TryHandleClientConnectionAsync(clientSocket), cancellationToken).RunInBackground(_logger);
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
@@ -2,15 +2,15 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using MQTTnet.Channel;
using MQTTnet.Internal;
using System; using System;
using System.Net; using System.Net;
using System.Net.WebSockets; using System.Net.WebSockets;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Channel;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Internal;
namespace MQTTnet.Implementations namespace MQTTnet.Implementations
{ {
@@ -35,12 +35,12 @@ namespace MQTTnet.Implementations
ClientCertificate = clientCertificate; ClientCertificate = clientCertificate;
} }
public X509Certificate2 ClientCertificate { get; }
public string Endpoint { get; } public string Endpoint { get; }
public bool IsSecureConnection { get; private set; } public bool IsSecureConnection { get; private set; }
public X509Certificate2 ClientCertificate { get; }
public async Task ConnectAsync(CancellationToken cancellationToken) public async Task ConnectAsync(CancellationToken cancellationToken)
{ {
var uri = _options.Uri; var uri = _options.Uri;
@@ -89,26 +89,14 @@ namespace MQTTnet.Implementations
Cleanup(); Cleanup();
} }
public void Dispose()
{
Cleanup();
}
public async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) public async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{ {
var response = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer, offset, count), cancellationToken).ConfigureAwait(false); var response = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer, offset, count), cancellationToken).ConfigureAwait(false);
return response.Count; return response.Count;
} }
public async Task WriteAsync(ArraySegment<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken) public async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{ {
cancellationToken.ThrowIfCancellationRequested();
#if NET5_0_OR_GREATER
// MQTT Control Packets MUST be sent in WebSocket binary data frames. If any other type of data frame is received the recipient MUST close the Network Connection [MQTT-6.0.0-1].
// A single WebSocket data frame can contain multiple or partial MQTT Control Packets. The receiver MUST NOT assume that MQTT Control Packets are aligned on WebSocket frame boundaries [MQTT-6.0.0-2].
await _webSocket.SendAsync(buffer, WebSocketMessageType.Binary, isEndOfPacket, cancellationToken).ConfigureAwait(false);
#else
// The lock is required because the client will throw an exception if _SendAsync_ is // The lock is required because the client will throw an exception if _SendAsync_ is
// called from multiple threads at the same time. But this issue only happens with several // called from multiple threads at the same time. But this issue only happens with several
// framework versions. // framework versions.
@@ -117,11 +105,88 @@ namespace MQTTnet.Implementations
return; return;
} }
using (await _sendLock.EnterAsync(cancellationToken).ConfigureAwait(false)) using (await _sendLock.WaitAsync(cancellationToken).ConfigureAwait(false))
{ {
await _webSocket.SendAsync(buffer, WebSocketMessageType.Binary, isEndOfPacket, cancellationToken).ConfigureAwait(false); await _webSocket.SendAsync(new ArraySegment<byte>(buffer, offset, count), WebSocketMessageType.Binary, true, cancellationToken).ConfigureAwait(false);
} }
}
public void Dispose()
{
Cleanup();
}
void SetupClientWebSocket(ClientWebSocket clientWebSocket)
{
if (_options.ProxyOptions != null)
{
clientWebSocket.Options.Proxy = CreateProxy();
}
if (_options.RequestHeaders != null)
{
foreach (var requestHeader in _options.RequestHeaders)
{
clientWebSocket.Options.SetRequestHeader(requestHeader.Key, requestHeader.Value);
}
}
if (_options.SubProtocols != null)
{
foreach (var subProtocol in _options.SubProtocols)
{
clientWebSocket.Options.AddSubProtocol(subProtocol);
}
}
if (_options.CookieContainer != null)
{
clientWebSocket.Options.Cookies = _options.CookieContainer;
}
if (_options.TlsOptions?.UseTls == true && _options.TlsOptions?.Certificates != null)
{
clientWebSocket.Options.ClientCertificates = new X509CertificateCollection();
foreach (var certificate in _options.TlsOptions.Certificates)
{
#if WINDOWS_UWP
clientWebSocket.Options.ClientCertificates.Add(new X509Certificate(certificate));
#else
clientWebSocket.Options.ClientCertificates.Add(certificate);
#endif #endif
}
}
var certificateValidationHandler = _options.TlsOptions?.CertificateValidationHandler;
if (certificateValidationHandler != null)
{
#if NETSTANDARD1_3
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'netstandard1.3'.");
#elif NETSTANDARD2_0
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'netstandard2.0'.");
#elif WINDOWS_UWP
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'uap10.0'.");
#elif NET452
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'net452'.");
#elif NET461
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'net461'.");
#else
clientWebSocket.Options.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
{
// TODO: Find a way to add client options to same callback. Problem is that they have a different type.
var context = new MqttClientCertificateValidationEventArgs
{
Certificate = certificate,
Chain = chain,
SslPolicyErrors = sslPolicyErrors,
ClientOptions = _options
};
return certificateValidationHandler(context);
};
#endif
}
} }
void Cleanup() void Cleanup()
@@ -165,77 +230,5 @@ namespace MQTTnet.Implementations
return new WebProxy(proxyUri, _options.ProxyOptions.BypassOnLocal, _options.ProxyOptions.BypassList); return new WebProxy(proxyUri, _options.ProxyOptions.BypassOnLocal, _options.ProxyOptions.BypassList);
#endif #endif
} }
void SetupClientWebSocket(ClientWebSocket clientWebSocket)
{
if (_options.ProxyOptions != null)
{
clientWebSocket.Options.Proxy = CreateProxy();
}
if (_options.RequestHeaders != null)
{
foreach (var requestHeader in _options.RequestHeaders)
{
clientWebSocket.Options.SetRequestHeader(requestHeader.Key, requestHeader.Value);
}
}
if (_options.SubProtocols != null)
{
foreach (var subProtocol in _options.SubProtocols)
{
clientWebSocket.Options.AddSubProtocol(subProtocol);
}
}
if (_options.CookieContainer != null)
{
clientWebSocket.Options.Cookies = _options.CookieContainer;
}
if (_options.TlsOptions?.UseTls == true && _options.TlsOptions?.Certificates != null)
{
clientWebSocket.Options.ClientCertificates = new X509CertificateCollection();
foreach (var certificate in _options.TlsOptions.Certificates)
{
#if WINDOWS_UWP
clientWebSocket.Options.ClientCertificates.Add(new X509Certificate(certificate));
#else
clientWebSocket.Options.ClientCertificates.Add(certificate);
#endif
}
}
var certificateValidationHandler = _options.TlsOptions?.CertificateValidationHandler;
if (certificateValidationHandler != null)
{
#if NETSTANDARD1_3
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'netstandard1.3'.");
#elif NETSTANDARD2_0
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'netstandard2.0'.");
#elif WINDOWS_UWP
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'uap10.0'.");
#elif NET452
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'net452'.");
#elif NET461
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'net461'.");
#else
clientWebSocket.Options.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
{
// TODO: Find a way to add client options to same callback. Problem is that they have a different type.
var context = new MqttClientCertificateValidationEventArgs
{
Certificate = certificate,
Chain = chain,
SslPolicyErrors = sslPolicyErrors,
ClientOptions = _options
};
return certificateValidationHandler(context);
};
#endif
}
}
} }
} }
@@ -0,0 +1,43 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading;
using System.Threading.Tasks;
namespace MQTTnet.Implementations
{
public static class PlatformAbstractionLayer
{
#if NET452
public static Task CompletedTask => Task.FromResult(0);
public static byte[] EmptyByteArray { get; } = new byte[0];
#else
public static Task CompletedTask => Task.CompletedTask;
public static byte[] EmptyByteArray { get; } = Array.Empty<byte>();
#endif
public static ArraySegment<byte> EmptyByteArraySegment { get; } = new ArraySegment<byte>(EmptyByteArray);
public static void Sleep(TimeSpan timeout)
{
#if !NETSTANDARD1_3 && !WINDOWS_UWP
try
{
System.Threading.Thread.Sleep(timeout);
}
catch (ThreadAbortException)
{
// The ThreadAbortException is not actively catched in this project.
// So we use a one which is similar and will be catched properly.
throw new OperationCanceledException();
}
#else
Task.Delay(timeout).Wait();
#endif
}
}
}
+6 -48
View File
@@ -13,16 +13,7 @@ namespace MQTTnet.Internal
{ {
readonly List<AsyncEventInvocator<TEventArgs>> _handlers = new List<AsyncEventInvocator<TEventArgs>>(); readonly List<AsyncEventInvocator<TEventArgs>> _handlers = new List<AsyncEventInvocator<TEventArgs>>();
ICollection<AsyncEventInvocator<TEventArgs>> _handlersForInvoke; public bool HasHandlers => _handlers.Count > 0;
public AsyncEvent()
{
_handlersForInvoke = _handlers;
}
// Track the existence of handlers in a separate field so that checking it all the time will not
// require locking the actual list (_handlers).
public bool HasHandlers { get; private set; }
public void AddHandler(Func<TEventArgs, Task> handler) public void AddHandler(Func<TEventArgs, Task> handler)
{ {
@@ -31,13 +22,7 @@ namespace MQTTnet.Internal
throw new ArgumentNullException(nameof(handler)); throw new ArgumentNullException(nameof(handler));
} }
lock (_handlers) _handlers.Add(new AsyncEventInvocator<TEventArgs>(null, handler));
{
_handlers.Add(new AsyncEventInvocator<TEventArgs>(null, handler));
HasHandlers = true;
_handlersForInvoke = new List<AsyncEventInvocator<TEventArgs>>(_handlers);
}
} }
public void AddHandler(Action<TEventArgs> handler) public void AddHandler(Action<TEventArgs> handler)
@@ -47,27 +32,12 @@ namespace MQTTnet.Internal
throw new ArgumentNullException(nameof(handler)); throw new ArgumentNullException(nameof(handler));
} }
lock (_handlers) _handlers.Add(new AsyncEventInvocator<TEventArgs>(handler, null));
{
_handlers.Add(new AsyncEventInvocator<TEventArgs>(handler, null));
HasHandlers = true;
_handlersForInvoke = new List<AsyncEventInvocator<TEventArgs>>(_handlers);
}
} }
public async Task InvokeAsync(TEventArgs eventArgs) public async Task InvokeAsync(TEventArgs eventArgs)
{ {
if (!HasHandlers) foreach (var handler in _handlers)
{
return;
}
// Adding or removing handlers will produce a new list instance all the time.
// So locking here is not required since only the reference to an immutable list
// of handlers is used.
var handlers = _handlersForInvoke;
foreach (var handler in handlers)
{ {
await handler.InvokeAsync(eventArgs).ConfigureAwait(false); await handler.InvokeAsync(eventArgs).ConfigureAwait(false);
} }
@@ -80,13 +50,7 @@ namespace MQTTnet.Internal
throw new ArgumentNullException(nameof(handler)); throw new ArgumentNullException(nameof(handler));
} }
lock (_handlers) _handlers.RemoveAll(h => h.WrapsHandler(handler));
{
_handlers.RemoveAll(h => h.WrapsHandler(handler));
HasHandlers = _handlers.Count > 0;
_handlersForInvoke = new List<AsyncEventInvocator<TEventArgs>>(_handlers);
}
} }
public void RemoveHandler(Action<TEventArgs> handler) public void RemoveHandler(Action<TEventArgs> handler)
@@ -96,13 +60,7 @@ namespace MQTTnet.Internal
throw new ArgumentNullException(nameof(handler)); throw new ArgumentNullException(nameof(handler));
} }
lock (_handlers) _handlers.RemoveAll(h => h.WrapsHandler(handler));
{
_handlers.RemoveAll(h => h.WrapsHandler(handler));
HasHandlers = _handlers.Count > 0;
_handlersForInvoke = new List<AsyncEventInvocator<TEventArgs>>(_handlers);
}
} }
public async Task TryInvokeAsync(TEventArgs eventArgs, MqttNetSourceLogger logger) public async Task TryInvokeAsync(TEventArgs eventArgs, MqttNetSourceLogger logger)
@@ -36,7 +36,7 @@ namespace MQTTnet.Internal
if (_handler != null) if (_handler != null)
{ {
_handler.Invoke(eventArgs); _handler.Invoke(eventArgs);
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
} }
if (_asyncHandler != null) if (_asyncHandler != null)
+43 -120
View File
@@ -3,8 +3,6 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -12,151 +10,76 @@ namespace MQTTnet.Internal
{ {
public sealed class AsyncLock : IDisposable public sealed class AsyncLock : IDisposable
{ {
readonly Task<IDisposable> _completedTask;
readonly IDisposable _releaser;
readonly object _syncRoot = new object(); readonly object _syncRoot = new object();
readonly Queue<AsyncLockWaiter> _waiters = new Queue<AsyncLockWaiter>(64); readonly Task<IDisposable> _releaser;
volatile bool _isDisposed; SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
bool _isLocked;
public AsyncLock() public AsyncLock()
{ {
_releaser = new Releaser(this); _releaser = Task.FromResult((IDisposable)new Releaser(this));
_completedTask = Task.FromResult(_releaser); }
public Task<IDisposable> WaitAsync(CancellationToken cancellationToken)
{
Task task;
// This lock is required to avoid ObjectDisposedExceptions.
// These are fired when this lock gets disposed (and thus the semaphore)
// and a worker thread tries to call this method at the same time.
// Another way would be catching all ObjectDisposedExceptions but this situation happens
// quite often when clients are disconnecting.
lock (_syncRoot)
{
task = _semaphore?.WaitAsync(cancellationToken);
}
if (task == null)
{
throw new ObjectDisposedException("The AsyncLock is disposed.");
}
if (task.Status == TaskStatus.RanToCompletion)
{
return _releaser;
}
// Wait for the _WaitAsync_ method and return the releaser afterwards.
return task.ContinueWith(
(_, state) => (IDisposable)state,
_releaser.Result,
cancellationToken, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
} }
public void Dispose() public void Dispose()
{ {
lock (_syncRoot) lock (_syncRoot)
{ {
_isDisposed = true; _semaphore?.Dispose();
_semaphore = null;
while (_waiters.Any())
{
_waiters.Dequeue().Dispose();
}
} }
} }
public Task<IDisposable> EnterAsync(CancellationToken cancellationToken = default) internal void Release()
{
cancellationToken.ThrowIfCancellationRequested();
if (_isDisposed)
{
throw new ObjectDisposedException(nameof(AsyncLock));
}
lock (_syncRoot)
{
if (!_isLocked)
{
_isLocked = true;
return _completedTask;
}
var waiter = new AsyncLockWaiter(cancellationToken);
_waiters.Enqueue(waiter);
return waiter.Task;
}
}
void Release()
{ {
lock (_syncRoot) lock (_syncRoot)
{ {
if (_isDisposed) _semaphore?.Release();
{
// All waiters have been canceled with a ObjectDisposedException.
// So there is nothing left to do.
return;
}
// Assume that there is no waiter left first.
_isLocked = false;
// Try to find the next waiter which can be approved.
// Some of them might be canceled already so it is not
// guaranteed that the very next waiter is the correct one.
while (_waiters.Any())
{
var waiter = _waiters.Dequeue();
var isApproved = waiter.Approve(_releaser);
waiter.Dispose();
if (isApproved)
{
_isLocked = true;
return;
}
}
} }
} }
sealed class AsyncLockWaiter : IDisposable sealed class Releaser : IDisposable
{ {
readonly CancellationTokenRegistration _cancellationRegistration; readonly AsyncLock _lock;
readonly bool _hasCancellationRegistration;
readonly AsyncTaskCompletionSource<IDisposable> _promise = new AsyncTaskCompletionSource<IDisposable>();
public AsyncLockWaiter(CancellationToken cancellationToken) internal Releaser(AsyncLock @lock)
{ {
cancellationToken.ThrowIfCancellationRequested(); _lock = @lock;
if (cancellationToken.CanBeCanceled)
{
_cancellationRegistration = cancellationToken.Register(Cancel);
_hasCancellationRegistration = true;
}
}
public Task<IDisposable> Task => _promise.Task;
public bool Approve(IDisposable scope)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
if (_promise.Task.IsCompleted)
{
return false;
}
return _promise.TrySetResult(scope);
} }
public void Dispose() public void Dispose()
{ {
if (_hasCancellationRegistration) _lock.Release();
{
_cancellationRegistration.Dispose();
}
_promise.TrySetException(new ObjectDisposedException(nameof(AsyncLockWaiter)));
}
void Cancel()
{
_promise.TrySetCanceled();
}
}
readonly struct Releaser : IDisposable
{
readonly AsyncLock _asyncLock;
public Releaser(AsyncLock asyncLock)
{
_asyncLock = asyncLock ?? throw new ArgumentNullException(nameof(asyncLock));
}
public void Dispose()
{
_asyncLock.Release();
} }
} }
} }
@@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; using System;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace MQTTnet.Internal namespace MQTTnet.Internal
@@ -23,7 +22,7 @@ namespace MQTTnet.Internal
public Task<TResult> Task => _taskCompletionSource.Task; public Task<TResult> Task => _taskCompletionSource.Task;
public void TrySetCanceled() public bool TrySetCanceled()
{ {
#if NET452 #if NET452
// To prevent deadlocks it is required to call the _TrySetCanceled_ method // To prevent deadlocks it is required to call the _TrySetCanceled_ method
@@ -32,14 +31,19 @@ namespace MQTTnet.Internal
// do it. But _this_ thread is also reading incoming packets -> deadlock. // do it. But _this_ thread is also reading incoming packets -> deadlock.
// NET452 does not support RunContinuationsAsynchronously // NET452 does not support RunContinuationsAsynchronously
System.Threading.Tasks.Task.Run(() => _taskCompletionSource.TrySetCanceled()); System.Threading.Tasks.Task.Run(() => _taskCompletionSource.TrySetCanceled());
SpinWait.SpinUntil(() => _taskCompletionSource.Task.IsCompleted); return true;
#else #else
_taskCompletionSource.TrySetCanceled(); return _taskCompletionSource.TrySetCanceled();
#endif #endif
} }
public void TrySetException(Exception exception) public bool TrySetException(Exception exception)
{ {
if (exception == null)
{
throw new ArgumentNullException(nameof(exception));
}
#if NET452 #if NET452
// To prevent deadlocks it is required to call the _TrySetException_ method // To prevent deadlocks it is required to call the _TrySetException_ method
// from a new thread because the awaiting code will not(!) be executed in // from a new thread because the awaiting code will not(!) be executed in
@@ -47,9 +51,9 @@ namespace MQTTnet.Internal
// do it. But _this_ thread is also reading incoming packets -> deadlock. // do it. But _this_ thread is also reading incoming packets -> deadlock.
// NET452 does not support RunContinuationsAsynchronously // NET452 does not support RunContinuationsAsynchronously
System.Threading.Tasks.Task.Run(() => _taskCompletionSource.TrySetException(exception)); System.Threading.Tasks.Task.Run(() => _taskCompletionSource.TrySetException(exception));
SpinWait.SpinUntil(() => _taskCompletionSource.Task.IsCompleted); return true;
#else #else
_taskCompletionSource.TrySetException(exception); return _taskCompletionSource.TrySetException(exception);
#endif #endif
} }
@@ -61,14 +65,7 @@ namespace MQTTnet.Internal
// a new thread automatically (due to await). Furthermore _this_ thread will // a new thread automatically (due to await). Furthermore _this_ thread will
// do it. But _this_ thread is also reading incoming packets -> deadlock. // do it. But _this_ thread is also reading incoming packets -> deadlock.
// NET452 does not support RunContinuationsAsynchronously // NET452 does not support RunContinuationsAsynchronously
if (_taskCompletionSource.Task.IsCompleted)
{
return false;
}
System.Threading.Tasks.Task.Run(() => _taskCompletionSource.TrySetResult(result)); System.Threading.Tasks.Task.Run(() => _taskCompletionSource.TrySetResult(result));
SpinWait.SpinUntil(() => _taskCompletionSource.Task.IsCompleted);
return true; return true;
#else #else
return _taskCompletionSource.TrySetResult(result); return _taskCompletionSource.TrySetResult(result);
-17
View File
@@ -1,17 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Threading.Tasks;
namespace MQTTnet.Internal
{
public static class CompletedTask
{
#if NET452
public static readonly Task Instance = Task.FromResult(true);
#else
public static readonly Task Instance = Task.CompletedTask;
#endif
}
}
-19
View File
@@ -1,19 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace MQTTnet.Internal
{
public static class EmptyBuffer
{
#if NET452
public static readonly byte[] Array = new byte[0];
#else
public static readonly byte[] Array = System.Array.Empty<byte>();
#endif
public static readonly ArraySegment<byte> ArraySegment = new ArraySegment<byte>(Array, 0, 0);
}
}
+1
View File
@@ -14,6 +14,7 @@
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
<LangVersion>7.3</LangVersion> <LangVersion>7.3</LangVersion>
<AssemblyName>MQTTnet</AssemblyName> <AssemblyName>MQTTnet</AssemblyName>
<RootNamespace>MQTTnet</RootNamespace> <RootNamespace>MQTTnet</RootNamespace>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild> <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
@@ -10,7 +10,6 @@ using System.Text;
using MQTTnet.Adapter; using MQTTnet.Adapter;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Implementations; using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
@@ -89,7 +88,7 @@ namespace MQTTnet.Server
/// </summary> /// </summary>
public uint MaximumPacketSize => _connectPacket.MaximumPacketSize; public uint MaximumPacketSize => _connectPacket.MaximumPacketSize;
public string Password => Encoding.UTF8.GetString(RawPassword ?? EmptyBuffer.Array); public string Password => Encoding.UTF8.GetString(RawPassword ?? PlatformAbstractionLayer.EmptyByteArray);
public MqttProtocolVersion ProtocolVersion => ChannelAdapter.PacketFormatterAdapter.ProtocolVersion; public MqttProtocolVersion ProtocolVersion => ChannelAdapter.PacketFormatterAdapter.ProtocolVersion;
+10 -35
View File
@@ -98,7 +98,7 @@ namespace MQTTnet.Server
try try
{ {
_ = Task.Factory.StartNew(() => SendPacketsLoop(cancellationToken), cancellationToken, TaskCreationOptions.PreferFairness, TaskScheduler.Default).ConfigureAwait(false); Task.Run(() => SendPacketsLoop(cancellationToken), cancellationToken).RunInBackground(_logger);
IsRunning = true; IsRunning = true;
@@ -147,20 +147,13 @@ namespace MQTTnet.Server
{ {
IsRunning = false; IsRunning = false;
// Sending DISCONNECT packets from the server to the client is only supported when using MQTTv5+. if (reason == MqttDisconnectReasonCode.SessionTakenOver || reason == MqttDisconnectReasonCode.KeepAliveTimeout)
if (ChannelAdapter.PacketFormatterAdapter.ProtocolVersion == MqttProtocolVersion.V500)
{ {
// The Client or Server MAY send a DISCONNECT packet before closing the Network Connection. // Is is very important to send the DISCONNECT packet here BEFORE cancelling the
// This library does not sent a DISCONNECT packet for a normal disconnection. Maybe adding // token because the entire connection is closed (disposed) as soon as the cancellation
// a configuration option is requested in the future. // token is cancelled. To there is no chance that the DISCONNECT packet will ever arrive
if (reason != MqttDisconnectReasonCode.NormalDisconnection) // at the client!
{ await TrySendDisconnectPacket(reason).ConfigureAwait(false);
// Is is very important to send the DISCONNECT packet here BEFORE cancelling the
// token because the entire connection is closed (disposed) as soon as the cancellation
// token is cancelled. To there is no chance that the DISCONNECT packet will ever arrive
// at the client!
await TrySendDisconnectPacket(reason).ConfigureAwait(false);
}
} }
StopInternal(); StopInternal();
@@ -181,7 +174,7 @@ namespace MQTTnet.Server
return _eventContainer.ClientAcknowledgedPublishPacketEvent.TryInvokeAsync(eventArgs, _logger); return _eventContainer.ClientAcknowledgedPublishPacketEvent.TryInvokeAsync(eventArgs, _logger);
} }
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
} }
Task HandleIncomingPubAckPacket(MqttPubAckPacket pubAckPacket) Task HandleIncomingPubAckPacket(MqttPubAckPacket pubAckPacket)
@@ -193,7 +186,7 @@ namespace MQTTnet.Server
return ClientAcknowledgedPublishPacket(acknowledgedPublishPacket, pubAckPacket); return ClientAcknowledgedPublishPacket(acknowledgedPublishPacket, pubAckPacket);
} }
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
} }
Task HandleIncomingPubCompPacket(MqttPubCompPacket pubCompPacket) Task HandleIncomingPubCompPacket(MqttPubCompPacket pubCompPacket)
@@ -205,7 +198,7 @@ namespace MQTTnet.Server
return ClientAcknowledgedPublishPacket(acknowledgedPublishPacket, pubCompPacket); return ClientAcknowledgedPublishPacket(acknowledgedPublishPacket, pubCompPacket);
} }
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
} }
async Task HandleIncomingPublishPacket(MqttPublishPacket publishPacket, CancellationToken cancellationToken) async Task HandleIncomingPublishPacket(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
@@ -392,19 +385,6 @@ namespace MQTTnet.Server
return; return;
} }
// Check for cancellation again because receive packet might block some time.
if (cancellationToken.IsCancellationRequested)
{
return;
}
// The TCP connection of this client may be still open but the client has already been taken over by
// a new TCP connection. So we must exit here to make sure to no longer process any message.
if (IsTakenOver || !IsRunning)
{
return;
}
var processPacket = true; var processPacket = true;
if (_eventContainer.InterceptingInboundPacketEvent.HasHandlers) if (_eventContainer.InterceptingInboundPacketEvent.HasHandlers)
@@ -506,11 +486,6 @@ namespace MQTTnet.Server
return; return;
} }
if (IsTakenOver || !IsRunning)
{
return;
}
try try
{ {
await SendPacketAsync(packetBusItem.Packet, cancellationToken).ConfigureAwait(false); await SendPacketAsync(packetBusItem.Packet, cancellationToken).ConfigureAwait(false);
@@ -24,7 +24,6 @@ namespace MQTTnet.Server
readonly Dictionary<string, MqttClient> _clients = new Dictionary<string, MqttClient>(4096); readonly Dictionary<string, MqttClient> _clients = new Dictionary<string, MqttClient>(4096);
readonly AsyncLock _createConnectionSyncRoot = new AsyncLock(); readonly AsyncLock _createConnectionSyncRoot = new AsyncLock();
readonly MqttServerEventContainer _eventContainer; readonly MqttServerEventContainer _eventContainer;
readonly MqttNetSourceLogger _logger; readonly MqttNetSourceLogger _logger;
readonly MqttServerOptions _options; readonly MqttServerOptions _options;
@@ -76,8 +75,6 @@ namespace MQTTnet.Server
public async Task DeleteSessionAsync(string clientId) public async Task DeleteSessionAsync(string clientId)
{ {
_logger.Verbose("Deleting session for client '{0}'.", clientId);
MqttClient connection; MqttClient connection;
lock (_clients) lock (_clients)
@@ -196,7 +193,7 @@ namespace MQTTnet.Server
public void Dispose() public void Dispose()
{ {
_createConnectionSyncRoot.Dispose(); _createConnectionSyncRoot?.Dispose();
lock (_sessionsManagementLock) lock (_sessionsManagementLock)
{ {
@@ -306,9 +303,6 @@ namespace MQTTnet.Server
await client.RunAsync().ConfigureAwait(false); await client.RunAsync().ConfigureAwait(false);
} }
catch (ObjectDisposedException)
{
}
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
} }
@@ -444,18 +438,13 @@ namespace MQTTnet.Server
return GetClientSession(clientId).SubscriptionsManager.Unsubscribe(fakeUnsubscribePacket, CancellationToken.None); return GetClientSession(clientId).SubscriptionsManager.Unsubscribe(fakeUnsubscribePacket, CancellationToken.None);
} }
MqttClient CreateClient(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter, MqttSession session)
{
return new MqttClient(connectPacket, channelAdapter, session, _options, _eventContainer, this, _rootLogger);
}
async Task<MqttClient> CreateClientConnection( async Task<MqttClient> CreateClientConnection(
MqttConnectPacket connectPacket, MqttConnectPacket connectPacket,
MqttConnAckPacket connAckPacket, MqttConnAckPacket connAckPacket,
IMqttChannelAdapter channelAdapter, IMqttChannelAdapter channelAdapter,
ValidatingConnectionEventArgs validatingConnectionEventArgs) ValidatingConnectionEventArgs validatingConnectionEventArgs)
{ {
MqttClient client; MqttClient connection;
bool sessionShouldPersist; bool sessionShouldPersist;
@@ -482,7 +471,7 @@ namespace MQTTnet.Server
sessionShouldPersist = !connectPacket.CleanSession; sessionShouldPersist = !connectPacket.CleanSession;
} }
using (await _createConnectionSyncRoot.EnterAsync().ConfigureAwait(false)) using (await _createConnectionSyncRoot.WaitAsync(CancellationToken.None).ConfigureAwait(false))
{ {
MqttSession session; MqttSession session;
lock (_sessionsManagementLock) lock (_sessionsManagementLock)
@@ -495,7 +484,7 @@ namespace MQTTnet.Server
{ {
if (connectPacket.CleanSession) if (connectPacket.CleanSession)
{ {
_logger.Verbose("Deleting existing session of client '{0}' due to clean start.", connectPacket.ClientId); _logger.Verbose("Deleting existing session of client '{0}'.", connectPacket.ClientId);
session = CreateSession(connectPacket.ClientId, validatingConnectionEventArgs.SessionItems, sessionShouldPersist); session = CreateSession(connectPacket.ClientId, validatingConnectionEventArgs.SessionItems, sessionShouldPersist);
} }
else else
@@ -518,40 +507,40 @@ namespace MQTTnet.Server
await _eventContainer.PreparingSessionEvent.InvokeAsync(preparingSessionEventArgs).ConfigureAwait(false); await _eventContainer.PreparingSessionEvent.InvokeAsync(preparingSessionEventArgs).ConfigureAwait(false);
} }
MqttClient existingClient; MqttClient existing;
lock (_clients) lock (_clients)
{ {
_clients.TryGetValue(connectPacket.ClientId, out existingClient); _clients.TryGetValue(connectPacket.ClientId, out existing);
client = CreateClient(connectPacket, channelAdapter, session); connection = CreateConnection(connectPacket, channelAdapter, session);
_clients[connectPacket.ClientId] = client; _clients[connectPacket.ClientId] = connection;
} }
if (existingClient != null) if (existing != null)
{ {
existingClient.IsTakenOver = true; existing.IsTakenOver = true;
await existingClient.StopAsync(MqttDisconnectReasonCode.SessionTakenOver).ConfigureAwait(false); await existing.StopAsync(MqttDisconnectReasonCode.SessionTakenOver).ConfigureAwait(false);
if (_eventContainer.ClientConnectedEvent.HasHandlers) if (_eventContainer.ClientConnectedEvent.HasHandlers)
{ {
var eventArgs = new ClientDisconnectedEventArgs( var eventArgs = new ClientDisconnectedEventArgs(existing.Id, MqttClientDisconnectType.Takeover, existing.Endpoint, existing.Session.Items);
existingClient.Id,
MqttClientDisconnectType.Takeover,
existingClient.Endpoint,
existingClient.Session.Items);
await _eventContainer.ClientDisconnectedEvent.InvokeAsync(eventArgs).ConfigureAwait(false); await _eventContainer.ClientDisconnectedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
} }
} }
} }
return client; return connection;
}
MqttClient CreateConnection(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter, MqttSession session)
{
return new MqttClient(connectPacket, channelAdapter, session, _options, _eventContainer, this, _rootLogger);
} }
MqttSession CreateSession(string clientId, IDictionary sessionItems, bool isPersistent) MqttSession CreateSession(string clientId, IDictionary sessionItems, bool isPersistent)
{ {
_logger.Verbose("Created new session for client '{0}'.", clientId); _logger.Verbose("Created a new session for client '{0}'.", clientId);
return new MqttSession(clientId, isPersistent, sessionItems, _options, _eventContainer, _retainedMessagesManager, this); return new MqttSession(clientId, isPersistent, sessionItems, _options, _eventContainer, _retainedMessagesManager, this);
} }
@@ -5,8 +5,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
using MQTTnet.Implementations;
using MQTTnet.Internal; using MQTTnet.Internal;
namespace MQTTnet.Server namespace MQTTnet.Server
@@ -83,7 +85,7 @@ namespace MQTTnet.Server
} }
else else
{ {
if (existingMessage.QualityOfServiceLevel != applicationMessage.QualityOfServiceLevel || !existingMessage.Payload.SequenceEqual(applicationMessage.Payload ?? EmptyBuffer.Array)) if (existingMessage.QualityOfServiceLevel != applicationMessage.QualityOfServiceLevel || !existingMessage.Payload.SequenceEqual(applicationMessage.Payload ?? PlatformAbstractionLayer.EmptyByteArray))
{ {
_messages[applicationMessage.Topic] = applicationMessage; _messages[applicationMessage.Topic] = applicationMessage;
saveIsRequired = true; saveIsRequired = true;
@@ -101,7 +103,7 @@ namespace MQTTnet.Server
if (saveIsRequired) if (saveIsRequired)
{ {
using (await _storageAccessLock.EnterAsync().ConfigureAwait(false)) using (await _storageAccessLock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
{ {
var eventArgs = new RetainedMessageChangedEventArgs(clientId, applicationMessage, messagesForSave); var eventArgs = new RetainedMessageChangedEventArgs(clientId, applicationMessage, messagesForSave);
await _eventContainer.RetainedMessageChangedEvent.InvokeAsync(eventArgs).ConfigureAwait(false); await _eventContainer.RetainedMessageChangedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
@@ -130,7 +132,7 @@ namespace MQTTnet.Server
_messages.Clear(); _messages.Clear();
} }
using (await _storageAccessLock.EnterAsync().ConfigureAwait(false)) using (await _storageAccessLock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
{ {
await _eventContainer.RetainedMessagesClearedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false); await _eventContainer.RetainedMessagesClearedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false);
} }
@@ -6,6 +6,7 @@ using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
using MQTTnet.Implementations;
using MQTTnet.Internal; using MQTTnet.Internal;
using MQTTnet.Protocol; using MQTTnet.Protocol;
@@ -13,20 +14,16 @@ namespace MQTTnet.Server
{ {
public sealed class MqttServerKeepAliveMonitor public sealed class MqttServerKeepAliveMonitor
{ {
readonly MqttNetSourceLogger _logger;
readonly MqttServerOptions _options; readonly MqttServerOptions _options;
readonly MqttClientSessionsManager _sessionsManager; readonly MqttClientSessionsManager _sessionsManager;
readonly MqttNetSourceLogger _logger;
public MqttServerKeepAliveMonitor(MqttServerOptions options, MqttClientSessionsManager sessionsManager, IMqttNetLogger logger) public MqttServerKeepAliveMonitor(MqttServerOptions options, MqttClientSessionsManager sessionsManager, IMqttNetLogger logger)
{ {
_options = options ?? throw new ArgumentNullException(nameof(options)); _options = options ?? throw new ArgumentNullException(nameof(options));
_sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager)); _sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager));
if (logger == null) if (logger == null) throw new ArgumentNullException(nameof(logger));
{
throw new ArgumentNullException(nameof(logger));
}
_logger = logger.WithSource(nameof(MqttServerKeepAliveMonitor)); _logger = logger.WithSource(nameof(MqttServerKeepAliveMonitor));
} }
@@ -48,7 +45,7 @@ namespace MQTTnet.Server
while (!cancellationToken.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested)
{ {
TryProcessClients(); TryProcessClients();
Sleep(_options.KeepAliveMonitorInterval); PlatformAbstractionLayer.Sleep(_options.KeepAliveMonitorInterval);
} }
} }
catch (OperationCanceledException) catch (OperationCanceledException)
@@ -64,22 +61,13 @@ namespace MQTTnet.Server
} }
} }
static void Sleep(TimeSpan timeout) void TryProcessClients()
{ {
#if !NETSTANDARD1_3 && !WINDOWS_UWP var now = DateTime.UtcNow;
try foreach (var client in _sessionsManager.GetClients())
{ {
Thread.Sleep(timeout); TryProcessClient(client, now);
} }
catch (ThreadAbortException)
{
// The ThreadAbortException is not actively catched in this project.
// So we use a one which is similar and will be catched properly.
throw new OperationCanceledException();
}
#else
Task.Delay(timeout).Wait();
#endif
} }
void TryProcessClient(MqttClient connection, DateTime now) void TryProcessClient(MqttClient connection, DateTime now)
@@ -131,14 +119,5 @@ namespace MQTTnet.Server
_logger.Error(exception, "Client {0}: Unhandled exception while checking keep alive timeouts.", connection.Id); _logger.Error(exception, "Client {0}: Unhandled exception while checking keep alive timeouts.", connection.Id);
} }
} }
void TryProcessClients()
{
var now = DateTime.UtcNow;
foreach (var client in _sessionsManager.GetClients())
{
TryProcessClient(client, now);
}
}
} }
} }
+2 -1
View File
@@ -11,6 +11,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Adapter; using MQTTnet.Adapter;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
using MQTTnet.Implementations;
using MQTTnet.Internal; using MQTTnet.Internal;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
@@ -171,7 +172,7 @@ namespace MQTTnet.Server
{ {
ThrowIfNotStarted(); ThrowIfNotStarted();
return _retainedMessagesManager?.ClearMessages() ?? CompletedTask.Instance; return _retainedMessagesManager?.ClearMessages() ?? PlatformAbstractionLayer.CompletedTask;
} }
public Task DisconnectClientAsync(string id, MqttDisconnectReasonCode reasonCode) public Task DisconnectClientAsync(string id, MqttDisconnectReasonCode reasonCode)
@@ -6,6 +6,7 @@ using System;
using System.Collections; using System.Collections;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Internal; using MQTTnet.Internal;
namespace MQTTnet.Server namespace MQTTnet.Server
@@ -34,7 +35,7 @@ namespace MQTTnet.Server
var publishPacketFactory = new MqttPublishPacketFactory(); var publishPacketFactory = new MqttPublishPacketFactory();
_session.EnqueueDataPacket(new MqttPacketBusItem(publishPacketFactory.Create(applicationMessage))); _session.EnqueueDataPacket(new MqttPacketBusItem(publishPacketFactory.Create(applicationMessage)));
return CompletedTask.Instance; return PlatformAbstractionLayer.CompletedTask;
} }
public Task DeliverApplicationMessageAsync(MqttApplicationMessage applicationMessage) public Task DeliverApplicationMessageAsync(MqttApplicationMessage applicationMessage)