mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-20 03:24:33 +08:00
Refactor code
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
* [Core] Fixed a memory leak in _AsyncSignal_ implementation (#1586, thanks to @mario-zelger).
|
* [Core] Fixed a memory leak in _AsyncSignal_ implementation (#1586, thanks to @mario-zelger).
|
||||||
* [Core] Fixed an issue with bounds handling in _MqttBufferReader_ (#1593).
|
* [Core] Fixed an issue with bounds handling in _MqttBufferReader_ (#1593).
|
||||||
|
* [Core] Performance improvements and reduced memory usage due to improved buffer copies (#1599, thanks to @xljiulang).
|
||||||
* [Server] Fix not properly reset statistics (#1587, thanks to @damikun).
|
* [Server] Fix not properly reset statistics (#1587, thanks to @damikun).
|
||||||
* [Server] Now using an empty string as the sender client ID for injected application messages (#1583, thanks to @xljiulang).
|
* [Server] Now using an empty string as the sender client ID for injected application messages (#1583, thanks to @xljiulang).
|
||||||
* [Server] Improved memory usage for ASP.NET connections (#1582, thanks to @xljiulang).
|
* [Server] Improved memory usage for ASP.NET connections (#1582, thanks to @xljiulang).
|
||||||
|
* [Server] Improved memory usage and performance for ASP.NET integration (#1596, thanks to @xljiulang).
|
||||||
@@ -33,7 +33,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
[Benchmark]
|
[Benchmark]
|
||||||
public void Memory_Copy()
|
public void Memory_Copy()
|
||||||
{
|
{
|
||||||
MQTTnet.Internal.Memory.Copy(source, 0, target, 0, Length);
|
MQTTnet.Internal.MqttMemoryHelper.Copy(source, 0, target, 0, Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
// 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.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using MQTTnet.Exceptions;
|
using MQTTnet.Exceptions;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
@@ -67,6 +70,172 @@ namespace MQTTnet.Tests.Formatter
|
|||||||
Assert.AreEqual(5, remainingData.Length);
|
Assert.AreEqual(5, remainingData.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Read_Various_Positions_and_Offsets()
|
||||||
|
{
|
||||||
|
const int NumBufferElements = 1000;
|
||||||
|
const int NumTestSequences = 10000;
|
||||||
|
|
||||||
|
const string TestString = "The quick brown fox jumps over the lazy dog.";
|
||||||
|
|
||||||
|
var rndSeed = Environment.TickCount;
|
||||||
|
// rndSeed = ...; // assign random seed here if there is an error so that it can be repeated
|
||||||
|
var rnd = new Random(rndSeed);
|
||||||
|
// rndSeed is printed when an assert fails
|
||||||
|
|
||||||
|
var bufferElements = new List<ElementReference>();
|
||||||
|
|
||||||
|
byte[] elementBuffer;
|
||||||
|
|
||||||
|
// Fill a buffer with strings or numbers in big endian format
|
||||||
|
using (var ms = new MemoryStream())
|
||||||
|
{
|
||||||
|
using (var bw = new BinaryWriter(ms))
|
||||||
|
{
|
||||||
|
var bufferOffset = 0;
|
||||||
|
|
||||||
|
for (var i = 0; i < NumBufferElements; i++)
|
||||||
|
{
|
||||||
|
var nextElementType = (ElementReference.BufferElementType)rnd.Next(ElementReference.NumBufferElementTypes);
|
||||||
|
|
||||||
|
byte[] elementBytes = null;
|
||||||
|
uint elementNumberValue = 0; // all element number values fit into uint
|
||||||
|
string elementStringValue = null;
|
||||||
|
var elementSize = 0;
|
||||||
|
|
||||||
|
var alreadyBigEndian = false;
|
||||||
|
switch (nextElementType)
|
||||||
|
{
|
||||||
|
case ElementReference.BufferElementType.Byte:
|
||||||
|
var byteValue = (byte)i;
|
||||||
|
elementNumberValue = byteValue;
|
||||||
|
elementSize = sizeof(byte);
|
||||||
|
elementBytes = new byte[1] { byteValue };
|
||||||
|
alreadyBigEndian = true; // nothing to swap
|
||||||
|
break;
|
||||||
|
case ElementReference.BufferElementType.TwoByteInt:
|
||||||
|
var ushortValue = (ushort)i;
|
||||||
|
elementNumberValue = ushortValue;
|
||||||
|
elementSize = sizeof(ushort);
|
||||||
|
elementBytes = BitConverter.GetBytes(ushortValue);
|
||||||
|
break;
|
||||||
|
case ElementReference.BufferElementType.FourByteInt:
|
||||||
|
var uintValue = (uint)i;
|
||||||
|
elementNumberValue = uintValue;
|
||||||
|
elementSize = sizeof(uint);
|
||||||
|
elementBytes = BitConverter.GetBytes(uintValue);
|
||||||
|
break;
|
||||||
|
case ElementReference.BufferElementType.VariableSizeInt:
|
||||||
|
{
|
||||||
|
elementNumberValue = (uint)i;
|
||||||
|
var writer = new MqttBufferWriter(4, 4);
|
||||||
|
writer.WriteVariableByteInteger(elementNumberValue);
|
||||||
|
elementSize = writer.Length;
|
||||||
|
elementBytes = new byte[elementSize];
|
||||||
|
var buffer = writer.GetBuffer();
|
||||||
|
Array.Copy(buffer, elementBytes, elementSize);
|
||||||
|
alreadyBigEndian = true; // nothing to swap
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ElementReference.BufferElementType.String:
|
||||||
|
{
|
||||||
|
var stringLen = rnd.Next(TestString.Length);
|
||||||
|
elementStringValue = TestString.Substring(0, stringLen); // could be empty
|
||||||
|
var writer = new MqttBufferWriter(stringLen + 1, stringLen + 1);
|
||||||
|
writer.WriteString(elementStringValue);
|
||||||
|
elementSize = writer.Length;
|
||||||
|
elementBytes = new byte[elementSize];
|
||||||
|
var buffer = writer.GetBuffer();
|
||||||
|
Array.Copy(buffer, elementBytes, elementSize);
|
||||||
|
alreadyBigEndian = true; // nothing to swap
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BitConverter.IsLittleEndian && !alreadyBigEndian)
|
||||||
|
{
|
||||||
|
Array.Reverse(elementBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
bw.Write(elementBytes);
|
||||||
|
bufferElements.Add(new ElementReference(nextElementType, elementSize, elementNumberValue, elementStringValue, bufferOffset));
|
||||||
|
bufferOffset += elementSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
elementBuffer = ms.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < NumTestSequences; i++)
|
||||||
|
{
|
||||||
|
var firstElementIndex = rnd.Next(NumBufferElements - 1);
|
||||||
|
// segmentElementIndex will be < NumBufferElements - 1, which means there is at least one byte left for segment element count
|
||||||
|
var elementCount = rnd.Next(NumBufferElements - firstElementIndex) + 1;
|
||||||
|
var lastElementIndex = firstElementIndex + elementCount - 1;
|
||||||
|
// Use element index to get buffer offsets
|
||||||
|
var lastElement = bufferElements[lastElementIndex];
|
||||||
|
var segmentStartPosition = bufferElements[firstElementIndex].BufferOffset;
|
||||||
|
var segmentEndPosition = lastElement.BufferOffset + lastElement.Size;
|
||||||
|
var segmentLength = segmentEndPosition - segmentStartPosition;
|
||||||
|
|
||||||
|
var reader = new MqttBufferReader();
|
||||||
|
reader.SetBuffer(elementBuffer, segmentStartPosition, segmentLength);
|
||||||
|
|
||||||
|
// read all elements in the buffer segment; values should be as expected
|
||||||
|
for (var n = 0; n < elementCount; n++)
|
||||||
|
{
|
||||||
|
var element = bufferElements[firstElementIndex + n];
|
||||||
|
uint elementNumberValue = 0;
|
||||||
|
string elementStringValue = null;
|
||||||
|
|
||||||
|
var expectedPosition = element.BufferOffset - segmentStartPosition;
|
||||||
|
Assert.AreEqual(expectedPosition, reader.Position);
|
||||||
|
|
||||||
|
switch (element.Type)
|
||||||
|
{
|
||||||
|
case ElementReference.BufferElementType.Byte:
|
||||||
|
{
|
||||||
|
elementNumberValue = reader.ReadByte();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ElementReference.BufferElementType.TwoByteInt:
|
||||||
|
{
|
||||||
|
elementNumberValue = reader.ReadTwoByteInteger();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ElementReference.BufferElementType.FourByteInt:
|
||||||
|
{
|
||||||
|
elementNumberValue = reader.ReadFourByteInteger();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ElementReference.BufferElementType.VariableSizeInt:
|
||||||
|
{
|
||||||
|
elementNumberValue = reader.ReadVariableByteInteger();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ElementReference.BufferElementType.String:
|
||||||
|
{
|
||||||
|
elementStringValue = reader.ReadString();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elementStringValue != null)
|
||||||
|
{
|
||||||
|
Assert.AreEqual(elementStringValue, element.StringValue, $"'{elementStringValue}' not equal '{element.StringValue}' with random seed {rndSeed}.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Assert.AreEqual(elementNumberValue, element.NumberValue, $"{elementNumberValue} not equal {element.NumberValue} with random seed {rndSeed}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// confirm end of stream
|
||||||
|
Assert.AreEqual(0, reader.BytesLeft, "BytesLeft not zero as expected with random seed " + rndSeed);
|
||||||
|
Assert.IsTrue(reader.EndOfStream, "Not end of stream as expected with random seed " + rndSeed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Report_Correct_Length_For_Full_Buffer()
|
public void Report_Correct_Length_For_Full_Buffer()
|
||||||
{
|
{
|
||||||
@@ -106,5 +275,36 @@ namespace MQTTnet.Tests.Formatter
|
|||||||
Assert.IsFalse(reader.EndOfStream);
|
Assert.IsFalse(reader.EndOfStream);
|
||||||
Assert.AreEqual(5, reader.BytesLeft);
|
Assert.AreEqual(5, reader.BytesLeft);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Helper class to build up a reference to elements of various types in a buffer
|
||||||
|
class ElementReference
|
||||||
|
{
|
||||||
|
public enum BufferElementType
|
||||||
|
{
|
||||||
|
Byte,
|
||||||
|
TwoByteInt,
|
||||||
|
FourByteInt,
|
||||||
|
VariableSizeInt,
|
||||||
|
String
|
||||||
|
}
|
||||||
|
|
||||||
|
public const int NumBufferElementTypes = 5;
|
||||||
|
|
||||||
|
public ElementReference(BufferElementType type, int size, uint numberValue, string stringValue, int bufferOffset)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
Size = size;
|
||||||
|
NumberValue = numberValue;
|
||||||
|
StringValue = stringValue;
|
||||||
|
BufferOffset = bufferOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int BufferOffset { get; }
|
||||||
|
public uint NumberValue { get; }
|
||||||
|
public int Size { get; }
|
||||||
|
public string StringValue { get; }
|
||||||
|
public BufferElementType Type { get; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,21 +2,23 @@
|
|||||||
// 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.Exceptions;
|
|
||||||
using MQTTnet.Internal;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using MQTTnet.Exceptions;
|
||||||
|
using MQTTnet.Internal;
|
||||||
|
#if NETCOREAPP3_0_OR_GREATER
|
||||||
|
using System.Buffers.Binary;
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace MQTTnet.Formatter
|
namespace MQTTnet.Formatter
|
||||||
{
|
{
|
||||||
public sealed class MqttBufferReader
|
public sealed class MqttBufferReader
|
||||||
{
|
{
|
||||||
byte[] _buffer = EmptyBuffer.Array;
|
byte[] _buffer = EmptyBuffer.Array;
|
||||||
|
int _maxPosition;
|
||||||
int _offset;
|
int _offset;
|
||||||
int _position;
|
int _position;
|
||||||
int _maxPosition;
|
|
||||||
|
|
||||||
public int BytesLeft => _maxPosition - _position;
|
public int BytesLeft => _maxPosition - _position;
|
||||||
|
|
||||||
@@ -36,7 +38,7 @@ namespace MQTTnet.Formatter
|
|||||||
ValidateReceiveBuffer(length);
|
ValidateReceiveBuffer(length);
|
||||||
|
|
||||||
var result = new byte[length];
|
var result = new byte[length];
|
||||||
Memory.Copy(_buffer, _position, result, 0, length);
|
MqttMemoryHelper.Copy(_buffer, _position, result, 0, length);
|
||||||
_position += length;
|
_position += length;
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -53,11 +55,7 @@ namespace MQTTnet.Formatter
|
|||||||
ValidateReceiveBuffer(4);
|
ValidateReceiveBuffer(4);
|
||||||
|
|
||||||
#if NETCOREAPP3_0_OR_GREATER
|
#if NETCOREAPP3_0_OR_GREATER
|
||||||
var value = MemoryMarshal.Read<uint>(_buffer.AsSpan(_position));
|
var value = BinaryPrimitives.ReadUInt32BigEndian(_buffer.AsSpan(_position));
|
||||||
if (BitConverter.IsLittleEndian)
|
|
||||||
{
|
|
||||||
value = System.Buffers.Binary.BinaryPrimitives.ReverseEndianness(value);
|
|
||||||
}
|
|
||||||
#else
|
#else
|
||||||
var byte0 = _buffer[_position];
|
var byte0 = _buffer[_position];
|
||||||
var byte1 = _buffer[_position + 1];
|
var byte1 = _buffer[_position + 1];
|
||||||
@@ -80,7 +78,7 @@ namespace MQTTnet.Formatter
|
|||||||
}
|
}
|
||||||
|
|
||||||
var buffer = new byte[bufferLength];
|
var buffer = new byte[bufferLength];
|
||||||
Memory.Copy(_buffer, _position, buffer, 0, bufferLength);
|
MqttMemoryHelper.Copy(_buffer, _position, buffer, 0, bufferLength);
|
||||||
_position += bufferLength;
|
_position += bufferLength;
|
||||||
|
|
||||||
return buffer;
|
return buffer;
|
||||||
@@ -102,7 +100,7 @@ namespace MQTTnet.Formatter
|
|||||||
var result = Encoding.UTF8.GetString(_buffer.AsSpan(_position, length));
|
var result = Encoding.UTF8.GetString(_buffer.AsSpan(_position, length));
|
||||||
#else
|
#else
|
||||||
var result = Encoding.UTF8.GetString(_buffer, _position, length);
|
var result = Encoding.UTF8.GetString(_buffer, _position, length);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
_position += length;
|
_position += length;
|
||||||
return result;
|
return result;
|
||||||
@@ -113,11 +111,7 @@ namespace MQTTnet.Formatter
|
|||||||
ValidateReceiveBuffer(2);
|
ValidateReceiveBuffer(2);
|
||||||
|
|
||||||
#if NETCOREAPP3_0_OR_GREATER
|
#if NETCOREAPP3_0_OR_GREATER
|
||||||
var value = MemoryMarshal.Read<ushort>(_buffer.AsSpan(_position));
|
var value = BinaryPrimitives.ReadUInt16BigEndian(_buffer.AsSpan(_position));
|
||||||
if (BitConverter.IsLittleEndian)
|
|
||||||
{
|
|
||||||
value = System.Buffers.Binary.BinaryPrimitives.ReverseEndianness(value);
|
|
||||||
}
|
|
||||||
#else
|
#else
|
||||||
var msb = _buffer[_position];
|
var msb = _buffer[_position];
|
||||||
var lsb = _buffer[_position + 1];
|
var lsb = _buffer[_position + 1];
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ namespace MQTTnet.Formatter
|
|||||||
|
|
||||||
EnsureAdditionalCapacity(count);
|
EnsureAdditionalCapacity(count);
|
||||||
|
|
||||||
Memory.Copy(buffer, offset, _buffer, _position, count);
|
MqttMemoryHelper.Copy(buffer, offset, _buffer, _position, count);
|
||||||
IncreasePosition(count);
|
IncreasePosition(count);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +150,7 @@ namespace MQTTnet.Formatter
|
|||||||
_buffer[_position] = (byte)(valueLength >> 8);
|
_buffer[_position] = (byte)(valueLength >> 8);
|
||||||
_buffer[_position + 1] = (byte)valueLength;
|
_buffer[_position + 1] = (byte)valueLength;
|
||||||
|
|
||||||
Memory.Copy(value, 0, _buffer, _position + 2, valueLength);
|
MqttMemoryHelper.Copy(value, 0, _buffer, _position + 2, valueLength);
|
||||||
IncreasePosition(valueLength + 2);
|
IncreasePosition(valueLength + 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ namespace MQTTnet.Formatter
|
|||||||
}
|
}
|
||||||
|
|
||||||
var buffer = new byte[Length];
|
var buffer = new byte[Length];
|
||||||
Memory.Copy(Packet.Array, Packet.Offset, buffer, 0, Packet.Count);
|
MqttMemoryHelper.Copy(Packet.Array, Packet.Offset, buffer, 0, Packet.Count);
|
||||||
Memory.Copy(Payload.Array, Payload.Offset, buffer, Packet.Count, Payload.Count);
|
MqttMemoryHelper.Copy(Payload.Array, Payload.Offset, buffer, Packet.Count, Payload.Count);
|
||||||
|
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
@@ -57,8 +57,8 @@ namespace MQTTnet.Formatter
|
|||||||
}
|
}
|
||||||
|
|
||||||
var buffer = new byte[Length];
|
var buffer = new byte[Length];
|
||||||
Memory.Copy(Packet.Array, Packet.Offset, buffer, 0, Packet.Count);
|
MqttMemoryHelper.Copy(Packet.Array, Packet.Offset, buffer, 0, Packet.Count);
|
||||||
Memory.Copy(Payload.Array, Payload.Offset, buffer, Packet.Count, Payload.Count);
|
MqttMemoryHelper.Copy(Payload.Array, Payload.Offset, buffer, Packet.Count, Payload.Count);
|
||||||
|
|
||||||
return new ArraySegment<byte>(buffer);
|
return new ArraySegment<byte>(buffer);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using System.Runtime.CompilerServices;
|
|||||||
|
|
||||||
namespace MQTTnet.Internal
|
namespace MQTTnet.Internal
|
||||||
{
|
{
|
||||||
public static class Memory
|
public static class MqttMemoryHelper
|
||||||
{
|
{
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void Copy(byte[] source, int sourceIndex, byte[] destination, int destinationIndex, int length)
|
public static void Copy(byte[] source, int sourceIndex, byte[] destination, int destinationIndex, int length)
|
||||||
@@ -13,11 +13,11 @@ namespace MQTTnet.Internal
|
|||||||
#elif NET461_OR_GREATER || NETSTANDARD1_3_OR_GREATER
|
#elif NET461_OR_GREATER || NETSTANDARD1_3_OR_GREATER
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
fixed (byte* pSoure = &source[sourceIndex])
|
fixed (byte* sourceHandle = &source[sourceIndex])
|
||||||
{
|
{
|
||||||
fixed (byte* pDestination = &destination[destinationIndex])
|
fixed (byte* destinationHandle = &destination[destinationIndex])
|
||||||
{
|
{
|
||||||
System.Buffer.MemoryCopy(pSoure, pDestination, length, length);
|
System.Buffer.MemoryCopy(sourceHandle, destinationHandle, length, length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user