Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Version>9.0.6</Version>
<Version>9.0.7</Version>
</PropertyGroup>

<PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ public static string ToString(byte[]? bytes, string? separator = "-", bool upper
return string.Join(separator, bytes.Select(i => upper ? i.ToString("X2") : i.ToString("x2")));
}

/// <summary>
/// 将 byte[] 转为 16 进制字符串
/// <para>Converts a byte array to its hexadecimal string representation.</para>
/// </summary>
/// <param name="span"></param>
/// <param name="separator"></param>
/// <param name="upper"></param>
/// <returns></returns>
public static string ToString(ReadOnlySpan<byte> span, string? separator = "-", bool upper = true) => ToString(span.ToArray(), separator, upper);

/// <summary>
/// 将字符串转换为字节数组
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/

using BootstrapBlazor.Socket.Logging;
using System.Reflection;

namespace System;
Expand All @@ -20,7 +21,17 @@ public static class ActivatorExtensions
public static object? CreateInstance(this Type type, object?[]? args = null)
{
var bindings = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance;
return Activator.CreateInstance(type, bindings, null, args, null);

object? instance = null;
try
{
instance = Activator.CreateInstance(type, bindings, null, args, null);
}
catch (Exception ex)
{
SocketLogging.LogError(ex, $"Create Instance {type.FullName} failed");
}
return instance;
}

/// <summary>
Expand Down
18 changes: 10 additions & 8 deletions src/extensions/BootstrapBlazor.Socket/Logging/SocketLogging.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,17 @@ public static void Init(ILogger logger)
}

/// <summary>
///
/// </summary>
/// <param name="message"></param>
public static void LogError(string message) => _logger?.LogError(message);

/// <summary>
///
/// 记录异常信息方法
/// </summary>
/// <param name="ex"></param>
/// <param name="message"></param>
public static void LogError(Exception ex, string? message) => _logger?.LogError(ex, message);
public static void LogError(Exception ex, string? message = null)
{
if (_logger == null)
{
return;
}

_logger.LogError(ex, "{message}", message);
}
}
109 changes: 109 additions & 0 deletions src/extensions/BootstrapBlazor.Socket/Utility/ModbusCrc16.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/

namespace BootstrapBlazor.Components.Utility;

/// <summary>
/// Modubs CRC-16 查表算法
Comment thread
ArgoZhang marked this conversation as resolved.
/// </summary>
public static class ModbusCrc16
{
// 预计算的CRC表(256个条目)
private static readonly ushort[] CrcTable =
[
0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,
0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841,
0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40,
0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41,
0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641,
0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,
0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,
0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41,
0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,
0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,
0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,
0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640,
0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,
0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,
0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,
0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41,
0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840,
0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41,
0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,
0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640,
0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,
0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040
];

/// <summary>
/// 计算 Modbus CRC-16 校验码
/// </summary>
/// <param name="data">要计算的数据字节数组</param>
/// <returns>CRC校验码(低字节在前)</returns>
public static ushort Compute(ReadOnlySpan<byte> data)
{
ushort crc = 0xFFFF;
foreach (var b in data)
{
crc = (ushort)((crc >> 8) ^ CrcTable[(crc ^ b) & 0xFF]);
}
return crc;
}

/// <summary>
/// 计算 CRC 并将结果添加到消息末尾(Modbus格式)
/// </summary>
/// <param name="data">原始数据</param>
/// <returns>带 CRC 校验码的数据</returns>
public static ReadOnlySpan<byte> Append(ReadOnlySpan<byte> data)
{
ushort crc = Compute(data);

// 使用 stackalloc 避免堆分配(小数据时)
if (data.Length <= 256)
Comment thread
ArgoZhang marked this conversation as resolved.
{
Span<byte> result = stackalloc byte[data.Length + 2];
data.CopyTo(result);
result[data.Length] = (byte)(crc & 0xFF);
Comment thread
ArgoZhang marked this conversation as resolved.
result[data.Length + 1] = (byte)(crc >> 8);
return result.ToArray();
}
else
{
// 大数据使用常规数组
byte[] result = new byte[data.Length + 2];
data.CopyTo(result);
result[data.Length] = (byte)(crc & 0xFF);
result[data.Length + 1] = (byte)(crc >> 8);
return result;
}
Comment thread
ArgoZhang marked this conversation as resolved.
}

/// <summary>
/// 验证带 CRC 的数据是否有效
/// </summary>
/// <param name="dataWithCrc">包含 CRC 校验码的数据</param>
/// <returns>验证结果</returns>
public static bool Validate(ReadOnlySpan<byte> dataWithCrc)
{
if (dataWithCrc.Length < 2)
{
return false;
}

ushort receivedCrc = (ushort)(dataWithCrc[^1] << 8 | dataWithCrc[^2]);
Comment thread
ArgoZhang marked this conversation as resolved.
ushort calculatedCrc = Compute(dataWithCrc[..^2]);
return receivedCrc == calculatedCrc;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class DefaultTcpSocketClient(TcpSocketClientOptions options) : IServiceProvider,
/// <summary>
/// <inheritdoc/>
/// </summary>
public Func<ReadOnlyMemory<byte>, ValueTask>? ReceivedCallBack { get; set; }
public Func<ReadOnlyMemory<byte>, ValueTask>? ReceivedCallback { get; set; }

/// <summary>
/// <inheritdoc/>
Expand Down Expand Up @@ -329,10 +329,10 @@ private async ValueTask<int> ReceiveCoreAsync(ITcpSocketClientProvider client, M
buffer = buffer[..len];
}

if (ReceivedCallBack != null)
if (ReceivedCallback != null)
{
// 如果订阅回调则触发回调
await ReceivedCallBack(buffer);
await ReceivedCallback(buffer);
}
}
catch (OperationCanceledException ex)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ async ValueTask ReceivedCallback(ReadOnlyMemory<byte> buffer)
Cache.Add(client, [(adapter, ReceivedCallback)]);
}

client.ReceivedCallBack += ReceivedCallback;
client.ReceivedCallback += ReceivedCallback;

// 设置 DataPackageAdapter 的回调函数
adapter.ReceivedCallBack = callback;
Expand All @@ -94,7 +94,7 @@ public static void RemoveDataPackageAdapter(this ITcpSocketClient client, Func<R
var items = list.Where(i => i.Adapter.ReceivedCallBack == callback).ToList();
foreach (var c in items)
{
client.ReceivedCallBack -= c.Callback;
client.ReceivedCallback -= c.Callback;
list.Remove(c);
}
}
Expand Down Expand Up @@ -142,7 +142,7 @@ async ValueTask ReceivedCallback(ReadOnlyMemory<byte> buffer)
EntityCache.Add(client, [(ReceivedCallback, callback)]);
}

client.ReceivedCallBack += ReceivedCallback;
client.ReceivedCallback += ReceivedCallback;

// 设置 DataPackageAdapter 的回调函数
adapter.ReceivedCallBack = async buffer =>
Expand All @@ -168,7 +168,7 @@ public static void RemoveDataPackageAdapter<TEntity>(this ITcpSocketClient clien
var items = list.Where(i => i.EntityCallback.Equals(callback)).ToList();
foreach (var c in items)
{
client.ReceivedCallBack -= c.ReceivedCallback;
client.ReceivedCallback -= c.ReceivedCallback;
list.Remove(c);
}
}
Expand Down Expand Up @@ -217,7 +217,7 @@ async ValueTask ReceivedCallback(ReadOnlyMemory<byte> buffer)
EntityCache.Add(client, [(ReceivedCallback, callback)]);
}

client.ReceivedCallBack += ReceivedCallback;
client.ReceivedCallback += ReceivedCallback;

IDataConverter<TEntity>? converter = null;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public interface ITcpSocketClient : IAsyncDisposable
/// <remarks>The callback function should be designed to handle the received data efficiently and
/// asynchronously. Ensure that the implementation does not block or perform long-running operations, as this may
/// impact performance.</remarks>
Func<ReadOnlyMemory<byte>, ValueTask>? ReceivedCallBack { get; set; }
Func<ReadOnlyMemory<byte>, ValueTask>? ReceivedCallback { get; set; }

/// <summary>
/// Gets or sets the callback function that is invoked when a connection attempt is initiated.
Expand Down
45 changes: 45 additions & 0 deletions test/UnitTestTcpSocket/ActivationExtensionsTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/

namespace UnitTestTcpSocket;

public class ActivationExtensionsTest
{
[Fact]
public void Activation_Ok()
{
var type = typeof(Foo);
var o = type.CreateInstance();
Assert.NotNull(o);

var foo = o as Foo;
Assert.NotNull(foo);

var foo1 = type.CreateInstance<Foo>();
Assert.NotNull(foo1);
}

[Fact]
public void Activation_Nest()
{
var o = typeof(MockNestEntity).CreateInstance<MockNestEntity>([0.01f]);
Assert.Equal(0.01f, o?.Rate);
}

[Fact]
public void Activation_Fail()
{
var type = typeof(string);
var o = type.CreateInstance([123]);
Assert.Null(o);

var foo = type.CreateInstance<Foo>();
Assert.Null(foo);
}

class MockNestEntity(float rate)
{
public float Rate { get; } = rate;
}
}
5 changes: 4 additions & 1 deletion test/UnitTestTcpSocket/HexConverterTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ public void ToHexString_Ok()
var actual = HexConverter.ToString(data);
Assert.Equal("1A-02-13-04-FE", actual);

actual = HexConverter.ToString(data, " ");
actual = HexConverter.ToString(data, " ", false);
Assert.Equal("1a 02 13 04 fe", actual);

actual = HexConverter.ToString(data, " ", true);
Assert.Equal("1A 02 13 04 FE", actual);
}

Expand Down
36 changes: 36 additions & 0 deletions test/UnitTestTcpSocket/ModbusCrcTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/

using BootstrapBlazor.Components.Utility;

namespace UnitTestTcpSocket;

public class ModbusCrcTest
{
[Fact]
public void Computer_Ok()
{
// 06 00 00 01 7F C9 BA
var data = new byte[] { 0x01, 0x06, 0x00, 0x00, 0x01, 0x7F };

var crc = ModbusCrc16.Compute(data);
Assert.Equal("BAC9", crc.ToString("X4"));
Assert.Equal("01060000017FC9BA", HexConverter.ToString(ModbusCrc16.Append(data), ""));
}

[Fact]
public void Validate_Ok()
{
var result = ModbusCrc16.Validate([0x01]);
Assert.False(result);

result = ModbusCrc16.Validate([0x01, 0x06, 0x00, 0x00, 0x01, 0x7F, 0xC9, 0xBA]);
Assert.True(result);

result = false;
Comment thread
ArgoZhang marked this conversation as resolved.
var data = Enumerable.Range(0, 300).Select(i => (byte)Random.Shared.Next(0, 255));
result = ModbusCrc16.Validate(ModbusCrc16.Append(data.ToArray()));
Assert.True(result);
}
}
16 changes: 16 additions & 0 deletions test/UnitTestTcpSocket/SocketLoggingTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/

using BootstrapBlazor.Socket.Logging;

namespace UnitTestTcpSocket;

public class SocketLoggingTest
{
[Fact]
public void Logger_Ok()
{
SocketLogging.LogError(new Exception());
}
}
Loading