-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Client
These samples are only valid for version 3. Please check the Samples directory for samples and documentation for version 4.0+
This video tutorial shows how to setup a MQTTnet client for Windows (UWP and IoT Core): https://www.youtube.com/watch?v=PSerr2fvnyc
The following code shows how to create a new MQTT client in the most simple way using the MqttFactory.
// Create a new MQTT client.
var factory = new MqttFactory();
var mqttClient = factory.CreateMqttClient();
All options for the MQTT client are bundled in one class named MqttClientOptions. It is possible to fill options manually in code via the properties but it is recommended to use the MqttClientOptionsBuilder. This class provides a fluent API and allows setting the options easily by providing several overloads and helper methods. The following code shows how to use the builder with several random options.
// Create TCP based options using the builder.
var options = new MqttClientOptionsBuilder()
.WithClientId("Client1")
.WithTcpServer("broker.hivemq.com")
.WithCredentials("bud", "%spencer%")
.WithTls()
.WithCleanSession()
.Build();
The default implementation uses a string
to hold the client password. However this is a security vulnerability because the password is stored in the heap as clear text. It is recommended to use a SecureString
for this purpose. But this class is not available for all supported platforms (UWP, netstandard 1.3). This library does not implement it because for other platforms custom implementations like async encryption are required. It is recommended to implement an own IMqttClientCredentials class which returns the decrypted password but does not store it unencrypted.
The following code shows how to set the options of the MQTT client to make use of a TCP based connection.
// Use TCP connection.
var options = new MqttClientOptionsBuilder()
.WithTcpServer("broker.hivemq.com", 1883) // Port is optional
.Build();
The following code shows how to use a TLS secured TCP connection (properties are only set as reference):
// Use secure TCP connection.
var options = new MqttClientOptionsBuilder()
.WithTcpServer("broker.hivemq.com")
.WithTls()
.Build();
The following code shows how to connect to the server by using certificate based authentication:
// Certificate based authentication
List<X509Certificate> certs = new List<X509Certificate>
{
new X509Certificate2("myCert.pfx")
};
var options = new MqttClientOptionBuilder()
.WithTcpServer(broker, port)
.WithTls(new MqttClientOptionsBuilderTlsParameters
{
UseTls = true,
Certificates = certs
})
.Build();
If your server is using a certificate by an unrecognized CA (that is not in the system store), you'll need to add a callback to verify if the certificate is valid. Note this does not work prior to .NET 5.
// Load the ca.crt. On some platforms you may also be able to use X509Certificate2.Import
X509Certificate2 caCrt = new X509Certificate2(File.ReadAllBytes("ca.crt"));
var options = new MqttClientOptionsBuilder()
.WithTls(
new MqttClientOptionsBuilderTlsParameters() {
UseTls = true,
SslProtocol = System.Security.Authentication.SslProtocols.Tls12,
CertificateValidationHandler = (certContext) => {
X509Chain chain = new X509Chain();
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
chain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot;
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;
chain.ChainPolicy.VerificationTime = DateTime.Now;
chain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 0, 0);
chain.ChainPolicy.CustomTrustStore.Add(caCrt);
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
// convert provided X509Certificate to X509Certificate2
var x5092 = new X509Certificate2(certContext.Certificate);
return chain.Build(x5092);
}
}
)
.Build();
The following code shows how to connect to the server by using a client certificate based authentication:
var caCert = X509Certificate.CreateFromCertFile(@"CA-cert.crt");
var clientCert = new X509Certificate2(@"client-certificate.pfx", "ExportPasswordUsedWhenCreatingPfxFile");
var options = new ManagedMqttClientOptionsBuilder()
.WithClientOptions(new MqttClientOptionsBuilder()
.WithClientId(Guid.NewGuid().ToString())
.WithTcpServer(host, port)
.WithTls(new MqttClientOptionsBuilderTlsParameters()
{
UseTls = true,
SslProtocol = System.Security.Authentication.SslProtocols.Tls12,
Certificates = new List<X509Certificate>()
{
clientCert, caCert
}
})
.Build())
.Build();
The CA certificate is in the *.crt format, the client certificate should be in *.pfx and should have the password that was used to export the file from the private key and certificate originally. The *.pfx file can becreated using openssl as below:
openssl pkcs12 -export -out certificate.pfx -inkey privateKey.key -in clientCertificate.cer
In order to deal with special certificate errors a special validation callback is available (.NET Framework & netstandard). For UWP apps, a property is available.
// For .NET Framework & netstandard apps:
var options = new MqttClientOptionsBuilder()
.WithTls(new MqttClientOptionsBuilderTlsParameters
{
UseTls = true,
CertificateValidationCallback = (X509Certificate x, X509Chain y, SslPolicyErrors z, IMqttClientOptions o) =>
{
// TODO: Check conditions of certificate by using above parameters.
return true;
}
})
.Build();
// For UWP apps:
MqttTcpChannel.CustomIgnorableServerCertificateErrorsResolver = o =>
{
if (o.Server == "server_with_revoked_cert")
{
return new []{ ChainValidationResult.Revoked };
}
return new ChainValidationResult[0];
};
In order to use a WebSocket communication channel the following code is required.
// Use WebSocket connection.
var options = new MqttClientOptionsBuilder()
.WithWebSocketServer("broker.hivemq.com:8000/mqtt")
.Build();
Also secure web socket connections can be used via calling the UseTls() method which will switch the protocol from ws:// to wss://. Usually the sub protocol is required which can be added to the URI directly or to a dedicated property.
After setting up the MQTT client options a connection can be established. The following code shows how to connect with a server. The CancellationToken.None
can be replaced by a valid CancellationToken, of course.
// Use WebSocket connection.
var options = new MqttClientOptionsBuilder()
.WithWebSocketServer("broker.hivemq.com:8000/mqtt")
.Build();
await mqttClient.ConnectAsync(options, CancellationToken.None); // Since 3.0.5 with CancellationToken
If the connection to the server is lost the Disconnected event is fired. The event is also fired if a call to ConnectAsync has failed because the server is not reachable etc. This allows calling the ConnectAsync method only one time and dealing with retries etc. via consuming the Disconnected event. If the reconnect fails the Disconnected event is fired again. The following code shows how to setup this behavior including a short delay. The CancellationToken.None
can be replaced by a valid CancellationToken, of course.
mqttClient.UseDisconnectedHandler(async e =>
{
Console.WriteLine("### DISCONNECTED FROM SERVER ###");
await Task.Delay(TimeSpan.FromSeconds(5));
try
{
await mqttClient.ConnectAsync(options, CancellationToken.None); // Since 3.0.5 with CancellationToken
}
catch
{
Console.WriteLine("### RECONNECTING FAILED ###");
}
});
The following code shows how to handle incoming messages:
mqttClient.UseApplicationMessageReceivedHandler(e =>
{
Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###");
Console.WriteLine($"+ Topic = {e.ApplicationMessage.Topic}");
Console.WriteLine($"+ Payload = {Encoding.UTF8.GetString(e.ApplicationMessage.Payload)}");
Console.WriteLine($"+ QoS = {e.ApplicationMessage.QualityOfServiceLevel}");
Console.WriteLine($"+ Retain = {e.ApplicationMessage.Retain}");
Console.WriteLine();
Task.Run(() => mqttClient.PublishAsync("hello/world"));
});
It is also supported to use an async method instead of a synchronized one like in the above example.
Once a connection with the server is established subscribing to a topic is possible. The following code shows how to subscribe to a topic after the MQTT client has connected.
mqttClient.UseConnectedHandler(async e =>
{
Console.WriteLine("### CONNECTED WITH SERVER ###");
// Subscribe to a topic
await mqttClient.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("my/topic").Build());
Console.WriteLine("### SUBSCRIBED ###");
});
Application messages can be created using the properties directly or via using the MqttApplicationMessageBuilder. This class has some useful overloads which allows dealing with different payload formats easily. The API of the builder is a fluent API. The following code shows how to compose an application message and publishing them:
var message = new MqttApplicationMessageBuilder()
.WithTopic("MyTopic")
.WithPayload("Hello World")
.WithExactlyOnceQoS()
.WithRetainFlag()
.Build();
await mqttClient.PublishAsync(message, CancellationToken.None); // Since 3.0.5 with CancellationToken
It is not required to fill all properties of an application message. The following code shows how to create a very basic application message:
var message = new MqttApplicationMessageBuilder()
.WithTopic("/MQTTnet/is/awesome")
.Build();
The extension MQTTnet.Extensions.Rpc (available as nuget) allows sending a request and waiting for the matching reply. This is done via defining a pattern which uses the topic to correlate the request and the response. From client usage it is possible to define a timeout. The following code shows how to send a RPC call.
var rpcClient = new MqttRpcClient(_mqttClient);
var timeout = TimeSpan.FromSeconds(5);
var qos = MqttQualityOfServiceLevel.AtMostOnce;
var response = await rpcClient.ExecuteAsync(timeout, "myMethod", payload, qos);
The device (Arduino, ESP8266 etc.) which responds to the request needs to parse the topic and reply to it. The following code shows how to implement the handler.
// If using the MQTT client PubSubClient it must be ensured
// that the request topic for each method is subscribed like the following.
mqttClient.subscribe("MQTTnet.RPC/+/ping");
mqttClient.subscribe("MQTTnet.RPC/+/do_something");
// It is not allowed to change the structure of the topic.
// Otherwise RPC will not work.
// So method names can be separated using an _ or . but no +, # or /.
// If it is required to distinguish between devices
// own rules can be defined like the following:
mqttClient.subscribe("MQTTnet.RPC/+/deviceA.ping");
mqttClient.subscribe("MQTTnet.RPC/+/deviceB.ping");
mqttClient.subscribe("MQTTnet.RPC/+/deviceC.getTemperature");
// Within the callback of the MQTT client the topic must be checked
// if it belongs to MQTTnet RPC. The following code shows one
// possible way of doing this.
void mqtt_Callback(char *topic, byte *payload, unsigned int payloadLength)
{
String topicString = String(topic);
if (topicString.startsWith("MQTTnet.RPC/")) {
String responseTopic = topicString + String("/response");
if (topicString.endsWith("/deviceA.ping")) {
mqtt_publish(responseTopic, "pong", false);
return;
}
}
}
// Important notes:
// ! Do not send response message with the _retain_ flag set to true.
// ! All required data for a RPC call and the result must be placed into the payload.
Due to a known issue with WebSocket implementation for .NET it is not possible to connect with Amazon AWS with MQTTnet because it relies on the .NET implementation of WebSockets.
But there is a sample available which shows how to use a different transport layer implementation.
https://github.com/aws-samples/aws-iot-core-dotnet-app-mqtt-over-websockets-sigv4
- Remove the Amazon root certificate and just pass the client certificate in the
.pfx
format:
List<X509Certificate> certs = new List<X509Certificate>
{
new X509Certificate2("ClientCertPath", "ClientCertPass", X509KeyStorageFlags.Exportable)
};
- Use the following client code:
var clientOptions = new MqttClientOptionsBuilder()
.WithTcpServer(endpoint, port)
.WithKeepAlivePeriod(new TimeSpan(0, 0, 0, 300))
.WithTls(new MqttClientOptionsBuilderTlsParameters
{
UseTls = true,
#pragma warning disable CS0618 // Type or member is obsolete
CertificateValidationCallback = (X509Certificate x, X509Chain y, System.Net.Security.SslPolicyErrors z, IMqttClientOptions o) =>
#pragma warning restore CS0618 // Type or member is obsolete
{
return true;
},
AllowUntrustedCertificates = false,
IgnoreCertificateChainErrors = false,
IgnoreCertificateRevocationErrors = false,
Certificates = certs
})
.WithProtocolVersion(MqttProtocolVersion.V311)
.Build();
Before digging dip on certificates, make sure that
- you have the correct URL. Copy it from "Interact" section of the "thing".
- you can connect to that URL using openssl:
openssl s_client -CAfile awsca.pem -cert xxx.pem.crt -key xxx.pem.key -connect yyy.iot.region.amazonaws.com:8883
- the certificate attached to the thing is activated.
- you have attached one policy (or several) that allows the thing to connect, subscribe, publish and receive.
- you're not using a feature that isn't supported (Retained messages, will messages, Unsupported QoS 2).
If you're absolutely sure of the above, then start troubleshooting certificates.
- Net Framework: https://gist.github.com/jkoplo/16f25875f7aca9503a7520fcda99005f
- NetCore: https://gist.github.com/jkoplo/bd60cfe1a02c6e13b0a2d753289ae00f
When using the client there is no difference between the .Net Framework, .Net Core or ASP.NET Core. The configuration above applies. The client cannot be made available using dependency injection.
A sample example is available here mqtt-client-dotnet-core