Performance optimizations

This commit is contained in:
Christian Kratky
2017-11-25 23:10:48 +01:00
parent 24ab285066
commit 611fabcd01
11 changed files with 105 additions and 88 deletions
@@ -60,8 +60,11 @@ namespace MQTTnet.Adapter
_logger.Trace<MqttChannelAdapter>("TX >>> {0} [Timeout={1}]", packet, timeout); _logger.Trace<MqttChannelAdapter>("TX >>> {0} [Timeout={1}]", packet, timeout);
var writeBuffer = PacketSerializer.Serialize(packet); var chunks = PacketSerializer.Serialize(packet);
await _channel.SendStream.WriteAsync(writeBuffer, 0, writeBuffer.Length, cancellationToken).ConfigureAwait(false); foreach (var chunk in chunks)
{
await _channel.SendStream.WriteAsync(chunk.Array, chunk.Offset, chunk.Count, cancellationToken).ConfigureAwait(false);
}
} }
if (timeout > TimeSpan.Zero) if (timeout > TimeSpan.Zero)
@@ -114,7 +117,7 @@ namespace MQTTnet.Adapter
{ {
receivedMqttPacket?.Dispose(); receivedMqttPacket?.Dispose();
} }
}); }).ConfigureAwait(false);
return packet; return packet;
} }
@@ -141,7 +144,7 @@ namespace MQTTnet.Adapter
offset += readBytesCount; offset += readBytesCount;
} while (offset < header.BodyLength); } while (offset < header.BodyLength);
return new ReceivedMqttPacket(header, new MemoryStream(body, 0, body.Length)); return new ReceivedMqttPacket(header, new MemoryStream(body, 0, body.Length, false, true));
} }
private static async Task ExecuteAndWrapExceptionAsync(Func<Task> action) private static async Task ExecuteAndWrapExceptionAsync(Func<Task> action)
@@ -1,4 +1,6 @@
using MQTTnet.Adapter; using System;
using System.Collections.Generic;
using MQTTnet.Adapter;
using MQTTnet.Packets; using MQTTnet.Packets;
namespace MQTTnet.Serializer namespace MQTTnet.Serializer
@@ -7,7 +9,7 @@ namespace MQTTnet.Serializer
{ {
MqttProtocolVersion ProtocolVersion { get; set; } MqttProtocolVersion ProtocolVersion { get; set; }
byte[] Serialize(MqttBasePacket mqttPacket); IEnumerable<ArraySegment<byte>> Serialize(MqttBasePacket mqttPacket);
MqttBasePacket Deserialize(ReceivedMqttPacket receivedMqttPacket); MqttBasePacket Deserialize(ReceivedMqttPacket receivedMqttPacket);
} }
@@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
@@ -57,12 +56,22 @@ namespace MQTTnet.Serializer
public string ReadStringWithLengthPrefix() public string ReadStringWithLengthPrefix()
{ {
var buffer = ReadWithLengthPrefix(); var buffer = ReadWithLengthPrefix();
if (buffer.Length == 0)
{
return string.Empty;
}
return Encoding.UTF8.GetString(buffer, 0, buffer.Length); return Encoding.UTF8.GetString(buffer, 0, buffer.Length);
} }
public byte[] ReadWithLengthPrefix() public byte[] ReadWithLengthPrefix()
{ {
var length = ReadUInt16(); var length = ReadUInt16();
if (length == 0)
{
return new byte[0];
}
return ReadBytes(length); return ReadBytes(length);
} }
@@ -78,7 +87,7 @@ namespace MQTTnet.Serializer
var value = 0; var value = 0;
byte encodedByte; byte encodedByte;
var readBytes = new List<int>(); ////var readBytes = new List<int>();
do do
{ {
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
@@ -87,19 +96,20 @@ namespace MQTTnet.Serializer
} }
var buffer = stream.ReadByte(); var buffer = stream.ReadByte();
readBytes.Add(buffer);
if (buffer == -1) if (buffer == -1)
{ {
throw new MqttCommunicationException("Connection closed while reading remaining length data."); throw new MqttCommunicationException("Connection closed while reading remaining length data.");
} }
////readBytes.Add(buffer);
encodedByte = (byte)buffer; encodedByte = (byte)buffer;
value += (byte)(encodedByte & 127) * multiplier; value += (byte)(encodedByte & 127) * multiplier;
multiplier *= 128; multiplier *= 128;
if (multiplier > 128 * 128 * 128) if (multiplier > 128 * 128 * 128)
{ {
throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", readBytes)})."); //throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", readBytes)}).");
throw new MqttProtocolViolationException("Remaining length is invalid.");
} }
} while ((encodedByte & 128) != 0); } while ((encodedByte & 128) != 0);
@@ -1,12 +1,12 @@
using System; using MQTTnet.Adapter;
using MQTTnet.Exceptions;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using MQTTnet.Adapter;
using MQTTnet.Exceptions;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Serializer namespace MQTTnet.Serializer
{ {
@@ -17,25 +17,29 @@ namespace MQTTnet.Serializer
public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311; public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311;
public byte[] Serialize(MqttBasePacket packet) public IEnumerable<ArraySegment<byte>> Serialize(MqttBasePacket packet)
{ {
if (packet == null) throw new ArgumentNullException(nameof(packet)); if (packet == null) throw new ArgumentNullException(nameof(packet));
using (var stream = new MemoryStream()) using (var stream = new MemoryStream(128))
using (var writer = new MqttPacketWriter(stream)) using (var writer = new MqttPacketWriter(stream))
{ {
var fixedHeader = SerializePacket(packet, writer); var fixedHeader = SerializePacket(packet, writer);
var headerBuffer = new List<byte> { fixedHeader }; var remainingLength = (int)stream.Length;
MqttPacketWriter.WriteRemainingLength((int)stream.Length, headerBuffer); writer.Write(fixedHeader);
MqttPacketWriter.WriteRemainingLength(remainingLength, writer);
var headerLength = (int)stream.Length - remainingLength;
var header = headerBuffer.ToArray(); #if NET461 || NET452 || NETSTANDARD2_0
var body = stream.ToArray(); var buffer = stream.GetBuffer();
#else
var buffer = new byte[header.Length + body.Length]; var buffer = stream.ToArray();
Buffer.BlockCopy(header, 0, buffer, 0, header.Length); #endif
Buffer.BlockCopy(body, 0, buffer, header.Length, body.Length); return new List<ArraySegment<byte>>
{
return buffer; new ArraySegment<byte>(buffer, remainingLength, headerLength),
new ArraySegment<byte>(buffer, 0, remainingLength)
};
} }
} }
@@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using MQTTnet.Protocol; using MQTTnet.Protocol;
@@ -56,11 +55,11 @@ namespace MQTTnet.Serializer
Write(value); Write(value);
} }
public static void WriteRemainingLength(int length, List<byte> target) public static void WriteRemainingLength(int length, BinaryWriter target)
{ {
if (length == 0) if (length == 0)
{ {
target.Add(0); target.Write((byte)0);
return; return;
} }
@@ -75,7 +74,7 @@ namespace MQTTnet.Serializer
encodedByte = encodedByte | 128; encodedByte = encodedByte | 128;
} }
target.Add((byte)encodedByte); target.Write((byte)encodedByte);
} while (x > 0); } while (x > 0);
} }
} }
@@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Adapter; using MQTTnet.Adapter;
@@ -14,8 +13,6 @@ namespace MQTTnet.Server
{ {
public sealed class MqttClientSession public sealed class MqttClientSession
{ {
private readonly HashSet<ushort> _unacknowledgedPublishPackets = new HashSet<ushort>();
private readonly MqttClientSubscriptionsManager _subscriptionsManager; private readonly MqttClientSubscriptionsManager _subscriptionsManager;
private readonly MqttClientSessionsManager _sessionsManager; private readonly MqttClientSessionsManager _sessionsManager;
private readonly MqttClientPendingMessagesQueue _pendingMessagesQueue; private readonly MqttClientPendingMessagesQueue _pendingMessagesQueue;
@@ -99,21 +96,22 @@ namespace MQTTnet.Server
if (willMessage != null) if (willMessage != null)
{ {
_willMessage = null; //clear willmessage so it is send just once _willMessage = null; //clear willmessage so it is send just once
await _sessionsManager.DispatchApplicationMessageAsync(this, willMessage); await _sessionsManager.DispatchApplicationMessageAsync(this, willMessage).ConfigureAwait(false);
} }
} }
} }
public void EnqueuePublishPacket(MqttPublishPacket publishPacket) public void EnqueueApplicationMessage(MqttApplicationMessage applicationMessage)
{ {
if (publishPacket == null) throw new ArgumentNullException(nameof(publishPacket)); if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
var result = _subscriptionsManager.CheckSubscriptions(publishPacket); var result = _subscriptionsManager.CheckSubscriptions(applicationMessage);
if (!result.IsSubscribed) if (!result.IsSubscribed)
{ {
return; return;
} }
var publishPacket = applicationMessage.ToPublishPacket();
publishPacket.QualityOfServiceLevel = result.QualityOfServiceLevel; publishPacket.QualityOfServiceLevel = result.QualityOfServiceLevel;
_pendingMessagesQueue.Enqueue(publishPacket); _pendingMessagesQueue.Enqueue(publishPacket);
} }
@@ -134,12 +132,12 @@ namespace MQTTnet.Server
catch (MqttCommunicationException exception) catch (MqttCommunicationException exception)
{ {
_logger.Warning<MqttClientSession>(exception, "Client '{0}': Communication exception while processing client packets.", ClientId); _logger.Warning<MqttClientSession>(exception, "Client '{0}': Communication exception while processing client packets.", ClientId);
await StopAsync(); await StopAsync().ConfigureAwait(false);
} }
catch (Exception exception) catch (Exception exception)
{ {
_logger.Error<MqttClientSession>(exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId); _logger.Error<MqttClientSession>(exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId);
await StopAsync(); await StopAsync().ConfigureAwait(false);
} }
} }
@@ -200,7 +198,7 @@ namespace MQTTnet.Server
if (subscribeResult.CloseConnection) if (subscribeResult.CloseConnection)
{ {
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new MqttDisconnectPacket()).ConfigureAwait(false); await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new MqttDisconnectPacket()).ConfigureAwait(false);
await StopAsync(); await StopAsync().ConfigureAwait(false);
} }
} }
@@ -209,7 +207,7 @@ namespace MQTTnet.Server
var retainedMessages = await _sessionsManager.GetRetainedMessagesAsync(subscribePacket).ConfigureAwait(false); var retainedMessages = await _sessionsManager.GetRetainedMessagesAsync(subscribePacket).ConfigureAwait(false);
foreach (var publishPacket in retainedMessages) foreach (var publishPacket in retainedMessages)
{ {
EnqueuePublishPacket(publishPacket.ToPublishPacket()); EnqueueApplicationMessage(publishPacket);
} }
} }
@@ -221,27 +219,22 @@ namespace MQTTnet.Server
{ {
case MqttQualityOfServiceLevel.AtMostOnce: case MqttQualityOfServiceLevel.AtMostOnce:
{ {
await _sessionsManager.DispatchApplicationMessageAsync(this, applicationMessage); await _sessionsManager.DispatchApplicationMessageAsync(this, applicationMessage).ConfigureAwait(false);
return; return;
} }
case MqttQualityOfServiceLevel.AtLeastOnce: case MqttQualityOfServiceLevel.AtLeastOnce:
{ {
await _sessionsManager.DispatchApplicationMessageAsync(this, applicationMessage); await _sessionsManager.DispatchApplicationMessageAsync(this, applicationMessage).ConfigureAwait(false);
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken,
new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier }); new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier }).ConfigureAwait(false);
return; return;
} }
case MqttQualityOfServiceLevel.ExactlyOnce: case MqttQualityOfServiceLevel.ExactlyOnce:
{ {
// QoS 2 is implement as method "B" [4.3.3 QoS 2: Exactly once delivery] // QoS 2 is implement as method "B" [4.3.3 QoS 2: Exactly once delivery]
lock (_unacknowledgedPublishPackets) await _sessionsManager.DispatchApplicationMessageAsync(this, applicationMessage).ConfigureAwait(false);
{
_unacknowledgedPublishPackets.Add(publishPacket.PacketIdentifier);
}
await _sessionsManager.DispatchApplicationMessageAsync(this, applicationMessage);
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken,
new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }).ConfigureAwait(false); new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }).ConfigureAwait(false);
@@ -255,11 +248,6 @@ namespace MQTTnet.Server
private Task HandleIncomingPubRelPacketAsync(IMqttChannelAdapter adapter, MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken) private Task HandleIncomingPubRelPacketAsync(IMqttChannelAdapter adapter, MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
{ {
lock (_unacknowledgedPublishPackets)
{
_unacknowledgedPublishPackets.Remove(pubRelPacket.PacketIdentifier);
}
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new MqttPubCompPacket { PacketIdentifier = pubRelPacket.PacketIdentifier }); return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new MqttPubCompPacket { PacketIdentifier = pubRelPacket.PacketIdentifier });
} }
} }
@@ -6,7 +6,6 @@ using System.Threading.Tasks;
using MQTTnet.Adapter; using MQTTnet.Adapter;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
using MQTTnet.Internal;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Serializer; using MQTTnet.Serializer;
@@ -59,7 +58,7 @@ namespace MQTTnet.Server
return; return;
} }
var clientSession = await GetOrCreateClientSessionAsync(connectPacket); var clientSession = await GetOrCreateClientSessionAsync(connectPacket).ConfigureAwait(false);
await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new MqttConnAckPacket await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new MqttConnAckPacket
{ {
@@ -105,7 +104,7 @@ namespace MQTTnet.Server
{ {
foreach (var session in _sessions) foreach (var session in _sessions)
{ {
await session.Value.StopAsync(); await session.Value.StopAsync().ConfigureAwait(false);
} }
_sessions.Clear(); _sessions.Clear();
@@ -162,7 +161,7 @@ namespace MQTTnet.Server
{ {
foreach (var clientSession in _sessions.Values.ToList()) foreach (var clientSession in _sessions.Values.ToList())
{ {
clientSession.EnqueuePublishPacket(applicationMessage.ToPublishPacket()); clientSession.EnqueueApplicationMessage(applicationMessage);
} }
} }
} }
@@ -200,7 +199,7 @@ namespace MQTTnet.Server
if (connectPacket.CleanSession) if (connectPacket.CleanSession)
{ {
_sessions.Remove(connectPacket.ClientId); _sessions.Remove(connectPacket.ClientId);
await clientSession.StopAsync(); await clientSession.StopAsync().ConfigureAwait(false);
clientSession = null; clientSession = null;
_logger.Trace<MqttClientSessionsManager>("Stopped existing session of client '{0}'.", connectPacket.ClientId); _logger.Trace<MqttClientSessionsManager>("Stopped existing session of client '{0}'.", connectPacket.ClientId);
@@ -64,23 +64,23 @@ namespace MQTTnet.Server
return unsubscribePacket.CreateResponse<MqttUnsubAckPacket>(); return unsubscribePacket.CreateResponse<MqttUnsubAckPacket>();
} }
public CheckSubscriptionsResult CheckSubscriptions(MqttPublishPacket publishPacket) public CheckSubscriptionsResult CheckSubscriptions(MqttApplicationMessage applicationMessage)
{ {
if (publishPacket == null) throw new ArgumentNullException(nameof(publishPacket)); if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
lock (_subscriptions) lock (_subscriptions)
{ {
foreach (var subscription in _subscriptions) foreach (var subscription in _subscriptions)
{ {
if (!MqttTopicFilterComparer.IsMatch(publishPacket.Topic, subscription.Key)) if (!MqttTopicFilterComparer.IsMatch(applicationMessage.Topic, subscription.Key))
{ {
continue; continue;
} }
var effectiveQos = subscription.Value; var effectiveQos = subscription.Value;
if (publishPacket.QualityOfServiceLevel < effectiveQos) if (applicationMessage.QualityOfServiceLevel < effectiveQos)
{ {
effectiveQos = publishPacket.QualityOfServiceLevel; effectiveQos = applicationMessage.QualityOfServiceLevel;
} }
return new CheckSubscriptionsResult return new CheckSubscriptionsResult
@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
@@ -392,9 +393,9 @@ namespace MQTTnet.Core.Tests
private static void SerializeAndCompare(MqttBasePacket packet, string expectedBase64Value, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311) private static void SerializeAndCompare(MqttBasePacket packet, string expectedBase64Value, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311)
{ {
var serializer = new MqttPacketSerializer { ProtocolVersion = protocolVersion }; var serializer = new MqttPacketSerializer { ProtocolVersion = protocolVersion };
var buffer = serializer.Serialize(packet); var chunks = serializer.Serialize(packet);
Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(buffer)); Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(Join(chunks)));
} }
private static void DeserializeAndCompare(MqttBasePacket packet, string expectedBase64Value) private static void DeserializeAndCompare(MqttBasePacket packet, string expectedBase64Value)
@@ -403,18 +404,29 @@ namespace MQTTnet.Core.Tests
var buffer1 = serializer.Serialize(packet); var buffer1 = serializer.Serialize(packet);
using (var headerStream = new MemoryStream(buffer1)) using (var headerStream = new MemoryStream(Join(buffer1)))
{ {
var header = MqttPacketReader.ReadHeaderFromSource(headerStream, CancellationToken.None); var header = MqttPacketReader.ReadHeaderFromSource(headerStream, CancellationToken.None);
using (var bodyStream = new MemoryStream(buffer1, (int)headerStream.Position, header.BodyLength)) using (var bodyStream = new MemoryStream(Join(buffer1), (int)headerStream.Position, header.BodyLength))
{ {
var deserializedPacket = serializer.Deserialize(new ReceivedMqttPacket(header, bodyStream)); var deserializedPacket = serializer.Deserialize(new ReceivedMqttPacket(header, bodyStream));
var buffer2 = serializer.Serialize(deserializedPacket); var buffer2 = serializer.Serialize(deserializedPacket);
Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(buffer2)); Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(Join(buffer2)));
} }
} }
} }
private static byte[] Join(IEnumerable<ArraySegment<byte>> chunks)
{
var buffer = new MemoryStream();
foreach (var chunk in chunks)
{
buffer.Write(chunk.Array, chunk.Offset, chunk.Count);
}
return buffer.ToArray();
}
} }
} }
@@ -18,7 +18,7 @@ namespace MQTTnet.Core.Tests
sm.Subscribe(sp, ""); sm.Subscribe(sp, "");
var pp = new MqttPublishPacket var pp = new MqttApplicationMessage
{ {
Topic = "A/B/C", Topic = "A/B/C",
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce
@@ -37,7 +37,7 @@ namespace MQTTnet.Core.Tests
sm.Subscribe(sp, ""); sm.Subscribe(sp, "");
var pp = new MqttPublishPacket var pp = new MqttApplicationMessage
{ {
Topic = "A/B/X", Topic = "A/B/X",
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce
@@ -56,7 +56,7 @@ namespace MQTTnet.Core.Tests
sm.Subscribe(sp, ""); sm.Subscribe(sp, "");
var pp = new MqttPublishPacket var pp = new MqttApplicationMessage
{ {
Topic = "A/B/C", Topic = "A/B/C",
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce
@@ -171,16 +171,16 @@ namespace MQTTnet.TestApp.NetCore
var msgs = 0; var msgs = 0;
var stopwatch = Stopwatch.StartNew(); var stopwatch = Stopwatch.StartNew();
mqttServer.ApplicationMessageReceived += (sender, args) => ////mqttServer.ApplicationMessageReceived += (sender, args) =>
{ ////{
msgs++; //// msgs++;
if (stopwatch.ElapsedMilliseconds > 1000) //// if (stopwatch.ElapsedMilliseconds > 1000)
{ //// {
Console.WriteLine($"received {msgs}"); //// Console.WriteLine($"received {msgs}");
msgs = 0; //// msgs = 0;
stopwatch.Restart(); //// stopwatch.Restart();
} //// }
}; ////};
await mqttServer.StartAsync(new MqttServerOptions()); await mqttServer.StartAsync(new MqttServerOptions());
Console.WriteLine("Press any key to exit."); Console.WriteLine("Press any key to exit.");