Compare commits

...

35 Commits

Author SHA1 Message Date
2248356998 qq.com
6660ce3e34 适配远程管理客户端 2025-08-08 18:01:24 +08:00
2248356998 qq.com
7499162c1a 适配远程管理客户端 2025-08-08 02:51:42 +08:00
2248356998 qq.com
40208a5cd6 适配远程网关管理客户端 2025-08-08 02:16:05 +08:00
2248356998 qq.com
fa347f4f68 修改报警事件时间字段,增加变量表报警类 2025-08-07 19:19:36 +08:00
Diego
d7df6fc605 10.10.12 2025-08-07 11:24:57 +08:00
2248356998 qq.com
eb4bb2fd48 10.10.11 2025-08-07 10:19:28 +08:00
2248356998 qq.com
faa9858974 10.10.11 2025-08-07 10:18:22 +08:00
2248356998 qq.com
1b3d2dda49 QuestDbRestAPI 2025-08-07 10:11:37 +08:00
Diego
a8a9453611 !70 fix: questdb restapi多实例 2025-08-07 01:58:32 +00:00
2248356998 qq.com
e84f42ce14 10.10.10 2025-08-06 21:42:46 +08:00
2248356998 qq.com
6f814cf6b8 更新questdb restapi启用字段 2025-08-06 21:42:23 +08:00
2248356998 qq.com
e36432e4e9 10.10.9 2025-08-06 19:33:30 +08:00
Diego
ebd71e807b !69 更新依赖 2025-08-06 11:25:31 +00:00
Diego
34000d8d7d !68 10.10.7 2025-08-05 09:22:11 +00:00
2248356998 qq.com
e785f6660c 10.10.5 2025-08-01 21:53:47 +08:00
Diego
831c611797 10.10.4 2025-08-01 17:30:37 +08:00
Diego
453817ef86 添加IAsyncDisposable 2025-08-01 16:36:27 +08:00
2248356998 qq.com
8ce0b981c1 no message 2025-08-01 12:55:01 +08:00
2248356998 qq.com
4e5c51b54c 2025-08-01 12:47:21 +08:00
2248356998 qq.com
3cc9d31f28 修改可用内存策略 2025-08-01 12:43:39 +08:00
2248356998 qq.com
10391f869b 支持相对路径创建sqlite 2025-07-31 23:41:58 +08:00
Diego
fba0723a6d 更新依赖 2025-07-31 18:39:46 +08:00
Diego
2db3f78f0c 添加 控制写操作与读操作的比率 的插件配置属性 2025-07-31 18:18:41 +08:00
Diego
badf61fe01 10.9.99 2025-07-31 16:25:47 +08:00
Diego
d74e0952dc 10.9.98 2025-07-31 15:57:33 +08:00
Diego
fb1699ce80 10.9.97 2025-07-31 14:06:47 +08:00
Diego
44adddbcd4 10.9.97 2025-07-31 13:56:49 +08:00
Diego
0eab889452 10.9.97 2025-07-31 13:56:34 +08:00
Diego
e14d39a459 添加 rpc写入 多写日志 2025-07-31 12:52:40 +08:00
2248356998 qq.com
7575264ede 添加变量初始化标记 2025-07-31 00:40:03 +08:00
2248356998 qq.com
3e1a077b96 添加变量初始化标记 2025-07-31 00:30:53 +08:00
2248356998 qq.com
a921cb8400 refactor: 连接时判断setup 2025-07-30 23:27:35 +08:00
2248356998 qq.com
4de7c31ed7 10.9.92 2025-07-30 22:00:23 +08:00
2248356998 qq.com
08326a2cfd fix: 单属性验证异常 2025-07-30 21:11:35 +08:00
Diego
e045de5acb !67 更新依赖 2025-07-30 10:01:20 +00:00
501 changed files with 9532 additions and 8923 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -8,7 +8,7 @@
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Gateway.Application;
namespace ThingsGateway.Admin.Application;
public class USheetDatas
{

View File

@@ -18,6 +18,7 @@ public class SessionOutput : PrimaryIdEntity
/// <summary>
/// 主键Id
/// </summary>
[System.ComponentModel.DataAnnotations.Key]
public override long Id { get; set; }
/// <summary>

View File

@@ -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;
}
}

View File

@@ -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>

View File

@@ -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
{

View File

@@ -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'">

View File

@@ -39,7 +39,7 @@
<BlazorReconnector @rendermode="new InteractiveServerRenderMode(false)" />
<script src=@($"_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js?v={this.GetType().Assembly.GetName().Version}")></script>
<script src=@($"{WebsiteConst.DefaultResourceUrl}js/culture.js?v={this.GetType().Assembly.GetName().Version}")></script>
<script src=@($"{WebsiteConst.DefaultResourceUrl}js/localStorageUtil.js?v={this.GetType().Assembly.GetName().Version}")></script>
<script src="_framework/blazor.web.js"></script>
<!-- PWA Service Worker -->
<script type="text/javascript">'serviceWorker' in navigator && navigator.serviceWorker.register('./service-worker.js')</script>

View File

@@ -45,7 +45,7 @@
</app>
<script src="_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js"></script>
<script src=@($"{WebsiteConst.DefaultResourceUrl}js/culture.js")></script>
<script src=@($"{WebsiteConst.DefaultResourceUrl}js/localStorageUtil.js")></script>
<script src="_framework/blazor.server.js"></script>
<!-- PWA Service Worker -->

View File

@@ -45,11 +45,11 @@ public class Startup : AppStartup
options.ServicesStopConcurrently = true;
});
//// 事件总线
//services.AddEventBus(options =>
//{
// 事件总线
services.AddEventBus(options =>
{
//});
});
// 任务调度
services.AddSchedule(options => options.AddPersistence<JobPersistence>());

View File

@@ -8,7 +8,7 @@
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Gateway.Application;
namespace ThingsGateway.Common;
public class SmartTriggerScheduler
{

View File

@@ -8,7 +8,7 @@
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Gateway.Application;
namespace ThingsGateway.Common;
public sealed class StringOrdinalIgnoreCaseEqualityComparer : EqualityComparer<string>
{

View File

@@ -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>

View File

@@ -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>

View File

@@ -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; }
}

View File

@@ -123,6 +123,15 @@ public static class QueryPageOptionsExtensions
};
var items = datas.GetData(option, out var totalCount, where);
ret.TotalCount = totalCount;
if (totalCount > 0)
{
if (!items.Any() && option.PageIndex != 1)
{
option.PageIndex = 1;
items = datas.GetData(option, out totalCount, where);
}
}
ret.Items = items.ToList();
return ret;
}

View File

@@ -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));

View File

@@ -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>

View File

@@ -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
{

View File

@@ -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)
{

View File

@@ -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();

View File

@@ -20,6 +20,7 @@ namespace ThingsGateway;
/// </summary>
public sealed class AppSettingsOptions : IConfigurableOptions<AppSettingsOptions>
{
/// <summary>
/// 是否启用规范化文档
/// </summary>

View 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; }
}

View File

@@ -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>

View File

@@ -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)

View File

@@ -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>

View File

@@ -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温度获取BuildrootCPU温度和主板温度

View File

@@ -306,8 +306,19 @@ public class TimerScheduler : ILogFeature
return;
}
var func = timer.Method.As<Func<Object?, Task>>(target);
await func!(timer.State).ConfigureAwait(false);
#if NET6_0_OR_GREATER
if (timer.IsValueTask)
{
var func = timer.Method.As<Func<Object?, ValueTask>>(target);
await func!(timer.State).ConfigureAwait(false);
}
else
#endif
{
var func = timer.Method.As<Func<Object?, Task>>(target);
await func!(timer.State).ConfigureAwait(false);
}
}
catch (ThreadAbortException) { throw; }
catch (ThreadInterruptedException) { throw; }

View File

@@ -87,6 +87,8 @@ public class TimerX : ITimer, ITimerx, IDisposable
private DateTime _AbsolutelyNext;
private readonly Cron[]? _crons;
internal bool IsValueTask { get; }
#endregion
// #region 静态
@@ -158,6 +160,29 @@ public class TimerX : ITimer, ITimerx, IDisposable
Init(dueTime);
}
#if NET6_0_OR_GREATER
/// <summary>实例化一个不可重入的定时器</summary>
/// <param name="callback">委托</param>
/// <param name="state">用户数据</param>
/// <param name="dueTime">多久之后开始。毫秒</param>
/// <param name="period">间隔周期。毫秒</param>
/// <param name="scheduler">调度器</param>
public TimerX(Func<Object, ValueTask> callback, Object? state, Int32 dueTime, Int32 period, String? scheduler = null) : this(callback.Target, callback.Method, state, scheduler)
{
IsValueTask = true;
if (callback == null) throw new ArgumentNullException(nameof(callback));
if (dueTime < 0) throw new ArgumentOutOfRangeException(nameof(dueTime));
IsAsyncTask = true;
Async = true;
Period = period;
Init(dueTime);
}
#endif
/// <summary>实例化一个绝对定时器指定时刻执行跟当前时间和SetNext无关</summary>
/// <param name="callback">委托</param>
/// <param name="state">用户数据</param>
@@ -210,6 +235,37 @@ public class TimerX : ITimer, ITimerx, IDisposable
Init(ms);
}
#if NET6_0_OR_GREATER
/// <summary>实例化一个绝对定时器指定时刻执行跟当前时间和SetNext无关</summary>
/// <param name="callback">委托</param>
/// <param name="state">用户数据</param>
/// <param name="startTime">绝对开始时间</param>
/// <param name="period">间隔周期。毫秒</param>
/// <param name="scheduler">调度器</param>
public TimerX(Func<Object, ValueTask> callback, Object? state, DateTime startTime, Int32 period, String? scheduler = null) : this(callback.Target, callback.Method, state, scheduler)
{
IsValueTask = true;
if (callback == null) throw new ArgumentNullException(nameof(callback));
if (startTime <= DateTime.MinValue) throw new ArgumentOutOfRangeException(nameof(startTime));
if (period <= 0) throw new ArgumentOutOfRangeException(nameof(period));
IsAsyncTask = true;
Async = true;
Period = period;
Absolutely = true;
//var now = DateTime.Now;
var now = Scheduler.GetNow();
var next = startTime;
while (next < now) next = next.AddMilliseconds(period);
var ms = (Int64)(next - now).TotalMilliseconds;
_AbsolutelyNext = next;
Init(ms);
}
#endif
/// <summary>实例化一个Cron定时器</summary>
/// <param name="callback">委托</param>
/// <param name="state">用户数据</param>
@@ -274,6 +330,42 @@ public class TimerX : ITimer, ITimerx, IDisposable
//Init(_AbsolutelyNext = _cron.GetNext(DateTime.Now));
}
#if NET6_0_OR_GREATER
/// <summary>实例化一个Cron定时器</summary>
/// <param name="callback">委托</param>
/// <param name="state">用户数据</param>
/// <param name="cronExpression">Cron表达式。支持多个表达式分号分隔</param>
/// <param name="scheduler">调度器</param>
public TimerX(Func<Object, ValueTask> callback, Object? state, String cronExpression, String? scheduler = null) : this(callback.Target, callback.Method, state, scheduler)
{
IsValueTask = true;
if (callback == null) throw new ArgumentNullException(nameof(callback));
if (cronExpression.IsNullOrEmpty()) throw new ArgumentNullException(nameof(cronExpression));
var list = new List<Cron>();
foreach (var item in cronExpression.Split(";"))
{
var cron = new Cron();
if (!cron.Parse(item)) throw new ArgumentException($"Invalid Cron expression[{item}]", nameof(cronExpression));
list.Add(cron);
}
_crons = list.ToArray();
IsAsyncTask = true;
Async = true;
Absolutely = true;
//var now = DateTime.Now;
var now = Scheduler.GetNow();
var next = _crons.Min(e => e.GetNext(now));
var ms = (Int64)(next - now).TotalMilliseconds;
_AbsolutelyNext = next;
Init(ms);
//Init(_AbsolutelyNext = _cron.GetNext(DateTime.Now));
}
#endif
public bool Disposed { get; private set; }
/// <summary>销毁定时器</summary>
public void Dispose()

View File

@@ -8,8 +8,8 @@
// QQ群605534569
//------------------------------------------------------------------------------
using ThingsGateway.Common.Extension;
using ThingsGateway.NewLife;
using ThingsGateway.Razor.Extension;
namespace ThingsGateway.Razor;

View File

@@ -0,0 +1,6 @@
@namespace ThingsGateway.Razor
@if (show)
{
<Spinner class="ms-auto"></Spinner>
}

View File

@@ -8,7 +8,7 @@
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Gateway.Razor;
namespace ThingsGateway.Razor;
public partial class SpinnerComponent
{

View File

@@ -10,7 +10,7 @@
using Microsoft.JSInterop;
namespace ThingsGateway.Common.Extension;
namespace ThingsGateway.Razor.Extension;
/// <summary>
/// JSRuntime扩展方法
@@ -49,4 +49,28 @@ public static class JSRuntimeExtensions
{
}
}
public static async ValueTask<T> GetLocalStorage<T>(this IJSRuntime jsRuntime, string name)
{
try
{
return await jsRuntime.InvokeAsync<T>("getLocalStorage", name).ConfigureAwait(false);
}
catch
{
return default;
}
}
public static async ValueTask SetLocalStorage<T>(this IJSRuntime jsRuntime, string name, T data)
{
try
{
await jsRuntime.InvokeVoidAsync("setLocalStorage", name, data).ConfigureAwait(false);
}
catch
{
}
}
}

View File

@@ -1,9 +0,0 @@
// 设置 culture
function setCultureLocalStorage(culture) {
localStorage.setItem("culture", culture);
}
// 获取 culture
function getCultureLocalStorage() {
return localStorage.getItem("culture");
}

View File

@@ -0,0 +1,18 @@
// 设置 culture
function setCultureLocalStorage(culture) {
localStorage.setItem("culture", culture);
}
// 获取 culture
function getCultureLocalStorage() {
return localStorage.getItem("culture");
}
function getLocalStorage(name) {
return JSON.parse(localStorage.getItem(name)) ?? 0;
}
function setLocalStorage(name, data) {
if (localStorage) {
localStorage.setItem(name, JSON.stringify(data));
}
}

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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);
}
@@ -51,9 +51,14 @@ namespace ThingsGateway.SqlSugar
// HTTP GET 请求执行SQL
var result = string.Empty;
var url = $"{this.url}/exec?query={HttpUtility.UrlEncode(sql)}";
var request = new HttpRequestMessage(HttpMethod.Get, url);
if (!string.IsNullOrWhiteSpace(authorization))
client.DefaultRequestHeaders.Add("Authorization", authorization);
var httpResponseMessage = await client.GetAsync(url).ConfigureAwait(false);
{
request.Headers.Authorization = AuthenticationHeaderValue.Parse(authorization);
}
using var httpResponseMessage = await client.SendAsync(request).ConfigureAwait(false);
result = await httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
return result;
}
@@ -68,34 +73,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 +120,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 +133,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 +176,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");
@@ -183,8 +189,8 @@ namespace ThingsGateway.SqlSugar
"multipart/form-data; boundary=" + boundary);
// 发送请求并处理响应
var httpResponseMessage =
await Post(client, name, httpContent).ConfigureAwait(false);
using var httpResponseMessage =
await Post(client, tableName, httpContent).ConfigureAwait(false);
var readAsStringAsync = await httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
var splitByLine = QuestDbRestAPHelper.SplitByLine(readAsStringAsync);
@@ -266,11 +272,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 +287,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('/'))

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -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;
});

View File

@@ -8,6 +8,6 @@
V Get<V>(string key);
IEnumerable<string> GetAllKey<V>();
void Remove<V>(string key);
V GetOrCreate<V>(string cacheKey, Func<V> create, int cacheDurationInSeconds = int.MaxValue);
V GetOrCreate<V>(string cacheKey, Func<V> create, int cacheDurationInSeconds = 3600);
}
}

View File

@@ -31,7 +31,7 @@ namespace ThingsGateway.SqlSugar
return ReflectionInoCore<V>.GetInstance().GetAllKey();
}
public V GetOrCreate<V>(string cacheKey, Func<V> create, int cacheDurationInSeconds = int.MaxValue)
public V GetOrCreate<V>(string cacheKey, Func<V> create, int cacheDurationInSeconds = 3600)
{
return ReflectionInoCore<V>.GetInstance().GetOrCreate(cacheKey, create);
}
@@ -43,10 +43,13 @@ namespace ThingsGateway.SqlSugar
}
public class ReflectionInoCore<V>
{
private MemoryCache InstanceCache => MemoryCache.Instance;
private MemoryCache InstanceCache = new MemoryCache() { Expire = 180 };
private static ReflectionInoCore<V> _instance = null;
private static readonly object _instanceLock = new object();
private ReflectionInoCore() { }
private ReflectionInoCore()
{
}
public V this[string key]
{
@@ -86,7 +89,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)
@@ -107,9 +110,10 @@ namespace ThingsGateway.SqlSugar
return this.InstanceCache.Keys;
}
public V GetOrCreate(string cacheKey, Func<V> create)
public V GetOrCreate(string cacheKey, Func<V> create, int expire = 3600)
{
return InstanceCache.GetOrAdd<V>(cacheKey, (a) => create());
return InstanceCache.GetOrAdd<V>(cacheKey, (a) =>
create(), expire);
}
}
public static class ReflectionInoHelper

View File

@@ -447,6 +447,28 @@ namespace ThingsGateway.SqlSugar
}
public override List<DbColumnInfo> GetColumnInfosByTableName(string tableName, bool isCache = true)
{
if (string.IsNullOrEmpty(tableName)) return new List<DbColumnInfo>();
string cacheKey = "QuestDB.GetColumnInfosByTableName." + this.SqlBuilder.GetNoTranslationColumnName(tableName).ToLower() + this.Context.CurrentConnectionConfig.ConfigId;
cacheKey = GetCacheKey(cacheKey);
if (isCache)
{
return this.Context.Utilities.GetReflectionInoCacheInstance().GetOrCreate(cacheKey, () =>
{
return GetColInfo(tableName);
});
}
else
{
return GetColInfo(tableName);
}
}
private List<DbColumnInfo> GetColInfo(string tableName)
{
var sql = String.Format(GetColumnInfosByTableNameSql, tableName);
List<DbColumnInfo> result = new List<DbColumnInfo>();

View File

@@ -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;

View File

@@ -717,8 +717,32 @@ namespace ThingsGateway.SqlSugar
/// <returns>列信息列表</returns>
public override List<DbColumnInfo> GetColumnInfosByTableName(string tableName, bool isCache = true)
{
var sql = $"select * from {this.SqlBuilder.GetTranslationColumnName(tableName)} where 1=2 ";
if (string.IsNullOrEmpty(tableName)) return new List<DbColumnInfo>();
string cacheKey = "TDengine.GetColumnInfosByTableName." + this.SqlBuilder.GetNoTranslationColumnName(tableName).ToLower() + this.Context.CurrentConnectionConfig.ConfigId;
cacheKey = GetCacheKey(cacheKey);
if (isCache)
{
return this.Context.Utilities.GetReflectionInoCacheInstance().GetOrCreate(cacheKey, () =>
{
return GetColInfo(tableName);
});
}
else
{
return GetColInfo(tableName);
}
}
private List<DbColumnInfo> GetColInfo(string tableName)
{
List<DbColumnInfo> result = new List<DbColumnInfo>();
var sql = $"select * from {this.SqlBuilder.GetTranslationColumnName(tableName)} where 1=2 ";
DataTable dt = null;
try
{

View File

@@ -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>

View File

@@ -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.12</PluginVersion>
<ProPluginVersion>10.10.12</ProPluginVersion>
<DefaultVersion>10.10.15</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>

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<TargetFrameworks>net462;netstandard2.0;net6.0;</TargetFrameworks>
<TargetFrameworks>net462;netstandard2.0;net6.0;net8.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>

View File

@@ -7,7 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CS-Script" Version="4.10.0" />
<PackageReference Include="CS-Script" Version="4.10.1" />
</ItemGroup>
<ItemGroup>

View File

@@ -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;

View File

@@ -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"]}");

View File

@@ -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.GetLogFilesAsync(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.LastLogDataAsync(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.GetLogFilesAsync(LogPath);
if (files.IsSuccess)
{
foreach (var item in files.Content)

View File

@@ -26,7 +26,7 @@ public class PlatformService : IPlatformService
public async Task OnLogExport(string logPath)
{
var files = TextFileReader.GetFiles(logPath);
var files = TextFileReader.GetLogFilesAsync(logPath);
if (!files.IsSuccess)
{
return;

View File

@@ -33,5 +33,6 @@ public class Startup : AppStartup
}
services.AddScoped<IPlatformService, PlatformService>();
services.AddSingleton<ITextFileReadService, TextFileReadService>();
}
}

View File

@@ -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)

View File

@@ -1,4 +1,4 @@
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议

View File

@@ -1,4 +1,4 @@
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议

View File

@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议

View File

@@ -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)

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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 _);

View File

@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议

View File

@@ -1,4 +1,4 @@
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议

View File

@@ -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>

View File

@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议

View File

@@ -1,4 +1,4 @@
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议

View File

@@ -1,4 +1,4 @@
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议

View File

@@ -1,4 +1,4 @@
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议

View File

@@ -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()
{

View File

@@ -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");

View File

@@ -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;

View File

@@ -1,4 +1,4 @@
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议

View File

@@ -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);
});
};

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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/>

View File

@@ -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);
}
}

View File

@@ -1,4 +1,4 @@
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议

View File

@@ -1,4 +1,4 @@
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议

View File

@@ -1,4 +1,4 @@
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议

View File

@@ -1,4 +1,4 @@
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议

View File

@@ -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
{

View File

@@ -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
{

View File

@@ -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
{
}

View File

@@ -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>
/// 发送前的信息处理,例如存储某些特征信息:站号/功能码等等用于验证后续的返回信息是否合法

View File

@@ -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
{
}

View File

@@ -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;
}

View File

@@ -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)
{

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -1,4 +1,4 @@
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议

View File

@@ -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);
}

View File

@@ -1,4 +1,4 @@
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议

View File

@@ -1,4 +1,4 @@
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议

View File

@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议

Some files were not shown because too many files have changed in this diff Show More