mirror of
https://gitee.com/ThingsGateway/ThingsGateway.git
synced 2025-10-23 20:13:22 +08:00
Compare commits
26 Commits
10.9.70.0
...
10.10.10.0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
e84f42ce14 | ||
![]() |
6f814cf6b8 | ||
![]() |
e36432e4e9 | ||
![]() |
ebd71e807b | ||
![]() |
34000d8d7d | ||
![]() |
e785f6660c | ||
![]() |
831c611797 | ||
![]() |
453817ef86 | ||
![]() |
8ce0b981c1 | ||
![]() |
4e5c51b54c | ||
![]() |
3cc9d31f28 | ||
![]() |
10391f869b | ||
![]() |
fba0723a6d | ||
![]() |
2db3f78f0c | ||
![]() |
badf61fe01 | ||
![]() |
d74e0952dc | ||
![]() |
fb1699ce80 | ||
![]() |
44adddbcd4 | ||
![]() |
0eab889452 | ||
![]() |
e14d39a459 | ||
![]() |
7575264ede | ||
![]() |
3e1a077b96 | ||
![]() |
a921cb8400 | ||
![]() |
4de7c31ed7 | ||
![]() |
08326a2cfd | ||
![]() |
e045de5acb |
@@ -1,5 +1,5 @@
|
||||
|
||||
<div align="center"><h1 align="center">ThingsBlazor</a></h1></div>
|
||||
<div align="center"><h1 align="center">ThingsBlazor</h1></div>
|
||||
<div align="center"><h3 align="center">权限管理框架</h3></div>
|
||||
|
||||
|
||||
|
@@ -38,6 +38,7 @@ public class VerificatInfo : PrimaryIdEntity
|
||||
[AutoGenerateColumn(Filterable = true, Sortable = true)]
|
||||
[SugarColumn(ColumnDescription = "Id", IsPrimaryKey = true)]
|
||||
[IgnoreExcel]
|
||||
[System.ComponentModel.DataAnnotations.Key]
|
||||
public override long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
@@ -8,7 +8,7 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
namespace ThingsGateway.Admin.Application;
|
||||
|
||||
public class USheetDatas
|
||||
{
|
@@ -18,6 +18,7 @@ public class SessionOutput : PrimaryIdEntity
|
||||
/// <summary>
|
||||
/// 主键Id
|
||||
/// </summary>
|
||||
[System.ComponentModel.DataAnnotations.Key]
|
||||
public override long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
@@ -0,0 +1,57 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Admin.Application;
|
||||
|
||||
public static class USheetDataHelpers
|
||||
{
|
||||
public static USheetDatas GetUSheetDatas(Dictionary<string, object> data)
|
||||
{
|
||||
var uSheetDatas = new USheetDatas();
|
||||
|
||||
foreach (var a in data)
|
||||
{
|
||||
var value = (a.Value as IEnumerable<Dictionary<string, object>>).ToList();
|
||||
|
||||
var uSheetData = new USheetData();
|
||||
uSheetData.id = a.Key;
|
||||
uSheetData.name = a.Key;
|
||||
|
||||
for (int row1 = 0; row1 < value.Count; row1++)
|
||||
{
|
||||
if (row1 == 0)
|
||||
{
|
||||
Dictionary<int, USheetCelldata> usheetColldata = new();
|
||||
int col = 0;
|
||||
foreach (var colData in value[row1])
|
||||
{
|
||||
usheetColldata.Add(col, new USheetCelldata() { v = colData.Key });
|
||||
col++;
|
||||
}
|
||||
uSheetData.cellData.Add(row1, usheetColldata);
|
||||
}
|
||||
{
|
||||
Dictionary<int, USheetCelldata> usheetColldata = new();
|
||||
int col = 0;
|
||||
foreach (var colData in value[row1])
|
||||
{
|
||||
usheetColldata.Add(col, new USheetCelldata() { v = colData.Value });
|
||||
col++;
|
||||
}
|
||||
uSheetData.cellData.Add(row1 + 1, usheetColldata);
|
||||
}
|
||||
}
|
||||
uSheetData.rowCount = uSheetData.cellData.Count + 100;
|
||||
uSheetData.columnCount = uSheetData.cellData.FirstOrDefault().Value?.Count ?? 0;
|
||||
uSheetDatas.sheets.Add(a.Key, uSheetData);
|
||||
}
|
||||
return uSheetDatas;
|
||||
}
|
||||
}
|
@@ -1,7 +1,6 @@
|
||||
@namespace ThingsGateway.Gateway.Razor
|
||||
@namespace ThingsGateway.Admin.Razor
|
||||
@using ThingsGateway.Admin.Application
|
||||
@using ThingsGateway.Admin.Razor
|
||||
@using ThingsGateway.Gateway.Application
|
||||
|
||||
<div class="h-600px">
|
||||
<UniverSheet @ref="_sheetExcel" OnReadyAsync="OnReadyAsync"></UniverSheet>
|
@@ -8,9 +8,10 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using ThingsGateway.Admin.Application;
|
||||
using ThingsGateway.NewLife.Json.Extension;
|
||||
|
||||
namespace ThingsGateway.Gateway.Razor;
|
||||
namespace ThingsGateway.Admin.Razor;
|
||||
|
||||
public partial class USheet
|
||||
{
|
@@ -6,6 +6,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ThingsGateway.Admin.Application\ThingsGateway.Admin.Application.csproj" />
|
||||
<PackageReference Include="BootstrapBlazor.Chart" Version="9.0.0" />
|
||||
<PackageReference Include="BootstrapBlazor.UniverSheet" Version="9.0.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='net8.0'">
|
||||
|
@@ -45,11 +45,11 @@ public class Startup : AppStartup
|
||||
options.ServicesStopConcurrently = true;
|
||||
});
|
||||
|
||||
//// 事件总线
|
||||
//services.AddEventBus(options =>
|
||||
//{
|
||||
// 事件总线
|
||||
services.AddEventBus(options =>
|
||||
{
|
||||
|
||||
//});
|
||||
});
|
||||
|
||||
// 任务调度
|
||||
services.AddSchedule(options => options.AddPersistence<JobPersistence>());
|
||||
|
@@ -8,7 +8,7 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
namespace ThingsGateway.Common;
|
||||
|
||||
public class SmartTriggerScheduler
|
||||
{
|
@@ -8,7 +8,7 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
namespace ThingsGateway.Common;
|
||||
|
||||
public sealed class StringOrdinalIgnoreCaseEqualityComparer : EqualityComparer<string>
|
||||
{
|
@@ -27,7 +27,11 @@ public class WebsiteOptions : IConfigurableOptions
|
||||
/// </summary>
|
||||
public bool Demo { get; set; }
|
||||
|
||||
public bool WebPageEnable { get; set; } = true;
|
||||
|
||||
public int MaxBlazorConnections { get; set; } = 5;
|
||||
public bool BlazorConnectionLimitEnable { get; set; } = false;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 是否显示关于页面
|
||||
/// </summary>
|
||||
|
@@ -13,7 +13,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.6" />
|
||||
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
|
||||
<PackageReference Include="BootstrapBlazor" Version="9.8.2" />
|
||||
<PackageReference Include="BootstrapBlazor" Version="9.9.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@@ -23,6 +23,7 @@ public abstract class PrimaryIdEntity : IPrimaryIdEntity
|
||||
[SugarColumn(ColumnDescription = "Id", IsPrimaryKey = true)]
|
||||
[IgnoreExcel]
|
||||
[AutoGenerateColumn(Visible = false, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false, Sortable = true, DefaultSort = true, DefaultSortOrder = SortOrder.Asc)]
|
||||
[System.ComponentModel.DataAnnotations.Key]
|
||||
public virtual long Id { get; set; }
|
||||
}
|
||||
|
||||
|
@@ -43,12 +43,12 @@ public class SugarAopService : ISugarAopService
|
||||
}
|
||||
if (sql.StartsWith("INSERT"))
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.ForegroundColor = ConsoleColor.Blue;
|
||||
DbContext.WriteLog($"添加{config.ConfigId}库操作");
|
||||
}
|
||||
if (sql.StartsWith("DELETE"))
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.ForegroundColor = ConsoleColor.Blue;
|
||||
DbContext.WriteLog($"删除{config.ConfigId}库操作");
|
||||
}
|
||||
DbContext.WriteLogWithSql(UtilMethods.GetNativeSql(sql, pars));
|
||||
|
@@ -46,7 +46,7 @@ public class BaseService<T> : IDataService<T>, IDisposable where T : class, new(
|
||||
public async Task<bool> DeleteAsync(IEnumerable<T> models)
|
||||
{
|
||||
using var db = GetDB();
|
||||
return await db.Deleteable<T>().In(models.ToList()).ExecuteCommandHasChangeAsync().ConfigureAwait(false);
|
||||
return await db.Deleteable<T>(models.ToList()).ExecuteCommandHasChangeAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -140,18 +140,22 @@ public class BaseService<T> : IDataService<T>, IDisposable where T : class, new(
|
||||
return (await db.UpdateableT(model).ExecuteCommandAsync().ConfigureAwait(false)) > 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async Task<bool> SaveAsync(List<T> model, ItemChangedType changedType)
|
||||
{
|
||||
return (await SaveReturnCountAsync(model, changedType).ConfigureAwait(false)) > 0;
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public async Task<int> SaveReturnCountAsync(List<T> model, ItemChangedType changedType)
|
||||
{
|
||||
using var db = GetDB();
|
||||
if (changedType == ItemChangedType.Add)
|
||||
{
|
||||
return (await db.Insertable(model).ExecuteCommandAsync().ConfigureAwait(false)) > 0;
|
||||
return (await db.Insertable(model).ExecuteCommandAsync().ConfigureAwait(false));
|
||||
}
|
||||
else
|
||||
{
|
||||
return (await db.Updateable(model).ExecuteCommandAsync().ConfigureAwait(false)) > 0;
|
||||
return (await db.Updateable(model).ExecuteCommandAsync().ConfigureAwait(false));
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
|
@@ -27,18 +27,27 @@ using System.Security.Claims;
|
||||
using ThingsGateway.ConfigurableOptions;
|
||||
using ThingsGateway.NewLife.Caching;
|
||||
using ThingsGateway.NewLife.Collections;
|
||||
using ThingsGateway.NewLife.Extension;
|
||||
using ThingsGateway.NewLife.Log;
|
||||
using ThingsGateway.Reflection;
|
||||
using ThingsGateway.Templates;
|
||||
|
||||
namespace ThingsGateway;
|
||||
|
||||
|
||||
public static class WebEnableVariable
|
||||
{
|
||||
public static bool WebEnable => Environment.GetEnvironmentVariable(nameof(WebEnable)).ToBoolean(true);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 全局应用类
|
||||
/// </summary>
|
||||
[SuppressSniffer]
|
||||
public static class App
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 私有设置,避免重复解析
|
||||
/// </summary>
|
||||
@@ -157,7 +166,7 @@ public static class App
|
||||
var httpContextAccessor = RootServices?.GetService<IHttpContextAccessor>();
|
||||
try
|
||||
{
|
||||
return httpContextAccessor.HttpContext;
|
||||
return httpContextAccessor?.HttpContext;
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
@@ -213,12 +213,18 @@ public static class AppServiceCollectionExtensions
|
||||
// 缓存
|
||||
if (cacheOptions.CacheType == CacheType.Memory)
|
||||
{
|
||||
services.AddSingleton<ICache, MemoryCache>(a => new()
|
||||
services.AddSingleton<ICache>(a =>
|
||||
{
|
||||
Capacity = cacheOptions.MemoryCacheOptions.Capacity,
|
||||
Expire = cacheOptions.MemoryCacheOptions.Expire,
|
||||
Period = cacheOptions.MemoryCacheOptions.Period
|
||||
});
|
||||
Cache.Default = new MemoryCache()
|
||||
{
|
||||
Capacity = cacheOptions.MemoryCacheOptions.Capacity,
|
||||
Expire = cacheOptions.MemoryCacheOptions.Expire,
|
||||
Period = cacheOptions.MemoryCacheOptions.Period
|
||||
};
|
||||
return Cache.Default;
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
else if (cacheOptions.CacheType == CacheType.Redis)
|
||||
{
|
||||
|
@@ -85,11 +85,14 @@ internal static class InternalApp
|
||||
// 存储根服务(解决 Web 主机还未启动时在 HostedService 中使用 App.GetService 问题
|
||||
services.AddHostedService<GenericHostLifetimeEventsHostedService>();
|
||||
|
||||
// 注册 Startup 过滤器
|
||||
services.AddTransient<IStartupFilter, StartupFilter>();
|
||||
if (WebEnableVariable.WebEnable == true)
|
||||
{
|
||||
// 注册 Startup 过滤器
|
||||
services.AddTransient<IStartupFilter, StartupFilter>();
|
||||
|
||||
// 注册 HttpContextAccessor 服务
|
||||
services.AddHttpContextAccessor();
|
||||
// 注册 HttpContextAccessor 服务
|
||||
services.AddHttpContextAccessor();
|
||||
}
|
||||
|
||||
// 初始化应用服务
|
||||
services.AddApp();
|
||||
|
@@ -20,6 +20,7 @@ namespace ThingsGateway;
|
||||
/// </summary>
|
||||
public sealed class AppSettingsOptions : IConfigurableOptions<AppSettingsOptions>
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用规范化文档
|
||||
/// </summary>
|
||||
|
341
src/Admin/ThingsGateway.Furion/App/Options/MiniRunOptions.cs
Normal file
341
src/Admin/ThingsGateway.Furion/App/Options/MiniRunOptions.cs
Normal file
@@ -0,0 +1,341 @@
|
||||
// ------------------------------------------------------------------------
|
||||
// 版权信息
|
||||
// 版权归百小僧及百签科技(广东)有限公司所有。
|
||||
// 所有权利保留。
|
||||
// 官方网站:https://baiqian.com
|
||||
//
|
||||
// 许可证信息
|
||||
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
|
||||
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
using ThingsGateway;
|
||||
|
||||
namespace System;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="WebApplication"/> 方式配置选项
|
||||
/// </summary>
|
||||
[SuppressSniffer]
|
||||
public sealed class MiniRunOptions : IRunOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 内部构造函数
|
||||
/// </summary>
|
||||
internal MiniRunOptions()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 默认配置
|
||||
/// </summary>
|
||||
public static MiniRunOptions Default { get; } = new MiniRunOptions();
|
||||
|
||||
/// <summary>
|
||||
/// 默认配置(带启动参数)
|
||||
/// </summary>
|
||||
public static MiniRunOptions Main(string[] args)
|
||||
{
|
||||
return Default.WithArgs(args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 默认配置(静默启动)
|
||||
/// </summary>
|
||||
public static MiniRunOptions DefaultSilence { get; } = new MiniRunOptions().Silence();
|
||||
|
||||
/// <summary>
|
||||
/// 默认配置(静默启动 + 启动参数)
|
||||
/// </summary>
|
||||
public static MiniRunOptions MainSilence(string[] args)
|
||||
{
|
||||
return DefaultSilence.WithArgs(args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 配置 <see cref="WebApplicationOptions"/>
|
||||
/// </summary>
|
||||
/// <param name="options"></param>
|
||||
/// <returns><see cref="MiniRunOptions"/></returns>
|
||||
public MiniRunOptions ConfigureOptions(WebApplicationOptions options)
|
||||
{
|
||||
Options = options;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 配置 <see cref="IWebHostBuilder"/>
|
||||
/// </summary>
|
||||
/// <param name="configureAction"></param>
|
||||
/// <returns><see cref="MiniRunOptions"/></returns>
|
||||
public MiniRunOptions ConfigureBuilder(Action<IWebHostBuilder> configureAction)
|
||||
{
|
||||
ActionBuilder = configureAction;
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// 配置 <see cref="IHostBuilder"/>
|
||||
/// </summary>
|
||||
/// <param name="configureAction"></param>
|
||||
/// <returns><see cref="MiniRunOptions"/></returns>
|
||||
public MiniRunOptions ConfigureFirstActionBuilder(Action<IHostBuilder> configureAction)
|
||||
{
|
||||
FirstActionBuilder = configureAction;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 配置 <see cref="IServiceCollection"/>
|
||||
/// </summary>
|
||||
/// <param name="configureAction"></param>
|
||||
/// <returns><see cref="MiniRunOptions"/></returns>
|
||||
public MiniRunOptions ConfigureServices(Action<IServiceCollection> configureAction)
|
||||
{
|
||||
ActionServices = configureAction;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 配置 <see cref="InjectOptions"/>
|
||||
/// </summary>
|
||||
/// <param name="configureAction"></param>
|
||||
/// <returns><see cref="MiniRunOptions"/></returns>
|
||||
public MiniRunOptions ConfigureInject(Action<IWebHostBuilder, InjectOptions> configureAction)
|
||||
{
|
||||
ActionInject = configureAction;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 配置 <see cref="WebApplication"/>
|
||||
/// </summary>
|
||||
/// <param name="configureAction">配置委托</param>
|
||||
/// <returns><see cref="MiniRunOptions"/></returns>
|
||||
public MiniRunOptions Configure(Action<IHost> configureAction)
|
||||
{
|
||||
ActionConfigure = configureAction;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 配置 <see cref="ConfigurationManager"/>
|
||||
/// </summary>
|
||||
/// <param name="configureAction">配置委托</param>
|
||||
/// <returns><see cref="MiniRunOptions"/></returns>
|
||||
public MiniRunOptions ConfigureConfiguration(Action<IHostEnvironment, IConfiguration> configureAction)
|
||||
{
|
||||
ActionConfigurationManager = configureAction;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加应用服务组件
|
||||
/// </summary>
|
||||
/// <typeparam name="TComponent">组件类型</typeparam>
|
||||
/// <returns></returns>
|
||||
public MiniRunOptions AddComponent<TComponent>()
|
||||
where TComponent : class, IServiceComponent, new()
|
||||
{
|
||||
ServiceComponents.Add(typeof(TComponent), null);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加应用服务组件
|
||||
/// </summary>
|
||||
/// <typeparam name="TComponent">组件类型</typeparam>
|
||||
/// <typeparam name="TComponentOptions"></typeparam>
|
||||
/// <param name="options">组件参数</param>
|
||||
/// <returns></returns>
|
||||
public MiniRunOptions AddComponent<TComponent, TComponentOptions>(TComponentOptions options)
|
||||
where TComponent : class, IServiceComponent, new()
|
||||
{
|
||||
ServiceComponents.Add(typeof(TComponent), options);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加应用服务组件
|
||||
/// </summary>
|
||||
/// <param name="componentType">组件类型</param>
|
||||
/// <param name="options">组件参数</param>
|
||||
/// <returns></returns>
|
||||
public MiniRunOptions AddComponent(Type componentType, object options)
|
||||
{
|
||||
ServiceComponents.Add(componentType, options);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加应用中间件组件
|
||||
/// </summary>
|
||||
/// <typeparam name="TComponent">组件类型</typeparam>
|
||||
/// <returns></returns>
|
||||
public MiniRunOptions UseComponent<TComponent>()
|
||||
where TComponent : class, IApplicationComponent, new()
|
||||
{
|
||||
ApplicationComponents.Add(typeof(TComponent), null);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加应用中间件组件
|
||||
/// </summary>
|
||||
/// <typeparam name="TComponent">组件类型</typeparam>
|
||||
/// <typeparam name="TComponentOptions"></typeparam>
|
||||
/// <param name="options">组件参数</param>
|
||||
/// <returns></returns>
|
||||
public MiniRunOptions UseComponent<TComponent, TComponentOptions>(TComponentOptions options)
|
||||
where TComponent : class, IApplicationComponent, new()
|
||||
{
|
||||
ApplicationComponents.Add(typeof(TComponent), options);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加应用中间件组件
|
||||
/// </summary>
|
||||
/// <param name="componentType">组件类型</param>
|
||||
/// <param name="options">组件参数</param>
|
||||
/// <returns></returns>
|
||||
public MiniRunOptions UseComponent(Type componentType, object options)
|
||||
{
|
||||
ApplicationComponents.Add(componentType, options);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加 IWebHostBuilder 组件
|
||||
/// </summary>
|
||||
/// <typeparam name="TComponent">组件类型</typeparam>
|
||||
/// <returns></returns>
|
||||
public MiniRunOptions AddWebComponent<TComponent>()
|
||||
where TComponent : class, IWebComponent, new()
|
||||
{
|
||||
WebComponents.Add(typeof(TComponent), null);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加 IWebHostBuilder 组件
|
||||
/// </summary>
|
||||
/// <typeparam name="TComponent">组件类型</typeparam>
|
||||
/// <typeparam name="TComponentOptions"></typeparam>
|
||||
/// <param name="options">组件参数</param>
|
||||
/// <returns></returns>
|
||||
public MiniRunOptions AddWebComponent<TComponent, TComponentOptions>(TComponentOptions options)
|
||||
where TComponent : class, IWebComponent, new()
|
||||
{
|
||||
WebComponents.Add(typeof(TComponent), options);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加 IWebHostBuilder 组件
|
||||
/// </summary>
|
||||
/// <param name="componentType">组件类型</param>
|
||||
/// <param name="options">组件参数</param>
|
||||
/// <returns></returns>
|
||||
public MiniRunOptions AddWebComponent(Type componentType, object options)
|
||||
{
|
||||
WebComponents.Add(componentType, options);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 标识主机静默启动
|
||||
/// </summary>
|
||||
/// <remarks>不阻塞程序运行</remarks>
|
||||
/// <param name="silence">静默启动</param>
|
||||
/// <param name="logging">静默启动日志状态,默认 false</param>
|
||||
/// <returns></returns>
|
||||
public MiniRunOptions Silence(bool silence = true, bool logging = false)
|
||||
{
|
||||
IsSilence = silence;
|
||||
SilenceLogging = logging;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置进程启动参数
|
||||
/// </summary>
|
||||
/// <param name="args">启动参数</param>
|
||||
/// <returns></returns>
|
||||
public MiniRunOptions WithArgs(string[] args)
|
||||
{
|
||||
Args = args;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="WebApplicationOptions"/>
|
||||
/// </summary>
|
||||
internal WebApplicationOptions Options { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 自定义 <see cref="IServiceCollection"/> 委托
|
||||
/// </summary>
|
||||
internal Action<IServiceCollection> ActionServices { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 自定义 <see cref="IWebHostBuilder"/> 委托
|
||||
/// </summary>
|
||||
internal Action<IHostBuilder> FirstActionBuilder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 自定义 <see cref="IWebHostBuilder"/> 委托
|
||||
/// </summary>
|
||||
internal Action<IWebHostBuilder> ActionBuilder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 自定义 <see cref="InjectOptions"/> 委托
|
||||
/// </summary>
|
||||
internal Action<IWebHostBuilder, InjectOptions> ActionInject { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 自定义 <see cref="IHost"/> 委托
|
||||
/// </summary>
|
||||
internal Action<IHost> ActionConfigure { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 自定义 <see cref="IConfiguration"/> 委托
|
||||
/// </summary>
|
||||
internal Action<IHostEnvironment, IConfiguration> ActionConfigurationManager { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 应用服务组件
|
||||
/// </summary>
|
||||
internal Dictionary<Type, object> ServiceComponents { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// IWebHostBuilder 组件
|
||||
/// </summary>
|
||||
internal Dictionary<Type, object> WebComponents { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 应用中间件组件
|
||||
/// </summary>
|
||||
internal Dictionary<Type, object> ApplicationComponents { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 静默启动
|
||||
/// </summary>
|
||||
/// <remarks>不阻塞程序运行</remarks>
|
||||
internal bool IsSilence { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 静默启动日志状态
|
||||
/// </summary>
|
||||
internal bool SilenceLogging { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 命令行参数
|
||||
/// </summary>
|
||||
internal string[] Args { get; set; }
|
||||
}
|
@@ -602,6 +602,33 @@ public static class Serve
|
||||
return app;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动 WebApplication 主机
|
||||
/// </summary>
|
||||
/// <remarks>未包含 Web 基础功能,需手动注册服务/中间件</remarks>
|
||||
/// <param name="options">配置选项</param>
|
||||
/// <param name="urls">默认 5000/5001 端口</param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns><see cref="IHost"/></returns>
|
||||
public static async Task<IHost> RunAsync(MiniRunOptions options, string urls = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 构建 WebApplication 对象
|
||||
BuildMiniApplication(options, urls, out var app);
|
||||
|
||||
// 是否静默启动
|
||||
if (!options.IsSilence)
|
||||
{
|
||||
// 配置启动地址和端口
|
||||
await app.RunAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await app.StartAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建 WebApplication 对象
|
||||
/// </summary>
|
||||
@@ -616,8 +643,8 @@ public static class Serve
|
||||
|
||||
// 初始化 WebApplicationBuilder
|
||||
var builder = (options.Options == null
|
||||
? WebApplication.CreateBuilder(args)
|
||||
: WebApplication.CreateBuilder(options.Options));
|
||||
? WebApplication.CreateBuilder(args)
|
||||
: WebApplication.CreateBuilder(options.Options));
|
||||
|
||||
// 调用自定义配置服务
|
||||
options?.FirstActionBuilder?.Invoke(builder);
|
||||
@@ -799,6 +826,132 @@ public static class Serve
|
||||
App.AppStartups.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建 IHost 对象
|
||||
/// </summary>
|
||||
/// <param name="options">配置选项</param>
|
||||
/// <param name="urls">默认 5000/5001 端口</param>
|
||||
/// <param name="app"><see cref="IHost"/></param>
|
||||
public static void BuildMiniApplication(MiniRunOptions options, string urls, out IHost app)
|
||||
{
|
||||
// 获取命令行参数
|
||||
var args = options.Args ?? Environment.GetCommandLineArgs().Skip(1).ToArray();
|
||||
|
||||
|
||||
var builder = Host.CreateDefaultBuilder(args);
|
||||
|
||||
// 静默启动排除指定日志类名
|
||||
if (options.IsSilence && !options.SilenceLogging)
|
||||
{
|
||||
builder = builder.ConfigureLogging(logging =>
|
||||
{
|
||||
logging.AddFilter((provider, category, logLevel) => !SilenceExcludesOfLogCategoryName.Any(u => category.StartsWith(u)));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 配置 Web 主机
|
||||
builder = builder.ConfigureWebHost(webHostBuilder =>
|
||||
{
|
||||
|
||||
|
||||
// 调用自定义配置服务
|
||||
options?.FirstActionBuilder?.Invoke(builder);
|
||||
|
||||
// 注册 WebApplicationBuilder 组件
|
||||
if (options.WebComponents.Count > 0)
|
||||
{
|
||||
foreach (var (componentType, opt) in options.WebComponents)
|
||||
{
|
||||
webHostBuilder.AddWebComponent(componentType, opt);
|
||||
}
|
||||
}
|
||||
|
||||
webHostBuilder.Configure((WebHostBuilderContext app, IApplicationBuilder applicationBuilder) =>
|
||||
{
|
||||
|
||||
// 添加自定义配置
|
||||
options.ActionConfigurationManager?.Invoke(app.HostingEnvironment, app.Configuration);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
// 初始化框架
|
||||
webHostBuilder.Inject(options.ActionInject);
|
||||
|
||||
|
||||
|
||||
// 配置服务
|
||||
if (options.ServiceComponents.Count > 0)
|
||||
{
|
||||
webHostBuilder = webHostBuilder.ConfigureServices(services =>
|
||||
{
|
||||
// 注册应用服务组件
|
||||
foreach (var (componentType, opt) in options.ServiceComponents)
|
||||
{
|
||||
services.AddComponent(componentType, opt);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
// 配置启动地址和端口
|
||||
var startUrls = !string.IsNullOrWhiteSpace(urls) ? urls : webHostBuilder.GetSetting(nameof(urls));
|
||||
|
||||
// 自定义启动端口
|
||||
if (!string.IsNullOrWhiteSpace(startUrls))
|
||||
{
|
||||
webHostBuilder = webHostBuilder.UseUrls(startUrls);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// 调用自定义配置
|
||||
options?.ActionBuilder?.Invoke(webHostBuilder);
|
||||
|
||||
// 配置中间件
|
||||
if (options.ApplicationComponents.Count > 0)
|
||||
{
|
||||
webHostBuilder = webHostBuilder.Configure((context, app) =>
|
||||
{
|
||||
// 注册应用中间件组件
|
||||
foreach (var (componentType, opt) in options.ApplicationComponents)
|
||||
{
|
||||
app.UseComponent(context.HostingEnvironment, componentType, opt);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
builder = builder.ConfigureServices(services =>
|
||||
{
|
||||
// 调用自定义配置服务
|
||||
options?.ActionServices?.Invoke(services);
|
||||
});
|
||||
|
||||
// 构建主机
|
||||
app = builder.Build();
|
||||
|
||||
InternalApp.RootServices ??= app.Services;
|
||||
|
||||
var applicationPartManager = app.Services.GetService<ApplicationPartManager>();
|
||||
|
||||
applicationPartManager?.ApplicationParts?.RemoveWhere(p => App.BakImageNames.Any(b => b == p.Name));
|
||||
// 配置所有 Starup Configure
|
||||
UseStartups(app.Services);
|
||||
// 释放内存
|
||||
App.AppStartups.Clear();
|
||||
// 调用自定义配置
|
||||
options?.ActionConfigure?.Invoke(app);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 构建 IHost 对象
|
||||
/// </summary>
|
||||
|
@@ -127,7 +127,8 @@ public sealed class DatabaseLogger : ILogger, IDisposable
|
||||
// 设置日志消息模板
|
||||
logMsg.Message = _options.MessageFormat != null
|
||||
? _options.MessageFormat(logMsg)
|
||||
: Penetrates.OutputStandardMessage(logMsg, _options.DateFormat, withTraceId: _options.WithTraceId, withStackFrame: _options.WithStackFrame, provider: _options.FormatProvider);
|
||||
: string.Empty;
|
||||
//: Penetrates.OutputStandardMessage(logMsg, _options.DateFormat, withTraceId: _options.WithTraceId, withStackFrame: _options.WithStackFrame, provider: _options.FormatProvider);
|
||||
|
||||
// 空检查
|
||||
if (logMsg.Message is null)
|
||||
|
@@ -31,6 +31,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.3" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="$(NET9Version)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
|
||||
@@ -43,6 +44,7 @@
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="$(NET9Version)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="$(NET9Version)" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="$(NET9Version)" />
|
||||
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
|
@@ -683,13 +683,20 @@ public class MachineInfo : IExtend
|
||||
if (dic.TryGetValue("MemTotal", out var str) && !str.IsNullOrEmpty())
|
||||
Memory = (UInt64)str.TrimEnd(" kB").ToLong();
|
||||
|
||||
ulong ma = 0;
|
||||
if (dic.TryGetValue("MemAvailable", out str) && !str.IsNullOrEmpty())
|
||||
AvailableMemory = (UInt64)str.TrimEnd(" kB").ToLong();
|
||||
else if (dic.TryGetValue("MemFree", out str) && !str.IsNullOrEmpty())
|
||||
AvailableMemory =
|
||||
(UInt64)(str.TrimEnd(" kB").ToLong() +
|
||||
dic["Buffers"]?.TrimEnd(" kB").ToLong() ?? 0 +
|
||||
dic["Cached"]?.TrimEnd(" kB").ToLong() ?? 0);
|
||||
{
|
||||
ma = (UInt64)(str.TrimEnd(" kB").ToLong());
|
||||
}
|
||||
|
||||
//低于3.14内核的版本用 free+cache
|
||||
var mf = (UInt64)(dic["MemFree"]?.TrimEnd(" kB").ToLong() ?? 0);
|
||||
var mc = (UInt64)(dic["Cached"]?.TrimEnd(" kB").ToLong() ?? 0);
|
||||
var bf = (UInt64)(dic["Buffers"]?.TrimEnd(" kB").ToLong() ?? 0);
|
||||
|
||||
var free = mf + mc + bf;
|
||||
|
||||
AvailableMemory = ma > free ? ma : free;
|
||||
}
|
||||
|
||||
// A2/A4温度获取,Buildroot,CPU温度和主板温度
|
||||
|
@@ -0,0 +1,6 @@
|
||||
@namespace ThingsGateway.Razor
|
||||
|
||||
@if (show)
|
||||
{
|
||||
<Spinner class="ms-auto"></Spinner>
|
||||
}
|
@@ -8,7 +8,7 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Gateway.Razor;
|
||||
namespace ThingsGateway.Razor;
|
||||
|
||||
public partial class SpinnerComponent
|
||||
{
|
@@ -27,16 +27,17 @@
|
||||
/// </summary>
|
||||
/// <typeparam name="T">数据类型</typeparam>
|
||||
/// <param name="insertDatas">要插入的数据列表</param>
|
||||
/// <param name="tableName">表名称</param>
|
||||
/// <param name="dateFormat">日期格式字符串</param>
|
||||
/// <returns>插入的记录数</returns>
|
||||
public int BulkCopy<T>(IEnumerable<T> insertDatas, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
|
||||
public int BulkCopy<T>(IEnumerable<T> insertDatas, string tableName = null, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
|
||||
{
|
||||
int result = 0;
|
||||
// 使用分页方式处理大数据量插入
|
||||
db.Utilities.PageEach(insertDatas, pageSize, pageItems =>
|
||||
{
|
||||
// 同步调用批量插入API并累加结果
|
||||
result += questDbRestAPI.BulkCopyAsync(pageItems, dateFormat).GetAwaiter().GetResult();
|
||||
result += questDbRestAPI.BulkCopyAsync(pageItems, tableName, dateFormat).GetAwaiter().GetResult();
|
||||
});
|
||||
return result;
|
||||
}
|
||||
@@ -46,16 +47,17 @@
|
||||
/// </summary>
|
||||
/// <typeparam name="T">数据类型</typeparam>
|
||||
/// <param name="insertDatas">要插入的数据列表</param>
|
||||
/// <param name="tableName">表名称</param>
|
||||
/// <param name="dateFormat">日期格式字符串</param>
|
||||
/// <returns>插入的记录数</returns>
|
||||
public async Task<int> BulkCopyAsync<T>(IEnumerable<T> insertDatas, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
|
||||
public async Task<int> BulkCopyAsync<T>(IEnumerable<T> insertDatas, string tableName = null, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
|
||||
{
|
||||
int result = 0;
|
||||
// 异步分页处理大数据量插入
|
||||
await db.Utilities.PageEachAsync(insertDatas, pageSize, async pageItems =>
|
||||
{
|
||||
// 异步调用批量插入API并累加结果
|
||||
result += await questDbRestAPI.BulkCopyAsync(pageItems, dateFormat).ConfigureAwait(false);
|
||||
result += await questDbRestAPI.BulkCopyAsync(pageItems, tableName, dateFormat).ConfigureAwait(false);
|
||||
}).ConfigureAwait(false);
|
||||
return result;
|
||||
}
|
||||
|
@@ -7,16 +7,12 @@ namespace ThingsGateway.SqlSugar
|
||||
/// <summary>
|
||||
/// 绑定RestAPI需要的信息
|
||||
/// </summary>
|
||||
public static void SetRestApiInfo(DbConnectionStringBuilder builder, ref string host, ref string httpPort, ref string username, ref string password)
|
||||
public static void SetRestApiInfo(DbConnectionStringBuilder builder, ref string host, ref string username, ref string password)
|
||||
{
|
||||
if (builder.TryGetValue("Host", out object hostValue))
|
||||
{
|
||||
host = Convert.ToString(hostValue);
|
||||
}
|
||||
if (builder.TryGetValue("HttpPort", out object httpPortValue))
|
||||
{
|
||||
httpPort = Convert.ToString(httpPortValue);
|
||||
}
|
||||
if (builder.TryGetValue("Username", out object usernameValue))
|
||||
{
|
||||
username = Convert.ToString(usernameValue);
|
||||
|
@@ -28,16 +28,16 @@ namespace ThingsGateway.SqlSugar
|
||||
/// 初始化 QuestDbRestAPI 实例
|
||||
/// </summary>
|
||||
/// <param name="db">SqlSugar 数据库客户端</param>
|
||||
public QuestDbRestAPI(ISqlSugarClient db)
|
||||
/// <param name="httpPort">restApi端口</param>
|
||||
public QuestDbRestAPI(ISqlSugarClient db, int httpPort = 9000)
|
||||
{
|
||||
var builder = new DbConnectionStringBuilder();
|
||||
builder.ConnectionString = db.CurrentConnectionConfig.ConnectionString;
|
||||
this.db = db;
|
||||
string httpPort = String.Empty;
|
||||
string host = String.Empty;
|
||||
string username = String.Empty;
|
||||
string password = String.Empty;
|
||||
QuestDbRestAPHelper.SetRestApiInfo(builder, ref host, ref httpPort, ref username, ref password);
|
||||
QuestDbRestAPHelper.SetRestApiInfo(builder, ref host, ref username, ref password);
|
||||
BindHost(host, httpPort, username, password);
|
||||
}
|
||||
|
||||
@@ -68,34 +68,34 @@ namespace ThingsGateway.SqlSugar
|
||||
return ExecuteCommandAsync(sql).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步批量插入单条数据
|
||||
/// </summary>
|
||||
/// <typeparam name="T">数据类型</typeparam>
|
||||
/// <param name="insertData">要插入的数据</param>
|
||||
/// <param name="dateFormat">日期格式字符串</param>
|
||||
/// <returns>影响的行数</returns>
|
||||
public async Task<int> BulkCopyAsync<T>(T insertData, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
|
||||
{
|
||||
if (db.CurrentConnectionConfig.MoreSettings == null)
|
||||
db.CurrentConnectionConfig.MoreSettings = new ConnMoreSettings();
|
||||
db.CurrentConnectionConfig.MoreSettings.DisableNvarchar = true;
|
||||
var sql = db.InsertableT(insertData).ToSqlString();
|
||||
var result = await ExecuteCommandAsync(sql).ConfigureAwait(false);
|
||||
return result.Contains("OK", StringComparison.OrdinalIgnoreCase) ? 1 : 0;
|
||||
}
|
||||
///// <summary>
|
||||
///// 异步批量插入单条数据
|
||||
///// </summary>
|
||||
///// <typeparam name="T">数据类型</typeparam>
|
||||
///// <param name="insertData">要插入的数据</param>
|
||||
///// <param name="dateFormat">日期格式字符串</param>
|
||||
///// <returns>影响的行数</returns>
|
||||
//public async Task<int> BulkCopyAsync<T>(T insertData, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
|
||||
//{
|
||||
// if (db.CurrentConnectionConfig.MoreSettings == null)
|
||||
// db.CurrentConnectionConfig.MoreSettings = new ConnMoreSettings();
|
||||
// db.CurrentConnectionConfig.MoreSettings.DisableNvarchar = true;
|
||||
// var sql = db.InsertableT(insertData).ToSqlString();
|
||||
// var result = await ExecuteCommandAsync(sql).ConfigureAwait(false);
|
||||
// return result.Contains("OK", StringComparison.OrdinalIgnoreCase) ? 1 : 0;
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// 同步批量插入单条数据
|
||||
/// </summary>
|
||||
/// <typeparam name="T">数据类型</typeparam>
|
||||
/// <param name="insertData">要插入的数据</param>
|
||||
/// <param name="dateFormat">日期格式字符串</param>
|
||||
/// <returns>影响的行数</returns>
|
||||
public int BulkCopy<T>(T insertData, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
|
||||
{
|
||||
return BulkCopyAsync(insertData, dateFormat).GetAwaiter().GetResult();
|
||||
}
|
||||
///// <summary>
|
||||
///// 同步批量插入单条数据
|
||||
///// </summary>
|
||||
///// <typeparam name="T">数据类型</typeparam>
|
||||
///// <param name="insertData">要插入的数据</param>
|
||||
///// <param name="dateFormat">日期格式字符串</param>
|
||||
///// <returns>影响的行数</returns>
|
||||
//public int BulkCopy<T>(T insertData, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
|
||||
//{
|
||||
// return BulkCopyAsync(insertData, dateFormat).GetAwaiter().GetResult();
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// 创建分页批量插入器
|
||||
@@ -115,9 +115,10 @@ namespace ThingsGateway.SqlSugar
|
||||
/// </summary>
|
||||
/// <typeparam name="T">数据类型</typeparam>
|
||||
/// <param name="insertList">要插入的数据列表</param>
|
||||
/// <param name="tableName">表名称</param>
|
||||
/// <param name="dateFormat">日期格式字符串</param>
|
||||
/// <returns>插入的记录数</returns>
|
||||
public async Task<int> BulkCopyAsync<T>(List<T> insertList, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
|
||||
public async Task<int> BulkCopyAsync<T>(List<T> insertList, string tableName = null, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
|
||||
{
|
||||
var result = 0;
|
||||
var fileName = $"{Guid.NewGuid()}.csv";
|
||||
@@ -127,12 +128,12 @@ namespace ThingsGateway.SqlSugar
|
||||
// 准备多部分表单数据
|
||||
var boundary = "---------------" + DateTime.Now.Ticks.ToString("x");
|
||||
var list = new List<Hashtable>();
|
||||
var name = db.EntityMaintenance.GetEntityInfo<T>().DbTableName;
|
||||
tableName ??= db.EntityMaintenance.GetEntityInfo<T>().DbTableName;
|
||||
|
||||
// 获取或创建列信息缓存
|
||||
var key = "QuestDbBulkCopy" + typeof(T).FullName + typeof(T).GetHashCode();
|
||||
var columns = ReflectionInoCacheService.Instance.GetOrCreate(key, () =>
|
||||
db.CopyNew().DbMaintenance.GetColumnInfosByTableName(name));
|
||||
db.CopyNew().DbMaintenance.GetColumnInfosByTableName(tableName));
|
||||
|
||||
// 构建schema信息
|
||||
columns.ForEach(d =>
|
||||
@@ -170,8 +171,8 @@ namespace ThingsGateway.SqlSugar
|
||||
// 准备HTTP请求内容
|
||||
using var httpContent = new MultipartFormDataContent(boundary);
|
||||
using var fileStream = File.OpenRead(filePath);
|
||||
if (!string.IsNullOrWhiteSpace(this.authorization))
|
||||
client.DefaultRequestHeaders.Add("Authorization", this.authorization);
|
||||
//if (!string.IsNullOrWhiteSpace(this.authorization))
|
||||
// client.DefaultRequestHeaders.Add("Authorization", this.authorization);
|
||||
httpContent.Add(new StringContent(schema), "schema");
|
||||
var streamContent = new StreamContent(fileStream);
|
||||
streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
|
||||
@@ -184,7 +185,7 @@ namespace ThingsGateway.SqlSugar
|
||||
|
||||
// 发送请求并处理响应
|
||||
var httpResponseMessage =
|
||||
await Post(client, name, httpContent).ConfigureAwait(false);
|
||||
await Post(client, tableName, httpContent).ConfigureAwait(false);
|
||||
var readAsStringAsync = await httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
var splitByLine = QuestDbRestAPHelper.SplitByLine(readAsStringAsync);
|
||||
|
||||
@@ -266,11 +267,12 @@ namespace ThingsGateway.SqlSugar
|
||||
/// </summary>
|
||||
/// <typeparam name="T">数据类型</typeparam>
|
||||
/// <param name="insertList">要插入的数据列表</param>
|
||||
/// <param name="tableName">表名称</param>
|
||||
/// <param name="dateFormat">日期格式字符串</param>
|
||||
/// <returns>插入的记录数</returns>
|
||||
public int BulkCopy<T>(List<T> insertList, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
|
||||
public int BulkCopy<T>(List<T> insertList, string tableName = null, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
|
||||
{
|
||||
return BulkCopyAsync(insertList, dateFormat).GetAwaiter().GetResult();
|
||||
return BulkCopyAsync(insertList, tableName, dateFormat).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -280,7 +282,7 @@ namespace ThingsGateway.SqlSugar
|
||||
/// <param name="httpPort">HTTP端口</param>
|
||||
/// <param name="username">用户名</param>
|
||||
/// <param name="password">密码</param>
|
||||
private void BindHost(string host, string httpPort, string username, string password)
|
||||
private void BindHost(string host, int httpPort, string username, string password)
|
||||
{
|
||||
url = host;
|
||||
if (url.EndsWith('/'))
|
||||
|
@@ -2,9 +2,9 @@
|
||||
{
|
||||
public static class QuestDbSqlSugarClientExtensions
|
||||
{
|
||||
public static QuestDbRestAPI RestApi(this ISqlSugarClient db)
|
||||
public static QuestDbRestAPI RestApi(this ISqlSugarClient db, int httpPort = 9000)
|
||||
{
|
||||
return new QuestDbRestAPI(db);
|
||||
return new QuestDbRestAPI(db, httpPort);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -111,7 +111,7 @@ namespace ThingsGateway.SqlSugar
|
||||
/// <param name="context">SqlSugar提供者</param>
|
||||
/// <param name="dataRecord">数据记录器</param>
|
||||
/// <param name="fieldNames">字段名列表</param>
|
||||
public IDataReaderEntityBuilder(SqlSugarProvider context, IDataRecord dataRecord, List<string> fieldNames)
|
||||
public IDataReaderEntityBuilder(SqlSugarProvider context, IDataRecord dataRecord, IEnumerable<string> fieldNames)
|
||||
{
|
||||
this.Context = context;
|
||||
this.DataRecord = dataRecord;
|
||||
|
@@ -679,7 +679,7 @@ namespace ThingsGateway.SqlSugar
|
||||
IDataReaderEntityBuilder<T> entytyList = this.Context.Utilities.GetReflectionInoCacheInstance().GetOrCreate(cacheKey, () =>
|
||||
{
|
||||
var cacheResult = new IDataReaderEntityBuilder<T>(this.Context, dr,
|
||||
columns.Select(it => it.Item1).ToList()).CreateBuilder(typeof(T));
|
||||
columns.Select(it => it.Item1)).CreateBuilder(typeof(T));
|
||||
return cacheResult;
|
||||
});
|
||||
using (dr)
|
||||
@@ -706,7 +706,7 @@ namespace ThingsGateway.SqlSugar
|
||||
IDataReaderEntityBuilder<T> entytyList = this.Context.Utilities.GetReflectionInoCacheInstance().GetOrCreate(cacheKey, () =>
|
||||
{
|
||||
var cacheResult = new IDataReaderEntityBuilder<T>(this.Context, dr,
|
||||
columns.Select(it => it.Item1).ToList()).CreateBuilder(typeof(T));
|
||||
columns.Select(it => it.Item1)).CreateBuilder(typeof(T));
|
||||
return cacheResult;
|
||||
});
|
||||
if (cancellationToken.IsCancellationRequested) yield break;
|
||||
@@ -743,7 +743,7 @@ namespace ThingsGateway.SqlSugar
|
||||
IDataReaderEntityBuilder<T> entytyList = this.Context.Utilities.GetReflectionInoCacheInstance().GetOrCreate(cacheKey, () =>
|
||||
{
|
||||
var cacheResult = new IDataReaderEntityBuilder<T>(this.Context, dr,
|
||||
columns.Select(it => it.Item1).ToList()).CreateBuilder(typeof(T));
|
||||
columns.Select(it => it.Item1)).CreateBuilder(typeof(T));
|
||||
return cacheResult;
|
||||
});
|
||||
using (dr)
|
||||
@@ -775,7 +775,7 @@ namespace ThingsGateway.SqlSugar
|
||||
IDataReaderEntityBuilder<T> entytyList = this.Context.Utilities.GetReflectionInoCacheInstance().GetOrCreate(cacheKey, () =>
|
||||
{
|
||||
var cacheResult = new IDataReaderEntityBuilder<T>(this.Context, dr,
|
||||
columns.Select(it => it.Item1).ToList()).CreateBuilder(typeof(T));
|
||||
columns.Select(it => it.Item1)).CreateBuilder(typeof(T));
|
||||
return cacheResult;
|
||||
});
|
||||
|
||||
|
@@ -43,7 +43,7 @@ namespace ThingsGateway.SqlSugar
|
||||
}
|
||||
public class ReflectionInoCore<V>
|
||||
{
|
||||
private MemoryCache InstanceCache => MemoryCache.Instance;
|
||||
private MemoryCache InstanceCache => new MemoryCache() { Expire = 60 };
|
||||
private static ReflectionInoCore<V> _instance = null;
|
||||
private static readonly object _instanceLock = new object();
|
||||
private ReflectionInoCore() { }
|
||||
@@ -86,7 +86,7 @@ namespace ThingsGateway.SqlSugar
|
||||
|
||||
public void Add(string key, V value, int cacheDurationInSeconds)
|
||||
{
|
||||
Check.ThrowNotSupportedException("ReflectionInoCache.Add(string key, V value, int cacheDurationInSeconds)");
|
||||
this.InstanceCache.Add<V>(key, value, cacheDurationInSeconds);
|
||||
}
|
||||
|
||||
public void Remove(string key)
|
||||
@@ -109,7 +109,8 @@ namespace ThingsGateway.SqlSugar
|
||||
|
||||
public V GetOrCreate(string cacheKey, Func<V> create)
|
||||
{
|
||||
return InstanceCache.GetOrAdd<V>(cacheKey, (a) => create());
|
||||
return InstanceCache.GetOrAdd<V>(cacheKey, (a) =>
|
||||
create());
|
||||
}
|
||||
}
|
||||
public static class ReflectionInoHelper
|
||||
|
@@ -406,22 +406,24 @@ AND sql LIKE '%" + tableName + "%'");
|
||||
public override bool CreateDatabase(string databaseName, string databaseDirectory = null)
|
||||
{
|
||||
var connString = this.Context.CurrentConnectionConfig.ConnectionString;
|
||||
var path = Regex.Match(connString, @"[a-z,A-Z]\:\\.+\\").Value;
|
||||
if (path.IsNullOrEmpty())
|
||||
|
||||
|
||||
// 提取 Data Source=xxx(不管是绝对还是相对路径)
|
||||
var match = Regex.Match(connString, @"(?i)Data\s+Source\s*=\s*(.+?)(;|$)");
|
||||
if (match.Success)
|
||||
{
|
||||
path = Regex.Match(connString, @"\/.+\/").Value;
|
||||
}
|
||||
if (path.IsNullOrEmpty())
|
||||
{
|
||||
path = Regex.Match(connString, @"[a-z,A-Z]\:\\").Value;
|
||||
}
|
||||
if (!path.IsNullOrEmpty())
|
||||
{
|
||||
if (!FileHelper.IsExistDirectory(path))
|
||||
var filePath = match.Groups[1].Value.Trim(); // => ./DB/data.sqlite
|
||||
var folderPath = Path.GetDirectoryName(filePath); // => ./DB
|
||||
|
||||
if (!folderPath.IsNullOrEmpty())
|
||||
{
|
||||
FileHelper.CreateDirectory(path);
|
||||
if (!FileHelper.IsExistDirectory(folderPath))
|
||||
{
|
||||
FileHelper.CreateDirectory(folderPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.Context.Ado.Connection.Open();
|
||||
this.Context.Ado.Connection.Close();
|
||||
return true;
|
||||
|
@@ -31,11 +31,12 @@
|
||||
<PackageReference Include="CsvHelper" Version="33.1.0" />
|
||||
<PackageReference Include="TDengine.Connector" Version="3.1.7" />
|
||||
<PackageReference Include="Oracle.ManagedDataAccess.Core" Version="23.9.1" />
|
||||
<PackageReference Include="Oscar.Data.SqlClient" Version="4.2.20" />
|
||||
<PackageReference Include="Oscar.Data.SqlClient" Version="4.2.21" />
|
||||
<PackageReference Include="System.Data.Common" Version="4.3.0" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.0" />
|
||||
<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
|
||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
<PackageReference Include="System.Formats.Asn1" Version="8.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@@ -1,14 +1,15 @@
|
||||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<PluginVersion>10.9.70</PluginVersion>
|
||||
<ProPluginVersion>10.9.70</ProPluginVersion>
|
||||
<DefaultVersion>10.9.70</DefaultVersion>
|
||||
<AuthenticationVersion>2.9.28</AuthenticationVersion>
|
||||
<SourceGeneratorVersion>10.9.25</SourceGeneratorVersion>
|
||||
<NET8Version>8.0.18</NET8Version>
|
||||
<NET9Version>9.0.7</NET9Version>
|
||||
<PluginVersion>10.10.7</PluginVersion>
|
||||
<ProPluginVersion>10.10.6</ProPluginVersion>
|
||||
<DefaultVersion>10.10.10</DefaultVersion>
|
||||
<AuthenticationVersion>10.10.1</AuthenticationVersion>
|
||||
<SourceGeneratorVersion>10.10.1</SourceGeneratorVersion>
|
||||
<NET8Version>8.0.19</NET8Version>
|
||||
<NET9Version>9.0.8</NET9Version>
|
||||
<SatelliteResourceLanguages>zh-Hans;en-US</SatelliteResourceLanguages>
|
||||
<IsTrimmable>false</IsTrimmable>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net462;netstandard2.0;net6.0;</TargetFrameworks>
|
||||
<TargetFrameworks>net462;netstandard2.0;net6.0;net8.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@@ -7,7 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CS-Script" Version="4.10.0" />
|
||||
<PackageReference Include="CS-Script" Version="4.10.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@@ -84,7 +84,7 @@ public partial class DeviceComponent : DeviceComponentBase
|
||||
{
|
||||
try
|
||||
{
|
||||
var result1 = item.VariableRuntimes.PraseStructContent(Plc, result.Content, exWhenAny: true);
|
||||
var result1 = item.VariableRuntimes.PraseStructContent(Plc, result.Content.Span, exWhenAny: true);
|
||||
if (!result1.IsSuccess)
|
||||
{
|
||||
item.LastErrorMessage = result1.ErrorMessage;
|
||||
|
@@ -87,7 +87,7 @@ public abstract class DeviceComponentBase : ComponentBase, IDisposable
|
||||
{
|
||||
try
|
||||
{
|
||||
var data = await Plc.ReadAsync(RegisterAddress, ArrayLength, DataType);
|
||||
var data = await Plc.ReadArrayAsync(RegisterAddress, ArrayLength, DataType);
|
||||
if (data.IsSuccess)
|
||||
{
|
||||
Plc.Logger?.LogInformation(data.Content.ToSystemTextJsonString());
|
||||
@@ -111,7 +111,7 @@ public abstract class DeviceComponentBase : ComponentBase, IDisposable
|
||||
{
|
||||
try
|
||||
{
|
||||
var data = await Plc.WriteAsync(RegisterAddress, WriteValue.GetJTokenFromString(), DataType);
|
||||
var data = await Plc.WriteJTokenAsync(RegisterAddress, WriteValue.GetJTokenFromString(), DataType);
|
||||
if (data.IsSuccess)
|
||||
{
|
||||
Plc.Logger?.LogInformation($" {WriteValue.GetJTokenFromString()} {Localizer["WriteSuccess"]}");
|
||||
|
@@ -77,7 +77,8 @@ public partial class LogConsole : IDisposable
|
||||
|
||||
[Inject]
|
||||
private ToastService ToastService { get; set; }
|
||||
|
||||
[Inject]
|
||||
ITextFileReadService TextFileReadService { get; set; }
|
||||
public void Dispose()
|
||||
{
|
||||
Disposed = true;
|
||||
@@ -94,7 +95,7 @@ public partial class LogConsole : IDisposable
|
||||
|
||||
if (LogPath != null)
|
||||
{
|
||||
var files = TextFileReader.GetFiles(LogPath);
|
||||
var files = await TextFileReadService.GetLogFiles(LogPath);
|
||||
if (!files.IsSuccess)
|
||||
{
|
||||
Messages = new List<LogMessage>();
|
||||
@@ -105,7 +106,7 @@ public partial class LogConsole : IDisposable
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
Stopwatch sw = Stopwatch.StartNew();
|
||||
var result = TextFileReader.LastLog(files.Content.FirstOrDefault());
|
||||
var result = await TextFileReadService.LastLogData(files.Content.FirstOrDefault());
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
Messages = result.Content.Where(a => a.LogLevel >= LogLevel).Select(a => new LogMessage((int)a.LogLevel, $"{a.LogTime} - {a.Message}{(a.ExceptionString.IsNullOrWhiteSpace() ? null : $"{Environment.NewLine}{a.ExceptionString}")}")).ToList();
|
||||
@@ -143,7 +144,7 @@ public partial class LogConsole : IDisposable
|
||||
{
|
||||
if (LogPath != null)
|
||||
{
|
||||
var files = TextFileReader.GetFiles(LogPath);
|
||||
var files = await TextFileReadService.GetLogFiles(LogPath);
|
||||
if (files.IsSuccess)
|
||||
{
|
||||
foreach (var item in files.Content)
|
||||
|
@@ -26,7 +26,7 @@ public class PlatformService : IPlatformService
|
||||
|
||||
public async Task OnLogExport(string logPath)
|
||||
{
|
||||
var files = TextFileReader.GetFiles(logPath);
|
||||
var files = TextFileReader.GetLogFiles(logPath);
|
||||
if (!files.IsSuccess)
|
||||
{
|
||||
return;
|
||||
|
@@ -33,5 +33,6 @@ public class Startup : AppStartup
|
||||
}
|
||||
|
||||
services.AddScoped<IPlatformService, PlatformService>();
|
||||
services.AddSingleton<ITextFileReadService, TextFileReadService>();
|
||||
}
|
||||
}
|
||||
|
@@ -83,7 +83,7 @@ public abstract class VariableObject
|
||||
/// GetBytes
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual byte[] GetBytes(Expression<Func<object>> accessor)
|
||||
public virtual ReadOnlyMemory<byte> GetBytes(Expression<Func<object>> accessor)
|
||||
{
|
||||
if (accessor.Body == null)
|
||||
{
|
||||
@@ -107,8 +107,8 @@ public abstract class VariableObject
|
||||
}
|
||||
|
||||
var func = accessor.Compile();
|
||||
|
||||
return variable.VariableClass.ThingsGatewayBitConverter.GetBytesFormData(GetExpressionsValue(func(), variable), variable.VariableClass.DataType);
|
||||
var data = GetExpressionsValue(func(), variable);
|
||||
return variable.VariableClass.ThingsGatewayBitConverter.GetBytesFormData(data, variable.VariableClass.DataType, data is JArray jArray && jArray.Count > 1 ? true : false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -149,7 +149,7 @@ public abstract class VariableObject
|
||||
var result = await Device.ReadAsync(item.RegisterAddress, item.Length, cancellationToken).ConfigureAwait(false);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
var result1 = item.VariableRuntimes.PraseStructContent(Device, result.Content, exWhenAny: true);
|
||||
var result1 = item.VariableRuntimes.PraseStructContent(Device, result.Content.Span, exWhenAny: true);
|
||||
if (!result1.IsSuccess)
|
||||
{
|
||||
item.LastErrorMessage = result1.ErrorMessage;
|
||||
@@ -221,7 +221,7 @@ public abstract class VariableObject
|
||||
|
||||
JToken jToken = GetExpressionsValue(value, variableRuntimeProperty);
|
||||
|
||||
var result = await Device.WriteAsync(variableRuntimeProperty.VariableClass.RegisterAddress, jToken, variableRuntimeProperty.VariableClass.DataType, cancellationToken).ConfigureAwait(false);
|
||||
var result = await Device.WriteJTokenAsync(variableRuntimeProperty.VariableClass.RegisterAddress, jToken, variableRuntimeProperty.VariableClass.DataType, cancellationToken).ConfigureAwait(false);
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// ------------------------------------------------------------------------------
|
||||
// ------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
@@ -30,8 +30,8 @@ public abstract class DDPMessage : MessageBase, IResultMessage
|
||||
|
||||
public override bool CheckHead<TByteBlock>(ref TByteBlock byteBlock)
|
||||
{
|
||||
var code = byteBlock.ReadByte();
|
||||
Type = byteBlock.ReadByte();
|
||||
var code = ReaderExtension.ReadValue<TByteBlock, byte>(ref byteBlock);
|
||||
Type = ReaderExtension.ReadValue<TByteBlock, byte>(ref byteBlock);
|
||||
|
||||
if (code != 0x7B)
|
||||
{
|
||||
@@ -44,15 +44,15 @@ public abstract class DDPMessage : MessageBase, IResultMessage
|
||||
}
|
||||
}
|
||||
|
||||
public abstract int GetBodyLength<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlock;
|
||||
public abstract byte[] GetContent<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlock;
|
||||
public abstract int GetBodyLength<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlockReader;
|
||||
public abstract byte[] GetContent<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlockReader;
|
||||
}
|
||||
|
||||
public class DDPTcpMessage : DDPMessage
|
||||
{
|
||||
public override int GetBodyLength<TByteBlock>(ref TByteBlock byteBlock)
|
||||
{
|
||||
return byteBlock.ReadUInt16(EndianType.Big) - 4;
|
||||
return ReaderExtension.ReadValue<TByteBlock, ushort>(ref byteBlock, EndianType.Big) - 4;
|
||||
}
|
||||
|
||||
public override byte[] GetContent<TByteBlock>(ref TByteBlock byteBlock)
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
using System.Text;
|
||||
|
||||
using ThingsGateway.NewLife.Extension;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
@@ -30,40 +32,37 @@ public class DDPSend : ISendMessage
|
||||
Id = id;
|
||||
Command = command;
|
||||
}
|
||||
public void Build<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlock
|
||||
public void Build<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlockWriter
|
||||
{
|
||||
byteBlock.WriteByte(0x7b);
|
||||
byteBlock.WriteByte(Command);
|
||||
byteBlock.WriteUInt16(0x10, EndianType.Big);//len
|
||||
byteBlock.Write(PadTo11Byte(Id.Remove(0, 3)));
|
||||
WriterExtension.WriteValue(ref byteBlock, (byte)0x7b);
|
||||
WriterExtension.WriteValue(ref byteBlock, (byte)Command);
|
||||
var id = PadTo11Byte(Id.Remove(0, 3));
|
||||
if (Tcp)
|
||||
{
|
||||
byteBlock.Write(ReadOnlyMemory.Span);
|
||||
byteBlock.WriteByte(0x7b);
|
||||
byteBlock.Position = 2;
|
||||
byteBlock.WriteUInt16((ushort)byteBlock.Length, EndianType.Big);//len
|
||||
WriterExtension.WriteValue(ref byteBlock, (ushort)(id.Length + ReadOnlyMemory.Length + 3), EndianType.Big);//len
|
||||
}
|
||||
else
|
||||
{
|
||||
byteBlock.WriteByte(0x7b);
|
||||
WriterExtension.WriteValue(ref byteBlock, (ushort)0x10, EndianType.Big);//len
|
||||
}
|
||||
byteBlock.Write(id);
|
||||
if (Tcp)
|
||||
{
|
||||
byteBlock.Write(ReadOnlyMemory.Span);
|
||||
WriterExtension.WriteValue(ref byteBlock, (byte)0x7b);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriterExtension.WriteValue(ref byteBlock, (byte)0x7b);
|
||||
byteBlock.Write(ReadOnlyMemory.Span);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] PadTo11Byte(string id)
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(id);
|
||||
|
||||
if (bytes.Length < 11)
|
||||
{
|
||||
byte[] newBytes = new byte[11];
|
||||
Array.Copy(bytes, newBytes, bytes.Length);
|
||||
for (int i = bytes.Length; i < 11; i++)
|
||||
{
|
||||
newBytes[i] = 0;
|
||||
}
|
||||
return newBytes;
|
||||
}
|
||||
return bytes;
|
||||
var buffer = new byte[11];
|
||||
var str = id.AsSpan();
|
||||
int byteCount = Encoding.UTF8.GetBytes(str.Slice(0, Math.Min(str.Length, 11)), buffer);
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
@@ -42,17 +42,17 @@ public class DDPTcpSessionClientChannel : TcpSessionClientChannel
|
||||
DataHandlingAdapter.SendAsyncCallBack = DefaultSendAsync;
|
||||
return base.OnTcpConnected(e);
|
||||
}
|
||||
protected Task DefaultSendAsync(ReadOnlyMemory<byte> memory)
|
||||
protected Task DefaultSendAsync(ReadOnlyMemory<byte> memory, CancellationToken cancellationToken)
|
||||
{
|
||||
return DDPAdapter.SendInputAsync(new DDPSend(memory, Id, true));
|
||||
return DDPAdapter.SendInputAsync(new DDPSend(memory, Id, true), cancellationToken);
|
||||
}
|
||||
protected Task DDPSendAsync(ReadOnlyMemory<byte> memory)
|
||||
protected Task DDPSendAsync(ReadOnlyMemory<byte> memory, CancellationToken cancellationToken)
|
||||
{
|
||||
return base.ProtectedDefaultSendAsync(memory);
|
||||
return base.ProtectedDefaultSendAsync(memory, cancellationToken);
|
||||
}
|
||||
|
||||
private DDPMessage DDPMessage { get; set; }
|
||||
private Task DDPHandleReceivedData(ByteBlock byteBlock, IRequestInfo requestInfo)
|
||||
private Task DDPHandleReceivedData(IByteBlockReader byteBlock, IRequestInfo requestInfo)
|
||||
{
|
||||
if (requestInfo is DDPMessage dDPMessage)
|
||||
DDPMessage = dDPMessage;
|
||||
@@ -62,13 +62,14 @@ public class DDPTcpSessionClientChannel : TcpSessionClientChannel
|
||||
|
||||
private DeviceSingleStreamDataHandleAdapter<DDPTcpMessage> DDPAdapter = new();
|
||||
private WaitLock _waitLock = new(nameof(DDPTcpSessionClientChannel));
|
||||
protected override async ValueTask<bool> OnTcpReceiving(ByteBlock byteBlock)
|
||||
|
||||
protected override async ValueTask<bool> OnTcpReceiving(IByteBlockReader byteBlock)
|
||||
{
|
||||
DDPMessage? message = null;
|
||||
try
|
||||
{
|
||||
await _waitLock.WaitAsync().ConfigureAwait(false);
|
||||
await DDPAdapter.ReceivedInputAsync(byteBlock).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
|
||||
await DDPAdapter.ReceivedInputAsync(byteBlock).ConfigureAwait(false);
|
||||
|
||||
message = DDPMessage;
|
||||
DDPMessage = null;
|
||||
@@ -85,9 +86,18 @@ public class DDPTcpSessionClientChannel : TcpSessionClientChannel
|
||||
var id = $"ID={message.Id}";
|
||||
if (message.Type == 0x09)
|
||||
{
|
||||
byteBlock.Reset();
|
||||
byteBlock.Write(message.Content);
|
||||
return false;
|
||||
var reader = new ByteBlockReader(message.Content);
|
||||
|
||||
if (this.DataHandlingAdapter == null)
|
||||
{
|
||||
await this.OnTcpReceived(new ReceivedDataEventArgs(reader, default)).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
|
||||
}
|
||||
else
|
||||
{
|
||||
await this.DataHandlingAdapter.ReceivedInputAsync(reader).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -102,7 +112,7 @@ public class DDPTcpSessionClientChannel : TcpSessionClientChannel
|
||||
Logger?.Debug($"Old socket connections with the same ID {id} will be closed");
|
||||
try
|
||||
{
|
||||
await oldClient.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both).ConfigureAwait(false);
|
||||
//await oldClient.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both).ConfigureAwait(false);
|
||||
await oldClient.CloseAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
@@ -120,13 +130,13 @@ public class DDPTcpSessionClientChannel : TcpSessionClientChannel
|
||||
await ResetIdAsync(id).ConfigureAwait(false);
|
||||
|
||||
//发送成功
|
||||
await DDPAdapter.SendInputAsync(new DDPSend(ReadOnlyMemory<byte>.Empty, id, true, 0x81)).ConfigureAwait(false);
|
||||
await DDPAdapter.SendInputAsync(new DDPSend(ReadOnlyMemory<byte>.Empty, id, true, 0x81), ClosedToken).ConfigureAwait(false);
|
||||
if (log)
|
||||
Logger?.Info(string.Format(AppResource.DtuConnected, Id));
|
||||
}
|
||||
else if (message.Type == 0x02)
|
||||
{
|
||||
await DDPAdapter.SendInputAsync(new DDPSend(ReadOnlyMemory<byte>.Empty, Id, true, 0x82)).ConfigureAwait(false);
|
||||
await DDPAdapter.SendInputAsync(new DDPSend(ReadOnlyMemory<byte>.Empty, Id, true, 0x82), ClosedToken).ConfigureAwait(false);
|
||||
Logger?.Info(string.Format(AppResource.DtuDisconnecting, Id));
|
||||
await Task.Delay(100).ConfigureAwait(false);
|
||||
await this.CloseAsync().ConfigureAwait(false);
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
@@ -49,11 +49,12 @@ public class DDPUdpSessionChannel : UdpSessionChannel, IClientChannel, IDtuUdpSe
|
||||
DataHandlingAdapter.SendCallBackAsync = DefaultSendAsync;
|
||||
}
|
||||
|
||||
protected Task DefaultSendAsync(EndPoint endPoint, ReadOnlyMemory<byte> memory)
|
||||
|
||||
protected Task DefaultSendAsync(EndPoint endPoint, ReadOnlyMemory<byte> memory, CancellationToken token)
|
||||
{
|
||||
if (TryGetId(endPoint, out var id))
|
||||
{
|
||||
return DDPAdapter.SendInputAsync(endPoint, new DDPSend(memory, id, false));
|
||||
return DDPAdapter.SendInputAsync(endPoint, new DDPSend(memory, id, false), token);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -61,14 +62,14 @@ public class DDPUdpSessionChannel : UdpSessionChannel, IClientChannel, IDtuUdpSe
|
||||
}
|
||||
}
|
||||
|
||||
protected Task DDPSendAsync(EndPoint endPoint, ReadOnlyMemory<byte> memory)
|
||||
protected Task DDPSendAsync(EndPoint endPoint, ReadOnlyMemory<byte> memory, CancellationToken token)
|
||||
{
|
||||
//获取endpoint
|
||||
return base.ProtectedDefaultSendAsync(endPoint, memory);
|
||||
return base.ProtectedDefaultSendAsync(endPoint, memory, token);
|
||||
}
|
||||
|
||||
private ConcurrentDictionary<EndPoint, DDPMessage> DDPMessageDict { get; set; } = new();
|
||||
private Task DDPHandleReceivedData(EndPoint endPoint, ByteBlock byteBlock, IRequestInfo requestInfo)
|
||||
private Task DDPHandleReceivedData(EndPoint endPoint, IByteBlockReader byteBlock, IRequestInfo requestInfo)
|
||||
{
|
||||
if (requestInfo is DDPMessage dDPMessage)
|
||||
{
|
||||
@@ -126,9 +127,18 @@ public class DDPUdpSessionChannel : UdpSessionChannel, IClientChannel, IDtuUdpSe
|
||||
var id = $"ID={message.Id}";
|
||||
if (message.Type == 0x09)
|
||||
{
|
||||
byteBlock.Reset();
|
||||
byteBlock.Write(message.Content);
|
||||
return false;
|
||||
var reader = new ByteBlockReader(message.Content);
|
||||
|
||||
if (this.DataHandlingAdapter == null)
|
||||
{
|
||||
await this.OnUdpReceived(new UdpReceivedDataEventArgs(endPoint, reader, default)).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
|
||||
}
|
||||
else
|
||||
{
|
||||
await this.DataHandlingAdapter.ReceivedInput(endPoint, reader).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -155,13 +165,13 @@ public class DDPUdpSessionChannel : UdpSessionChannel, IClientChannel, IDtuUdpSe
|
||||
}
|
||||
|
||||
//发送成功
|
||||
await DDPAdapter.SendInputAsync(endPoint, new DDPSend(ReadOnlyMemory<byte>.Empty, id, false, 0x81)).ConfigureAwait(false);
|
||||
await DDPAdapter.SendInputAsync(endPoint, new DDPSend(ReadOnlyMemory<byte>.Empty, id, false, 0x81), ClosedToken).ConfigureAwait(false);
|
||||
if (log)
|
||||
Logger?.Info(string.Format(AppResource.DtuConnected, id));
|
||||
}
|
||||
else if (message.Type == 0x02)
|
||||
{
|
||||
await DDPAdapter.SendInputAsync(endPoint, new DDPSend(ReadOnlyMemory<byte>.Empty, id, false, 0x82)).ConfigureAwait(false);
|
||||
await DDPAdapter.SendInputAsync(endPoint, new DDPSend(ReadOnlyMemory<byte>.Empty, id, false, 0x82), ClosedToken).ConfigureAwait(false);
|
||||
Logger?.Info(string.Format(AppResource.DtuDisconnecting, id));
|
||||
await Task.Delay(100).ConfigureAwait(false);
|
||||
IdDict.TryRemove(endPoint, out _);
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// ------------------------------------------------------------------------------
|
||||
// ------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
@@ -37,10 +37,7 @@ public interface IChannel : ISetupConfigObject, IDisposable, IClosableClient, IC
|
||||
/// </summary>
|
||||
public ConcurrentList<IDevice> Collects { get; }
|
||||
|
||||
/// <summary>
|
||||
/// MaxSign
|
||||
/// </summary>
|
||||
int MaxSign { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 通道启动成功后
|
||||
@@ -66,6 +63,11 @@ public interface IChannel : ISetupConfigObject, IDisposable, IClosableClient, IC
|
||||
/// 主动请求时的等待池
|
||||
/// </summary>
|
||||
public ConcurrentDictionary<long, Func<IClientChannel, ReceivedDataEventArgs, bool, Task>> ChannelReceivedWaitDict { get; }
|
||||
|
||||
void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// ------------------------------------------------------------------------------
|
||||
// ------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
@@ -12,6 +12,8 @@ using System.Collections.Concurrent;
|
||||
|
||||
using ThingsGateway.NewLife;
|
||||
|
||||
using TouchSocket.SerialPorts;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
@@ -25,17 +27,20 @@ public class OtherChannel : SetupConfigObject, IClientChannel
|
||||
public OtherChannel(IChannelOptions channelOptions)
|
||||
{
|
||||
ChannelOptions = channelOptions;
|
||||
|
||||
WaitHandlePool.MaxSign = ushort.MaxValue;
|
||||
ResetSign();
|
||||
}
|
||||
|
||||
public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config;
|
||||
|
||||
public void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue)
|
||||
{
|
||||
var pool = WaitHandlePool;
|
||||
WaitHandlePool = new WaitHandlePool<MessageBase>(minSign, maxSign);
|
||||
pool?.CancelAll();
|
||||
pool?.SafeDispose();
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public int MaxSign { get => WaitHandlePool.MaxSign; set => WaitHandlePool.MaxSign = value; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelReceivedEventHandler ChannelReceived { get; set; } = new();
|
||||
public ChannelReceivedEventHandler ChannelReceived { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IChannelOptions ChannelOptions { get; }
|
||||
@@ -47,20 +52,20 @@ public class OtherChannel : SetupConfigObject, IClientChannel
|
||||
public ConcurrentList<IDevice> Collects { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Started { get; set; } = new();
|
||||
public ChannelEventHandler Started { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Starting { get; set; } = new();
|
||||
public ChannelEventHandler Starting { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Stoped { get; set; } = new();
|
||||
public ChannelEventHandler Stoped { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Stoping { get; set; } = new();
|
||||
public ChannelEventHandler Stoping { get; } = new();
|
||||
/// <summary>
|
||||
/// 等待池
|
||||
/// </summary>
|
||||
public WaitHandlePool<MessageBase> WaitHandlePool { get; } = new();
|
||||
public WaitHandlePool<MessageBase> WaitHandlePool { get; internal set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public WaitLock WaitLock => ChannelOptions.WaitLock;
|
||||
@@ -109,7 +114,7 @@ public class OtherChannel : SetupConfigObject, IClientChannel
|
||||
m_dataHandlingAdapter = adapter;
|
||||
}
|
||||
|
||||
private async Task PrivateHandleReceivedData(ByteBlock byteBlock, IRequestInfo requestInfo)
|
||||
private async Task PrivateHandleReceivedData(IByteBlockReader byteBlock, IRequestInfo requestInfo)
|
||||
{
|
||||
LastReceivedTime = DateTime.Now;
|
||||
await this.OnChannelReceivedEvent(new ReceivedDataEventArgs(byteBlock, requestInfo), ChannelReceived).ConfigureAwait(false);
|
||||
@@ -119,8 +124,9 @@ public class OtherChannel : SetupConfigObject, IClientChannel
|
||||
/// 异步发送数据,保护方法。
|
||||
/// </summary>
|
||||
/// <param name="memory">待发送的字节数据内存。</param>
|
||||
/// <param name="cancellationToken">cancellationToken</param>
|
||||
/// <returns>异步任务。</returns>
|
||||
protected Task ProtectedDefaultSendAsync(ReadOnlyMemory<byte> memory)
|
||||
protected Task ProtectedDefaultSendAsync(ReadOnlyMemory<byte> memory, CancellationToken cancellationToken)
|
||||
{
|
||||
LastSentTime = DateTime.Now;
|
||||
return Task.CompletedTask;
|
||||
@@ -134,77 +140,58 @@ public class OtherChannel : SetupConfigObject, IClientChannel
|
||||
|
||||
public bool IsClient => true;
|
||||
|
||||
public bool Online => true;
|
||||
|
||||
public bool Online => online;
|
||||
public CancellationToken ClosedToken => this.m_transport == null ? new CancellationToken(true) : this.m_transport.Token;
|
||||
private CancellationTokenSource m_transport;
|
||||
public Task<Result> CloseAsync(string msg, CancellationToken token)
|
||||
{
|
||||
var cts = m_transport;
|
||||
m_transport = null;
|
||||
cts?.SafeCancel();
|
||||
cts?.SafeDispose();
|
||||
online = false;
|
||||
|
||||
return Task.FromResult(Result.Success);
|
||||
}
|
||||
|
||||
public volatile bool online;
|
||||
public Task ConnectAsync(int millisecondsTimeout, CancellationToken token)
|
||||
{
|
||||
var cts = m_transport;
|
||||
m_transport = new();
|
||||
cts?.SafeCancel();
|
||||
cts?.SafeDispose();
|
||||
online = true;
|
||||
if (this.m_dataHandlingAdapter == null)
|
||||
{
|
||||
var adapter = this.Config.GetValue(SerialPortConfigExtension.SerialDataHandlingAdapterProperty)?.Invoke();
|
||||
if (adapter != null)
|
||||
{
|
||||
this.SetAdapter(adapter);
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task SendAsync(IList<ArraySegment<byte>> transferBytes)
|
||||
{
|
||||
// 检查数据处理适配器是否存在且支持拼接发送
|
||||
if (m_dataHandlingAdapter?.CanSplicingSend != true)
|
||||
{
|
||||
// 如果不支持拼接发送,则计算所有字节片段的总长度
|
||||
var length = 0;
|
||||
foreach (var item in transferBytes)
|
||||
{
|
||||
length += item.Count;
|
||||
}
|
||||
// 使用计算出的总长度创建一个连续的内存块
|
||||
using (var byteBlock = new ByteBlock(length))
|
||||
{
|
||||
// 将每个字节片段写入连续的内存块
|
||||
foreach (var item in transferBytes)
|
||||
{
|
||||
byteBlock.Write(new ReadOnlySpan<byte>(item.Array, item.Offset, item.Count));
|
||||
}
|
||||
// 根据数据处理适配器的存在与否,选择不同的发送方式
|
||||
if (m_dataHandlingAdapter == null)
|
||||
{
|
||||
// 如果没有数据处理适配器,则使用默认方式发送
|
||||
await ProtectedDefaultSendAsync(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果有数据处理适配器,则通过适配器发送
|
||||
await m_dataHandlingAdapter.SendInputAsync(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果数据处理适配器支持拼接发送,则直接发送字节列表
|
||||
await m_dataHandlingAdapter.SendInputAsync(transferBytes).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
|
||||
}
|
||||
}
|
||||
|
||||
public Task SendAsync(ReadOnlyMemory<byte> memory)
|
||||
public Task SendAsync(ReadOnlyMemory<byte> memory, CancellationToken cancellationToken)
|
||||
{
|
||||
if (m_dataHandlingAdapter == null)
|
||||
{
|
||||
return ProtectedDefaultSendAsync(memory);
|
||||
return ProtectedDefaultSendAsync(memory, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 否则,使用适配器的发送方法进行数据发送。
|
||||
return m_dataHandlingAdapter.SendInputAsync(memory);
|
||||
return m_dataHandlingAdapter.SendInputAsync(memory, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
public Task SendAsync(IRequestInfo requestInfo)
|
||||
public Task SendAsync(IRequestInfo requestInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
// 检查是否具备发送请求的条件,如果不具备则抛出异常
|
||||
ThrowIfCannotSendRequestInfo();
|
||||
|
||||
// 使用数据处理适配器异步发送输入请求
|
||||
return m_dataHandlingAdapter.SendInputAsync(requestInfo);
|
||||
return m_dataHandlingAdapter.SendInputAsync(requestInfo, cancellationToken);
|
||||
}
|
||||
private void ThrowIfCannotSendRequestInfo()
|
||||
{
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
@@ -31,13 +31,13 @@ public class DtuPlugin : PluginBase, ITcpReceivingPlugin
|
||||
{
|
||||
_heartbeat = value;
|
||||
if (!heartbeatHex)
|
||||
HeartbeatByte = new ArraySegment<byte>(Encoding.UTF8.GetBytes(_heartbeat ?? string.Empty));
|
||||
HeartbeatByte = (Encoding.UTF8.GetBytes(_heartbeat ?? string.Empty));
|
||||
else
|
||||
HeartbeatByte = new ArraySegment<byte>(_heartbeat?.HexStringToBytes() ?? Array.Empty<byte>());
|
||||
HeartbeatByte = (_heartbeat?.HexStringToBytes() ?? Array.Empty<byte>());
|
||||
}
|
||||
}
|
||||
private string _heartbeat;
|
||||
private ArraySegment<byte> HeartbeatByte = new();
|
||||
private Memory<byte> HeartbeatByte = new();
|
||||
|
||||
|
||||
private bool heartbeatHex;
|
||||
@@ -51,9 +51,9 @@ public class DtuPlugin : PluginBase, ITcpReceivingPlugin
|
||||
{
|
||||
heartbeatHex = value;
|
||||
if (!heartbeatHex)
|
||||
HeartbeatByte = new ArraySegment<byte>(Encoding.UTF8.GetBytes(_heartbeat ?? string.Empty));
|
||||
HeartbeatByte = (Encoding.UTF8.GetBytes(_heartbeat ?? string.Empty));
|
||||
else
|
||||
HeartbeatByte = new ArraySegment<byte>(_heartbeat?.HexStringToBytes() ?? Array.Empty<byte>());
|
||||
HeartbeatByte = (_heartbeat?.HexStringToBytes() ?? Array.Empty<byte>());
|
||||
}
|
||||
}
|
||||
public bool DtuIdHex { get; set; }
|
||||
@@ -61,7 +61,7 @@ public class DtuPlugin : PluginBase, ITcpReceivingPlugin
|
||||
/// <inheritdoc/>
|
||||
public async Task OnTcpReceiving(ITcpSession client, ByteBlockEventArgs e)
|
||||
{
|
||||
var len = HeartbeatByte.Count;
|
||||
var len = HeartbeatByte.Length;
|
||||
if (client is TcpSessionClientChannel socket && socket.Service is ITcpServiceChannel tcpServiceChannel)
|
||||
{
|
||||
if (!socket.Id.StartsWith("ID="))
|
||||
@@ -71,7 +71,7 @@ public class DtuPlugin : PluginBase, ITcpReceivingPlugin
|
||||
{
|
||||
try
|
||||
{
|
||||
await oldClient.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both).ConfigureAwait(false);
|
||||
//await oldClient.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both).ConfigureAwait(false);
|
||||
await oldClient.CloseAsync().ConfigureAwait(false);
|
||||
oldClient.Dispose();
|
||||
}
|
||||
@@ -88,7 +88,7 @@ public class DtuPlugin : PluginBase, ITcpReceivingPlugin
|
||||
{
|
||||
try
|
||||
{
|
||||
await socket.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both).ConfigureAwait(false);
|
||||
//await socket.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both).ConfigureAwait(false);
|
||||
await socket.CloseAsync().ConfigureAwait(false);
|
||||
socket.Dispose();
|
||||
}
|
||||
@@ -102,14 +102,14 @@ public class DtuPlugin : PluginBase, ITcpReceivingPlugin
|
||||
|
||||
if (len > 0)
|
||||
{
|
||||
if (HeartbeatByte.SequenceEqual(e.ByteBlock.AsSegment(0, len)))
|
||||
if (HeartbeatByte.Span.SequenceEqual(e.ByteBlock.Memory.Slice(0, len).Span))
|
||||
{
|
||||
if (DateTimeOffset.Now - socket.LastSentTime < TimeSpan.FromMilliseconds(200))
|
||||
{
|
||||
await Task.Delay(200).ConfigureAwait(false);
|
||||
}
|
||||
//回应心跳包
|
||||
await socket.SendAsync(HeartbeatByte).ConfigureAwait(false);
|
||||
await socket.SendAsync(HeartbeatByte, socket.ClosedToken).ConfigureAwait(false);
|
||||
e.Handled = true;
|
||||
if (socket.Logger?.LogLevel <= LogLevel.Trace)
|
||||
socket.Logger?.Trace($"{socket}- Heartbeat");
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
@@ -15,7 +15,7 @@ using ThingsGateway.Foundation.Extension.String;
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
[PluginOption(Singleton = true)]
|
||||
internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugin, ITcpReceivingPlugin, ITcpClosingPlugin
|
||||
internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugin, ITcpReceivingPlugin, ITcpClosedPlugin
|
||||
{
|
||||
public string DtuId
|
||||
{
|
||||
@@ -27,13 +27,13 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi
|
||||
{
|
||||
_dtuId = value;
|
||||
if (!dtuIdHex)
|
||||
DtuIdByte = new ArraySegment<byte>(Encoding.UTF8.GetBytes(_dtuId ?? string.Empty));
|
||||
DtuIdByte = (Encoding.UTF8.GetBytes(_dtuId ?? string.Empty));
|
||||
else
|
||||
DtuIdByte = new ArraySegment<byte>(_dtuId?.HexStringToBytes() ?? Array.Empty<byte>());
|
||||
DtuIdByte = (_dtuId?.HexStringToBytes() ?? Array.Empty<byte>());
|
||||
}
|
||||
}
|
||||
private string _dtuId;
|
||||
private ArraySegment<byte> DtuIdByte;
|
||||
private Memory<byte> DtuIdByte;
|
||||
|
||||
/// <summary>
|
||||
/// 心跳字符串
|
||||
@@ -48,13 +48,13 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi
|
||||
{
|
||||
_heartbeat = value;
|
||||
if (!heartbeatHex)
|
||||
HeartbeatByte = new ArraySegment<byte>(Encoding.UTF8.GetBytes(_heartbeat ?? string.Empty));
|
||||
HeartbeatByte = (Encoding.UTF8.GetBytes(_heartbeat ?? string.Empty));
|
||||
else
|
||||
HeartbeatByte = new ArraySegment<byte>(_heartbeat?.HexStringToBytes() ?? Array.Empty<byte>());
|
||||
HeartbeatByte = (_heartbeat?.HexStringToBytes() ?? Array.Empty<byte>());
|
||||
}
|
||||
}
|
||||
private string _heartbeat;
|
||||
private ArraySegment<byte> HeartbeatByte;
|
||||
private Memory<byte> HeartbeatByte;
|
||||
|
||||
|
||||
|
||||
@@ -73,12 +73,12 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi
|
||||
heartbeatHex = value;
|
||||
if (!heartbeatHex)
|
||||
{
|
||||
HeartbeatByte = new ArraySegment<byte>(Encoding.UTF8.GetBytes(_heartbeat ?? string.Empty));
|
||||
HeartbeatByte = (Encoding.UTF8.GetBytes(_heartbeat ?? string.Empty));
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
HeartbeatByte = new ArraySegment<byte>(_heartbeat?.HexStringToBytes() ?? Array.Empty<byte>());
|
||||
HeartbeatByte = (_heartbeat?.HexStringToBytes() ?? Array.Empty<byte>());
|
||||
|
||||
}
|
||||
}
|
||||
@@ -95,12 +95,12 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi
|
||||
dtuIdHex = value;
|
||||
if (!dtuIdHex)
|
||||
{
|
||||
DtuIdByte = new ArraySegment<byte>(Encoding.UTF8.GetBytes(_dtuId ?? string.Empty));
|
||||
DtuIdByte = (Encoding.UTF8.GetBytes(_dtuId ?? string.Empty));
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
DtuIdByte = new ArraySegment<byte>(_dtuId?.HexStringToBytes() ?? Array.Empty<byte>());
|
||||
DtuIdByte = (_dtuId?.HexStringToBytes() ?? Array.Empty<byte>());
|
||||
|
||||
}
|
||||
}
|
||||
@@ -123,7 +123,7 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi
|
||||
|
||||
if (client is ITcpClient tcpClient)
|
||||
{
|
||||
await tcpClient.SendAsync(DtuIdByte).ConfigureAwait(false);
|
||||
await tcpClient.SendAsync(DtuIdByte, tcpClient.ClosedToken).ConfigureAwait(false);
|
||||
|
||||
if (Task == null)
|
||||
{
|
||||
@@ -145,7 +145,7 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi
|
||||
await Task.Delay(200).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await tcpClient.SendAsync(HeartbeatByte).ConfigureAwait(false);
|
||||
await tcpClient.SendAsync(HeartbeatByte, tcpClient.ClosedToken).ConfigureAwait(false);
|
||||
tcpClient.Logger?.Trace($"{tcpClient}- Heartbeat");
|
||||
failedCount = 0;
|
||||
}
|
||||
@@ -178,10 +178,10 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi
|
||||
|
||||
if (client is ITcpClient tcpClient)
|
||||
{
|
||||
var len = HeartbeatByte.Count;
|
||||
var len = HeartbeatByte.Length;
|
||||
if (len > 0)
|
||||
{
|
||||
if (HeartbeatByte.SequenceEqual(e.ByteBlock.AsSegment(0, len)))
|
||||
if (HeartbeatByte.Span.SequenceEqual(e.ByteBlock.Memory.Slice(0, len).Span))
|
||||
{
|
||||
e.Handled = true;
|
||||
}
|
||||
@@ -190,7 +190,7 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi
|
||||
}
|
||||
}
|
||||
|
||||
public Task OnTcpClosing(ITcpSession client, ClosingEventArgs e)
|
||||
public Task OnTcpClosed(ITcpSession client, ClosedEventArgs e)
|
||||
{
|
||||
SendHeartbeat = false;
|
||||
return EasyTask.CompletedTask;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
@@ -79,7 +79,7 @@ public static class PluginUtil
|
||||
.SetTick(TimeSpan.FromMilliseconds(channelOptions.CheckClearTime))
|
||||
.SetOnClose(async (c, t) =>
|
||||
{
|
||||
await c.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both).ConfigureAwait(false);
|
||||
//await c.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both).ConfigureAwait(false);
|
||||
await c.CloseAsync($"{channelOptions.CheckClearTime}ms Timeout").ConfigureAwait(false);
|
||||
});
|
||||
};
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
@@ -24,17 +24,20 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
|
||||
public SerialPortChannel(IChannelOptions channelOptions)
|
||||
{
|
||||
ChannelOptions = channelOptions;
|
||||
|
||||
WaitHandlePool.MaxSign = ushort.MaxValue;
|
||||
ResetSign();
|
||||
}
|
||||
|
||||
public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config;
|
||||
|
||||
public void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue)
|
||||
{
|
||||
var pool = WaitHandlePool;
|
||||
WaitHandlePool = new WaitHandlePool<MessageBase>(minSign, maxSign);
|
||||
pool?.CancelAll();
|
||||
pool?.SafeDispose();
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public int MaxSign { get => WaitHandlePool.MaxSign; set => WaitHandlePool.MaxSign = value; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelReceivedEventHandler ChannelReceived { get; set; } = new();
|
||||
public ChannelReceivedEventHandler ChannelReceived { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IChannelOptions ChannelOptions { get; }
|
||||
@@ -49,20 +52,20 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
|
||||
public DataHandlingAdapter ReadOnlyDataHandlingAdapter => ProtectedDataHandlingAdapter;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Started { get; set; } = new();
|
||||
public ChannelEventHandler Started { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Starting { get; set; } = new();
|
||||
public ChannelEventHandler Starting { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Stoped { get; set; } = new();
|
||||
public ChannelEventHandler Stoped { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Stoping { get; set; } = new();
|
||||
public ChannelEventHandler Stoping { get; } = new();
|
||||
/// <summary>
|
||||
/// 等待池
|
||||
/// </summary>
|
||||
public WaitHandlePool<MessageBase> WaitHandlePool { get; } = new();
|
||||
public WaitHandlePool<MessageBase> WaitHandlePool { get; internal set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public WaitLock WaitLock => ChannelOptions.WaitLock;
|
||||
@@ -82,6 +85,7 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
|
||||
//await _connectLock.WaitAsync().ConfigureAwait(false);
|
||||
if (Online)
|
||||
{
|
||||
PortName = null;
|
||||
var result = await base.CloseAsync(msg, token).ConfigureAwait(false);
|
||||
if (!Online)
|
||||
{
|
||||
@@ -99,7 +103,7 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public new async Task ConnectAsync(int millisecondsTimeout, CancellationToken token)
|
||||
public override async Task ConnectAsync(int millisecondsTimeout, CancellationToken token)
|
||||
{
|
||||
if (!Online)
|
||||
{
|
||||
@@ -109,6 +113,12 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
|
||||
if (!Online)
|
||||
{
|
||||
if (token.IsCancellationRequested) return;
|
||||
|
||||
|
||||
var port = Config?.GetValue(SerialPortConfigExtension.SerialPortOptionProperty);
|
||||
if (port != null)
|
||||
PortName = $"{port.PortName}";
|
||||
|
||||
await base.ConnectAsync(millisecondsTimeout, token).ConfigureAwait(false);
|
||||
if (Online)
|
||||
{
|
||||
@@ -130,20 +140,16 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
|
||||
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
|
||||
SetAdapter(singleStreamDataHandlingAdapter);
|
||||
}
|
||||
|
||||
private string PortName { get; set; }
|
||||
/// <inheritdoc/>
|
||||
public override string? ToString()
|
||||
{
|
||||
if (ProtectedMainSerialPort != null)
|
||||
{
|
||||
return $"{ProtectedMainSerialPort.PortName}";
|
||||
}
|
||||
else
|
||||
{
|
||||
var port = Config?.GetValue(SerialPortConfigExtension.SerialPortOptionProperty);
|
||||
if (port != null)
|
||||
return $"{port.PortName}";
|
||||
}
|
||||
if (!PortName.IsNullOrEmpty())
|
||||
return PortName;
|
||||
|
||||
var port = Config?.GetValue(SerialPortConfigExtension.SerialPortOptionProperty);
|
||||
if (port != null)
|
||||
return $"{port.PortName}";
|
||||
return base.ToString();
|
||||
}
|
||||
|
||||
@@ -192,9 +198,9 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Dispose(bool disposing)
|
||||
protected override void SafetyDispose(bool disposing)
|
||||
{
|
||||
WaitHandlePool.SafeDispose();
|
||||
base.Dispose(disposing);
|
||||
base.SafetyDispose(disposing);
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
@@ -23,11 +23,16 @@ public class TcpClientChannel : TcpClient, IClientChannel
|
||||
public TcpClientChannel(IChannelOptions channelOptions)
|
||||
{
|
||||
ChannelOptions = channelOptions;
|
||||
|
||||
WaitHandlePool.MaxSign = ushort.MaxValue;
|
||||
ResetSign();
|
||||
}
|
||||
public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config;
|
||||
public int MaxSign { get => WaitHandlePool.MaxSign; set => WaitHandlePool.MaxSign = value; }
|
||||
public void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue)
|
||||
{
|
||||
var pool = WaitHandlePool;
|
||||
WaitHandlePool = new WaitHandlePool<MessageBase>(minSign, maxSign);
|
||||
pool?.CancelAll();
|
||||
pool?.SafeDispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelReceivedEventHandler ChannelReceived { get; } = new();
|
||||
@@ -57,7 +62,7 @@ public class TcpClientChannel : TcpClient, IClientChannel
|
||||
/// <summary>
|
||||
/// 等待池
|
||||
/// </summary>
|
||||
public WaitHandlePool<MessageBase> WaitHandlePool { get; } = new();
|
||||
public WaitHandlePool<MessageBase> WaitHandlePool { get; internal set; } = new();
|
||||
public virtual WaitLock GetLock(string key) => WaitLock;
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -181,9 +186,9 @@ public class TcpClientChannel : TcpClient, IClientChannel
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Dispose(bool disposing)
|
||||
protected override void SafetyDispose(bool disposing)
|
||||
{
|
||||
WaitHandlePool.SafeDispose();
|
||||
base.Dispose(disposing);
|
||||
base.SafetyDispose(disposing);
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
@@ -23,10 +23,10 @@ public abstract class TcpServiceChannelBase<TClient> : TcpService<TClient>, ITcp
|
||||
/// <inheritdoc/>
|
||||
public ConcurrentList<IDevice> Collects { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 停止时是否发送ShutDown
|
||||
/// </summary>
|
||||
public bool ShutDownEnable { get; set; } = true;
|
||||
///// <summary>
|
||||
///// 停止时是否发送ShutDown
|
||||
///// </summary>
|
||||
//public bool ShutDownEnable { get; set; } = true;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task ClearAsync()
|
||||
@@ -35,8 +35,8 @@ public abstract class TcpServiceChannelBase<TClient> : TcpService<TClient>, ITcp
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ShutDownEnable)
|
||||
await client.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both).ConfigureAwait(false);
|
||||
//if (ShutDownEnable)
|
||||
// await client.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both).ConfigureAwait(false);
|
||||
|
||||
await client.CloseAsync().ConfigureAwait(false);
|
||||
client.SafeDispose();
|
||||
@@ -51,8 +51,8 @@ public abstract class TcpServiceChannelBase<TClient> : TcpService<TClient>, ITcp
|
||||
{
|
||||
if (this.TryGetClient(id, out var client))
|
||||
{
|
||||
if (ShutDownEnable)
|
||||
await client.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both).ConfigureAwait(false);
|
||||
//if (ShutDownEnable)
|
||||
// await client.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both).ConfigureAwait(false);
|
||||
await client.CloseAsync().ConfigureAwait(false);
|
||||
client.SafeDispose();
|
||||
}
|
||||
@@ -85,6 +85,10 @@ public abstract class TcpServiceChannelBase<TClient> : TcpService<TClient>, ITcp
|
||||
}
|
||||
finally
|
||||
{
|
||||
var cts = m_transport;
|
||||
m_transport = new();
|
||||
cts?.SafeCancel();
|
||||
cts?.SafeDispose();
|
||||
_connectLock.Release();
|
||||
}
|
||||
}
|
||||
@@ -110,6 +114,10 @@ public abstract class TcpServiceChannelBase<TClient> : TcpService<TClient>, ITcp
|
||||
}
|
||||
finally
|
||||
{
|
||||
var cts = m_transport;
|
||||
m_transport = null;
|
||||
cts?.SafeCancel();
|
||||
cts?.SafeDispose();
|
||||
_connectLock.Release();
|
||||
}
|
||||
}
|
||||
@@ -121,6 +129,15 @@ public abstract class TcpServiceChannelBase<TClient> : TcpService<TClient>, ITcp
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public CancellationToken ClosedToken => this.m_transport == null ? new CancellationToken(true) : this.m_transport.Token;
|
||||
private CancellationTokenSource m_transport;
|
||||
|
||||
protected override void SafetyDispose(bool disposing)
|
||||
{
|
||||
m_transport?.SafeCancel();
|
||||
m_transport?.SafeDispose();
|
||||
base.SafetyDispose(disposing);
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
protected override Task OnTcpClosed(TClient socketClient, ClosedEventArgs e)
|
||||
{
|
||||
@@ -163,7 +180,7 @@ public class TcpServiceChannel<TClient> : TcpServiceChannelBase<TClient>, IChann
|
||||
public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelReceivedEventHandler ChannelReceived { get; set; } = new();
|
||||
public ChannelReceivedEventHandler ChannelReceived { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IChannelOptions ChannelOptions { get; }
|
||||
@@ -175,15 +192,15 @@ public class TcpServiceChannel<TClient> : TcpServiceChannelBase<TClient>, IChann
|
||||
public bool Online => ServerState == ServerState.Running;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Started { get; set; } = new();
|
||||
public ChannelEventHandler Started { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Starting { get; set; } = new();
|
||||
public ChannelEventHandler Starting { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Stoped { get; set; } = new();
|
||||
public ChannelEventHandler Stoped { get; } = new();
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Stoping { get; set; } = new();
|
||||
public ChannelEventHandler Stoping { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<Result> CloseAsync(string msg, CancellationToken token)
|
||||
@@ -208,10 +225,16 @@ public class TcpServiceChannel<TClient> : TcpServiceChannelBase<TClient>, IChann
|
||||
protected override TClient NewClient()
|
||||
{
|
||||
var data = new TClient();
|
||||
data.WaitHandlePool.MaxSign = MaxSign;
|
||||
data.ResetSign(MinSign, MaxSign);
|
||||
return data;
|
||||
}
|
||||
public int MaxSign { get; set; }
|
||||
public int MaxSign { get; private set; } = 0;
|
||||
public int MinSign { get; private set; } = ushort.MaxValue;
|
||||
public void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue)
|
||||
{
|
||||
MinSign = minSign;
|
||||
MaxSign = maxSign;
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnTcpClosing(TClient socketClient, ClosingEventArgs e)
|
||||
{
|
||||
@@ -264,6 +287,7 @@ public class TcpServiceChannel<TClient> : TcpServiceChannelBase<TClient>, IChann
|
||||
|
||||
IEnumerable<TcpSessionClientChannel> ITcpServiceChannel.Clients => base.Clients;
|
||||
|
||||
|
||||
protected override void ClientInitialized(TClient client)
|
||||
{
|
||||
client.ChannelOptions = ChannelOptions;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
@@ -22,13 +22,17 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
|
||||
/// <inheritdoc/>
|
||||
public TcpSessionClientChannel()
|
||||
{
|
||||
WaitHandlePool.MaxSign = ushort.MaxValue;
|
||||
}
|
||||
|
||||
public int MaxSign { get => WaitHandlePool.MaxSign; set => WaitHandlePool.MaxSign = value; }
|
||||
|
||||
public void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue)
|
||||
{
|
||||
var pool = WaitHandlePool;
|
||||
WaitHandlePool = new WaitHandlePool<MessageBase>(minSign, maxSign);
|
||||
pool?.CancelAll();
|
||||
pool?.SafeDispose();
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public ChannelReceivedEventHandler ChannelReceived { get; set; } = new();
|
||||
public ChannelReceivedEventHandler ChannelReceived { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IChannelOptions ChannelOptions { get; internal set; }
|
||||
@@ -43,15 +47,15 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
|
||||
public DataHandlingAdapter ReadOnlyDataHandlingAdapter => DataHandlingAdapter;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Started { get; set; } = new();
|
||||
public ChannelEventHandler Started { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Starting { get; set; } = new();
|
||||
public ChannelEventHandler Starting { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Stoped { get; set; } = new();
|
||||
public ChannelEventHandler Stoped { get; } = new();
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Stoping { get; set; } = new();
|
||||
public ChannelEventHandler Stoping { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 等待池
|
||||
@@ -92,10 +96,10 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Dispose(bool disposing)
|
||||
protected override void SafetyDispose(bool disposing)
|
||||
{
|
||||
WaitHandlePool.SafeDispose();
|
||||
base.Dispose(disposing);
|
||||
base.SafetyDispose(disposing);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
@@ -25,14 +25,20 @@ public class UdpSessionChannel : UdpSession, IClientChannel
|
||||
public UdpSessionChannel(IChannelOptions channelOptions)
|
||||
{
|
||||
ChannelOptions = channelOptions;
|
||||
WaitHandlePool.MaxSign = ushort.MaxValue;
|
||||
ResetSign();
|
||||
}
|
||||
public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config;
|
||||
|
||||
public int MaxSign { get => WaitHandlePool.MaxSign; set => WaitHandlePool.MaxSign = value; }
|
||||
public void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue)
|
||||
{
|
||||
var pool = WaitHandlePool;
|
||||
WaitHandlePool = new WaitHandlePool<MessageBase>(minSign, maxSign);
|
||||
pool?.CancelAll();
|
||||
pool?.SafeDispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelReceivedEventHandler ChannelReceived { get; set; } = new();
|
||||
public ChannelReceivedEventHandler ChannelReceived { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IChannelOptions ChannelOptions { get; }
|
||||
@@ -50,15 +56,15 @@ public class UdpSessionChannel : UdpSession, IClientChannel
|
||||
public DataHandlingAdapter ReadOnlyDataHandlingAdapter => DataHandlingAdapter;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Started { get; set; } = new();
|
||||
public ChannelEventHandler Started { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Starting { get; set; } = new();
|
||||
public ChannelEventHandler Starting { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Stoped { get; set; } = new();
|
||||
public ChannelEventHandler Stoped { get; } = new();
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Stoping { get; set; } = new();
|
||||
public ChannelEventHandler Stoping { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 等待池
|
||||
@@ -92,7 +98,8 @@ public class UdpSessionChannel : UdpSession, IClientChannel
|
||||
if (adapter is UdpDataHandlingAdapter udpDataHandlingAdapter)
|
||||
SetAdapter(udpDataHandlingAdapter);
|
||||
}
|
||||
|
||||
public CancellationToken ClosedToken => this.m_transport == null ? new CancellationToken(true) : this.m_transport.Token;
|
||||
private CancellationTokenSource m_transport;
|
||||
/// <inheritdoc/>
|
||||
public override async Task StartAsync()
|
||||
{
|
||||
@@ -120,6 +127,10 @@ public class UdpSessionChannel : UdpSession, IClientChannel
|
||||
}
|
||||
finally
|
||||
{
|
||||
var cts = m_transport;
|
||||
m_transport = new();
|
||||
cts?.SafeCancel();
|
||||
cts?.SafeDispose();
|
||||
_connectLock.Release();
|
||||
}
|
||||
}
|
||||
@@ -152,6 +163,10 @@ public class UdpSessionChannel : UdpSession, IClientChannel
|
||||
}
|
||||
finally
|
||||
{
|
||||
var cts = m_transport;
|
||||
m_transport = null;
|
||||
cts?.SafeCancel();
|
||||
cts?.SafeDispose();
|
||||
_connectLock.Release();
|
||||
}
|
||||
}
|
||||
@@ -188,9 +203,11 @@ public class UdpSessionChannel : UdpSession, IClientChannel
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Dispose(bool disposing)
|
||||
protected override void SafetyDispose(bool disposing)
|
||||
{
|
||||
m_transport?.SafeCancel();
|
||||
m_transport?.SafeDispose();
|
||||
WaitHandlePool.SafeDispose();
|
||||
base.Dispose(disposing);
|
||||
base.SafetyDispose(disposing);
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
@@ -27,9 +27,6 @@ public class DeviceSingleStreamDataHandleAdapter<TRequest> : TcpCustomDataHandli
|
||||
/// <inheritdoc/>
|
||||
public override bool CanSendRequestInfo => true;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanSplicingSend => false;
|
||||
|
||||
/// <summary>
|
||||
/// 报文输出时采用字符串还是HexString
|
||||
/// </summary>
|
||||
@@ -61,7 +58,7 @@ public class DeviceSingleStreamDataHandleAdapter<TRequest> : TcpCustomDataHandli
|
||||
protected override FilterResult Filter<TByteBlock>(ref TByteBlock byteBlock, bool beCached, ref TRequest request, ref int tempCapacity)
|
||||
{
|
||||
if (Logger?.LogLevel <= LogLevel.Trace)
|
||||
Logger?.Trace($"{ToString()}- Receive:{(IsHexLog ? byteBlock.AsSegmentTake().ToHexString() : byteBlock.ToString(byteBlock.Position))}");
|
||||
Logger?.Trace($"{ToString()}- Receive:{(IsHexLog ? byteBlock.AsSegmentTake().ToHexString(' ') : byteBlock.ToString(byteBlock.Position))}");
|
||||
|
||||
try
|
||||
{
|
||||
@@ -171,35 +168,36 @@ public class DeviceSingleStreamDataHandleAdapter<TRequest> : TcpCustomDataHandli
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task PreviewSendAsync(ReadOnlyMemory<byte> memory)
|
||||
protected override async Task PreviewSendAsync(ReadOnlyMemory<byte> memory, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (Logger?.LogLevel <= LogLevel.Trace)
|
||||
Logger?.Trace($"{ToString()}- Send:{(IsHexLog ? memory.Span.ToHexString() : (memory.Span.ToString(Encoding.UTF8)))}");
|
||||
Logger?.Trace($"{ToString()}- Send:{(IsHexLog ? memory.Span.ToHexString(' ') : (memory.Span.ToString(Encoding.UTF8)))}");
|
||||
|
||||
//发送
|
||||
await GoSendAsync(memory).ConfigureAwait(false);
|
||||
await GoSendAsync(memory, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task PreviewSendAsync(IRequestInfo requestInfo)
|
||||
protected override async Task PreviewSendAsync(IRequestInfo requestInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!(requestInfo is ISendMessage sendMessage))
|
||||
{
|
||||
throw new Exception($"Unable to convert {nameof(requestInfo)} to {nameof(ISendMessage)}");
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var byteBlock = new ValueByteBlock(sendMessage.MaxLength);
|
||||
try
|
||||
{
|
||||
sendMessage.Build(ref byteBlock);
|
||||
if (Logger?.LogLevel <= LogLevel.Trace)
|
||||
Logger?.Trace($"{ToString()}- Send:{(IsHexLog ? byteBlock.Span.ToHexString() : (byteBlock.Span.ToString(Encoding.UTF8)))}");
|
||||
Logger?.Trace($"{ToString()}- Send:{(IsHexLog ? byteBlock.Span.ToHexString(' ') : (byteBlock.Span.ToString(Encoding.UTF8)))}");
|
||||
//非并发主从协议
|
||||
if (IsSingleThread)
|
||||
{
|
||||
SetRequest(sendMessage, ref byteBlock);
|
||||
}
|
||||
await GoSendAsync(byteBlock.Memory).ConfigureAwait(false);
|
||||
await GoSendAsync(byteBlock.Memory, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
@@ -21,9 +21,6 @@ public class DeviceUdpDataHandleAdapter<TRequest> : UdpDataHandlingAdapter where
|
||||
/// <inheritdoc/>
|
||||
public override bool CanSendRequestInfo => true;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanSplicingSend => false;
|
||||
|
||||
/// <summary>
|
||||
/// 报文输出时采用字符串还是HexString
|
||||
/// </summary>
|
||||
@@ -61,14 +58,14 @@ public class DeviceUdpDataHandleAdapter<TRequest> : UdpDataHandlingAdapter where
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task PreviewReceived(EndPoint remoteEndPoint, ByteBlock byteBlock)
|
||||
protected override async Task PreviewReceived(EndPoint remoteEndPoint, IByteBlockReader byteBlock)
|
||||
{
|
||||
try
|
||||
{
|
||||
byteBlock.Position = 0;
|
||||
|
||||
if (Logger?.LogLevel <= LogLevel.Trace)
|
||||
Logger?.Trace($"{remoteEndPoint}- Receive:{(IsHexLog ? byteBlock.AsSegmentTake().ToHexString() : byteBlock.ToString(byteBlock.Position))}");
|
||||
Logger?.Trace($"{remoteEndPoint}- Receive:{(IsHexLog ? byteBlock.AsSegmentTake().ToHexString(' ') : byteBlock.ToString(byteBlock.Position))}");
|
||||
|
||||
TRequest request = null;
|
||||
if (IsSingleThread)
|
||||
@@ -149,35 +146,36 @@ public class DeviceUdpDataHandleAdapter<TRequest> : UdpDataHandlingAdapter where
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task PreviewSendAsync(EndPoint endPoint, ReadOnlyMemory<byte> memory)
|
||||
protected override async Task PreviewSendAsync(EndPoint endPoint, ReadOnlyMemory<byte> memory, CancellationToken cancellationToken)
|
||||
{
|
||||
if (Logger?.LogLevel <= LogLevel.Trace)
|
||||
Logger?.Trace($"{ToString()}- Send:{(IsHexLog ? memory.Span.ToHexString() : (memory.Span.ToString(Encoding.UTF8)))}");
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (Logger?.LogLevel <= LogLevel.Trace)
|
||||
Logger?.Trace($"{ToString()}- Send:{(IsHexLog ? memory.Span.ToHexString(' ') : (memory.Span.ToString(Encoding.UTF8)))}");
|
||||
//发送
|
||||
await GoSendAsync(endPoint, memory).ConfigureAwait(false);
|
||||
await GoSendAsync(endPoint, memory, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task PreviewSendAsync(EndPoint endPoint, IRequestInfo requestInfo)
|
||||
protected override async Task PreviewSendAsync(EndPoint endPoint, IRequestInfo requestInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!(requestInfo is ISendMessage sendMessage))
|
||||
{
|
||||
throw new Exception($"Unable to convert {nameof(requestInfo)} to {nameof(ISendMessage)}");
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var byteBlock = new ValueByteBlock(sendMessage.MaxLength);
|
||||
try
|
||||
{
|
||||
sendMessage.Build(ref byteBlock);
|
||||
if (Logger?.LogLevel <= LogLevel.Trace)
|
||||
Logger?.Trace($"{endPoint}- Send:{(IsHexLog ? byteBlock.Span.ToHexString() : (byteBlock.Span.ToString(Encoding.UTF8)))}");
|
||||
Logger?.Trace($"{endPoint}- Send:{(IsHexLog ? byteBlock.Span.ToHexString(' ') : (byteBlock.Span.ToString(Encoding.UTF8)))}");
|
||||
|
||||
if (IsSingleThread)
|
||||
{
|
||||
SetRequest(sendMessage, ref byteBlock);
|
||||
}
|
||||
await GoSendAsync(endPoint, byteBlock.Memory).ConfigureAwait(false);
|
||||
await GoSendAsync(endPoint, byteBlock.Memory, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
@@ -0,0 +1,46 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 定义了字节块构建器的接口,用于从内存池中构建和管理字节块。
|
||||
/// </summary>
|
||||
public interface IByteBlockWriterBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// 构建数据时,指示内存池的申请长度。
|
||||
/// <para>
|
||||
/// 建议:该值可以尽可能的设置大一些,这样可以避免内存池扩容。
|
||||
/// </para>
|
||||
/// </summary>
|
||||
int MaxLength { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 构建对象到<see cref="ByteBlock"/>
|
||||
/// </summary>
|
||||
/// <param name="writer">要构建的字节块对象引用。</param>
|
||||
void Build<TWriter>(ref TWriter writer) where TWriter : IByteBlockWriter
|
||||
#if AllowsRefStruct
|
||||
,allows ref struct
|
||||
#endif
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 指示<see cref="IRequestInfo"/>应当如何构建
|
||||
/// </summary>
|
||||
public interface IRequestInfoByteBlockWriterBuilder : IRequestInfo, IByteBlockWriterBuilder
|
||||
{
|
||||
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
@@ -23,7 +23,7 @@ public interface IResultMessage : IOperResult, IRequestInfo
|
||||
/// <summary>
|
||||
/// 解析的字节信息
|
||||
/// </summary>
|
||||
byte[] Content { get; set; }
|
||||
ReadOnlyMemory<byte> Content { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 消息头的指令长度,不固定时返回0
|
||||
@@ -42,14 +42,14 @@ public interface IResultMessage : IOperResult, IRequestInfo
|
||||
/// <para>然后返回<see cref="FilterResult.GoOn"/></para>
|
||||
/// </summary>
|
||||
/// <returns>是否成功有效</returns>
|
||||
FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlock;
|
||||
FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlockReader;
|
||||
|
||||
/// <summary>
|
||||
/// 检查头子节的合法性,并赋值<see cref="BodyLength"/><br />
|
||||
/// <para>如果返回false,意味着放弃本次解析的所有数据,包括已经解析完成的Header</para>
|
||||
/// </summary>
|
||||
/// <returns>是否成功的结果</returns>
|
||||
bool CheckHead<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlock;
|
||||
bool CheckHead<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlockReader;
|
||||
|
||||
/// <summary>
|
||||
/// 发送前的信息处理,例如存储某些特征信息:站号/功能码等等用于验证后续的返回信息是否合法
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
@@ -13,6 +13,6 @@ namespace ThingsGateway.Foundation;
|
||||
/// <summary>
|
||||
/// 发送消息
|
||||
/// </summary>
|
||||
public interface ISendMessage : IRequestInfo, IWaitHandle, IRequestInfoBuilder
|
||||
public interface ISendMessage : IRequestInfo, IWaitHandle, IRequestInfoByteBlockWriterBuilder
|
||||
{
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <inheritdoc cref="IResultMessage"/>
|
||||
public class MessageBase : OperResultClass<byte[]>, IResultMessage, IWaitHandle
|
||||
public class MessageBase : OperResultClass<ReadOnlyMemory<byte>>, IResultMessage, IWaitHandle
|
||||
{
|
||||
#region 构造
|
||||
|
||||
@@ -42,13 +42,13 @@ public class MessageBase : OperResultClass<byte[]>, IResultMessage, IWaitHandle
|
||||
public virtual int Sign { get; set; } = -1;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlock
|
||||
public virtual FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlockReader
|
||||
{
|
||||
return FilterResult.Success;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual bool CheckHead<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlock
|
||||
public virtual bool CheckHead<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlockReader
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权
|
||||
// CSDN博客:https://blog.csdn.net/qq_40374647
|
||||
@@ -13,14 +13,14 @@
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 用户自定义数据处理适配器,使用该适配器时,接收方收到的数据中,<see cref="ByteBlock"/>将为null,
|
||||
/// 用户自定义数据处理适配器,使用该适配器时,接收方收到的数据中,<see cref="ByteBlock"/>将为<see langword="null"/>,
|
||||
/// 同时<see cref="IRequestInfo"/>将实现为TRequest,发送数据直接发送。
|
||||
/// </summary>
|
||||
public abstract class TcpCustomDataHandlingAdapter<TRequest> : SingleStreamDataHandlingAdapter where TRequest : IRequestInfo
|
||||
{
|
||||
private ValueByteBlock m_tempByteBlock;
|
||||
|
||||
private readonly Type m_requestType;
|
||||
private ValueByteBlock m_tempByteBlock;
|
||||
private TRequest m_tempRequest;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化自定义数据处理适配器。
|
||||
@@ -33,16 +33,9 @@ public abstract class TcpCustomDataHandlingAdapter<TRequest> : SingleStreamDataH
|
||||
this.m_requestType = typeof(TRequest);
|
||||
}
|
||||
|
||||
private TRequest m_tempRequest;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanSendRequestInfo => false;
|
||||
|
||||
/// <summary>
|
||||
/// 默认不支持拼接发送
|
||||
/// </summary>
|
||||
public override bool CanSplicingSend => false;
|
||||
|
||||
/// <summary>
|
||||
/// 指示需要解析当前包的剩余长度。如果不能得知,请赋值<see cref="int.MaxValue"/>。
|
||||
/// </summary>
|
||||
@@ -52,10 +45,10 @@ public abstract class TcpCustomDataHandlingAdapter<TRequest> : SingleStreamDataH
|
||||
/// 尝试解析请求数据块。
|
||||
/// </summary>
|
||||
/// <typeparam name="TByteBlock">字节块类型,必须实现IByteBlock接口。</typeparam>
|
||||
/// <param name="byteBlock">待解析的字节块。</param>
|
||||
/// <param name="reader">待解析的字节块。</param>
|
||||
/// <param name="request">解析出的请求对象。</param>
|
||||
/// <returns>解析是否成功。</returns>
|
||||
public bool TryParseRequest<TByteBlock>(ref TByteBlock byteBlock, out TRequest request) where TByteBlock : IByteBlock
|
||||
public bool TryParseRequest<TByteBlock>(ref TByteBlock reader, out TRequest request) where TByteBlock : IByteBlockReader
|
||||
{
|
||||
// 检查缓存是否超时,如果超时则清除缓存。
|
||||
if (this.CacheTimeoutEnable && DateTimeOffset.UtcNow - this.LastCacheTime > this.CacheTimeout)
|
||||
@@ -66,7 +59,7 @@ public abstract class TcpCustomDataHandlingAdapter<TRequest> : SingleStreamDataH
|
||||
// 如果临时字节块为空,则尝试直接解析。
|
||||
if (this.m_tempByteBlock.IsEmpty)
|
||||
{
|
||||
return this.Single(ref byteBlock, out request) == FilterResult.Success;
|
||||
return this.Single(ref reader, out request) == FilterResult.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -77,16 +70,16 @@ public abstract class TcpCustomDataHandlingAdapter<TRequest> : SingleStreamDataH
|
||||
}
|
||||
|
||||
// 计算本次可以读取的长度。
|
||||
var len = Math.Min(this.SurLength, byteBlock.CanReadLength);
|
||||
var len = Math.Min(this.SurLength, reader.CanReadLength);
|
||||
|
||||
// 从输入字节块中读取数据到临时字节块中。
|
||||
var block = this.m_tempByteBlock;
|
||||
block.Write(byteBlock.Span.Slice(byteBlock.Position, len));
|
||||
byteBlock.Position += len;
|
||||
block.Write(reader.Span.Slice(reader.Position, len));
|
||||
reader.Position += len;
|
||||
this.SurLength -= len;
|
||||
|
||||
// 重置临时字节块并准备下一次使用。
|
||||
this.m_tempByteBlock = ValueByteBlock.Empty;
|
||||
this.m_tempByteBlock = default;
|
||||
|
||||
// 回到字节块的起始位置。
|
||||
block.SeekToStart();
|
||||
@@ -101,7 +94,7 @@ public abstract class TcpCustomDataHandlingAdapter<TRequest> : SingleStreamDataH
|
||||
// 如果临时字节块不为空,则继续缓存。
|
||||
if (!this.m_tempByteBlock.IsEmpty)
|
||||
{
|
||||
byteBlock.Position += this.m_tempByteBlock.Length;
|
||||
reader.Position += this.m_tempByteBlock.Length;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -110,7 +103,7 @@ public abstract class TcpCustomDataHandlingAdapter<TRequest> : SingleStreamDataH
|
||||
// 如果字节块中还有剩余数据,则回退指针。
|
||||
if (block.CanReadLength > 0)
|
||||
{
|
||||
byteBlock.Position -= block.CanReadLength;
|
||||
reader.Position -= block.CanReadLength;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -119,7 +112,7 @@ public abstract class TcpCustomDataHandlingAdapter<TRequest> : SingleStreamDataH
|
||||
// 对于需要继续解析的情况,也回退指针。
|
||||
if (block.CanReadLength > 0)
|
||||
{
|
||||
byteBlock.Position -= block.CanReadLength;
|
||||
reader.Position -= block.CanReadLength;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -138,13 +131,23 @@ public abstract class TcpCustomDataHandlingAdapter<TRequest> : SingleStreamDataH
|
||||
/// <para>当数据部分异常时,请移动<see cref="ByteBlock.Position"/>到指定位置,然后返回<see cref="FilterResult.GoOn"/></para>
|
||||
/// <para>当完全满足解析条件时,请返回<see cref="FilterResult.Success"/>最后将<see cref="ByteBlock.Position"/>移至指定位置。</para>
|
||||
/// </summary>
|
||||
/// <param name="byteBlock">字节块</param>
|
||||
/// <param name="reader">字节块</param>
|
||||
/// <param name="beCached">是否为上次遗留对象,当该参数为<see langword="true"/>时,request也将是上次实例化的对象。</param>
|
||||
/// <param name="request">对象。</param>
|
||||
/// <param name="tempCapacity">缓存容量。当需要首次缓存时,指示申请的ByteBlock的容量。合理的值可避免ByteBlock扩容带来的性能消耗。</param>
|
||||
/// <returns></returns>
|
||||
protected abstract FilterResult Filter<TByteBlock>(ref TByteBlock byteBlock, bool beCached, ref TRequest request, ref int tempCapacity)
|
||||
where TByteBlock : IByteBlock;
|
||||
protected abstract FilterResult Filter<TByteBlock>(ref TByteBlock reader, bool beCached, ref TRequest request, ref int tempCapacity)
|
||||
where TByteBlock : IByteBlockReader;
|
||||
|
||||
/// <summary>
|
||||
/// 判断请求对象是否应该被缓存。
|
||||
/// </summary>
|
||||
/// <param name="request">请求对象。</param>
|
||||
/// <returns>返回布尔值,指示请求对象是否应该被缓存。</returns>
|
||||
protected virtual bool IsBeCached(in TRequest request)
|
||||
{
|
||||
return this.m_requestType.IsValueType ? request.GetHashCode() != default(TRequest).GetHashCode() : request != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 成功执行接收以后。
|
||||
@@ -155,7 +158,7 @@ public abstract class TcpCustomDataHandlingAdapter<TRequest> : SingleStreamDataH
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 即将执行<see cref="SingleStreamDataHandlingAdapter.GoReceivedAsync(ByteBlock, IRequestInfo)"/>。
|
||||
/// 即将执行<see cref="SingleStreamDataHandlingAdapter.GoReceivedAsync(IByteBlockReader, IRequestInfo)"/>。
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns>返回值标识是否继续执行</returns>
|
||||
@@ -166,7 +169,7 @@ public abstract class TcpCustomDataHandlingAdapter<TRequest> : SingleStreamDataH
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <param name="byteBlock"></param>
|
||||
protected override async Task PreviewReceivedAsync(ByteBlock byteBlock)
|
||||
protected override async Task PreviewReceivedAsync(IByteBlockReader byteBlock)
|
||||
{
|
||||
if (this.CacheTimeoutEnable && DateTimeOffset.UtcNow - this.LastCacheTime > this.CacheTimeout)
|
||||
{
|
||||
@@ -174,14 +177,16 @@ public abstract class TcpCustomDataHandlingAdapter<TRequest> : SingleStreamDataH
|
||||
}
|
||||
if (this.m_tempByteBlock.IsEmpty)
|
||||
{
|
||||
await this.SingleAsync(byteBlock, false).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
|
||||
await this.SingleAsync(byteBlock).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.m_tempByteBlock.Write(byteBlock.Span);
|
||||
var block = this.m_tempByteBlock;
|
||||
this.m_tempByteBlock = ValueByteBlock.Empty;
|
||||
await this.SingleAsync(block, true).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
|
||||
using (var block = this.m_tempByteBlock)
|
||||
{
|
||||
this.m_tempByteBlock = default;
|
||||
await this.SingleAsync(block).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,37 +194,25 @@ public abstract class TcpCustomDataHandlingAdapter<TRequest> : SingleStreamDataH
|
||||
protected override void Reset()
|
||||
{
|
||||
this.m_tempByteBlock.SafeDispose();
|
||||
this.m_tempByteBlock = ValueByteBlock.Empty;
|
||||
this.m_tempByteBlock = default;
|
||||
this.m_tempRequest = default;
|
||||
this.SurLength = 0;
|
||||
base.Reset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断请求对象是否应该被缓存。
|
||||
/// </summary>
|
||||
/// <param name="request">请求对象。</param>
|
||||
/// <returns>返回布尔值,指示请求对象是否应该被缓存。</returns>
|
||||
protected virtual bool IsBeCached(in TRequest request)
|
||||
{
|
||||
// 如果请求对象类型是值类型,则判断其哈希码是否与默认值不同;
|
||||
// 如果是引用类型,则判断对象本身是否为null。
|
||||
return this.m_requestType.IsValueType ? request.GetHashCode() != default(TRequest).GetHashCode() : request != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理单个字节块,提取请求对象。
|
||||
/// </summary>
|
||||
/// <typeparam name="TByteBlock">字节块类型,需要实现IByteBlock接口。</typeparam>
|
||||
/// <param name="byteBlock">字节块,将被解析以提取请求对象。</param>
|
||||
/// <param name="reader">字节块,将被解析以提取请求对象。</param>
|
||||
/// <param name="request">输出参数,提取出的请求对象。</param>
|
||||
/// <returns>返回过滤结果,指示处理的状态。</returns>
|
||||
protected FilterResult Single<TByteBlock>(ref TByteBlock byteBlock, out TRequest request) where TByteBlock : IByteBlock
|
||||
protected FilterResult Single<TByteBlock>(ref TByteBlock reader, out TRequest request) where TByteBlock : IByteBlockReader
|
||||
{
|
||||
// 初始化临时缓存容量。
|
||||
var tempCapacity = 1024 * 64;
|
||||
// 执行过滤操作,根据是否应该缓存来决定如何处理字节块和请求对象。
|
||||
var filterResult = this.Filter(ref byteBlock, this.IsBeCached(this.m_tempRequest), ref this.m_tempRequest, ref tempCapacity);
|
||||
var filterResult = this.Filter(ref reader, this.IsBeCached(this.m_tempRequest), ref this.m_tempRequest, ref tempCapacity);
|
||||
switch (filterResult)
|
||||
{
|
||||
case FilterResult.Success:
|
||||
@@ -230,10 +223,10 @@ public abstract class TcpCustomDataHandlingAdapter<TRequest> : SingleStreamDataH
|
||||
|
||||
case FilterResult.Cache:
|
||||
// 如果过滤结果需要缓存,则创建一个新的字节块来缓存数据。
|
||||
if (byteBlock.CanReadLength > 0)
|
||||
if (reader.CanReadLength > 0)
|
||||
{
|
||||
this.m_tempByteBlock = new ValueByteBlock(tempCapacity);
|
||||
this.m_tempByteBlock.Write(byteBlock.Span.Slice(byteBlock.Position, byteBlock.CanReadLength));
|
||||
this.m_tempByteBlock.Write(reader.Span.Slice(reader.Position, reader.CanReadLength));
|
||||
|
||||
// 如果缓存的数据长度超过设定的最大包大小,则抛出异常。
|
||||
if (this.m_tempByteBlock.Length > this.MaxPackageSize)
|
||||
@@ -242,7 +235,7 @@ public abstract class TcpCustomDataHandlingAdapter<TRequest> : SingleStreamDataH
|
||||
}
|
||||
|
||||
// 将字节块指针移到末尾。
|
||||
byteBlock.SeekToEnd();
|
||||
reader.Advance((int)reader.BytesRemaining);
|
||||
}
|
||||
// 更新缓存时间。
|
||||
if (this.UpdateCacheTimeWhenRev)
|
||||
@@ -264,17 +257,17 @@ public abstract class TcpCustomDataHandlingAdapter<TRequest> : SingleStreamDataH
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SingleAsync<TByteBlock>(TByteBlock byteBlock, bool temp) where TByteBlock : IByteBlock
|
||||
private async Task SingleAsync<TByteBlock>(TByteBlock reader) where TByteBlock : IByteBlockReader
|
||||
{
|
||||
byteBlock.Position = 0;
|
||||
while (byteBlock.Position < byteBlock.Length)
|
||||
reader.Position = 0;
|
||||
while (reader.Position < reader.Length)
|
||||
{
|
||||
if (this.DisposedValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var tempCapacity = 1024 * 64;
|
||||
var filterResult = this.Filter(ref byteBlock, this.IsBeCached(this.m_tempRequest), ref this.m_tempRequest, ref tempCapacity);
|
||||
var filterResult = this.Filter(ref reader, this.IsBeCached(this.m_tempRequest), ref this.m_tempRequest, ref tempCapacity);
|
||||
|
||||
switch (filterResult)
|
||||
{
|
||||
@@ -288,26 +281,12 @@ public abstract class TcpCustomDataHandlingAdapter<TRequest> : SingleStreamDataH
|
||||
break;
|
||||
|
||||
case FilterResult.Cache:
|
||||
//if (byteBlock.CanReadLength > 0)
|
||||
{
|
||||
if (temp)
|
||||
{
|
||||
this.m_tempByteBlock = new ValueByteBlock(tempCapacity);
|
||||
this.m_tempByteBlock.Write(byteBlock.Span);
|
||||
//this.m_tempByteBlock.Write(byteBlock.Span.Slice(byteBlock.Position, byteBlock.CanReadLength));
|
||||
byteBlock.Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.m_tempByteBlock = new ValueByteBlock(tempCapacity);
|
||||
this.m_tempByteBlock.Write(byteBlock.Span);
|
||||
//this.m_tempByteBlock.Write(byteBlock.Span.Slice(byteBlock.Position, byteBlock.CanReadLength));
|
||||
}
|
||||
this.m_tempByteBlock = new ValueByteBlock(tempCapacity);
|
||||
this.m_tempByteBlock.Write(reader.Span);
|
||||
|
||||
if (this.m_tempByteBlock.Length > this.MaxPackageSize)
|
||||
{
|
||||
this.OnError(default, $"The parsed signal was not received when the cached data length {m_tempByteBlock.Length} exceeds the set value {MaxPackageSize}", true, true);
|
||||
}
|
||||
if (this.m_tempByteBlock.Length > this.MaxPackageSize)
|
||||
{
|
||||
this.OnError(default, $"The parsed signal was not received when the cached data length {m_tempByteBlock.Length} exceeds the set value {MaxPackageSize}", true, true);
|
||||
}
|
||||
if (this.UpdateCacheTimeWhenRev)
|
||||
{
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
@@ -17,12 +17,14 @@ using ThingsGateway.Foundation.Extension.String;
|
||||
using ThingsGateway.NewLife;
|
||||
using ThingsGateway.NewLife.Extension;
|
||||
|
||||
using TouchSocket.SerialPorts;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 协议基类
|
||||
/// </summary>
|
||||
public abstract class DeviceBase : DisposableObject, IDevice
|
||||
public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public IChannel Channel { get; private set; }
|
||||
@@ -70,13 +72,17 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
}
|
||||
else
|
||||
{
|
||||
channel.Config.SetSerialDataHandlingAdapter(() =>
|
||||
{
|
||||
var adapter = GetDataAdapter() as SingleStreamDataHandlingAdapter;
|
||||
return adapter;
|
||||
});
|
||||
channel.Config.SetTcpDataHandlingAdapter(() =>
|
||||
{
|
||||
var adapter = GetDataAdapter() as SingleStreamDataHandlingAdapter;
|
||||
return adapter;
|
||||
});
|
||||
}
|
||||
clientChannel.SetDataHandlingAdapter(GetDataAdapter());
|
||||
}
|
||||
else if (Channel is ITcpServiceChannel serviceChannel)
|
||||
{
|
||||
@@ -345,11 +351,11 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
|
||||
if (channel is IDtuUdpSessionChannel udpSession)
|
||||
{
|
||||
await udpSession.SendAsync(endPoint, sendMessage).ConfigureAwait(false);
|
||||
await udpSession.SendAsync(endPoint, sendMessage, token).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await channel.SendAsync(sendMessage).ConfigureAwait(false);
|
||||
await channel.SendAsync(sendMessage, token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return OperResult.Success;
|
||||
@@ -388,6 +394,8 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
await connectWaitLock.WaitAsync(token).ConfigureAwait(false);
|
||||
if (AutoConnect && Channel != null && Channel?.Online != true)
|
||||
{
|
||||
if (Channel.PluginManager == null)
|
||||
await Channel.SetupAsync(Channel.Config.Clone()).ConfigureAwait(false);
|
||||
await Channel.CloseAsync().ConfigureAwait(false);
|
||||
await Task.Delay(500, token).ConfigureAwait(false);
|
||||
await Channel.ConnectAsync(Channel.ChannelOptions.ConnectTimeout, token).ConfigureAwait(false);
|
||||
@@ -438,7 +446,13 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
public virtual OperResult<IClientChannel> GetChannel(string socketId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(socketId))
|
||||
return new OperResult<IClientChannel>() { Content = (IClientChannel)Channel };
|
||||
{
|
||||
if (Channel is IClientChannel clientChannel)
|
||||
return new OperResult<IClientChannel>() { Content = clientChannel };
|
||||
else
|
||||
return new OperResult<IClientChannel>("The communication link cannot be obtained, DtuId must be set!");
|
||||
}
|
||||
|
||||
|
||||
if (Channel is ITcpServiceChannel serviceChannel)
|
||||
{
|
||||
@@ -456,7 +470,12 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
}
|
||||
}
|
||||
else
|
||||
return new OperResult<IClientChannel>() { Content = (IClientChannel)Channel };
|
||||
{
|
||||
if (Channel is IClientChannel clientChannel)
|
||||
return new OperResult<IClientChannel>() { Content = clientChannel };
|
||||
else
|
||||
return new OperResult<IClientChannel>("The communication link cannot be obtained!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -487,28 +506,22 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ValueTask<OperResult<byte[]>> SendThenReturnAsync(ISendMessage sendMessage, CancellationToken cancellationToken = default)
|
||||
public virtual ValueTask<OperResult<ReadOnlyMemory<byte>>> SendThenReturnAsync(ISendMessage sendMessage, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var channelResult = GetChannel(this is IDtu dtu ? dtu.DtuId : null);
|
||||
if (!channelResult.IsSuccess) return EasyValueTask.FromResult(new OperResult<byte[]>(channelResult));
|
||||
if (!channelResult.IsSuccess) return EasyValueTask.FromResult(new OperResult<ReadOnlyMemory<byte>>(channelResult));
|
||||
return SendThenReturnAsync(sendMessage, channelResult.Content, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected virtual async ValueTask<MessageBase> SendThenReturnMessageAsync(ISendMessage sendMessage, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var channelResult = GetChannel(this is IDtu dtu ? dtu.DtuId : null);
|
||||
if (!channelResult.IsSuccess) return new MessageBase(channelResult);
|
||||
return await SendThenReturnMessageBaseAsync(sendMessage, channelResult.Content, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async ValueTask<OperResult<byte[]>> SendThenReturnAsync(ISendMessage sendMessage, IClientChannel channel, CancellationToken cancellationToken = default)
|
||||
public virtual async ValueTask<OperResult<ReadOnlyMemory<byte>>> SendThenReturnAsync(ISendMessage sendMessage, IClientChannel channel, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await SendThenReturnMessageBaseAsync(sendMessage, channel, cancellationToken).ConfigureAwait(false);
|
||||
return new OperResult<byte[]>(result) { Content = result.Content };
|
||||
var result = await SendThenReturnMessageAsync(sendMessage, channel, cancellationToken).ConfigureAwait(false);
|
||||
return new OperResult<ReadOnlyMemory<byte>>(result) { Content = result.Content };
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -517,7 +530,15 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected virtual ValueTask<MessageBase> SendThenReturnMessageBaseAsync(ISendMessage command, IClientChannel clientChannel = default, CancellationToken cancellationToken = default)
|
||||
protected virtual async ValueTask<MessageBase> SendThenReturnMessageAsync(ISendMessage sendMessage, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var channelResult = GetChannel(this is IDtu dtu ? dtu.DtuId : null);
|
||||
if (!channelResult.IsSuccess) return new MessageBase(channelResult);
|
||||
return await SendThenReturnMessageAsync(sendMessage, channelResult.Content, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected virtual ValueTask<MessageBase> SendThenReturnMessageAsync(ISendMessage command, IClientChannel clientChannel, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return GetResponsedDataAsync(command, clientChannel, Timeout, cancellationToken);
|
||||
}
|
||||
@@ -532,11 +553,11 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
WaitLock? waitLock = null;
|
||||
try
|
||||
{
|
||||
await BefortSendAsync(clientChannel, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var dtuId = this is IDtu dtu1 ? dtu1.DtuId : null;
|
||||
waitLock = GetWaitLock(clientChannel, dtuId);
|
||||
|
||||
await BefortSendAsync(clientChannel, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await waitLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
EndPoint? endPoint = GetUdpEndpoint(dtuId);
|
||||
@@ -554,12 +575,18 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
|
||||
var sendOperResult = await SendAsync(command, clientChannel, endPoint, cancellationToken).ConfigureAwait(false);
|
||||
if (!sendOperResult.IsSuccess)
|
||||
throw sendOperResult.Exception ?? new(sendOperResult.ErrorMessage ?? "unknown error");
|
||||
return new MessageBase(sendOperResult);
|
||||
|
||||
waitData.SetCancellationToken(cancellationToken);
|
||||
|
||||
await waitData.WaitAsync(timeout).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
waitData.SetCancellationToken(Channel.ClosedToken);
|
||||
|
||||
await waitData.WaitAsync(timeout).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new MessageBase(ex);
|
||||
}
|
||||
var result = waitData.Check();
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
@@ -567,25 +594,11 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
if (!this.DisposedValue)
|
||||
{
|
||||
await Task.Delay(timeout, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
return new MessageBase(result);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
if (!this.DisposedValue)
|
||||
{
|
||||
await Task.Delay(timeout, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
return new MessageBase(ex);
|
||||
}
|
||||
finally
|
||||
@@ -612,13 +625,13 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
#region 动态类型读写
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async ValueTask<IOperResult<Array>> ReadAsync(string address, int length, DataTypeEnum dataType, CancellationToken cancellationToken = default)
|
||||
public virtual async ValueTask<IOperResult<Array>> ReadArrayAsync(string address, int length, DataTypeEnum dataType, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return dataType switch
|
||||
{
|
||||
DataTypeEnum.String => await ReadStringAsync(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Boolean => await ReadBooleanAsync(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Byte => await ReadAsync(address, length, cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Byte => (await ReadAsync(address, length, cancellationToken).ConfigureAwait(false)).OperResultFrom(a => a.ToArray()),
|
||||
DataTypeEnum.Int16 => await ReadInt16Async(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.UInt16 => await ReadUInt16Async(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Int32 => await ReadInt32Async(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
@@ -627,12 +640,13 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
DataTypeEnum.UInt64 => await ReadUInt64Async(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Single => await ReadSingleAsync(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Double => await ReadDoubleAsync(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Decimal => await ReadDecimalAsync(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
_ => new OperResult<Array>(string.Format(AppResource.DataTypeNotSupported, dataType)),
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async ValueTask<OperResult> WriteAsync(string address, JToken value, DataTypeEnum dataType, CancellationToken cancellationToken = default)
|
||||
public virtual async ValueTask<OperResult> WriteJTokenAsync(string address, JToken value, DataTypeEnum dataType, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -641,17 +655,18 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
{
|
||||
return dataType switch
|
||||
{
|
||||
DataTypeEnum.String => await WriteAsync(address, jArray.ToObject<String[]>(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Boolean => await WriteAsync(address, jArray.ToObject<Boolean[]>(), cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Byte => await WriteAsync(address, jArray.ToObject<Byte[]>(), dataType, cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Int16 => await WriteAsync(address, jArray.ToObject<Int16[]>(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.UInt16 => await WriteAsync(address, jArray.ToObject<UInt16[]>(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Int32 => await WriteAsync(address, jArray.ToObject<Int32[]>(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.UInt32 => await WriteAsync(address, jArray.ToObject<UInt32[]>(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Int64 => await WriteAsync(address, jArray.ToObject<Int64[]>(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.UInt64 => await WriteAsync(address, jArray.ToObject<UInt64[]>(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Single => await WriteAsync(address, jArray.ToObject<Single[]>(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Double => await WriteAsync(address, jArray.ToObject<Double[]>(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.String => await WriteAsync(address, jArray.ToObject<String[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Boolean => await WriteAsync(address, jArray.ToObject<Boolean[]>().AsMemory(), cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Byte => await WriteAsync(address, jArray.ToObject<Byte[]>().AsMemory(), dataType, cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Int16 => await WriteAsync(address, jArray.ToObject<Int16[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.UInt16 => await WriteAsync(address, jArray.ToObject<UInt16[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Int32 => await WriteAsync(address, jArray.ToObject<Int32[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.UInt32 => await WriteAsync(address, jArray.ToObject<UInt32[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Int64 => await WriteAsync(address, jArray.ToObject<Int64[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.UInt64 => await WriteAsync(address, jArray.ToObject<UInt64[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Single => await WriteAsync(address, jArray.ToObject<Single[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Double => await WriteAsync(address, jArray.ToObject<Double[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Decimal => await WriteAsync(address, jArray.ToObject<Decimal[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
_ => new OperResult(string.Format(AppResource.DataTypeNotSupported, dataType)),
|
||||
};
|
||||
}
|
||||
@@ -670,6 +685,7 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
DataTypeEnum.UInt64 => await WriteAsync(address, value.ToObject<UInt64>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Single => await WriteAsync(address, value.ToObject<Single>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Double => await WriteAsync(address, value.ToObject<Double>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Decimal => await WriteAsync(address, value.ToObject<Decimal>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
||||
_ => new OperResult(string.Format(AppResource.DataTypeNotSupported, dataType)),
|
||||
};
|
||||
}
|
||||
@@ -685,16 +701,16 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
#region 读取
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract ValueTask<OperResult<byte[]>> ReadAsync(string address, int length, CancellationToken cancellationToken = default);
|
||||
public abstract ValueTask<OperResult<ReadOnlyMemory<byte>>> ReadAsync(string address, int length, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async ValueTask<OperResult<Boolean[]>> ReadBooleanAsync(string address, int length, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
public virtual async ValueTask<OperResult<bool[]>> ReadBooleanAsync(string address, int length, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(address);
|
||||
|
||||
var result = await ReadAsync(address, GetLength(address, length, RegisterByteLength, true), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return result.OperResultFrom(() => bitConverter.ToBoolean(result.Content, GetBitOffsetDefault(address), length, BitReverse(address)));
|
||||
return result.OperResultFrom(() => bitConverter.ToBoolean(result.Content.Span, GetBitOffsetDefault(address), length, BitReverse(address)));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -702,7 +718,7 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(address);
|
||||
var result = await ReadAsync(address, GetLength(address, length, 2), cancellationToken).ConfigureAwait(false);
|
||||
return result.OperResultFrom(() => bitConverter.ToInt16(result.Content, 0, length));
|
||||
return result.OperResultFrom(() => bitConverter.ToInt16(result.Content.Span, 0, length));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -710,7 +726,7 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(address);
|
||||
var result = await ReadAsync(address, GetLength(address, length, 2), cancellationToken).ConfigureAwait(false);
|
||||
return result.OperResultFrom(() => bitConverter.ToUInt16(result.Content, 0, length));
|
||||
return result.OperResultFrom(() => bitConverter.ToUInt16(result.Content.Span, 0, length));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -718,7 +734,7 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(address);
|
||||
var result = await ReadAsync(address, GetLength(address, length, 4), cancellationToken).ConfigureAwait(false);
|
||||
return result.OperResultFrom(() => bitConverter.ToInt32(result.Content, 0, length));
|
||||
return result.OperResultFrom(() => bitConverter.ToInt32(result.Content.Span, 0, length));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -726,7 +742,7 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(address);
|
||||
var result = await ReadAsync(address, GetLength(address, length, 4), cancellationToken).ConfigureAwait(false);
|
||||
return result.OperResultFrom(() => bitConverter.ToUInt32(result.Content, 0, length));
|
||||
return result.OperResultFrom(() => bitConverter.ToUInt32(result.Content.Span, 0, length));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -734,7 +750,7 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(address);
|
||||
var result = await ReadAsync(address, GetLength(address, length, 8), cancellationToken).ConfigureAwait(false);
|
||||
return result.OperResultFrom(() => bitConverter.ToInt64(result.Content, 0, length));
|
||||
return result.OperResultFrom(() => bitConverter.ToInt64(result.Content.Span, 0, length));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -742,7 +758,7 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(address);
|
||||
var result = await ReadAsync(address, GetLength(address, length, 8), cancellationToken).ConfigureAwait(false);
|
||||
return result.OperResultFrom(() => bitConverter.ToUInt64(result.Content, 0, length));
|
||||
return result.OperResultFrom(() => bitConverter.ToUInt64(result.Content.Span, 0, length));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -750,7 +766,7 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(address);
|
||||
var result = await ReadAsync(address, GetLength(address, length, 4), cancellationToken).ConfigureAwait(false);
|
||||
return result.OperResultFrom(() => bitConverter.ToSingle(result.Content, 0, length));
|
||||
return result.OperResultFrom(() => bitConverter.ToSingle(result.Content.Span, 0, length));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -758,9 +774,15 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(address);
|
||||
var result = await ReadAsync(address, GetLength(address, length, 8), cancellationToken).ConfigureAwait(false);
|
||||
return result.OperResultFrom(() => bitConverter.ToDouble(result.Content, 0, length));
|
||||
return result.OperResultFrom(() => bitConverter.ToDouble(result.Content.Span, 0, length));
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public virtual async ValueTask<OperResult<Decimal[]>> ReadDecimalAsync(string address, int length, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(address);
|
||||
var result = await ReadAsync(address, GetLength(address, length, 8), cancellationToken).ConfigureAwait(false);
|
||||
return result.OperResultFrom(() => bitConverter.ToDecimal(result.Content.Span, 0, length));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async ValueTask<OperResult<String[]>> ReadStringAsync(string address, int length, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -771,13 +793,13 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
var result = await ReadAsync(address, GetLength(address, len.Value, 1), cancellationToken).ConfigureAwait(false);
|
||||
return result.OperResultFrom(() =>
|
||||
{
|
||||
List<String> strings = new();
|
||||
String[] strings = new String[length];
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
var data = bitConverter.ToString(result.Content, i * bitConverter.StringLength.Value, bitConverter.StringLength.Value);
|
||||
strings.Add(data);
|
||||
var data = bitConverter.ToString(result.Content.Span, i * bitConverter.StringLength.Value, bitConverter.StringLength.Value);
|
||||
strings[i] = data;
|
||||
}
|
||||
return strings.ToArray();
|
||||
return strings;
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -787,10 +809,10 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
#region 写入
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract ValueTask<OperResult> WriteAsync(string address, byte[] value, DataTypeEnum dataType, CancellationToken cancellationToken = default);
|
||||
public abstract ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<byte> value, DataTypeEnum dataType, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract ValueTask<OperResult> WriteAsync(string address, bool[] value, CancellationToken cancellationToken = default);
|
||||
public abstract ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<bool> value, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, bool value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
@@ -802,7 +824,7 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, byte value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(address);
|
||||
return WriteAsync(address, new byte[] { value }, DataTypeEnum.Byte, cancellationToken);
|
||||
return WriteAsync(address, new byte[] { value }.AsMemory(), DataTypeEnum.Byte, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -860,7 +882,12 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(address);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value), DataTypeEnum.Double, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, decimal value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(address);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value), DataTypeEnum.Decimal, cancellationToken);
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, string value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -874,73 +901,81 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
#region 写入数组
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, short[] value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<short> value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(address);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value), DataTypeEnum.Int16, cancellationToken);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value.Span), DataTypeEnum.Int16, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, ushort[] value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<ushort> value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(address);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value), DataTypeEnum.UInt16, cancellationToken);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value.Span), DataTypeEnum.UInt16, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, int[] value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<int> value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(address);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value), DataTypeEnum.Int32, cancellationToken);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value.Span), DataTypeEnum.Int32, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, uint[] value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<uint> value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(address);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value), DataTypeEnum.UInt32, cancellationToken);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value.Span), DataTypeEnum.UInt32, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, long[] value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<long> value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(address);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value), DataTypeEnum.Int64, cancellationToken);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value.Span), DataTypeEnum.Int64, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, ulong[] value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<ulong> value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(address);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value), DataTypeEnum.UInt64, cancellationToken);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value.Span), DataTypeEnum.UInt64, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, float[] value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<float> value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(address);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value), DataTypeEnum.Single, cancellationToken);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value.Span), DataTypeEnum.Single, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, double[] value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<double> value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(address);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value), DataTypeEnum.Double, cancellationToken);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value.Span), DataTypeEnum.Double, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async ValueTask<OperResult> WriteAsync(string address, string[] value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<decimal> value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(address);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value.Span), DataTypeEnum.Decimal, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<string> value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(address);
|
||||
if (bitConverter.StringLength == null) return new OperResult(AppResource.StringAddressError);
|
||||
List<byte> bytes = new();
|
||||
foreach (var a in value)
|
||||
List<ReadOnlyMemory<byte>> bytes = new();
|
||||
foreach (var a in value.Span)
|
||||
{
|
||||
var data = bitConverter.GetBytes(a);
|
||||
bytes.AddRange(data.ArrayExpandToLength(bitConverter.StringLength ?? data.Length));
|
||||
bytes.Add(data.ArrayExpandToLength(bitConverter.StringLength ?? data.Length));
|
||||
}
|
||||
return await WriteAsync(address, bytes.ToArray(), DataTypeEnum.String, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return await WriteAsync(address, bytes.CombineMemoryBlocks(), DataTypeEnum.String, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
#endregion 写入数组
|
||||
@@ -999,6 +1034,56 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task DisposeAsync(bool disposing)
|
||||
{
|
||||
if (Channel != null)
|
||||
{
|
||||
Channel.Starting.Remove(ChannelStarting);
|
||||
Channel.Stoped.Remove(ChannelStoped);
|
||||
Channel.Started.Remove(ChannelStarted);
|
||||
Channel.Stoping.Remove(ChannelStoping);
|
||||
Channel.ChannelReceived.Remove(ChannelReceived);
|
||||
|
||||
if (Channel.Collects.Count == 1)
|
||||
{
|
||||
if (Channel is ITcpServiceChannel tcpServiceChannel)
|
||||
{
|
||||
tcpServiceChannel.Clients.ForEach(a => a.WaitHandlePool.SafeDispose());
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
//只关闭,不释放
|
||||
await Channel.CloseAsync().ConfigureAwait(false);
|
||||
if (Channel is IClientChannel client)
|
||||
{
|
||||
client.WaitHandlePool.SafeDispose();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger?.LogWarning(ex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Channel is ITcpServiceChannel tcpServiceChannel && this is IDtu dtu)
|
||||
{
|
||||
if (tcpServiceChannel.TryGetClient($"ID={dtu.DtuId}", out var client))
|
||||
{
|
||||
client.WaitHandlePool?.SafeDispose();
|
||||
await client.CloseAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Channel.Collects.Remove(this);
|
||||
}
|
||||
|
||||
_deviceLogger?.TryDispose();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public virtual Action<IPluginManager> ConfigurePlugins(TouchSocketConfig config)
|
||||
{
|
||||
@@ -1015,5 +1100,6 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
}
|
||||
return a => { };
|
||||
}
|
||||
public abstract ValueTask<OperResult<byte[]>> ReadAsync(object state, CancellationToken cancellationToken = default);
|
||||
public abstract ValueTask<OperResult<ReadOnlyMemory<byte>>> ReadAsync(object state, CancellationToken cancellationToken = default);
|
||||
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
@@ -99,7 +99,7 @@ public static partial class DeviceExtension
|
||||
/// <param name="buffer">返回的字节数组</param>
|
||||
/// <param name="exWhenAny">任意一个失败时抛出异常</param>
|
||||
/// <returns>解析结果</returns>
|
||||
public static OperResult PraseStructContent<T>(this IEnumerable<T> variables, IDevice device, byte[] buffer, bool exWhenAny) where T : IVariable
|
||||
public static OperResult PraseStructContent<T>(this IEnumerable<T> variables, IDevice device, ReadOnlySpan<byte> buffer, bool exWhenAny) where T : IVariable
|
||||
{
|
||||
var time = DateTime.Now;
|
||||
var result = OperResult.Success;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
@@ -15,7 +15,7 @@ namespace ThingsGateway.Foundation;
|
||||
/// <summary>
|
||||
/// 协议设备接口
|
||||
/// </summary>
|
||||
public interface IDevice : IDisposable, IDisposableObject
|
||||
public interface IDevice : IDisposable, IDisposableObject, IAsyncDisposable
|
||||
{
|
||||
#region 属性
|
||||
|
||||
@@ -121,7 +121,7 @@ public interface IDevice : IDisposable, IDisposableObject
|
||||
/// <param name="dataType">数据类型</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns></returns>
|
||||
ValueTask<IOperResult<Array>> ReadAsync(string address, int length, DataTypeEnum dataType, CancellationToken cancellationToken = default);
|
||||
ValueTask<IOperResult<Array>> ReadArrayAsync(string address, int length, DataTypeEnum dataType, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 根据数据类型,写入类型值
|
||||
@@ -131,7 +131,7 @@ public interface IDevice : IDisposable, IDisposableObject
|
||||
/// <param name="dataType">数据类型</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns></returns>
|
||||
ValueTask<OperResult> WriteAsync(string address, JToken value, DataTypeEnum dataType, CancellationToken cancellationToken = default);
|
||||
ValueTask<OperResult> WriteJTokenAsync(string address, JToken value, DataTypeEnum dataType, CancellationToken cancellationToken = default);
|
||||
|
||||
#endregion 动态类型读写
|
||||
|
||||
@@ -144,7 +144,7 @@ public interface IDevice : IDisposable, IDisposableObject
|
||||
/// <param name="length">读取寄存器数量,对于不同PLC,对应的字节数量可能不一样</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns></returns>
|
||||
ValueTask<OperResult<byte[]>> ReadAsync(string address, int length, CancellationToken cancellationToken = default);
|
||||
ValueTask<OperResult<ReadOnlyMemory<byte>>> ReadAsync(string address, int length, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 读取布尔量数组
|
||||
@@ -253,12 +253,12 @@ public interface IDevice : IDisposable, IDisposableObject
|
||||
/// <summary>
|
||||
/// 写入原始的byte数组数据到指定的地址,返回结果
|
||||
/// </summary>
|
||||
ValueTask<OperResult> WriteAsync(string address, byte[] value, DataTypeEnum dataType, CancellationToken cancellationToken = default);
|
||||
ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<byte> value, DataTypeEnum dataType, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 写入bool数组数据,返回结果
|
||||
/// </summary>
|
||||
ValueTask<OperResult> WriteAsync(string address, bool[] value, CancellationToken cancellationToken = default);
|
||||
ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<bool> value, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 写入bool数据,返回结果
|
||||
@@ -327,7 +327,7 @@ public interface IDevice : IDisposable, IDisposableObject
|
||||
/// <param name="bitConverter">转换规则</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns>写入结果</returns>
|
||||
ValueTask<OperResult> WriteAsync(string address, string[] value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<string> value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 写入Double数组
|
||||
@@ -337,7 +337,7 @@ public interface IDevice : IDisposable, IDisposableObject
|
||||
/// <param name="bitConverter">转换规则</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns>写入结果</returns>
|
||||
ValueTask<OperResult> WriteAsync(string address, double[] value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<double> value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 写入Single数组
|
||||
@@ -347,7 +347,7 @@ public interface IDevice : IDisposable, IDisposableObject
|
||||
/// <param name="bitConverter">转换规则</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns>写入结果</returns>
|
||||
ValueTask<OperResult> WriteAsync(string address, float[] value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<float> value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 写入Int32数组
|
||||
@@ -357,7 +357,7 @@ public interface IDevice : IDisposable, IDisposableObject
|
||||
/// <param name="bitConverter">转换规则</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns>写入结果</returns>
|
||||
ValueTask<OperResult> WriteAsync(string address, int[] value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<int> value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 写入Int64数组
|
||||
@@ -367,7 +367,7 @@ public interface IDevice : IDisposable, IDisposableObject
|
||||
/// <param name="bitConverter">转换规则</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns>写入结果</returns>
|
||||
ValueTask<OperResult> WriteAsync(string address, long[] value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<long> value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 写入Int16数组
|
||||
@@ -377,7 +377,7 @@ public interface IDevice : IDisposable, IDisposableObject
|
||||
/// <param name="bitConverter">转换规则</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns>写入结果</returns>
|
||||
ValueTask<OperResult> WriteAsync(string address, short[] value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<short> value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 写入UInt32数组
|
||||
@@ -387,7 +387,7 @@ public interface IDevice : IDisposable, IDisposableObject
|
||||
/// <param name="bitConverter">转换规则</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns>写入结果</returns>
|
||||
ValueTask<OperResult> WriteAsync(string address, uint[] value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<uint> value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 写入UInt64数组
|
||||
@@ -397,7 +397,7 @@ public interface IDevice : IDisposable, IDisposableObject
|
||||
/// <param name="bitConverter">转换规则</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns>写入结果</returns>
|
||||
ValueTask<OperResult> WriteAsync(string address, ulong[] value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<ulong> value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 写入UInt16数组
|
||||
@@ -407,7 +407,7 @@ public interface IDevice : IDisposable, IDisposableObject
|
||||
/// <param name="bitConverter">转换规则</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns>写入结果</returns>
|
||||
ValueTask<OperResult> WriteAsync(string address, ushort[] value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<ushort> value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
#endregion 写入数组
|
||||
|
||||
@@ -445,7 +445,7 @@ public interface IDevice : IDisposable, IDisposableObject
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <param name="channel">通道</param>
|
||||
/// <returns>返回消息体</returns>
|
||||
ValueTask<OperResult<byte[]>> SendThenReturnAsync(ISendMessage command, IClientChannel channel = default, CancellationToken cancellationToken = default);
|
||||
ValueTask<OperResult<ReadOnlyMemory<byte>>> SendThenReturnAsync(ISendMessage command, IClientChannel channel = default, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 发送并等待返回,会经过适配器,可传入socketId,如果为空,则默认通道必须为<see cref="IClientChannel"/>类型
|
||||
@@ -453,7 +453,7 @@ public interface IDevice : IDisposable, IDisposableObject
|
||||
/// <param name="sendMessage">发送字节数组</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns>返回消息体</returns>
|
||||
ValueTask<OperResult<byte[]>> SendThenReturnAsync(ISendMessage sendMessage, CancellationToken cancellationToken = default);
|
||||
ValueTask<OperResult<ReadOnlyMemory<byte>>> SendThenReturnAsync(ISendMessage sendMessage, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 支持通道多设备
|
||||
@@ -467,6 +467,6 @@ public interface IDevice : IDisposable, IDisposableObject
|
||||
/// <param name="channel">通道</param>
|
||||
/// <param name="deviceLog">单独设备日志</param>
|
||||
void InitChannel(IChannel channel, ILog? deviceLog = null);
|
||||
ValueTask<OperResult<byte[]>> ReadAsync(object state, CancellationToken cancellationToken = default);
|
||||
ValueTask<OperResult<ReadOnlyMemory<byte>>> ReadAsync(object state, CancellationToken cancellationToken = default);
|
||||
Task ConnectAsync(CancellationToken token);
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// ------------------------------------------------------------------------------
|
||||
// ------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
@@ -50,4 +50,7 @@ public enum DataTypeEnum
|
||||
|
||||
/// <inheritdoc/>
|
||||
Double,
|
||||
|
||||
/// <inheritdoc/>
|
||||
Decimal,
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
@@ -16,23 +16,32 @@ namespace ThingsGateway.Foundation;
|
||||
public static class BoolExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 将bool数组转换到byte数组
|
||||
/// 将布尔数组转换为压缩的字节数组(每 8 位布尔值压缩为 1 个字节,低位在前)。
|
||||
/// </summary>
|
||||
public static byte[] BoolArrayToByte(this bool[] array)
|
||||
/// <param name="array">布尔数组</param>
|
||||
/// <returns>压缩后的只读字节内存</returns>
|
||||
public static byte[] BoolArrayToByte(this ReadOnlySpan<bool> array)
|
||||
{
|
||||
if (array.IsEmpty)
|
||||
return Array.Empty<byte>();
|
||||
|
||||
int byteLength = (array.Length + 7) / 8;
|
||||
byte[] byteArray = new byte[byteLength];
|
||||
byte[] result = new byte[byteLength];
|
||||
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
{
|
||||
if (array[i])
|
||||
{
|
||||
int byteIndex = i / 8;
|
||||
int bitOffset = i % 8;
|
||||
byteArray[byteIndex] |= (byte)(1 << bitOffset);
|
||||
result[i / 8] |= (byte)(1 << (i % 8));
|
||||
}
|
||||
}
|
||||
|
||||
return byteArray;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将布尔数组转换为压缩的字节数组(每 8 位布尔值压缩为 1 个字节,低位在前)。
|
||||
/// </summary>
|
||||
public static byte[] BoolArrayToByte(this Span<bool> array)
|
||||
=> ((ReadOnlySpan<bool>)array).BoolArrayToByte();
|
||||
}
|
||||
|
@@ -0,0 +1,263 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Buffers;
|
||||
using System.Text;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 提供字节块扩展方法的静态类。
|
||||
/// </summary>
|
||||
public static class ByteBlockExtension
|
||||
{
|
||||
public static void WriteBackAddValue<TWriter>(ref TWriter writer, byte value, int pos)
|
||||
where TWriter : IByteBlockWriter
|
||||
{
|
||||
int nowPos = writer.Position;
|
||||
writer.Position = pos;
|
||||
WriterExtension.WriteValue(ref writer, (byte)(writer.Span[pos] + value));
|
||||
writer.Position = nowPos;
|
||||
}
|
||||
|
||||
|
||||
public static void WriteBackValue<TWriter, T>(ref TWriter writer, T value, int pos)
|
||||
where T : unmanaged
|
||||
where TWriter : IByteBlockWriter
|
||||
{
|
||||
int nowPos = writer.Position;
|
||||
writer.Position = pos;
|
||||
WriterExtension.WriteValue(ref writer, value);
|
||||
writer.Position = nowPos;
|
||||
}
|
||||
|
||||
public static void WriteBackValue<TWriter, T>(ref TWriter writer, T value, EndianType endianType, int pos)
|
||||
where T : unmanaged
|
||||
where TWriter : IByteBlockWriter
|
||||
{
|
||||
int nowPos = writer.Position;
|
||||
writer.Position = pos;
|
||||
WriterExtension.WriteValue(ref writer, value, endianType);
|
||||
writer.Position = nowPos;
|
||||
}
|
||||
|
||||
public static void WriteBackNormalString<TWriter>(ref TWriter writer, string value, Encoding encoding, int pos)
|
||||
where TWriter : IByteBlockWriter
|
||||
{
|
||||
|
||||
int nowPos = writer.Position;
|
||||
writer.Position = pos;
|
||||
WriterExtension.WriteNormalString(ref writer, value, encoding);
|
||||
writer.Position = nowPos;
|
||||
}
|
||||
|
||||
|
||||
public static string ReadNormalString<TReader>(ref TReader reader, int length)
|
||||
where TReader : IBytesReader
|
||||
{
|
||||
var span = reader.GetSpan(length).Slice(0, length);
|
||||
var str = span.ToString(Encoding.UTF8);
|
||||
reader.Advance(length);
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 将值类型的字节块转换为普通的字节块。
|
||||
/// </summary>
|
||||
/// <param name="valueByteBlock">要转换的值类型字节块。</param>
|
||||
/// <returns>一个新的字节块对象。</returns>
|
||||
public static ByteBlock AsByteBlock(this ValueByteBlock valueByteBlock)
|
||||
{
|
||||
ByteBlock byteBlock = new ByteBlock(valueByteBlock.TotalMemory.Slice(0, valueByteBlock.Length));
|
||||
byteBlock.Position = valueByteBlock.Position;
|
||||
byteBlock.SetLength(valueByteBlock.Length);
|
||||
return byteBlock;
|
||||
}
|
||||
|
||||
#region ToArray
|
||||
|
||||
/// <summary>
|
||||
/// 将指定的字节块转换为【新】字节数组。
|
||||
/// </summary>
|
||||
/// <typeparam name="TByteBlock">实现<see cref="IByteBlock"/>接口的字节块类型。</typeparam>
|
||||
/// <param name="byteBlock">字节块对象。</param>
|
||||
/// <param name="offset">起始偏移量。</param>
|
||||
/// <param name="length">要转换为数组的长度。</param>
|
||||
/// <returns>包含指定长度的【新】字节数组。</returns>
|
||||
public static byte[] ToArray<TByteBlock>(this TByteBlock byteBlock, int offset, int length) where TByteBlock : IByteBlockCore
|
||||
{
|
||||
return byteBlock.Span.Slice(offset, length).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将指定的字节块转换为【新】字节数组,从指定偏移量开始,直到字节块的末尾。
|
||||
/// </summary>
|
||||
/// <typeparam name="TByteBlock">实现<see cref="IByteBlock"/>接口的字节块类型。</typeparam>
|
||||
/// <param name="byteBlock">字节块对象。</param>
|
||||
/// <param name="offset">起始偏移量。</param>
|
||||
/// <returns>从指定偏移量到字节块末尾的【新】字节数组。</returns>
|
||||
public static byte[] ToArray<TByteBlock>(this TByteBlock byteBlock, int offset) where TByteBlock : IByteBlockCore
|
||||
{
|
||||
return ToArray(byteBlock, offset, byteBlock.Length - offset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将指定的字节块转换为【新】字节数组,从索引0开始,直到字节块的末尾。
|
||||
/// </summary>
|
||||
/// <typeparam name="TByteBlock">实现<see cref="IByteBlock"/>接口的字节块类型。</typeparam>
|
||||
/// <param name="byteBlock">字节块对象。</param>
|
||||
/// <returns>整个字节块的【新】字节数组。</returns>
|
||||
public static byte[] ToArray<TByteBlock>(this TByteBlock byteBlock) where TByteBlock : IByteBlockCore
|
||||
{
|
||||
return ToArray(byteBlock, 0, byteBlock.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将指定的字节块从当前位置<see cref="IByteBlockCore.Position"/>转换为【新】字节数组,直到字节块的末尾。
|
||||
/// </summary>
|
||||
/// <typeparam name="TByteBlock">实现<see cref="IByteBlock"/>接口的字节块类型。</typeparam>
|
||||
/// <param name="byteBlock">字节块对象。</param>
|
||||
/// <returns>从当前位置到字节块末尾的【新】字节数组。</returns>
|
||||
public static byte[] ToArrayTake<TByteBlock>(this TByteBlock byteBlock) where TByteBlock : IByteBlockReader
|
||||
{
|
||||
return ToArray(byteBlock, byteBlock.Position, byteBlock.CanReadLength);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将指定的字节块从当前位置<see cref="IByteBlockCore.Position"/>转换为【新】字节数组,指定长度。
|
||||
/// </summary>
|
||||
/// <typeparam name="TByteBlock">实现<see cref="IByteBlock"/>接口的字节块类型。</typeparam>
|
||||
/// <param name="byteBlock">字节块对象。</param>
|
||||
/// <param name="length">要转换为数组的长度。</param>
|
||||
/// <returns>从当前位置开始,指定长度的【新】字节数组。</returns>
|
||||
public static byte[] ToArrayTake<TByteBlock>(this TByteBlock byteBlock, int length) where TByteBlock : IByteBlockReader
|
||||
{
|
||||
return ToArray(byteBlock, byteBlock.Position, length);
|
||||
}
|
||||
|
||||
#endregion ToArray
|
||||
|
||||
#region AsSegment
|
||||
|
||||
/// <summary>
|
||||
/// 将字节块【作为】数组段。
|
||||
/// <para>
|
||||
/// 【作为】的意思是,导出的数据内存实际上依旧是<see cref="IByteBlockReader"/>生命周期内的,不能脱离生命周期使用。
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <typeparam name="TByteBlock">实现<see cref="IByteBlockReader"/>接口的字节块类型。</typeparam>
|
||||
/// <param name="byteBlock">要转换的字节块实例。</param>
|
||||
/// <param name="offset">数组段的起始偏移量。</param>
|
||||
/// <param name="length">数组段的长度。</param>
|
||||
/// <returns>一个包含指定偏移量和长度的数组段。</returns>
|
||||
public static ArraySegment<byte> AsSegment<TByteBlock>(this TByteBlock byteBlock, int offset, int length) where TByteBlock : IByteBlockReader
|
||||
{
|
||||
return byteBlock.Memory.Slice(offset, length).GetArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字节块【作为】数组段,从指定偏移量开始,长度为可读长度。
|
||||
/// <para>
|
||||
/// 【作为】的意思是,导出的数据内存实际上依旧是<see cref="IByteBlockReader"/>生命周期内的,不能脱离生命周期使用。
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <typeparam name="TByteBlock">实现<see cref="IByteBlockReader"/>接口的字节块类型。</typeparam>
|
||||
/// <param name="byteBlock">要转换的字节块实例。</param>
|
||||
/// <param name="offset">数组段的起始偏移量。</param>
|
||||
/// <returns>一个从指定偏移量开始,长度为可读长度的数组段。</returns>
|
||||
public static ArraySegment<byte> AsSegment<TByteBlock>(this TByteBlock byteBlock, int offset) where TByteBlock : IByteBlockReader
|
||||
{
|
||||
return AsSegment(byteBlock, offset, byteBlock.Length - offset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字节块【作为】数组段,从头开始,长度为指定长度。
|
||||
/// <para>
|
||||
/// 【作为】的意思是,导出的数据内存实际上依旧是<see cref="IByteBlockReader"/>生命周期内的,不能脱离生命周期使用。
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <typeparam name="TByteBlock">实现<see cref="IByteBlockReader"/>接口的字节块类型。</typeparam>
|
||||
/// <param name="byteBlock">要转换的字节块实例。</param>
|
||||
/// <returns>一个从头开始,长度为字节块长度的数组段。</returns>
|
||||
public static ArraySegment<byte> AsSegment<TByteBlock>(this TByteBlock byteBlock) where TByteBlock : IByteBlockReader
|
||||
{
|
||||
return AsSegment(byteBlock, 0, byteBlock.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字节块【作为】数组段,从当前位置开始,指定长度。
|
||||
/// <para>
|
||||
/// 【作为】的意思是,导出的数据内存实际上依旧是<see cref="IByteBlockReader"/>生命周期内的,不能脱离生命周期使用。
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <typeparam name="TByteBlock">实现<see cref="IByteBlockReader"/>接口的字节块类型。</typeparam>
|
||||
/// <param name="byteBlock">要转换的字节块实例。</param>
|
||||
/// <param name="length">数组段的长度。</param>
|
||||
/// <returns>一个从当前位置开始,指定长度的数组段。</returns>
|
||||
public static ArraySegment<byte> AsSegmentTake<TByteBlock>(this TByteBlock byteBlock, int length) where TByteBlock : IByteBlockReader
|
||||
{
|
||||
return AsSegment(byteBlock, byteBlock.Position, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字节块【作为】数组段,从当前位置开始,长度为可读长度。
|
||||
/// <para>
|
||||
/// 【作为】的意思是,导出的数据内存实际上依旧是<see cref="IByteBlockReader"/>生命周期内的,不能脱离生命周期使用。
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <typeparam name="TByteBlock">实现<see cref="IByteBlockReader"/>接口的字节块类型。</typeparam>
|
||||
/// <param name="byteBlock">要转换的字节块实例。</param>
|
||||
/// <returns>一个从当前位置开始,长度为可读长度的数组段。</returns>
|
||||
public static ArraySegment<byte> AsSegmentTake<TByteBlock>(this TByteBlock byteBlock) where TByteBlock : IByteBlockReader
|
||||
{
|
||||
return AsSegment(byteBlock, byteBlock.Position, byteBlock.CanReadLength);
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public static string ToString<TByteBlock>(this TByteBlock byteBlock, int offset, int length) where TByteBlock : IByteBlockReader
|
||||
{
|
||||
return byteBlock.Span.Slice(offset, length).ToString(Encoding.UTF8);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public static string ToString<TByteBlock>(this TByteBlock byteBlock, int offset) where TByteBlock : IByteBlockReader
|
||||
{
|
||||
return byteBlock.Span.Slice(offset, byteBlock.Length - offset).ToString(Encoding.UTF8);
|
||||
|
||||
}
|
||||
#endregion AsSegment
|
||||
|
||||
#region ToString
|
||||
|
||||
|
||||
public static string ToString<TByteBlock>(this TByteBlock byteBlock, long offset, long length) where TByteBlock : IBytesReader
|
||||
{
|
||||
return byteBlock.TotalSequence.Slice(offset, length).ToString(Encoding.UTF8);
|
||||
}
|
||||
public static string ToString(this ReadOnlySequence<byte> byteBlock, Encoding encoding)
|
||||
{
|
||||
# if NET6_0_OR_GREATER
|
||||
return encoding.GetString(byteBlock);
|
||||
#else
|
||||
using ContiguousMemoryBuffer contiguousMemoryBuffer = new(byteBlock);
|
||||
return contiguousMemoryBuffer.Memory.Span.ToString(encoding);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public static string ToString<TByteBlock>(this TByteBlock byteBlock, long offset) where TByteBlock : IBytesReader
|
||||
{
|
||||
return ToString(byteBlock, offset, byteBlock.BytesRead + byteBlock.BytesRemaining - offset);
|
||||
|
||||
}
|
||||
#endregion ToString
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
@@ -10,8 +10,6 @@
|
||||
|
||||
using System.Text;
|
||||
|
||||
using ThingsGateway.Foundation.Extension.Generic;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -21,6 +19,8 @@ public static class ByteExtensions
|
||||
{
|
||||
return DataTransUtil.SpliceArray<T>(array, values);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取byte数据类型的第offset位,是否为True<br />
|
||||
/// </summary>
|
||||
@@ -38,6 +38,24 @@ public static class ByteExtensions
|
||||
return (value & mask) == mask;
|
||||
}
|
||||
|
||||
public static byte[] BoolToByte(this ReadOnlySpan<bool> value, byte trueData = 0xff)
|
||||
{
|
||||
byte[] bytes = new byte[value.Length];
|
||||
for (int i = 0; i < value.Length; i++)
|
||||
{
|
||||
bytes[i] = value[i] ? (byte)trueData : (byte)0;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
public static bool[] ByteToBool(this ReadOnlySpan<byte> value)
|
||||
{
|
||||
bool[] bytes = new bool[value.Length];
|
||||
for (int i = 0; i < value.Length; i++)
|
||||
{
|
||||
bytes[i] = value[i] > 0 ? true : false;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
/// <summary>
|
||||
/// 数组内容分别相加某个数字
|
||||
/// </summary>
|
||||
@@ -46,28 +64,27 @@ public static class ByteExtensions
|
||||
/// <returns></returns>
|
||||
public static byte[] BytesAdd(this byte[] bytes, int value)
|
||||
{
|
||||
if (bytes == null || bytes.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("Input byte array is null or empty");
|
||||
}
|
||||
|
||||
byte[] result = new byte[bytes.Length];
|
||||
for (int index = 0; index < bytes.Length; index++)
|
||||
{
|
||||
result[index] = (byte)(bytes[index] + value);
|
||||
}
|
||||
|
||||
return result;
|
||||
return BytesAdd((ReadOnlySpan<byte>)bytes, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 数组内容分别相加某个数字
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static ReadOnlySpan<byte> BytesAdd(this ReadOnlySpan<byte> bytes, int value)
|
||||
public static byte[] BytesAdd(this Span<byte> bytes, int value)
|
||||
{
|
||||
return BytesAdd((ReadOnlySpan<byte>)bytes, value);
|
||||
}
|
||||
/// <summary>
|
||||
/// 数组内容分别相加某个数字
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static byte[] BytesAdd(this ReadOnlySpan<byte> bytes, int value)
|
||||
{
|
||||
if (bytes.IsEmpty) return Array.Empty<byte>();
|
||||
byte[] result = new byte[bytes.Length];
|
||||
for (int index = 0; index < bytes.Length; index++)
|
||||
{
|
||||
@@ -82,104 +99,143 @@ public static class ByteExtensions
|
||||
/// </summary>
|
||||
/// <param name="inBytes">输入的字节信息</param>
|
||||
/// <returns>反转后的数据</returns>
|
||||
public static byte[] BytesReverseByWord(this byte[] inBytes)
|
||||
/// <summary>
|
||||
/// 将字节数组按“字(2字节)”为单位反转高低位,奇数长度自动补齐 0。
|
||||
/// </summary>
|
||||
public static ReadOnlySpan<byte> BytesReverseByWord(this ReadOnlySpan<byte> inBytes)
|
||||
{
|
||||
if (inBytes.Length == 0)
|
||||
int len = inBytes.Length;
|
||||
if (len == 0)
|
||||
return ReadOnlySpan<byte>.Empty;
|
||||
|
||||
// 如果是奇数,自动补齐 0
|
||||
int evenLen = (len % 2 == 0) ? len : len + 1;
|
||||
if (evenLen == len) return inBytes;
|
||||
|
||||
Span<byte> result = new byte[evenLen];
|
||||
inBytes.CopyTo(result);
|
||||
|
||||
// 逐字(2 字节)交换
|
||||
for (int i = 0; i < evenLen; i += 2)
|
||||
{
|
||||
byte temp = result[i];
|
||||
result[i] = result[i + 1];
|
||||
result[i + 1] = temp;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将byte数组按照双字节进行反转,如果为单数的情况,则自动补齐<br />
|
||||
/// </summary>
|
||||
/// <param name="inBytes">输入的字节信息</param>
|
||||
/// <returns>反转后的数据</returns>
|
||||
/// <summary>
|
||||
/// 将字节数组按“字(2字节)”为单位反转高低位,奇数长度自动补齐 0。
|
||||
/// </summary>
|
||||
public static Memory<byte> BytesReverseByWord(this Memory<byte> inBytes)
|
||||
{
|
||||
int len = inBytes.Length;
|
||||
if (len == 0)
|
||||
return Memory<byte>.Empty;
|
||||
|
||||
// 如果是奇数,自动补齐 0
|
||||
int evenLen = (len % 2 == 0) ? len : len + 1;
|
||||
if (evenLen == len) return inBytes;
|
||||
|
||||
byte[] result = new byte[evenLen];
|
||||
inBytes.CopyTo(result);
|
||||
|
||||
// 逐字(2 字节)交换
|
||||
for (int i = 0; i < evenLen; i += 2)
|
||||
{
|
||||
byte temp = result[i];
|
||||
result[i] = result[i + 1];
|
||||
result[i + 1] = temp;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从字节数组中提取位数组,length 代表位数
|
||||
/// </summary>
|
||||
/// <param name="inBytes">原始的字节数组</param>
|
||||
/// <param name="length">想要转换的位数,如果超出字节数组长度 * 8,则自动缩小为数组最大长度</param>
|
||||
/// <returns>转换后的布尔数组</returns>
|
||||
public static ReadOnlySpan<bool> ByteToBoolArray(this ReadOnlySpan<byte> inBytes, int length)
|
||||
{
|
||||
// 计算字节数组能够提供的最大位数
|
||||
int maxBitLength = inBytes.Length * 8;
|
||||
|
||||
// 如果指定长度超出最大位数,则将长度缩小为最大位数
|
||||
if (length > maxBitLength)
|
||||
{
|
||||
length = maxBitLength;
|
||||
}
|
||||
|
||||
// 创建对应长度的布尔数组
|
||||
bool[] boolArray = new bool[length];
|
||||
|
||||
// 从字节数组中提取位信息并转换为布尔值存储到布尔数组中
|
||||
for (int index = 0; index < length; ++index)
|
||||
{
|
||||
boolArray[index] = inBytes[index / 8].BoolOnByteIndex(index % 8);
|
||||
}
|
||||
|
||||
return boolArray;
|
||||
}
|
||||
|
||||
|
||||
public static ReadOnlyMemory<byte> CombineMemoryBlocks(this List<ReadOnlyMemory<byte>> blocks)
|
||||
{
|
||||
if (blocks == null || blocks.Count == 0)
|
||||
return ReadOnlyMemory<byte>.Empty;
|
||||
|
||||
// 计算总长度
|
||||
int totalLength = 0;
|
||||
foreach (var block in blocks)
|
||||
{
|
||||
totalLength += block.Length;
|
||||
}
|
||||
|
||||
if (totalLength == 0)
|
||||
return ReadOnlyMemory<byte>.Empty;
|
||||
|
||||
// 分配目标数组
|
||||
byte[] result = new byte[totalLength];
|
||||
int offset = 0;
|
||||
|
||||
// 拷贝每一段内存
|
||||
foreach (var block in blocks)
|
||||
{
|
||||
block.Span.CopyTo(result.AsSpan(offset));
|
||||
offset += block.Length;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取异或校验,返回ASCII十六进制字符串的字节数组<br />
|
||||
/// </summary>
|
||||
public static byte[] GetAsciiXOR(this ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (data.IsEmpty)
|
||||
return Array.Empty<byte>();
|
||||
}
|
||||
// 创建新数组进行补齐
|
||||
byte[] lengthEven = inBytes.CopyArray<byte>().ArrayExpandToLengthEven();
|
||||
// 进行双字节反转
|
||||
for (int index = 0; index < lengthEven.Length / 2; ++index)
|
||||
|
||||
byte xor = data[0];
|
||||
for (int i = 1; i < data.Length; i++)
|
||||
{
|
||||
byte num = lengthEven[index * 2];
|
||||
lengthEven[index * 2] = lengthEven[index * 2 + 1];
|
||||
lengthEven[index * 2 + 1] = num;
|
||||
xor ^= data[i];
|
||||
}
|
||||
|
||||
return lengthEven;
|
||||
// 将结果转换为 2 位 ASCII 十六进制字符串,如 "3F" -> [0x33, 0x46]
|
||||
byte[] result = Encoding.ASCII.GetBytes(xor.ToString("X2"));
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从字节数组中提取位数组,length 代表位数
|
||||
/// </summary>
|
||||
/// <param name="inBytes">原始的字节数组</param>
|
||||
/// <param name="length">想要转换的位数,如果超出字节数组长度 * 8,则自动缩小为数组最大长度</param>
|
||||
/// <returns>转换后的布尔数组</returns>
|
||||
public static bool[] ByteToBoolArray(this byte[] inBytes, int length)
|
||||
{
|
||||
// 计算字节数组能够提供的最大位数
|
||||
int maxBitLength = inBytes.Length * 8;
|
||||
|
||||
// 如果指定长度超出最大位数,则将长度缩小为最大位数
|
||||
if (length > maxBitLength)
|
||||
{
|
||||
length = maxBitLength;
|
||||
}
|
||||
|
||||
// 创建对应长度的布尔数组
|
||||
bool[] boolArray = new bool[length];
|
||||
|
||||
// 从字节数组中提取位信息并转换为布尔值存储到布尔数组中
|
||||
for (int index = 0; index < length; ++index)
|
||||
{
|
||||
boolArray[index] = inBytes[index / 8].BoolOnByteIndex(index % 8);
|
||||
}
|
||||
|
||||
return boolArray;
|
||||
}
|
||||
/// <summary>
|
||||
/// 从字节数组中提取位数组,length 代表位数
|
||||
/// </summary>
|
||||
/// <param name="inBytes">原始的字节数组</param>
|
||||
/// <param name="length">想要转换的位数,如果超出字节数组长度 * 8,则自动缩小为数组最大长度</param>
|
||||
/// <returns>转换后的布尔数组</returns>
|
||||
public static bool[] ByteToBoolArray(this Span<byte> inBytes, int length)
|
||||
{
|
||||
// 计算字节数组能够提供的最大位数
|
||||
int maxBitLength = inBytes.Length * 8;
|
||||
|
||||
// 如果指定长度超出最大位数,则将长度缩小为最大位数
|
||||
if (length > maxBitLength)
|
||||
{
|
||||
length = maxBitLength;
|
||||
}
|
||||
|
||||
// 创建对应长度的布尔数组
|
||||
bool[] boolArray = new bool[length];
|
||||
|
||||
// 从字节数组中提取位信息并转换为布尔值存储到布尔数组中
|
||||
for (int index = 0; index < length; ++index)
|
||||
{
|
||||
boolArray[index] = inBytes[index / 8].BoolOnByteIndex(index % 8);
|
||||
}
|
||||
|
||||
return boolArray;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取异或校验
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="left"></param>
|
||||
/// <param name="right"></param>
|
||||
/// <returns></returns>
|
||||
public static byte[] GetAsciiXOR(this byte[] data, int left, int right)
|
||||
{
|
||||
if (data == null || left < 0 || right < 0 || left >= data.Length || right >= data.Length || left > right)
|
||||
{
|
||||
throw new ArgumentException("Invalid input parameters");
|
||||
}
|
||||
|
||||
byte tmp = data[left];
|
||||
for (int i = left + 1; i <= right; i++)
|
||||
{
|
||||
tmp ^= data[i];
|
||||
}
|
||||
|
||||
return Encoding.ASCII.GetBytes(tmp.ToString("X2"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取Byte数组的第 boolIndex 偏移的bool值,这个偏移值可以为 10,就是第 1 个字节的 第3位 <br />
|
||||
@@ -187,7 +243,7 @@ public static class ByteExtensions
|
||||
/// <param name="bytes">字节数组信息</param>
|
||||
/// <param name="boolIndex">指定字节的位偏移</param>
|
||||
/// <returns>bool值</returns>
|
||||
public static bool GetBoolByIndex(this byte[] bytes, int boolIndex)
|
||||
public static bool GetBoolByIndex(this ReadOnlySpan<byte> bytes, int boolIndex)
|
||||
{
|
||||
return bytes[boolIndex / 8].BoolOnByteIndex(boolIndex % 8);
|
||||
}
|
||||
@@ -195,42 +251,34 @@ public static class ByteExtensions
|
||||
/// <summary>
|
||||
/// 字节数组默认转16进制字符
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <param name="splite"></param>
|
||||
/// <returns></returns>
|
||||
public static string ToHexString(this ArraySegment<byte> buffer, char splite = ' ')
|
||||
public static string ToHexString(this ArraySegment<byte> buffer, char splite = default, int newLineCount = 0)
|
||||
{
|
||||
return DataTransUtil.ByteToHexString(buffer, splite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 字节数组默认转16进制字符
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <param name="splite"></param>
|
||||
/// <returns></returns>
|
||||
public static string ToHexString(this ReadOnlySpan<byte> buffer, char splite = ' ')
|
||||
{
|
||||
return DataTransUtil.ByteToHexString(buffer, splite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 字节数组默认转16进制字符
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <param name="splite"></param>
|
||||
/// <returns></returns>
|
||||
public static string ToHexString(this byte[] buffer, char splite = default)
|
||||
{
|
||||
return DataTransUtil.ByteToHexString(buffer, splite);
|
||||
return DataTransUtil.ByteToHexString(buffer, splite, newLineCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 字节数组默认转16进制字符
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string ToHexString(this byte[] buffer, int offset, int length, char splite = ' ', int newLineCount = 0)
|
||||
public static string ToHexString(this byte[] buffer, char splite = default, int newLineCount = 0)
|
||||
{
|
||||
return DataTransUtil.ByteToHexString(buffer, offset, length, splite, newLineCount);
|
||||
return DataTransUtil.ByteToHexString(buffer, splite, newLineCount);
|
||||
}
|
||||
/// <summary>
|
||||
/// 字节数组默认转16进制字符
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string ToHexString(this Span<byte> buffer, char splite = default, int newLineCount = 0)
|
||||
{
|
||||
return DataTransUtil.ByteToHexString(buffer, splite, newLineCount);
|
||||
}
|
||||
/// <summary>
|
||||
/// 字节数组默认转16进制字符
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string ToHexString(this ReadOnlySpan<byte> buffer, char splite = default, int newLineCount = 0)
|
||||
{
|
||||
return DataTransUtil.ByteToHexString(buffer, splite, newLineCount);
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
@@ -34,6 +34,7 @@ public static class DataTypeExtensions
|
||||
DataTypeEnum.UInt64 => 8,
|
||||
DataTypeEnum.Single => 4,
|
||||
DataTypeEnum.Double => 8,
|
||||
DataTypeEnum.Decimal => 16,
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
@@ -58,6 +59,7 @@ public static class DataTypeExtensions
|
||||
TypeCode.UInt64 => DataTypeEnum.UInt64,
|
||||
TypeCode.Single => DataTypeEnum.Single,
|
||||
TypeCode.Double => DataTypeEnum.Double,
|
||||
TypeCode.Decimal => DataTypeEnum.Decimal,
|
||||
_ => DataTypeEnum.Object,
|
||||
};
|
||||
}
|
||||
@@ -82,6 +84,7 @@ public static class DataTypeExtensions
|
||||
DataTypeEnum.UInt64 => typeof(ulong),
|
||||
DataTypeEnum.Single => typeof(float),
|
||||
DataTypeEnum.Double => typeof(double),
|
||||
DataTypeEnum.Decimal => typeof(decimal),
|
||||
_ => typeof(object),
|
||||
};
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// ------------------------------------------------------------------------------
|
||||
// ------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
|
@@ -0,0 +1,73 @@
|
||||
// ------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway;
|
||||
|
||||
|
||||
public static class DisposableExtensions
|
||||
{
|
||||
#region IDisposable
|
||||
|
||||
/// <summary>
|
||||
/// 安全性释放(不用判断对象是否为空)。不会抛出任何异常。
|
||||
/// </summary>
|
||||
/// <param name="dis"></param>
|
||||
/// <returns>释放状态,当对象为<see langword="null"/>,或者已被释放时,均会返回<see cref="Result.Success"/>,只有实际在释放时遇到异常时,才显示其他状态。</returns>
|
||||
public static async Task<Result> SafeDisposeAsync(this IAsyncDisposable dis)
|
||||
{
|
||||
if (dis == default)
|
||||
{
|
||||
return Result.Success;
|
||||
}
|
||||
try
|
||||
{
|
||||
await dis.DisposeAsync().ConfigureAwait(false);
|
||||
return Result.Success;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result.FromException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion IDisposable
|
||||
|
||||
|
||||
#if NET8_0_OR_GREATER
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 安全地取消 <see cref="CancellationTokenSource"/>,并返回操作结果。
|
||||
/// </summary>
|
||||
/// <param name="tokenSource">要取消的 <see cref="CancellationTokenSource"/>。</param>
|
||||
/// <returns>一个 <see cref="Result"/> 对象,表示操作的结果。</returns>
|
||||
public static async Task<Result> SafeCancelAsync(this CancellationTokenSource tokenSource)
|
||||
{
|
||||
if (tokenSource is null)
|
||||
{
|
||||
return Result.Success;
|
||||
}
|
||||
try
|
||||
{
|
||||
await tokenSource.CancelAsync().ConfigureAwait(false);
|
||||
return Result.Success;
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
return Result.Disposed;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result.FromException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// ------------------------------------------------------------------------------
|
||||
// ------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
@@ -32,6 +32,66 @@ public static class GenericExtensions
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将一个数组进行扩充到指定长度,或是缩短到指定长度<br />
|
||||
/// </summary>
|
||||
public static Memory<T> ArrayExpandToLength<T>(this Memory<T> data, int length)
|
||||
{
|
||||
if (data.IsEmpty)
|
||||
{
|
||||
return Memory<T>.Empty;
|
||||
}
|
||||
|
||||
if (data.Length == length)
|
||||
{
|
||||
return data;
|
||||
}
|
||||
|
||||
var result = new T[length];
|
||||
data.Slice(0, Math.Min(data.Length, length)).CopyTo(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将一个数组进行扩充到指定长度,或是缩短到指定长度<br />
|
||||
/// </summary>
|
||||
public static ReadOnlyMemory<T> ArrayExpandToLength<T>(this ReadOnlyMemory<T> data, int length)
|
||||
{
|
||||
if (data.IsEmpty)
|
||||
{
|
||||
return ReadOnlyMemory<T>.Empty;
|
||||
}
|
||||
|
||||
if (data.Length == length)
|
||||
{
|
||||
return data;
|
||||
}
|
||||
|
||||
var result = new T[length];
|
||||
data.Slice(0, Math.Min(data.Length, length)).CopyTo(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将一个数组进行扩充到指定长度,或是缩短到指定长度<br />
|
||||
/// </summary>
|
||||
public static ReadOnlySpan<T> ArrayExpandToLength<T>(this ReadOnlySpan<T> data, int length)
|
||||
{
|
||||
if (data.IsEmpty)
|
||||
{
|
||||
return ReadOnlySpan<T>.Empty;
|
||||
}
|
||||
|
||||
if (data.Length == length)
|
||||
{
|
||||
return data;
|
||||
}
|
||||
|
||||
var result = new T[length];
|
||||
data.Slice(0, Math.Min(data.Length, length)).CopyTo(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将一个数组进行扩充到偶数长度<br />
|
||||
/// </summary>
|
||||
@@ -44,11 +104,41 @@ public static class GenericExtensions
|
||||
|
||||
return data.Length % 2 == 1 ? data.ArrayExpandToLength(data.Length + 1) : data;
|
||||
}
|
||||
/// <summary>
|
||||
/// 将一个数组进行扩充到偶数长度<br />
|
||||
/// </summary>
|
||||
public static ReadOnlyMemory<T> ArrayExpandToLengthEven<T>(this ReadOnlyMemory<T> data)
|
||||
{
|
||||
if (data.IsEmpty)
|
||||
{
|
||||
return Array.Empty<T>();
|
||||
}
|
||||
|
||||
return data.Length % 2 == 1 ? data.ArrayExpandToLength(data.Length + 1) : data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="ArrayRemoveDouble{T}(T[], int, int)"/>
|
||||
/// 将一个数组进行扩充到偶数长度<br />
|
||||
/// </summary>
|
||||
public static T[] ArrayRemoveBegin<T>(T[] value, int length) => ArrayRemoveDouble(value, length, 0);
|
||||
public static Memory<T> ArrayExpandToLengthEven<T>(this Memory<T> data)
|
||||
{
|
||||
if (data.IsEmpty)
|
||||
{
|
||||
return Array.Empty<T>();
|
||||
}
|
||||
|
||||
return data.Length % 2 == 1 ? data.ArrayExpandToLength(data.Length + 1) : data;
|
||||
}
|
||||
|
||||
|
||||
public static T[] ArrayRemoveBegin<T>(this T[] value, int length) => ArrayRemoveDouble(value, length, 0);
|
||||
public static T[] ArrayRemoveLast<T>(this T[] value, int length) => ArrayRemoveDouble(value, 0, length);
|
||||
|
||||
public static ReadOnlySpan<T> ArrayRemoveBegin<T>(ReadOnlySpan<T> value, int length) => ArrayRemoveDouble(value, length, 0);
|
||||
public static ReadOnlySpan<T> ArrayRemoveLast<T>(ReadOnlySpan<T> value, int length) => ArrayRemoveDouble(value, 0, length);
|
||||
|
||||
public static ReadOnlyMemory<T> ArrayRemoveBegin<T>(ReadOnlyMemory<T> value, int length) => ArrayRemoveDouble(value, length, 0);
|
||||
public static ReadOnlyMemory<T> ArrayRemoveLast<T>(ReadOnlyMemory<T> value, int length) => ArrayRemoveDouble(value, 0, length);
|
||||
|
||||
/// <summary>
|
||||
/// 从数组中移除指定数量的元素,并返回新的数组
|
||||
@@ -59,9 +149,21 @@ public static class GenericExtensions
|
||||
/// <param name="rightLength">从右侧移除的元素个数</param>
|
||||
/// <returns>移除元素后的新数组</returns>
|
||||
public static T[] ArrayRemoveDouble<T>(T[] value, int leftLength, int rightLength)
|
||||
{
|
||||
return ArrayRemoveDouble((ReadOnlySpan<T>)value, leftLength, rightLength).ToArray();
|
||||
}
|
||||
/// <summary>
|
||||
/// 从数组中移除指定数量的元素,并返回新的数组
|
||||
/// </summary>
|
||||
/// <typeparam name="T">数组元素类型</typeparam>
|
||||
/// <param name="value">要移除元素的数组</param>
|
||||
/// <param name="leftLength">从左侧移除的元素个数</param>
|
||||
/// <param name="rightLength">从右侧移除的元素个数</param>
|
||||
/// <returns>移除元素后的新数组</returns>
|
||||
public static ReadOnlySpan<T> ArrayRemoveDouble<T>(ReadOnlySpan<T> value, int leftLength, int rightLength)
|
||||
{
|
||||
// 如果输入数组为空或者剩余长度不足以移除左右两侧指定的元素,则返回空数组
|
||||
if (value == null || value.Length <= leftLength + rightLength)
|
||||
if (value.IsEmpty || value.Length <= leftLength + rightLength)
|
||||
{
|
||||
return Array.Empty<T>();
|
||||
}
|
||||
@@ -69,13 +171,28 @@ public static class GenericExtensions
|
||||
// 计算新数组的长度
|
||||
int newLength = value.Length - leftLength - rightLength;
|
||||
|
||||
// 创建新数组
|
||||
T[] result = new T[newLength];
|
||||
return value.Slice(leftLength, newLength);
|
||||
}
|
||||
/// <summary>
|
||||
/// 从数组中移除指定数量的元素,并返回新的数组
|
||||
/// </summary>
|
||||
/// <typeparam name="T">数组元素类型</typeparam>
|
||||
/// <param name="value">要移除元素的数组</param>
|
||||
/// <param name="leftLength">从左侧移除的元素个数</param>
|
||||
/// <param name="rightLength">从右侧移除的元素个数</param>
|
||||
/// <returns>移除元素后的新数组</returns>
|
||||
public static ReadOnlyMemory<T> ArrayRemoveDouble<T>(ReadOnlyMemory<T> value, int leftLength, int rightLength)
|
||||
{
|
||||
// 如果输入数组为空或者剩余长度不足以移除左右两侧指定的元素,则返回空数组
|
||||
if (value.IsEmpty || value.Length <= leftLength + rightLength)
|
||||
{
|
||||
return Array.Empty<T>();
|
||||
}
|
||||
|
||||
// 将剩余的元素复制到新数组中
|
||||
Array.Copy(value, leftLength, result, 0, newLength);
|
||||
// 计算新数组的长度
|
||||
int newLength = value.Length - leftLength - rightLength;
|
||||
|
||||
return result;
|
||||
return value.Slice(leftLength, newLength);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -120,7 +237,13 @@ public static class GenericExtensions
|
||||
yield return chunk;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<ReadOnlyMemory<T>> ChunkBetter<T>(this ReadOnlyMemory<T> span, int groupSize)
|
||||
{
|
||||
for (int i = 0; i < span.Length; i += groupSize)
|
||||
{
|
||||
yield return span.Slice(i, Math.Min(groupSize, span.Length - i));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>拷贝当前的实例数组,是基于引用层的浅拷贝,如果类型为值类型,那就是深度拷贝,如果类型为引用类型,就是浅拷贝</summary>
|
||||
public static T[] CopyArray<T>(this T[] value)
|
||||
@@ -153,37 +276,7 @@ public static class GenericExtensions
|
||||
return arrayFromOneArray;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将一个数组的前后移除指定位数,返回新的一个数组<br />
|
||||
/// </summary>
|
||||
public static T[] RemoveArray<T>(this T[] value, int leftLength, int rightLength)
|
||||
{
|
||||
if (value == null || value.Length == 0)
|
||||
{
|
||||
return Array.Empty<T>();
|
||||
}
|
||||
|
||||
int newLength = value.Length - leftLength - rightLength;
|
||||
if (newLength <= 0)
|
||||
{
|
||||
return Array.Empty<T>();
|
||||
}
|
||||
|
||||
T[] result = new T[newLength];
|
||||
Array.Copy(value, leftLength, result, 0, newLength);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将一个数组的前面指定位数移除,返回新的一个数组<br />
|
||||
/// </summary>
|
||||
public static T[] RemoveBegin<T>(this T[] value, int length) => value.RemoveArray(length, 0);
|
||||
|
||||
/// <summary>
|
||||
/// 将一个数组的后面指定位数移除,返回新的一个数组<br />
|
||||
/// </summary>
|
||||
public static T[] RemoveLast<T>(this T[] value, int length) => value.RemoveArray(0, length);
|
||||
|
||||
/// <summary>
|
||||
/// 选择数组中的最后几个元素组成新的数组
|
||||
@@ -192,25 +285,7 @@ public static class GenericExtensions
|
||||
/// <param name="value">输入数组</param>
|
||||
/// <param name="length">选择的元素个数</param>
|
||||
/// <returns>由最后几个元素组成的新数组</returns>
|
||||
public static T[] SelectLast<T>(this T[] value, int length)
|
||||
{
|
||||
// 如果输入数组为空,则返回空数组
|
||||
if (value == null || value.Length == 0)
|
||||
{
|
||||
return Array.Empty<T>();
|
||||
}
|
||||
|
||||
// 计算实际需要复制的元素个数,取输入数组长度和指定长度的较小值
|
||||
int count = Math.Min(value.Length, length);
|
||||
|
||||
// 创建新数组来存储选择的元素
|
||||
T[] result = new T[count];
|
||||
|
||||
// 复制最后几个元素到新数组中
|
||||
Array.Copy(value, value.Length - count, result, 0, count);
|
||||
|
||||
return result;
|
||||
}
|
||||
public static T[] SelectLast<T>(this T[] value, int length) => ArrayRemoveBegin(value, value.Length - length);
|
||||
|
||||
/// <summary>
|
||||
/// 从数组中获取指定索引开始的中间一段长度的子数组
|
||||
@@ -220,23 +295,5 @@ public static class GenericExtensions
|
||||
/// <param name="index">起始索引</param>
|
||||
/// <param name="length">选择的元素个数</param>
|
||||
/// <returns>中间指定长度的子数组</returns>
|
||||
public static T[] SelectMiddle<T>(this T[] value, int index, int length)
|
||||
{
|
||||
// 如果输入数组为空,则返回空数组
|
||||
if (value == null || value.Length == 0)
|
||||
{
|
||||
return Array.Empty<T>();
|
||||
}
|
||||
|
||||
// 计算实际需要复制的元素个数,取输入数组剩余元素和指定长度的较小值
|
||||
int count = Math.Min(value.Length - index, length);
|
||||
|
||||
// 创建新数组来存储选择的元素
|
||||
T[] result = new T[count];
|
||||
|
||||
// 复制中间指定长度的元素到新数组中
|
||||
Array.Copy(value, index, result, 0, count);
|
||||
|
||||
return result;
|
||||
}
|
||||
public static T[] SelectMiddle<T>(this T[] value, int index, int length) => ArrayRemoveDouble(value, index, value.Length - index - length);
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user