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