diff --git a/.github/prompts/优化文档代码.prompt.md b/.github/prompts/优化文档代码.prompt.md new file mode 100644 index 000000000..c1401d68c --- /dev/null +++ b/.github/prompts/优化文档代码.prompt.md @@ -0,0 +1,36 @@ +--- +mode: agent +--- + +# 前提 + +你是一个CSharp编程文档生成专家,精通各种类型的文档写作,包括技术文档、用户手册、项目报告等。你能够根据用户提供的信息和要求,生成结构清晰、内容详实、语言流畅的文档。 + +# 文档生成前置工作 + +1. 阅读用户提供的需求,理解文档的主题、目标读者和预期用途。 +2. 你需要先阅读源代码内容,他们都在src文件夹下,src文件夹下有多个子文件夹,每个子文件夹代表一个独立的模块。每个模块中包含若干CSharp源代码文件(.cs)和一个README.md文件。README.md文件中包含该模块的简要介绍和使用说明。 +3. 在生成文档时,你还需要参考一些示例代码,这些示例代码都在examples文件夹下,examples文件夹下有多个子文件夹,每个子文件夹代表一个独立的示例。每个示例中包含若干CSharp源代码文件(.cs),文件中包含该示例的简要介绍和使用说明。 + +# 在文档中的代码要求(绝对必须) + +- 在生成的文档中,所有csharp代码绝对不要直接包含代码块。 +- 把需要包含的代码示例,全部在**示例工程中**编写,并使用region包裹,在文档中只需要使用`CustomCodeBlock`引用即可。 +- 如果需要引用的代码示例在示例工程中不方便使用region,或者不能简洁地表达,可以在**示例工程中**新建一个文件,或方法,专门用于放置这些代码示例。 + +# 示例代码 + +示例代码工程(整个解决方案)在文档编写完成后,必须保证能够编译通过。 + +# region 标签生成规则 + +- region 标签的命名必须简洁明了,能够准确表达代码示例的内容。 +- region 标签的命名主体使用中文。 +- region 标签的命名格式为:`<当前文档个主题>`+`<功能描述>`。例如:`Http启用跨域`、`WebApi使用FromQuery传参`、`Rpc使用调用上下文`等。 +- region 标签中(即`CustomCodeBlock`)只填充region即可,不用写filePath等其他东西。 +- region 标签格式:。 + +# 任务 + +- 你的任务是把文档中的代码示例提取到示例工程中,或者直接使用示例工程中的代码,并使用region包裹,然后在文档中使用`CustomCodeBlock`引用这些代码示例。你需要确保生成的文档符合用户的要求,内容准确无误,语言表达清晰流畅。 +- 如果文档中已经有`CustomCodeBlock`,则不再修改这些`CustomCodeBlock`,只需要处理没有`CustomCodeBlock`的代码示例即可。 diff --git a/.github/prompts/文档生成.prompt.md b/.github/prompts/文档生成.prompt.md new file mode 100644 index 000000000..13144807f --- /dev/null +++ b/.github/prompts/文档生成.prompt.md @@ -0,0 +1,45 @@ +--- +mode: agent +--- + +# 前提 + +你是一个CSharp编程文档生成专家,精通各种类型的文档写作,包括技术文档、用户手册、项目报告等。你能够根据用户提供的信息和要求,生成结构清晰、内容详实、语言流畅的文档。 + +# 文档生成前置工作 + +1. 阅读用户提供的需求,理解文档的主题、目标读者和预期用途。 +2. 你需要先阅读源代码内容,他们都在src文件夹下,src文件夹下有多个子文件夹,每个子文件夹代表一个独立的模块。每个模块中包含若干CSharp源代码文件(.cs)和一个README.md文件。README.md文件中包含该模块的简要介绍和使用说明。 +3. 在生成文档时,你还需要参考一些示例代码,这些示例代码都在examples文件夹下,examples文件夹下有多个子文件夹,每个子文件夹代表一个独立的示例。每个示例中包含若干CSharp源代码文件(.cs),文件中包含该示例的简要介绍和使用说明。 + +# 在文档中的代码要求(绝对必须) + +- 在生成的文档中,所有csharp代码绝对不要直接包含代码块。 +- 把需要包含的代码示例,全部在**示例工程中**编写,并使用region包裹,在文档中只需要使用`CustomCodeBlock`引用即可。 +- 如果需要引用的代码示例在示例工程中不方便使用region,或者不能简洁地表达,可以在**示例工程中**新建一个文件,或方法,专门用于放置这些代码示例。 + +# 示例代码 + +示例代码工程(整个解决方案)在文档编写完成后,必须保证能够编译通过。 + +# region 标签生成规则 + +- region 标签的命名必须简洁明了,能够准确表达代码示例的内容。 +- region 标签的命名主体使用中文。 +- region 标签的命名格式为:`<当前文档个主题>`+`<功能描述>`。例如:`Http启用跨域`、`WebApi使用FromQuery传参`、`Rpc使用调用上下文`等。 +- region 标签中(即`CustomCodeBlock`)只填充region即可,不用写filePath等其他东西。 +- region 标签格式:。 + +# 视频链接规则 + +- 如果文档中包含视频链接,在重写文档时,链接必须保持。 +- 不要生成不存在的视频链接。 + + + +# 任务 + +- 1. 提取关键信息,组织内容结构,撰写文档内容。如果文档已存在,则需要适当润色和扩展内容。 +- 2. 根据用户提供的需求,生成相应的CSharp编程文档。你需要根据源代码和示例代码,提取关键信息,组织内容结构,撰写文档内容。 +- 3. 确保生成的文档符合用户的要求,内容准确无误,语言表达清晰流畅。 +- 4. 在生成的文档中,添加示例Demo的链接,链接格式为:。其中,link属性表示示例代码的相对路径,这路径都是从`examples`文件夹开始的,例如:。如果一个文档中包含多个示例代码链接,则需要分别在对应章节中添加多个`CardLink`标签。 \ No newline at end of file diff --git a/.gitignore b/.gitignore index d322048f0..5c0c08434 100644 --- a/.gitignore +++ b/.gitignore @@ -340,3 +340,5 @@ ASALocalRun/ # BeatPulse healthcheck temp database healthchecksdb handbook/package-lock.json +handbook/src/components/CodeBlocks/codesData.js +handbook/docs/CodeBlocks/codesData.js diff --git a/.vscode/doc.code-snippets b/.vscode/doc.code-snippets new file mode 100644 index 000000000..b5c667e35 --- /dev/null +++ b/.vscode/doc.code-snippets @@ -0,0 +1,266 @@ +{ + // Place your handbook 工作区 snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and + // description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope + // is left empty or omitted, the snippet gets applied to all languages. The prefix is what is + // used to trigger the snippet and the body will be expanded and inserted. Possible variables are: + // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. + // Placeholders with the same ids are connected. + // Example: + "mdx": { + "prefix": "mdx", + "body": [ + "---", + "id: upgrade", + "title: 历史更新", + "---" + ], + "description": "mdx" + }, + "code": { + "prefix": "code", + "body": [ + "```csharp {1,4-6,11} showLineNumbers", + "$0", + "```" + ], + "description": "code" + }, + "note": { + "prefix": "note", + "body": [ + ":::note", + "", + "$0", + "", + "::: " + ], + "description": "note" + }, + "tip": { + "prefix": "tip", + "body": [ + ":::tip", + "", + "$0", + "", + "::: " + ], + "description": "tip" + }, + "info": { + "prefix": "info", + "body": [ + ":::info", + "", + "$0", + "", + "::: " + ], + "description": "info" + }, + "caution": { + "prefix": "caution", + "body": [ + ":::caution", + "", + "$0", + "", + "::: " + ], + "description": "caution" + }, + "danger": { + "prefix": "danger", + "body": [ + ":::danger", + "", + "$0", + "", + "::: " + ], + "description": "danger" + }, + "img": { + "prefix": "img", + "body": [ + "![]($0)", + ], + "description": "img" + }, + "imgstatic": { + "prefix": "imgstatic", + "body": [ + "![](\"$0\")", + ], + "description": "imgstatic" + }, + "imgLink": { + "prefix": "imgLink", + "body": [ + "", + ], + "description": "imgLink" + }, + "link": { + "prefix": "link", + "body": [ + "[$1]($0)", + ], + "description": "link" + }, + "linkstatic": { + "prefix": "linkstatic", + "body": [ + "[$1](\"./$0\")", + ], + "description": "linkstatic" + }, + "importHighlight": { + "prefix": "importHighlight", + "body": [ + "import Highlight from '@site/src/components/Highlight.js';" + ], + "description": "importHighlight" + }, + "importCustomCodeBlock": { + "prefix": "importCustomCodeBlock", + "body": [ + "import CustomCodeBlock from './CodeBlocks/CustomCodeBlock';" + ], + "description": "importCustomCodeBlock" + }, + "importBilibiliCard": { + "prefix": "importBilibiliCard", + "body": [ + "import BilibiliCard from '@site/src/components/BilibiliCard.js';" + ], + "description": "importBilibiliCard" + }, + "importTag": { + "prefix": "importTag", + "body": [ + "import Tag from \"@site/src/components/Tag.js\";" + ], + "description": "importTag" + }, + "importPro": { + "prefix": "importPro", + "body": [ + "import Pro from \"@site/src/components/Pro.js\";" + ], + "description": "importPro" + }, + "importCardLink": { + "prefix": "importCardLink", + "body": [ + "import CardLink from \"@site/src/components/CardLink.js\";" + ], + "description": "importCardLink" + }, + "CardLink": { + "prefix": "CardLink", + "body": [ + "" + ], + "description": "CardLink" + }, + "CustomCodeBlock": { + "prefix": "CustomCodeBlock", + "body": [ + "" + ], + "description": "CustomCodeBlock" + }, + "BilibiliCard": { + "prefix": "BilibiliCard", + "body": [ + "" + ], + "description": "BilibiliCard" + }, + "Tag": { + "prefix": "Tag", + "body": [ + "$0" + ], + "description": "importTag" + }, + "Highlight": { + "prefix": "Highlight", + "body": [ + "Docusaurus 绿" + ], + "description": "tagHighlight" + }, + "details": { + "prefix": "details", + "body": [ + "
", + "$1", + "
", + "", + "$0", + "", + "
", + "
" + ], + "description": "details" + }, + "ahref": { + "prefix": "ahref", + "body": [ + "$0" + ], + "description": "ahref" + }, + "table": { + "prefix": "table", + "body": [ + "| 表头 | 表头 |", + "| ---- | ---- |", + "| 单元格 | 单元格 |", + "| 单元格 | 单元格 |", + ], + "description": "table" + }, + "importTab": { + "prefix": "importTab", + "body": [ + "import Tabs from \"@theme/Tabs\";", + "import TabItem from \"@theme/TabItem\";" + ], + "description": "importTab" + }, + "Tabs": { + "prefix": "Tabs", + "body": [ + "", + "", + "", + "", + "", + "", + ], + "description": "importTab" + }, + "Mermaid": { + "prefix": "mermaid", + "body": [ + "```mermaid", + "graph TD;", + "\tA-->B;", + "\tA-->C;", + "\tB-->D;", + "\tC-->D;", + "```" + ], + "description": "mermaid" + } +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 368b643d5..b85160e31 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,65 @@ { - "commentTranslate.targetLanguage": "en", + "cSpell.enableFiletypes": [ + "mdx" + ], + "cSpell.words": [ + "appsettings", + "Avalonia", + "Bilibili", + "Boolens", + "bools", + "cref", + "Dmtp", + "endregion", + "Handshaked", + "modbus", + "Multifile", + "netstandard", + "Newtonsoft", + "Querys", + "Reenterable", + "Responsed", + "RRQM", + "Spliter", + "touchsocket", + "typeparam", + "Unpackage", + "ushort", + "winform" + ], + "editor.wordWrap": "on", + // "editor.wordWrapColumn": 80, + // 文件编码设置 + "files.encoding": "utf8", + "files.autoGuessEncoding": false, + // Git 提交配置 + "git.inputValidationLength": 50, + "git.inputValidationSubjectLength": 50, + "git.verboseCommit": true, + "scm.inputFontFamily": "Consolas, 'Courier New', monospace", + "scm.inputFontSize": 14, + // TouchSocket 项目提交规范提示 + "git.commitTemplate": "../../gitmessage-template.txt", + // Copilot 配置 + "github.copilot.enable": { + "*": true, + "plaintext": true, + "markdown": true, + "scminput": true + }, + // Git 提交相关的 Copilot 提示 + "github.copilot.editor.enableAutoCompletions": true, + // Paste Image 插件配置 + "pasteImage.path": "${projectRoot}/static/img/docs/", + "pasteImage.basePath": "${projectRoot}", + "pasteImage.forceUnixStyleSeparator": true, + "pasteImage.prefix": "", + "pasteImage.suffix": "", + "pasteImage.insertPattern": "", + "pasteImage.namePrefix": "", + "pasteImage.nameSuffix": "", + "pasteImage.encodePath": "urlEncodeSpace", + "pasteImage.showFilePathConfirmInputBox": false, + "pasteImage.filePathConfirmInputBoxMode": "onlyName", + "pasteImage.defaultName": "${currentFileNameWithoutExt}-YYYYMMDDHHmmss" } \ No newline at end of file diff --git a/TouchSocketVersion.props b/TouchSocketVersion.props index be3d44992..afe86b926 100644 --- a/TouchSocketVersion.props +++ b/TouchSocketVersion.props @@ -1,8 +1,9 @@ - + - 3.1.19 + 4.0.0-rc.50 + $(BaseVersion) diff --git a/examples/Adapter/AdapterConsoleApp/Program.cs b/examples/Adapter/AdapterConsoleApp/Program.cs deleted file mode 100644 index 6003bb0ac..000000000 --- a/examples/Adapter/AdapterConsoleApp/Program.cs +++ /dev/null @@ -1,273 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; -using TouchSocket.Core; -using TouchSocket.Sockets; - -namespace AdapterConsoleApp; - -internal class Program -{ - private static async Task Main(string[] args) - { - var service = await CreateService(); - var client = await CreateClient(); - - ConsoleLogger.Default.Info("输入任意内容,回车发送(将会循环发送10次)"); - while (true) - { - var str = Console.ReadLine(); - for (var i = 0; i < 10; i++) - { - await client.SendAsync(str); - } - } - } - - private static async Task CreateClient() - { - var client = new TcpClient(); - //载入配置 - await client.SetupAsync(new TouchSocketConfig() - .SetRemoteIPHost("127.0.0.1:7789") - .SetTcpDataHandlingAdapter(() => new MyDataHandleAdapter()) - .ConfigureContainer(a => - { - a.AddConsoleLogger();//添加一个日志注入 - })); - - await client.ConnectAsync();//调用连接,当连接不成功时,会抛出异常。 - client.Logger.Info("客户端成功连接"); - return client; - } - - private static async Task CreateService() - { - var service = new TcpService(); - service.Received = (client, e) => - { - //从客户端收到信息 - var mes = e.ByteBlock.Span.ToString(Encoding.UTF8); - client.Logger.Info($"已从{client.Id}接收到信息:{mes}"); - return Task.CompletedTask; - }; - - await service.SetupAsync(new TouchSocketConfig()//载入配置 - .SetListenIPHosts("tcp://127.0.0.1:7789", 7790)//同时监听两个地址 - .SetTcpDataHandlingAdapter(() => new PeriodPackageAdapter() { CacheTimeout = TimeSpan.FromSeconds(1) }) - .ConfigureContainer(a => - { - a.AddConsoleLogger();//添加一个控制台日志注入(注意:在maui中控制台日志不可用) - }) - .ConfigurePlugins(a => - { - //a.Add();//此处可以添加插件 - })); - await service.StartAsync();//启动 - service.Logger.Info("服务器已启动"); - return service; - } -} - -internal class MyDataHandleAdapter : SingleStreamDataHandlingAdapter -{ - /// - /// 包剩余长度 - /// - private byte m_surPlusLength; - - /// - /// 临时包,此包仅当前实例储存 - /// - private ByteBlock m_tempByteBlock; - - public override bool CanSendRequestInfo => false; - - public override bool CanSplicingSend => false; - - protected override async Task PreviewReceivedAsync(ByteBlock byteBlock) - { - //收到从原始流式数据。 - - var buffer = byteBlock.TotalMemory.GetArray().Array; - var r = byteBlock.Length; - if (this.m_tempByteBlock == null)//如果没有临时包,则直接分包。 - { - await this.SplitPackageAsync(buffer, 0, r); - } - else - { - if (this.m_surPlusLength == r)//接收长度正好等于剩余长度,组合完数据以后直接处理数据。 - { - this.m_tempByteBlock.Write(new ReadOnlySpan(buffer, 0, this.m_surPlusLength)); - await this.PreviewHandleAsync(this.m_tempByteBlock); - this.m_tempByteBlock = null; - this.m_surPlusLength = 0; - } - else if (this.m_surPlusLength < r)//接收长度大于剩余长度,先组合包,然后处理包,然后将剩下的分包。 - { - this.m_tempByteBlock.Write(new ReadOnlySpan(buffer, 0, this.m_surPlusLength)); - await this.PreviewHandleAsync(this.m_tempByteBlock); - this.m_tempByteBlock = null; - await this.SplitPackageAsync(buffer, this.m_surPlusLength, r); - } - else//接收长度小于剩余长度,无法处理包,所以必须先组合包,然后等下次接收。 - { - this.m_tempByteBlock.Write(new ReadOnlySpan(buffer, 0, r)); - this.m_surPlusLength -= (byte)r; - } - } - } - - - protected override async Task PreviewSendAsync(ReadOnlyMemory memory) - { - //在发送流式数据之前 - - var length = memory.Length; - if (length > byte.MaxValue)//超长判断 - { - throw new OverlengthException("发送数据太长。"); - } - - //从内存池申请内存块,因为此处数据绝不超过255,所以避免内存池碎片化,每次申请1K - //ByteBlock byteBlock = new ByteBlock(dataLen+1);//实际写法。 - using (var byteBlock = new ByteBlock(1024)) - { - byteBlock.WriteByte((byte)length);//先写长度 - byteBlock.Write(memory.Span);//再写数据 - await this.GoSendAsync(byteBlock.Memory); - } - } - - protected override async Task PreviewSendAsync(IList> transferBytes) - { - //使用拼接模式发送,在发送流式数据之前 - - var dataLen = 0; - foreach (var item in transferBytes) - { - dataLen += item.Count; - } - if (dataLen > byte.MaxValue)//超长判断 - { - throw new OverlengthException("发送数据太长。"); - } - - //从内存池申请内存块,因为此处数据绝不超过255,所以避免内存池碎片化,每次申请1K - //ByteBlock byteBlock = new ByteBlock(dataLen+1);//实际写法。 - - using (var byteBlock = new ByteBlock(1024)) - { - byteBlock.WriteByte((byte)dataLen);//先写长度 - foreach (var item in transferBytes) - { - byteBlock.Write(new ReadOnlySpan(item.Array, item.Offset, item.Count));//依次写入 - } - await this.GoSendAsync(byteBlock.Memory); - } - } - - protected override async Task PreviewSendAsync(IRequestInfo requestInfo) - { - //使用对象发送,在发送流式数据之前 - - if (requestInfo is MyClass myClass) - { - var data = myClass.Data ?? Array.Empty(); - if (data.Length > byte.MaxValue)//超长判断 - { - throw new OverlengthException("发送数据太长。"); - } - - //从内存池申请内存块,因为此处数据绝不超过255,所以避免内存池碎片化,每次申请1K - //ByteBlock byteBlock = new ByteBlock(dataLen+1);//实际写法。 - using (var byteBlock = new ByteBlock(1024)) - { - byteBlock.WriteByte((byte)data.Length);//先写长度 - byteBlock.WriteByte((byte)myClass.DataType);//然后数据类型 - byteBlock.WriteByte((byte)myClass.OrderType);//然后指令类型 - byteBlock.Write(data);//再写数据 - await this.GoSendAsync(byteBlock.Memory); - } - } - } - - /// - /// 处理数据 - /// - /// - private async Task PreviewHandleAsync(ByteBlock byteBlock) - { - try - { - await this.GoReceivedAsync(byteBlock, null); - } - finally - { - byteBlock.Dispose();//在框架里面将内存块释放 - } - } - - /// - /// 分解包 - /// - /// - /// - /// - private async Task SplitPackageAsync(byte[] dataBuffer, int index, int r) - { - while (index < r) - { - var length = dataBuffer[index]; - var recedSurPlusLength = r - index - 1; - if (recedSurPlusLength >= length) - { - var byteBlock = new ByteBlock(length); - byteBlock.Write(new ReadOnlySpan(dataBuffer, index + 1, length)); - await this.PreviewHandleAsync(byteBlock); - this.m_surPlusLength = 0; - } - else//半包 - { - this.m_tempByteBlock = new ByteBlock(length); - this.m_surPlusLength = (byte)(length - recedSurPlusLength); - this.m_tempByteBlock.Write(new ReadOnlySpan(dataBuffer, index + 1, recedSurPlusLength)); - } - index += length + 1; - } - } -} - -internal class MyClass : IRequestInfo -{ - public OrderType OrderType { get; set; } - public DataType DataType { get; set; } - - public byte[] Data { get; set; } -} - -internal enum DataType : byte -{ - Down = 0, - Up = 1 -} - -internal enum OrderType : byte -{ - Hold = 0, - Go = 1 -} \ No newline at end of file diff --git a/examples/Adapter/AdapterTesterConsoleApp/AdapterTesterConsoleApp.csproj b/examples/Adapter/AdapterTesterConsoleApp/AdapterTesterConsoleApp.csproj index ac1119c78..5b850cb08 100644 --- a/examples/Adapter/AdapterTesterConsoleApp/AdapterTesterConsoleApp.csproj +++ b/examples/Adapter/AdapterTesterConsoleApp/AdapterTesterConsoleApp.csproj @@ -7,6 +7,6 @@ - + diff --git a/examples/Adapter/AdapterTesterConsoleApp/Program.cs b/examples/Adapter/AdapterTesterConsoleApp/Program.cs index f82cf68fc..5759b6666 100644 --- a/examples/Adapter/AdapterTesterConsoleApp/Program.cs +++ b/examples/Adapter/AdapterTesterConsoleApp/Program.cs @@ -31,7 +31,7 @@ internal class Program Console.WriteLine(obj.Message); } - private static void TcpDataAdapterTester() + private static async Task TcpDataAdapterTester() { //Tcp适配器测试 //bufferLength的作用是模拟tcp接收缓存区,例如: @@ -45,8 +45,8 @@ internal class Program { var isSuccess = true; var data = new byte[] { 0, 1, 2, 3, 4 }; - var tester = TouchSocket.Sockets.TcpDataAdapterTester.CreateTester(new FixedHeaderPackageAdapter() - , bufferLength, async (byteBlock, requestInfo) => + var tester = TouchSocket.Core.TcpDataAdapterTester.CreateTester(new FixedHeaderPackageAdapter() + , async (byteBlock, requestInfo) => { //此处就是接收,如果是自定义适配器,可以将requestInfo强制转换为实际对象,然后判断数据的确定性 if (byteBlock.Length != 5 || (!byteBlock.ToArray().SequenceEqual(data))) @@ -63,8 +63,7 @@ internal class Program //随后的两个参数,10,10是测试次数,和期望次数,一般这两个值是相等的。 //意为:本次数据将循环发送10次,且会接收10次。不然此处会一直阻塞。 //最后一个参数是测试的最大超时时间。 - var time = tester.Run(data, 10, 10, 1000 * 10); - Thread.Sleep(1000); + var time = await tester.RunAsync(data, 10, 10, bufferLength, 1000 * 10); Console.WriteLine($"测试结束,状态:{isSuccess},用时:{time}"); } Console.WriteLine("测试结束"); diff --git a/examples/Adapter/BetweenAndConsoleApp/BetweenAndConsoleApp.csproj b/examples/Adapter/BetweenAndConsoleApp/BetweenAndConsoleApp.csproj index 401277729..a4e8e19f6 100644 --- a/examples/Adapter/BetweenAndConsoleApp/BetweenAndConsoleApp.csproj +++ b/examples/Adapter/BetweenAndConsoleApp/BetweenAndConsoleApp.csproj @@ -9,6 +9,6 @@ - + diff --git a/examples/Adapter/BetweenAndConsoleApp/Program.cs b/examples/Adapter/BetweenAndConsoleApp/Program.cs index 3c128c876..744ec4913 100644 --- a/examples/Adapter/BetweenAndConsoleApp/Program.cs +++ b/examples/Adapter/BetweenAndConsoleApp/Program.cs @@ -18,22 +18,6 @@ namespace BetweenAndConsoleApp; internal class Program { - private static async Task Main(string[] args) - { - var service = await CreateService(); - var client = await CreateClient(); - - ConsoleLogger.Default.Info("按任意键发送10次"); - while (true) - { - Console.ReadKey(); - for (var i = 0; i < 10; i++) - { - await client.SendAsync("**12##12##"); - } - } - } - private static async Task CreateClient() { var client = new TcpClient(); @@ -54,6 +38,7 @@ internal class Program private static async Task CreateService() { var service = new TcpService(); + #region 接收自定义区间适配器 service.Received = (client, e) => { //从客户端收到信息 @@ -64,7 +49,7 @@ internal class Program } return Task.CompletedTask; }; - + #endregion await service.SetupAsync(new TouchSocketConfig()//载入配置 .SetListenIPHosts("tcp://127.0.0.1:7789", 7790)//同时监听两个地址 .SetTcpDataHandlingAdapter(() => new MyCustomBetweenAndDataHandlingAdapter()) @@ -80,6 +65,44 @@ internal class Program service.Logger.Info("服务器已启动"); return service; } + + private static async Task Main(string[] args) + { + var service = await CreateService(); + var client = await CreateClient(); + + ConsoleLogger.Default.Info("按任意键发送10次"); + while (true) + { + Console.ReadKey(); + for (var i = 0; i < 10; i++) + { + await client.SendAsync("**12##12##"); + } + } + } +} + +#region 创建自定义区间适配器 +internal class MyCustomBetweenAndDataHandlingAdapter : CustomBetweenAndDataHandlingAdapter +{ + private readonly ReadOnlyMemory m_endCode; + + private readonly ReadOnlyMemory m_startCode; + + public MyCustomBetweenAndDataHandlingAdapter() + { + this.MinSize = 5;//表示,实际数据体不会小于5,例如“**12##12##”数据,解析后会解析成“12##12” + + this.m_startCode = Encoding.UTF8.GetBytes("**");//可以为0长度字节,意味着没有起始标识。 + this.m_endCode = Encoding.UTF8.GetBytes("##");//必须为有效值。 + } + public override ReadOnlyMemory EndCode => this.m_endCode; + public override ReadOnlyMemory StartCode => this.m_startCode; + protected override MyBetweenAndRequestInfo GetInstance(ReadOnlySpan body) + { + return new MyBetweenAndRequestInfo(body.ToArray()); + } } internal class MyBetweenAndRequestInfo : IRequestInfo @@ -91,26 +114,4 @@ internal class MyBetweenAndRequestInfo : IRequestInfo public byte[] Body { get; private set; } } - -internal class MyCustomBetweenAndDataHandlingAdapter : CustomBetweenAndDataHandlingAdapter -{ - public MyCustomBetweenAndDataHandlingAdapter() - { - this.MinSize = 5;//表示,实际数据体不会小于5,例如“**12##12##”数据,解析后会解析成“12##12” - - this.m_startCode = Encoding.UTF8.GetBytes("**");//可以为0长度字节,意味着没有起始标识。 - this.m_endCode = Encoding.UTF8.GetBytes("##");//必须为有效值。 - } - - private readonly byte[] m_startCode; - private readonly byte[] m_endCode; - - public override byte[] StartCode => this.m_startCode; - - public override byte[] EndCode => this.m_endCode; - - protected override MyBetweenAndRequestInfo GetInstance(ReadOnlySpan body) - { - return new MyBetweenAndRequestInfo(body.ToArray()); - } -} +#endregion \ No newline at end of file diff --git a/examples/Adapter/CustomAdapterConsoleApp/CustomAdapterConsoleApp.csproj b/examples/Adapter/CustomAdapterConsoleApp/CustomAdapterConsoleApp.csproj index ab8f18f2e..e79c4df47 100644 --- a/examples/Adapter/CustomAdapterConsoleApp/CustomAdapterConsoleApp.csproj +++ b/examples/Adapter/CustomAdapterConsoleApp/CustomAdapterConsoleApp.csproj @@ -1,4 +1,4 @@ - + Exe @@ -8,6 +8,6 @@ - + diff --git a/examples/Adapter/CustomAdapterConsoleApp/Program.cs b/examples/Adapter/CustomAdapterConsoleApp/Program.cs index b0a55df81..bad9dd821 100644 --- a/examples/Adapter/CustomAdapterConsoleApp/Program.cs +++ b/examples/Adapter/CustomAdapterConsoleApp/Program.cs @@ -29,22 +29,27 @@ internal class Program Console.ReadKey(); for (var i = 0; i < 10; i++) { - var myRequestInfo = new MyRequestInfo() + var myRequestInfo = new MyDataClass() { - Body = Encoding.UTF8.GetBytes("hello"), + Data = Encoding.UTF8.GetBytes("hello"), DataType = (byte)i, OrderType = (byte)i }; //构建发送数据 - using (var byteBlock = new ByteBlock(1024)) + var byteBlock = new ByteBlock(1024); + try { - byteBlock.WriteByte((byte)(myRequestInfo.Body.Length + 2));//先写长度,因为该长度还包含数据类型和指令类型,所以+2 - byteBlock.WriteByte(myRequestInfo.DataType);//然后数据类型 - byteBlock.WriteByte(myRequestInfo.OrderType);//然后指令类型 - byteBlock.Write(myRequestInfo.Body);//再写数据 + WriterExtension.WriteValue(ref byteBlock, (byte)(myRequestInfo.Data.Length + 2));//先写长度,因为该长度还包含数据类型和指令类型,所以+2 + WriterExtension.WriteValue(ref byteBlock, myRequestInfo.DataType);//然后数据类型 + WriterExtension.WriteValue(ref byteBlock, myRequestInfo.OrderType);//然后指令类型 + byteBlock.Write(myRequestInfo.Data);//再写数据 await client.SendAsync(byteBlock.Memory); } + finally + { + byteBlock.Dispose(); + } } } } @@ -69,17 +74,17 @@ internal class Program private static async Task CreateService() { var service = new TcpService(); + + #region 使用自定义适配器接收数据 {3} service.Received = (client, e) => { - //从客户端收到信息 - - if (e.RequestInfo is MyRequestInfo myRequest) + if (e.RequestInfo is MyDataClass myRequest) { - client.Logger.Info($"已从{client.Id}接收到:DataType={myRequest.DataType},OrderType={myRequest.OrderType},消息={Encoding.UTF8.GetString(myRequest.Body)}"); + client.Logger.Info($"已从{client.Id}接收到:DataType={myRequest.DataType},OrderType={myRequest.OrderType},消息={Encoding.UTF8.GetString(myRequest.Data)}"); } return Task.CompletedTask; }; - + #endregion await service.SetupAsync(new TouchSocketConfig()//载入配置 .SetListenIPHosts("tcp://127.0.0.1:7789", 7790)//同时监听两个地址 .SetTcpDataHandlingAdapter(() => new MyCustomDataHandlingAdapter()) @@ -97,77 +102,106 @@ internal class Program } } -internal class MyCustomDataHandlingAdapter : CustomDataHandlingAdapter +#region 用户自定义适配器 + +/// +/// 第1个字节表示指令类型 +/// 第2字节表示数据类型 +/// 第3字节表示后续数据的长度。使用ushort(大端)表示,最大长度为65535 +/// 后续字节表示载荷数据 +/// 最后2字节表示CRC16校验码 +/// +internal class MyCustomDataHandlingAdapter : CustomDataHandlingAdapter { - /// - /// 筛选解析数据。实例化的TRequest会一直保存,直至解析成功,或手动清除。 - /// 当不满足解析条件时,请返回,此时会保存的数据 - /// 当数据部分异常时,请移动到指定位置,然后返回 - /// 当完全满足解析条件时,请返回最后将移至指定位置。 - /// - /// 字节块 - /// 是否为上次遗留对象,当该参数为True时,request也将是上次实例化的对象。 - /// 对象。 - /// 缓存容量指导,指示当需要缓存时,应该申请多大的内存。 - /// - protected override FilterResult Filter(ref TByteBlock byteBlock, bool beCached, ref MyRequestInfo request, ref int tempCapacity) + private ushort m_payloadLength; + + protected override FilterResult Filter(ref TReader reader, bool beCached, ref MyDataClass request) { - //以下解析思路为一次性解析,不考虑缓存的临时对象。 - - if (byteBlock.CanReadLength < 3) + if (beCached) { - return FilterResult.Cache;//当头部都无法解析时,直接缓存 - } + //说明上次已经解析了header - var pos = byteBlock.Position;//记录初始游标位置,防止本次无法解析时,回退游标。 - - var myRequestInfo = new MyRequestInfo(); - - //此操作实际上有两个作用, - //1.填充header - //2.将byteBlock.Pos递增3的长度。 - var header = byteBlock.ReadToSpan(3);//填充header - - //因为第一个字节表示所有长度,而DataType、OrderType已经包含在了header里面。 - //所有只需呀再读取header[0]-2个长度即可。 - var bodyLength = (byte)(header[0] - 2); - - if (bodyLength > byteBlock.CanReadLength) - { - //body数据不足。 - byteBlock.Position = pos;//回退游标 - return FilterResult.Cache; + return this.ParseData(ref reader, request); } else { - //此操作实际上有两个作用, - //1.填充body - //2.将byteBlock.Pos递增bodyLength的长度。 - var body = byteBlock.ReadToSpan(bodyLength); + //首次解析 - myRequestInfo.DataType = header[1]; - myRequestInfo.OrderType = header[2]; - myRequestInfo.Body = body.ToArray(); - request = myRequestInfo;//赋值ref - return FilterResult.Success;//返回成功 + if (reader.BytesRemaining < 4) + { + //如果剩余数据小于4个字节,则继续等待 + return FilterResult.Cache; + } + + //读取前4个字节 + var header = reader.GetSpan(4); + + //推进已读取的4个字节 + reader.Advance(4); + + //获取指令类型 + var orderType = header[0]; + //获取数据类型 + var dataType = header[1]; + + //创建数据对象 + request = new MyDataClass() + { + OrderType = orderType, + DataType = dataType + }; + + //获取载荷长度 + this.m_payloadLength = TouchSocketBitConverter.BigEndian.To(header.Slice(2, 2)); + + return this.ParseData(ref reader, request); } } + + private FilterResult ParseData(ref TReader reader, MyDataClass myDataClass) + where TReader : IBytesReader + { + //判断剩余数据是否足够,+2是因为最后2个字节是CRC16校验码 + if (reader.BytesRemaining < this.m_payloadLength + 2) + { + return FilterResult.Cache; + } + + //读取数据 + var data = reader.GetSpan(this.m_payloadLength); + reader.Advance(this.m_payloadLength); + + //读取CRC16校验码 + var crcData = reader.GetSpan(2); + reader.Advance(2); + + //转换CRC16校验码为ushort,相比于byte[],更节省内存 + var crc16 = TouchSocketBitConverter.BigEndian.To(crcData); + + //计算CRC16 + var newCrc16 = Crc.Crc16Value(data); + if (crc16 != newCrc16) + { + //CRC校验失败 + throw new Exception("CRC校验失败"); + } + + //保存数据 + myDataClass.Data = data.ToArray(); + + //至此,数据接收完成,可以进行投递处理 + this.m_payloadLength = 0; + return FilterResult.Success; + } } -internal class MyRequestInfo : IRequestInfo +/// +/// 定义数据对象 +/// +internal class MyDataClass : IRequestInfo { - /// - /// 自定义属性,Body - /// - public byte[] Body { get; internal set; } - - /// - /// 自定义属性,DataType - /// - public byte DataType { get; internal set; } - - /// - /// 自定义属性,OrderType - /// - public byte OrderType { get; internal set; } -} \ No newline at end of file + public byte OrderType { get; set; } + public byte DataType { get; set; } + public byte[] Data { get; set; } +} +#endregion \ No newline at end of file diff --git a/examples/Adapter/CustomBigFixedHeaderConsoleApp/CustomBigFixedHeaderConsoleApp.csproj b/examples/Adapter/CustomBigFixedHeaderConsoleApp/CustomBigFixedHeaderConsoleApp.csproj index e215e7ade..e79c4df47 100644 --- a/examples/Adapter/CustomBigFixedHeaderConsoleApp/CustomBigFixedHeaderConsoleApp.csproj +++ b/examples/Adapter/CustomBigFixedHeaderConsoleApp/CustomBigFixedHeaderConsoleApp.csproj @@ -8,6 +8,6 @@ - + diff --git a/examples/Adapter/CustomBigFixedHeaderConsoleApp/Program.cs b/examples/Adapter/CustomBigFixedHeaderConsoleApp/Program.cs index 56208172e..651604e90 100644 --- a/examples/Adapter/CustomBigFixedHeaderConsoleApp/Program.cs +++ b/examples/Adapter/CustomBigFixedHeaderConsoleApp/Program.cs @@ -1,3 +1,15 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + using System.Text; using TouchSocket.Core; using TouchSocket.Sockets; @@ -6,38 +18,6 @@ namespace CustomBigFixedHeaderConsoleApp; internal class Program { - private static async Task Main(string[] args) - { - var service = await CreateService(); - var client = await CreateClient(); - - ConsoleLogger.Default.Info("按任意键发送10次"); - while (true) - { - Console.ReadKey(); - for (var i = 0; i < 10; i++) - { - var myRequestInfo = new MyBigFixedHeaderRequestInfo() - { - Body = Encoding.UTF8.GetBytes("hello"), - DataType = (byte)i, - OrderType = (byte)i - }; - - //构建发送数据 - using (var byteBlock = new ByteBlock(1024)) - { - byteBlock.WriteByte((byte)(myRequestInfo.Body.Length + 2));//先写长度,因为该长度还包含数据类型和指令类型,所以+2 - byteBlock.WriteByte(myRequestInfo.DataType);//然后数据类型 - byteBlock.WriteByte(myRequestInfo.OrderType);//然后指令类型 - byteBlock.Write(myRequestInfo.Body);//再写数据 - - await client.SendAsync(byteBlock.Memory); - } - } - } - } - private static async Task CreateClient() { var client = new TcpClient(); @@ -58,16 +38,18 @@ internal class Program private static async Task CreateService() { var service = new TcpService(); + #region 接收自定义大数据固定包头适配器 service.Received = (client, e) => { //从客户端收到信息 - if (e.RequestInfo is MyBigFixedHeaderRequestInfo myRequest) + if (e.RequestInfo is MyDataClass myRequest) { client.Logger.Info($"已从{client.Id}接收到:DataType={myRequest.DataType},OrderType={myRequest.OrderType},消息={Encoding.UTF8.GetString(myRequest.Body)}"); } return Task.CompletedTask; }; + #endregion await service.SetupAsync(new TouchSocketConfig()//载入配置 .SetListenIPHosts("tcp://127.0.0.1:7789", 7790)//同时监听两个地址 @@ -84,20 +66,75 @@ internal class Program service.Logger.Info("服务器已启动"); return service; } -} -internal class MyCustomBigFixedHeaderDataHandlingAdapter : CustomBigFixedHeaderDataHandlingAdapter -{ - public override int HeaderLength => 3; - - protected override MyBigFixedHeaderRequestInfo GetInstance() + private static async Task Main(string[] args) { - return new MyBigFixedHeaderRequestInfo(); + var service = await CreateService(); + var client = await CreateClient(); + + ConsoleLogger.Default.Info("按任意键发送10次"); + while (true) + { + Console.ReadKey(); + for (var i = 0; i < 10; i++) + { + var myRequestInfo = new MyDataClass() + { + Body = Encoding.UTF8.GetBytes("hello"), + DataType = (byte)i, + OrderType = (byte)i + }; + + //构建发送数据 + + var byteBlock = new ByteBlock(1024); + try + { + WriterExtension.WriteValue(ref byteBlock, (byte)(myRequestInfo.Body.Length + 2));//先写长度,因为该长度还包含数据类型和指令类型,所以+2 + WriterExtension.WriteValue(ref byteBlock, myRequestInfo.DataType);//然后数据类型 + WriterExtension.WriteValue(ref byteBlock, myRequestInfo.OrderType);//然后指令类型 + byteBlock.Write(myRequestInfo.Body);//再写数据 + + await client.SendAsync(byteBlock.Memory); + } + finally + { + byteBlock.Dispose(); + } + } + } } } -internal class MyBigFixedHeaderRequestInfo : IBigFixedHeaderRequestInfo +#region 创建自定义大数据固定包头适配器 {10,42-46,48-56,58-74} +/// +/// 第1个字节表示指令类型 +/// 第2字节表示数据类型 +/// 第3字节表示后续数据的长度。使用ushort(大端)表示,最大长度为65535 +/// 后续字节表示载荷数据 +/// 最后2字节表示CRC16校验码 +/// +internal class MyCustomBigFixedHeaderDataHandlingAdapter : CustomBigFixedHeaderDataHandlingAdapter { + public override int HeaderLength => 4; + + protected override MyDataClass GetInstance() + { + return new MyDataClass(); + } +} + +internal class MyDataClass : IBigFixedHeaderRequestInfo +{ + private readonly List m_bytes = new List(); + + private long m_length; + + /// + /// 自定义属性,标识实际数据 + /// + public byte[] Body { get; set; } + /// /// 自定义属性,标识数据类型 /// @@ -108,17 +145,7 @@ internal class MyBigFixedHeaderRequestInfo : IBigFixedHeaderRequestInfo /// public byte OrderType { get; set; } - /// - /// 自定义属性,标识实际数据 - /// - public byte[] Body { get; set; } - - private long m_bodyLength; - - private readonly List m_bytes = new List(); - - #region 接口成员 - long IBigFixedHeaderRequestInfo.BodyLength => this.m_bodyLength; + long IBigFixedHeaderRequestInfo.BodyLength => this.m_length; void IBigFixedHeaderRequestInfo.OnAppendBody(ReadOnlySpan buffer) { @@ -128,7 +155,7 @@ internal class MyBigFixedHeaderRequestInfo : IBigFixedHeaderRequestInfo bool IBigFixedHeaderRequestInfo.OnFinished() { - if (this.m_bytes.Count == this.m_bodyLength) + if (this.m_bytes.Count == this.m_length) { this.Body = this.m_bytes.ToArray(); return true; @@ -138,12 +165,20 @@ internal class MyBigFixedHeaderRequestInfo : IBigFixedHeaderRequestInfo bool IBigFixedHeaderRequestInfo.OnParsingHeader(ReadOnlySpan header) { - //在该示例中,第一个字节表示后续的所有数据长度,但是header设置的是3,所以后续还应当接收length-2个长度。 - this.m_bodyLength = header[0] - 2; + //这里header长度已经经过验证,一定是4字节 + + //获取指令类型 + this.OrderType = header[0]; + + //获取数据类型 this.DataType = header[1]; - this.OrderType = header[2]; + + var payloadLength = TouchSocketBitConverter.BigEndian.To(header.Slice(2, 2)); + + + this.m_length = payloadLength + 2;//+2是因为还包含Crc16的长度 + return true; } - #endregion - } +#endregion diff --git a/examples/Adapter/CustomBigUnfixedHeaderConsoleApp/CustomBigUnfixedHeaderConsoleApp.csproj b/examples/Adapter/CustomBigUnfixedHeaderConsoleApp/CustomBigUnfixedHeaderConsoleApp.csproj index 897fb96ef..e08d18664 100644 --- a/examples/Adapter/CustomBigUnfixedHeaderConsoleApp/CustomBigUnfixedHeaderConsoleApp.csproj +++ b/examples/Adapter/CustomBigUnfixedHeaderConsoleApp/CustomBigUnfixedHeaderConsoleApp.csproj @@ -8,6 +8,6 @@ - + diff --git a/examples/Adapter/CustomBigUnfixedHeaderConsoleApp/Program.cs b/examples/Adapter/CustomBigUnfixedHeaderConsoleApp/Program.cs index be7d01b34..b06e89241 100644 --- a/examples/Adapter/CustomBigUnfixedHeaderConsoleApp/Program.cs +++ b/examples/Adapter/CustomBigUnfixedHeaderConsoleApp/Program.cs @@ -1,3 +1,15 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + using System.Text; using TouchSocket.Core; using TouchSocket.Sockets; @@ -17,7 +29,7 @@ internal class Program Console.ReadKey(); for (var i = 0; i < 10; i++) { - var myRequestInfo = new MyBigUnfixedHeaderRequestInfo() + var myRequestInfo = new MyDataClass() { Body = Encoding.UTF8.GetBytes("hello"), DataType = (byte)i, @@ -25,15 +37,20 @@ internal class Program }; //构建发送数据 - using (var byteBlock = new ByteBlock(1024)) + var byteBlock = new ByteBlock(1024); + try { - byteBlock.WriteByte((byte)(myRequestInfo.Body.Length + 2));//先写长度,因为该长度还包含数据类型和指令类型,所以+2 - byteBlock.WriteByte(myRequestInfo.DataType);//然后数据类型 - byteBlock.WriteByte(myRequestInfo.OrderType);//然后指令类型 + WriterExtension.WriteValue(ref byteBlock, (byte)(myRequestInfo.Body.Length + 2));//先写长度,因为该长度还包含数据类型和指令类型,所以+2 + WriterExtension.WriteValue(ref byteBlock, myRequestInfo.DataType);//然后数据类型 + WriterExtension.WriteValue(ref byteBlock, myRequestInfo.OrderType);//然后指令类型 byteBlock.Write(myRequestInfo.Body);//再写数据 await client.SendAsync(byteBlock.Memory); } + finally + { + byteBlock.Dispose(); + } } } } @@ -58,16 +75,17 @@ internal class Program private static async Task CreateService() { var service = new TcpService(); + #region 接收自定义大数据非固定包头适配器 service.Received = (client, e) => { //从客户端收到信息 - - if (e.RequestInfo is MyBigUnfixedHeaderRequestInfo myRequest) + if (e.RequestInfo is MyDataClass myRequest) { client.Logger.Info($"已从{client.Id}接收到:DataType={myRequest.DataType},OrderType={myRequest.OrderType},消息={Encoding.UTF8.GetString(myRequest.Body)}"); } return Task.CompletedTask; }; + #endregion await service.SetupAsync(new TouchSocketConfig()//载入配置 .SetListenIPHosts("tcp://127.0.0.1:7789", 7790)//同时监听两个地址 @@ -85,16 +103,23 @@ internal class Program return service; } } - -internal class MyCustomBigUnfixedHeaderDataHandlingAdapter : CustomBigUnfixedHeaderDataHandlingAdapter +#region 创建自定义大数据非固定包头适配器 +/// +/// 第1个字节表示指令类型 +/// 第2字节表示数据类型 +/// 第3字节表示后续数据的长度。使用ushort(大端)表示,最大长度为65535 +/// 后续字节表示载荷数据 +/// 最后2字节表示CRC16校验码 +/// +internal class MyCustomBigUnfixedHeaderDataHandlingAdapter : CustomBigUnfixedHeaderDataHandlingAdapter { - protected override MyBigUnfixedHeaderRequestInfo GetInstance() + protected override MyDataClass GetInstance() { - return new MyBigUnfixedHeaderRequestInfo(); + return new MyDataClass(); } } -internal class MyBigUnfixedHeaderRequestInfo : IBigUnfixedHeaderRequestInfo +internal class MyDataClass : IBigUnfixedHeaderRequestInfo { /// /// 自定义属性,标识数据类型 @@ -117,7 +142,7 @@ internal class MyBigUnfixedHeaderRequestInfo : IBigUnfixedHeaderRequestInfo private int m_headerLength; private long m_bodyLength; - #region 接口成员 + int IBigUnfixedHeaderRequestInfo.HeaderLength => this.m_headerLength; long IBigUnfixedHeaderRequestInfo.BodyLength => this.m_bodyLength; @@ -138,26 +163,38 @@ internal class MyBigUnfixedHeaderRequestInfo : IBigUnfixedHeaderRequestInfo return false; } - bool IBigUnfixedHeaderRequestInfo.OnParsingHeader(ref TByteBlock byteBlock) + bool IBigUnfixedHeaderRequestInfo.OnParsingHeader(ref TReader reader) { - if (byteBlock.CanReadLength < 3)//判断可读数据是否满足一定长度 + //在使用不固定包头解析时 + + //【首先】需要先解析包头 + if (reader.BytesRemaining < 4) { + //即直接缓存 return false; } - var pos = byteBlock.Position;//可以先记录游标位置,当解析不能进行时回退游标 + //【然后】先获取4字节包头 + var header = reader.GetSpan(4); - //在该示例中,第一个字节表示后续的所有数据长度,但是header设置的是3,所以后续还应当接收length-2个长度。 - this.m_bodyLength = byteBlock.ReadByte() - 2; - this.DataType = byteBlock.ReadByte(); - this.OrderType = byteBlock.ReadByte(); + reader.Advance(4); //推进游标到包头结束位置 + + //【然后】解析包头,和BodyLength + //获取指令类型 + this.OrderType = header[0]; + + //获取数据类型 + this.DataType = header[1]; + + var payloadLength = TouchSocketBitConverter.BigEndian.To(header.Slice(2, 2)); - //当执行到这里时,byteBlock.Position已经递增了3个长度。 - //所以无需再其他操作,如果是其他,则需要手动移动byteBlock.Position到指定位置。 + //【最后】对HeaderLength做有效赋值 + this.m_headerLength = 4; + this.m_bodyLength = payloadLength + 2; - this.m_headerLength = 3;//表示Header消耗了3个字节,实际上可以省略这一行,但是为了性能,最好加上 return true; } - #endregion } + +#endregion diff --git a/examples/Adapter/CustomCountSpliterDataHandlingAdapterConsoleApp/CustomCountSpliterDataHandlingAdapterConsoleApp.csproj b/examples/Adapter/CustomCountSpliterDataHandlingAdapterConsoleApp/CustomCountSpliterDataHandlingAdapterConsoleApp.csproj index 18c1c8753..e08d18664 100644 --- a/examples/Adapter/CustomCountSpliterDataHandlingAdapterConsoleApp/CustomCountSpliterDataHandlingAdapterConsoleApp.csproj +++ b/examples/Adapter/CustomCountSpliterDataHandlingAdapterConsoleApp/CustomCountSpliterDataHandlingAdapterConsoleApp.csproj @@ -1,4 +1,4 @@ - + Exe @@ -8,6 +8,6 @@ - + diff --git a/examples/Adapter/CustomCountSpliterDataHandlingAdapterConsoleApp/Program.cs b/examples/Adapter/CustomCountSpliterDataHandlingAdapterConsoleApp/Program.cs index cef897445..fd95b2eb1 100644 --- a/examples/Adapter/CustomCountSpliterDataHandlingAdapterConsoleApp/Program.cs +++ b/examples/Adapter/CustomCountSpliterDataHandlingAdapterConsoleApp/Program.cs @@ -1,3 +1,15 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + using System.Text; using TouchSocket.Core; using TouchSocket.Sockets; @@ -48,6 +60,7 @@ internal class Program private static async Task CreateService() { var service = new TcpService(); + #region 接收自定义固定数量分隔符适配器 service.Received = (client, e) => { //从客户端收到信息 @@ -58,7 +71,7 @@ internal class Program } return Task.CompletedTask; }; - + #endregion await service.SetupAsync(new TouchSocketConfig()//载入配置 .SetListenIPHosts("tcp://127.0.0.1:7789", 7790)//同时监听两个地址 .SetTcpDataHandlingAdapter(() => new MyCustomCountSpliterDataHandlingAdapter()) @@ -76,16 +89,8 @@ internal class Program } } -internal class MyCountSpliterRequestInfo : IRequestInfo -{ - public string Data { get; private set; } - - public MyCountSpliterRequestInfo(string data) - { - this.Data = data; - } -} +#region 创建自定义固定数量分隔符适配器 internal class MyCustomCountSpliterDataHandlingAdapter : CustomCountSpliterDataHandlingAdapter { public MyCustomCountSpliterDataHandlingAdapter() : base(8, Encoding.UTF8.GetBytes("#")) @@ -97,3 +102,14 @@ internal class MyCustomCountSpliterDataHandlingAdapter : CustomCountSpliterDataH return new MyCountSpliterRequestInfo(dataSpan.ToString(Encoding.UTF8)); } } + +internal class MyCountSpliterRequestInfo : IRequestInfo +{ + public string Data { get; private set; } + + public MyCountSpliterRequestInfo(string data) + { + this.Data = data; + } +} +#endregion \ No newline at end of file diff --git a/examples/Adapter/CustomFixedHeaderConsoleApp/CustomFixedHeaderConsoleApp.csproj b/examples/Adapter/CustomFixedHeaderConsoleApp/CustomFixedHeaderConsoleApp.csproj index e215e7ade..e79c4df47 100644 --- a/examples/Adapter/CustomFixedHeaderConsoleApp/CustomFixedHeaderConsoleApp.csproj +++ b/examples/Adapter/CustomFixedHeaderConsoleApp/CustomFixedHeaderConsoleApp.csproj @@ -8,6 +8,6 @@ - + diff --git a/examples/Adapter/CustomFixedHeaderConsoleApp/Program.cs b/examples/Adapter/CustomFixedHeaderConsoleApp/Program.cs index a43aca56a..bcb5fedd8 100644 --- a/examples/Adapter/CustomFixedHeaderConsoleApp/Program.cs +++ b/examples/Adapter/CustomFixedHeaderConsoleApp/Program.cs @@ -29,23 +29,28 @@ internal class Program Console.ReadKey(); for (var i = 0; i < 10; i++) { - var myRequestInfo = new MyFixedHeaderRequestInfo() + var myRequestInfo = new MyDataClass() { - Body = Encoding.UTF8.GetBytes("hello"), + Data = Encoding.UTF8.GetBytes("hello"), DataType = (byte)i, OrderType = (byte)i }; //构建发送数据 - using (var byteBlock = new ByteBlock(1024)) + var byteBlock = new ByteBlock(1024); + try { - byteBlock.WriteByte((byte)(myRequestInfo.Body.Length + 2));//先写长度,因为该长度还包含数据类型和指令类型,所以+2 - byteBlock.WriteByte(myRequestInfo.DataType);//然后数据类型 - byteBlock.WriteByte(myRequestInfo.OrderType);//然后指令类型 - byteBlock.Write(myRequestInfo.Body);//再写数据 + WriterExtension.WriteValue(ref byteBlock, (byte)(myRequestInfo.Data.Length + 2));//先写长度,因为该长度还包含数据类型和指令类型,所以+2 + WriterExtension.WriteValue(ref byteBlock, myRequestInfo.DataType);//然后数据类型 + WriterExtension.WriteValue(ref byteBlock, myRequestInfo.OrderType);//然后指令类型 + byteBlock.Write(myRequestInfo.Data);//再写数据 await client.SendAsync(byteBlock.Memory); } + finally + { + byteBlock.Dispose(); + } } } } @@ -70,16 +75,18 @@ internal class Program private static async Task CreateService() { var service = new TcpService(); + + #region 接收自定义固定包头适配器 service.Received = (client, e) => { //从客户端收到信息 - - if (e.RequestInfo is MyFixedHeaderRequestInfo myRequest) + if (e.RequestInfo is MyDataClass myRequest) { - client.Logger.Info($"已从{client.Id}接收到:DataType={myRequest.DataType},OrderType={myRequest.OrderType},消息={Encoding.UTF8.GetString(myRequest.Body)}"); + client.Logger.Info($"已从{client.Id}接收到:DataType={myRequest.DataType},OrderType={myRequest.OrderType},消息={Encoding.UTF8.GetString(myRequest.Data)}"); } return Task.CompletedTask; }; + #endregion await service.SetupAsync(new TouchSocketConfig()//载入配置 .SetListenIPHosts("tcp://127.0.0.1:7789", 7790)//同时监听两个地址 @@ -97,30 +104,34 @@ internal class Program return service; } } - -public class MyFixedHeaderCustomDataHandlingAdapter : CustomFixedHeaderDataHandlingAdapter +#region 创建自定义固定包头适配器 {13,46-61,63-79} +/// +/// 第1个字节表示指令类型 +/// 第2字节表示数据类型 +/// 第3字节表示后续数据的长度。使用ushort(大端)表示,最大长度为65535 +/// 后续字节表示载荷数据 +/// 最后2字节表示CRC16校验码 +/// +public class MyFixedHeaderCustomDataHandlingAdapter : CustomFixedHeaderDataHandlingAdapter { /// /// 接口实现,指示固定包头长度 /// - public override int HeaderLength => 3; + public override int HeaderLength => 4; /// /// 获取新实例 /// /// - protected override MyFixedHeaderRequestInfo GetInstance() + protected override MyDataClass GetInstance() { - return new MyFixedHeaderRequestInfo(); + return new MyDataClass(); } } -public class MyFixedHeaderRequestInfo : IFixedHeaderRequestInfo +public class MyDataClass : IFixedHeaderRequestInfo { - /// - /// 接口实现,标识数据长度 - /// - public int BodyLength { get; private set; } + private int m_length; /// /// 自定义属性,标识数据类型 @@ -135,24 +146,43 @@ public class MyFixedHeaderRequestInfo : IFixedHeaderRequestInfo /// /// 自定义属性,标识实际数据 /// - public byte[] Body { get; set; } + public byte[] Data { get; set; } - public bool OnParsingBody(ReadOnlySpan body) + int IFixedHeaderRequestInfo.BodyLength => this.m_length; + + bool IFixedHeaderRequestInfo.OnParsingBody(ReadOnlySpan body) { - if (body.Length == this.BodyLength) + var data = body.Slice(0, body.Length - 2);//最后2个字节是CRC16校验码 + var crc16 = TouchSocketBitConverter.BigEndian.To(body.Slice(body.Length - 2, 2)); + + //计算CRC16 + var newCrc16 = Crc.Crc16Value(data); + if (crc16 != newCrc16) { - this.Body = body.ToArray(); - return true; + //CRC校验失败 + throw new Exception("CRC校验失败"); } - return false; - } - public bool OnParsingHeader(ReadOnlySpan header) - { - //在该示例中,第一个字节表示后续的所有数据长度,但是header设置的是3,所以后续还应当接收length-2个长度。 - this.BodyLength = header[0] - 2; - this.DataType = header[1]; - this.OrderType = header[2]; + this.Data = data.ToArray(); return true; } -} \ No newline at end of file + + bool IFixedHeaderRequestInfo.OnParsingHeader(ReadOnlySpan header) + { + //这里header长度已经经过验证,一定是4字节 + + //获取指令类型 + this.OrderType = header[0]; + + //获取数据类型 + this.DataType = header[1]; + + var payloadLength = TouchSocketBitConverter.BigEndian.To(header.Slice(2, 2)); + + + this.m_length = payloadLength + 2;//+2是因为还包含Crc16的长度 + + return true; + } +} +#endregion diff --git a/examples/Adapter/CustomJsonDataHandlingAdapterConsoleApp/CustomJsonDataHandlingAdapterConsoleApp.csproj b/examples/Adapter/CustomJsonDataHandlingAdapterConsoleApp/CustomJsonDataHandlingAdapterConsoleApp.csproj index 18c1c8753..e08d18664 100644 --- a/examples/Adapter/CustomJsonDataHandlingAdapterConsoleApp/CustomJsonDataHandlingAdapterConsoleApp.csproj +++ b/examples/Adapter/CustomJsonDataHandlingAdapterConsoleApp/CustomJsonDataHandlingAdapterConsoleApp.csproj @@ -1,4 +1,4 @@ - + Exe @@ -8,6 +8,6 @@ - + diff --git a/examples/Adapter/CustomJsonDataHandlingAdapterConsoleApp/Program.cs b/examples/Adapter/CustomJsonDataHandlingAdapterConsoleApp/Program.cs index 5e4cf3d29..8d4f05e7e 100644 --- a/examples/Adapter/CustomJsonDataHandlingAdapterConsoleApp/Program.cs +++ b/examples/Adapter/CustomJsonDataHandlingAdapterConsoleApp/Program.cs @@ -1,3 +1,15 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + using System.Text; using TouchSocket.Core; using TouchSocket.Sockets; @@ -48,6 +60,7 @@ internal class Program private static async Task CreateService() { var service = new TcpService(); + #region 接收自定义Json适配器 service.Received = (client, e) => { //从客户端收到信息 @@ -58,6 +71,7 @@ internal class Program } return Task.CompletedTask; }; + #endregion await service.SetupAsync(new TouchSocketConfig()//载入配置 .SetListenIPHosts("tcp://127.0.0.1:7789", 7790)//同时监听两个地址 @@ -76,6 +90,19 @@ internal class Program } } + +#region 创建自定义Json适配器 +internal class MyCustomJsonDataHandlingAdapter : CustomJsonDataHandlingAdapter +{ + public MyCustomJsonDataHandlingAdapter() : base(Encoding.UTF8) + { + } + + protected override MyJsonClass GetInstance(JsonPackageKind packageKind, Encoding encoding, ReadOnlyMemory dataMemory, ReadOnlyMemory impurityMemory) + { + return new MyJsonClass(packageKind, encoding, dataMemory, impurityMemory); + } +} internal class MyJsonClass : IRequestInfo { public MyJsonClass(JsonPackageKind packageKind, Encoding encoding, ReadOnlyMemory dataMemory, ReadOnlyMemory impurityMemory) @@ -92,14 +119,4 @@ internal class MyJsonClass : IRequestInfo public ReadOnlyMemory ImpurityMemory { get; } } -internal class MyCustomJsonDataHandlingAdapter : CustomJsonDataHandlingAdapter -{ - public MyCustomJsonDataHandlingAdapter() : base(Encoding.UTF8) - { - } - - protected override MyJsonClass GetInstance(JsonPackageKind packageKind, Encoding encoding, ReadOnlyMemory dataMemory, ReadOnlyMemory impurityMemory) - { - return new MyJsonClass(packageKind, encoding, dataMemory, impurityMemory); - } -} +#endregion diff --git a/examples/Adapter/CustomUnfixedHeaderConsoleApp/CustomUnfixedHeaderConsoleApp.csproj b/examples/Adapter/CustomUnfixedHeaderConsoleApp/CustomUnfixedHeaderConsoleApp.csproj index d6cfa539a..ddd0e8423 100644 --- a/examples/Adapter/CustomUnfixedHeaderConsoleApp/CustomUnfixedHeaderConsoleApp.csproj +++ b/examples/Adapter/CustomUnfixedHeaderConsoleApp/CustomUnfixedHeaderConsoleApp.csproj @@ -8,7 +8,7 @@ - + diff --git a/examples/Adapter/CustomUnfixedHeaderConsoleApp/Program.cs b/examples/Adapter/CustomUnfixedHeaderConsoleApp/Program.cs index 81e01c795..9cd1a45ec 100644 --- a/examples/Adapter/CustomUnfixedHeaderConsoleApp/Program.cs +++ b/examples/Adapter/CustomUnfixedHeaderConsoleApp/Program.cs @@ -1,3 +1,15 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + using System.Text; using TouchSocket.Core; using TouchSocket.Sockets; @@ -17,23 +29,28 @@ internal class Program Console.ReadKey(); for (var i = 0; i < 10; i++) { - var myRequestInfo = new MyUnfixedHeaderRequestInfo() + var myRequestInfo = new MyDataClass() { - Body = Encoding.UTF8.GetBytes("hello"), + Data = Encoding.UTF8.GetBytes("hello"), DataType = (byte)i, OrderType = (byte)i }; //构建发送数据 - using (var byteBlock = new ByteBlock(1024)) + var byteBlock = new ByteBlock(1024); + try { - byteBlock.WriteByte((byte)(myRequestInfo.Body.Length + 2));//先写长度,因为该长度还包含数据类型和指令类型,所以+2 - byteBlock.WriteByte(myRequestInfo.DataType);//然后数据类型 - byteBlock.WriteByte(myRequestInfo.OrderType);//然后指令类型 - byteBlock.Write(myRequestInfo.Body);//再写数据 + WriterExtension.WriteValue(ref byteBlock, (byte)(myRequestInfo.Data.Length + 2));//先写长度,因为该长度还包含数据类型和指令类型,所以+2 + WriterExtension.WriteValue(ref byteBlock, myRequestInfo.DataType);//然后数据类型 + WriterExtension.WriteValue(ref byteBlock, myRequestInfo.OrderType);//然后指令类型 + byteBlock.Write(myRequestInfo.Data);//再写数据 await client.SendAsync(byteBlock.Memory); } + finally + { + byteBlock.Dispose(); + } } } } @@ -58,16 +75,16 @@ internal class Program private static async Task CreateService() { var service = new TcpService(); + #region 接收自定义非固定包头适配器 service.Received = (client, e) => { - //从客户端收到信息 - - if (e.RequestInfo is MyUnfixedHeaderRequestInfo myRequest) + if (e.RequestInfo is MyDataClass myRequest) { - client.Logger.Info($"已从{client.Id}接收到:DataType={myRequest.DataType},OrderType={myRequest.OrderType},消息={Encoding.UTF8.GetString(myRequest.Body)}"); + client.Logger.Info($"已从{client.Id}接收到:DataType={myRequest.DataType},OrderType={myRequest.OrderType},消息={Encoding.UTF8.GetString(myRequest.Data)}"); } return Task.CompletedTask; }; + #endregion await service.SetupAsync(new TouchSocketConfig()//载入配置 .SetListenIPHosts("tcp://127.0.0.1:7789", 7790)//同时监听两个地址 @@ -86,15 +103,23 @@ internal class Program } } -public class MyUnfixedHeaderCustomDataHandlingAdapter : CustomUnfixedHeaderDataHandlingAdapter +#region 创建自定义非固定包头适配器 +/// +/// 第1个字节表示指令类型 +/// 第2字节表示数据类型 +/// 第3字节表示后续数据的长度。使用ushort(大端)表示,最大长度为65535 +/// 后续字节表示载荷数据 +/// 最后2字节表示CRC16校验码 +/// +public class MyUnfixedHeaderCustomDataHandlingAdapter : CustomUnfixedHeaderDataHandlingAdapter { - protected override MyUnfixedHeaderRequestInfo GetInstance() + protected override MyDataClass GetInstance() { - return new MyUnfixedHeaderRequestInfo(); + return new MyDataClass(); } } -public class MyUnfixedHeaderRequestInfo : IUnfixedHeaderRequestInfo +public class MyDataClass : IUnfixedHeaderRequestInfo { /// /// 接口实现,标识数据长度 @@ -114,53 +139,60 @@ public class MyUnfixedHeaderRequestInfo : IUnfixedHeaderRequestInfo /// /// 自定义属性,标识实际数据 /// - public byte[] Body { get; set; } - - + public byte[] Data { get; set; } public int HeaderLength { get; private set; } public bool OnParsingBody(ReadOnlySpan body) { - if (body.Length == this.BodyLength) + var data = body.Slice(0, body.Length - 2);//最后2个字节是CRC16校验码 + var crc16 = TouchSocketBitConverter.BigEndian.To(body.Slice(body.Length - 2, 2)); + + //计算CRC16 + var newCrc16 = Crc.Crc16Value(data); + if (crc16 != newCrc16) { - this.Body = body.ToArray(); - return true; + //CRC校验失败 + throw new Exception("CRC校验失败"); } - return false; + + this.Data = data.ToArray(); + return true; } - public bool OnParsingHeader(ref TByteBlock byteBlock) where TByteBlock : IByteBlock + public bool OnParsingHeader(ref TReader reader) where TReader : IBytesReader { //在使用不固定包头解析时 //【首先】需要先解析包头 - if (byteBlock.CanReadLength < 3) + if (reader.BytesRemaining < 4) { //即直接缓存 return false; } - //先保存一下初始游标,如果解析时还需要缓存,可能需要回退游标 - var position = byteBlock.Position; + //【然后】先获取4字节包头 + var header = reader.GetSpan(4); - //【然后】ReadToSpan会递增游标,所以不需要再递增游标 - var header = byteBlock.ReadToSpan(3); - - //如果使用Span自行裁剪的话,就需要手动递增游标 - //var header=byteBlock.Span.Slice(position,3); - //byteBlock.Position += 3; + reader.Advance(4); //推进游标到包头结束位置 //【然后】解析包头,和BodyLength - //在该示例中,第一个字节表示后续的所有数据长度,但是header设置的是3,所以后续还应当接收length-2个长度。 - this.BodyLength = header[0] - 2; + //获取指令类型 + this.OrderType = header[0]; + + //获取数据类型 this.DataType = header[1]; - this.OrderType = header[2]; + + var payloadLength = TouchSocketBitConverter.BigEndian.To(header.Slice(2, 2)); + //【最后】对HeaderLength做有效赋值 - this.HeaderLength = 3; + this.HeaderLength = 4; + this.BodyLength = payloadLength + 2; return true; } } + +#endregion diff --git a/examples/Adapter/JsonPackageAdapterConsoleApp/JsonPackageAdapterConsoleApp.csproj b/examples/Adapter/JsonPackageAdapterConsoleApp/JsonPackageAdapterConsoleApp.csproj index ab8f18f2e..e79c4df47 100644 --- a/examples/Adapter/JsonPackageAdapterConsoleApp/JsonPackageAdapterConsoleApp.csproj +++ b/examples/Adapter/JsonPackageAdapterConsoleApp/JsonPackageAdapterConsoleApp.csproj @@ -1,4 +1,4 @@ - + Exe @@ -8,6 +8,6 @@ - + diff --git a/examples/Adapter/JsonPackageAdapterConsoleApp/Program.cs b/examples/Adapter/JsonPackageAdapterConsoleApp/Program.cs index af3bfb719..b656cf4e6 100644 --- a/examples/Adapter/JsonPackageAdapterConsoleApp/Program.cs +++ b/examples/Adapter/JsonPackageAdapterConsoleApp/Program.cs @@ -1,3 +1,15 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + using System.Text; using TouchSocket.Core; using TouchSocket.Sockets; @@ -42,9 +54,10 @@ internal class Program private static async Task CreateService() { var service = new TcpService(); + + #region 内置包Json适配器按JsonPackage解析 {3-11} service.Received = async (client, e) => { - //从客户端收到信息 if (e.RequestInfo is JsonPackage jsonPackage) { var sb = new StringBuilder(); @@ -54,11 +67,9 @@ internal class Program sb.Append($"杂质数据:{jsonPackage.ImpurityData.Span.ToString(Encoding.UTF8)}"); client.Logger.Info(sb.ToString()); } - - await e.InvokeNext(); }; - + #endregion await service.SetupAsync(new TouchSocketConfig()//载入配置 .SetListenIPHosts("tcp://127.0.0.1:7789", 7790)//同时监听两个地址 .SetTcpDataHandlingAdapter(() => new JsonPackageAdapter(Encoding.UTF8)) diff --git a/examples/Adapter/MultipacketAdapterConsoleApp/MultipacketAdapterConsoleApp.csproj b/examples/Adapter/MultipacketAdapterConsoleApp/MultipacketAdapterConsoleApp.csproj new file mode 100644 index 000000000..a081538ee --- /dev/null +++ b/examples/Adapter/MultipacketAdapterConsoleApp/MultipacketAdapterConsoleApp.csproj @@ -0,0 +1,14 @@ + + + + Exe + net9.0 + enable + enable + + + + + + + diff --git a/examples/Adapter/MultipacketAdapterConsoleApp/Program.cs b/examples/Adapter/MultipacketAdapterConsoleApp/Program.cs new file mode 100644 index 000000000..afe76e247 --- /dev/null +++ b/examples/Adapter/MultipacketAdapterConsoleApp/Program.cs @@ -0,0 +1,163 @@ +using Newtonsoft.Json; +using System.Text; +using System.Threading.Tasks; +using TouchSocket.Core; +using TouchSocket.Sockets; + +namespace MultipacketAdapterConsoleApp; +internal class Program +{ + static async Task Main(string[] args) + { + var tcpService = await GetTcpService(); + var tcpClient = await GetTcpClient(); + + while (true) + { + var input = Console.ReadLine(); + if (input.HasValue()) + { + if (input == "1") + { + await tcpClient.SendAsync(new MyPack1() { MyProperty1 = 10 }); + } + else if (input == "2") + { + await tcpClient.SendAsync(new MyPack2() { MyProperty1 = "Hello World" }); + } + else + { + throw new Exception("输入错误"); + } + } + } + } + + static async Task GetTcpClient() + { + var tcpClient = new TcpClient(); + await tcpClient.SetupAsync(new TouchSocketConfig() + .SetTcpDataHandlingAdapter(() => new MyAdapter()) + .SetRemoteIPHost("tcp://127.0.0.1:7789")); + await tcpClient.ConnectAsync(); + + return tcpClient; + } + + static async Task GetTcpService() + { + var service = new TcpService(); + + service.Received = async (client, e) => + { + //从客户端收到信息 + switch (e.RequestInfo) + { + case MyPack1 pack: + { + Console.WriteLine($"收到MyPack1,MyProperty1={pack.MyProperty1}"); + break; + } + case MyPack2 pack: + { + Console.WriteLine($"收到MyPack2,MyProperty1={pack.MyProperty1}"); + break; + } + default: + break; + } + await EasyTask.CompletedTask; + }; + + await service.SetupAsync(new TouchSocketConfig() + .SetListenIPHosts(7789) + .SetTcpDataHandlingAdapter(() => new MyAdapter())); + + await service.StartAsync();//启动 + return service; + } +} + +class MyPackBase : IRequestInfo +{ + +} + +class MyPack1 : MyPackBase +{ + public int MyProperty1 { get; set; } +} + +class MyPack2 : MyPackBase +{ + public string? MyProperty1 { get; set; } +} + +class MyAdapter : CustomDataHandlingAdapter +{ + protected override FilterResult Filter(ref TReader reader, bool beCached, ref MyPackBase request) + { + if (reader.BytesRemaining < 5) + { + return FilterResult.Cache; + } + + var header = reader.GetSpan(5); + var type = header.ReadValue(); + + var length = header.ReadValue(EndianType.Big); + if (reader.BytesRemaining < length + 5) + { + return FilterResult.Cache; + } + + reader.Advance(5); + var body = reader.GetSpan(length); + var json = body.ToUtf8String(); + switch (type) + { + case 1: + request = JsonConvert.DeserializeObject(json); + break; + case 2: + request = JsonConvert.DeserializeObject(json); + break; + default: + request = null; + break; + } + reader.Advance(length); + return FilterResult.Success; + } + + public override bool CanSendRequestInfo => true; + + public override void SendInput(ref TWriter writer, IRequestInfo requestInfo) + { + switch (requestInfo) + { + case MyPack1 pack1: + { + WriterExtension.WriteValue(ref writer, (byte)1); + var data = JsonConvert.SerializeObject(pack1); + WriterAnchor writerAnchor = new WriterAnchor(ref writer, 4); + WriterExtension.WriteNormalString(ref writer, data, Encoding.UTF8); + var span = writerAnchor.Rewind(ref writer, out var length); + span.WriteValue(length, EndianType.Big); + break; + } + case MyPack2 pack2: + { + WriterExtension.WriteValue(ref writer, (byte)2); + var data = JsonConvert.SerializeObject(pack2); + WriterAnchor writerAnchor = new WriterAnchor(ref writer, 4); + WriterExtension.WriteNormalString(ref writer, data, Encoding.UTF8); + var span = writerAnchor.Rewind(ref writer, out var length); + span.WriteValue(length, EndianType.Big); + break; + } + default: + break; + } + } +} \ No newline at end of file diff --git a/examples/Adapter/PackageAdapterConsoleApp/PackageAdapterConsoleApp.csproj b/examples/Adapter/PackageAdapterConsoleApp/PackageAdapterConsoleApp.csproj index 897fb96ef..e08d18664 100644 --- a/examples/Adapter/PackageAdapterConsoleApp/PackageAdapterConsoleApp.csproj +++ b/examples/Adapter/PackageAdapterConsoleApp/PackageAdapterConsoleApp.csproj @@ -8,6 +8,6 @@ - + diff --git a/examples/Adapter/PackageAdapterConsoleApp/Program.cs b/examples/Adapter/PackageAdapterConsoleApp/Program.cs index a5ef78c0b..2b66268c8 100644 --- a/examples/Adapter/PackageAdapterConsoleApp/Program.cs +++ b/examples/Adapter/PackageAdapterConsoleApp/Program.cs @@ -60,13 +60,16 @@ internal class Program private static async Task CreateService() { var service = new TcpService(); + + #region 内置包适配器按Memory解析 {3} service.Received = (client, e) => { - //从客户端收到信息 - var mes = e.ByteBlock.Span.ToString(Encoding.UTF8);//注意:数据长度是byteBlock.Length + var mes = e.Memory.Span.ToString(Encoding.UTF8);//注意:数据长度是byteBlock.Length client.Logger.Info($"已从{client.Id}接收到信息:{mes}"); return EasyTask.CompletedTask; }; + #endregion + await service.SetupAsync(new TouchSocketConfig()//载入配置 .SetListenIPHosts("tcp://127.0.0.1:7789", 7790)//同时监听两个地址 @@ -83,6 +86,33 @@ internal class Program service.Logger.Info("服务器已启动"); return service; } + + private static void Test() + { + var config = new TouchSocketConfig(); + + #region 示例内置固定包头适配器 + config.SetTcpDataHandlingAdapter(() => new FixedHeaderPackageAdapter() { FixedHeaderType = FixedHeaderType.Int }); + #endregion + + #region 示例内置固定长度适配器 + config.SetTcpDataHandlingAdapter(() => new FixedSizePackageAdapter(10)); + #endregion + + #region 示例内置终止字符适配器 + config.SetTcpDataHandlingAdapter(() => new TerminatorPackageAdapter("\r\n")); + #endregion + + #region 示例内置周期时间适配器 + config.SetTcpDataHandlingAdapter(() => new PeriodPackageAdapter() { CacheTimeout = TimeSpan.FromMicroseconds(100) }); + #endregion + + #region 示例内置Json适配器 + config.SetTcpDataHandlingAdapter(() => new JsonPackageAdapter()); + #endregion + + + } } internal class MyFixedSizePackageAdapter : FixedSizePackageAdapter @@ -90,10 +120,4 @@ internal class MyFixedSizePackageAdapter : FixedSizePackageAdapter public MyFixedSizePackageAdapter(int fixedSize) : base(fixedSize) { } - - protected override Task PreviewSendAsync(ReadOnlyMemory memory) - { - //重写之后直接发送,当然也可以自己判断一些信息 - return this.GoSendAsync(memory); - } } \ No newline at end of file diff --git a/examples/Adapter/RawAdapterConsoleApp/Program.cs b/examples/Adapter/RawAdapterConsoleApp/Program.cs new file mode 100644 index 000000000..30bd5dd48 --- /dev/null +++ b/examples/Adapter/RawAdapterConsoleApp/Program.cs @@ -0,0 +1,225 @@ +//------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +//------------------------------------------------------------------------------ + +using System; +using System.Text; +using System.Threading.Tasks; +using TouchSocket.Core; +using TouchSocket.NamedPipe; +using TouchSocket.SerialPorts; +using TouchSocket.Sockets; + +namespace RawAdapterConsoleApp; + +internal class Program +{ + private static async Task Main(string[] args) + { + var service = await CreateService(); + var client = await CreateClient(); + + ConsoleLogger.Default.Info("输入任意内容,回车发送(将会循环发送10次)"); + while (true) + { + var str = Console.ReadLine(); + for (var i = 0; i < 10; i++) + { + await client.SendAsync(str); + } + } + } + + private static async Task CreateClient() + { + var client = new TcpClient(); + //载入配置 + await client.SetupAsync(new TouchSocketConfig() + .SetRemoteIPHost("127.0.0.1:7789") + .SetTcpDataHandlingAdapter(() => new MyRawDataHandleAdapter()) + .ConfigureContainer(a => + { + a.AddConsoleLogger();//添加一个日志注入 + })); + + await client.ConnectAsync();//调用连接,当连接不成功时,会抛出异常。 + client.Logger.Info("客户端成功连接"); + return client; + } + + private static async Task CreateService() + { + var service = new TcpService(); + service.Received = (client, e) => + { + //从客户端收到信息 + var mes = e.Memory.Span.ToString(Encoding.UTF8); + client.Logger.Info($"已从{client.Id}接收到信息:{mes}"); + return Task.CompletedTask; + }; + + await service.SetupAsync(new TouchSocketConfig()//载入配置 + .SetListenIPHosts("tcp://127.0.0.1:7789", 7790)//同时监听两个地址 + .SetTcpDataHandlingAdapter(() => new PeriodPackageAdapter() { CacheTimeout = TimeSpan.FromSeconds(1) }) + .ConfigureContainer(a => + { + a.AddConsoleLogger();//添加一个控制台日志注入(注意:在maui中控制台日志不可用) + }) + .ConfigurePlugins(a => + { + //a.Add();//此处可以添加插件 + })); + await service.StartAsync();//启动 + service.Logger.Info("服务器已启动"); + return service; + } + + public static void Config() + { + var config = new TouchSocketConfig(); + + #region 配置使用Tcp适配器 + config.SetTcpDataHandlingAdapter(() => new TerminatorPackageAdapter("\r\n")); + #endregion + + #region 配置使用NamedPipe适配器 + config.SetNamedPipeDataHandlingAdapter(() => new TerminatorPackageAdapter("\r\n")); + #endregion + + #region 配置使用Serial适配器 + config.SetSerialDataHandlingAdapter(() => new TerminatorPackageAdapter("\r\n")); + #endregion + + } +} + +#region 使用原始数据适配器解析 +/// +/// 第1个字节表示指令类型 +/// 第2字节表示数据类型 +/// 第3字节表示后续数据的长度。使用ushort(大端)表示,最大长度为65535 +/// 后续字节表示载荷数据 +/// 最后2字节表示CRC16校验码 +/// +internal class MyRawDataHandleAdapter : SingleStreamDataHandlingAdapter +{ + private MyDataClass m_myDataClass; + private int m_payloadLength; + protected override async Task PreviewReceivedAsync(TReader reader) + { + while (reader.BytesRemaining > 0) + { + if (this.m_myDataClass == null) + { + //首次接收该解析对象 + + if (reader.BytesRemaining < 4) + { + //如果剩余数据小于4个字节,则继续等待 + return; + } + + //读取前4个字节 + var header = reader.GetSpan(4); + + //推进已读取的4个字节 + reader.Advance(4); + + //获取指令类型 + var orderType = header[0]; + //获取数据类型 + var dataType = header[1]; + + //创建数据对象 + this.m_myDataClass = new MyDataClass() + { + OrderType = orderType, + DataType = dataType + }; + + //获取载荷长度 + this.m_payloadLength = TouchSocketBitConverter.BigEndian.To(header.Slice(2, 2)); + + await this.ParseData(reader); + } + else + { + //已经读取过头部,继续读取剩余数据 + await this.ParseData(reader); + } + } + } + + private async Task ParseData(TReader reader) + where TReader : class, IBytesReader + { + //判断剩余数据是否足够,+2是因为最后2个字节是CRC16校验码 + if (reader.BytesRemaining < this.m_payloadLength + 2) + { + return; + } + + //读取数据 + var data = reader.GetSpan(this.m_payloadLength); + reader.Advance(this.m_payloadLength); + + //读取CRC16校验码 + var crcData = reader.GetSpan(2); + reader.Advance(2); + + //转换CRC16校验码为ushort,相比于byte[],更节省内存 + var crc16 = TouchSocketBitConverter.BigEndian.To(crcData); + + //计算CRC16 + var newCrc16 = Crc.Crc16Value(data); + if (crc16 != newCrc16) + { + //CRC校验失败 + throw new Exception("CRC校验失败"); + } + + //保存数据 + this.m_myDataClass.Data = data.ToArray(); + + //至此,数据接收完成,可以进行投递处理 + + //此处可以选择投递方式,此处选择了使用IRequestInfo投递 + await base.GoReceivedAsync(ReadOnlyMemory.Empty, this.m_myDataClass); + + //清空数据,准备下次接收 + this.m_myDataClass = null; + this.m_payloadLength = 0; + } +} + +/// +/// 定义数据对象 +/// +internal class MyDataClass : IRequestInfo +{ + public byte OrderType { get; set; } + public byte DataType { get; set; } + public byte[] Data { get; set; } +} +#endregion + + +#region 示例Tcp客户端直接设置适配器 {6} +internal class MyTcpClient : TcpClient +{ + protected override Task OnTcpConnecting(ConnectingEventArgs e) + { + //直接设置适配器到当前客户端 + base.SetAdapter(new TerminatorPackageAdapter("\r\n")); + return base.OnTcpConnecting(e); + } +} +#endregion diff --git a/examples/Adapter/RawAdapterConsoleApp/RawAdapterConsoleApp.csproj b/examples/Adapter/RawAdapterConsoleApp/RawAdapterConsoleApp.csproj new file mode 100644 index 000000000..bde18f61d --- /dev/null +++ b/examples/Adapter/RawAdapterConsoleApp/RawAdapterConsoleApp.csproj @@ -0,0 +1,13 @@ + + + + Exe + net9.0 + + + + + + + + diff --git a/examples/Adapter/TLVWinFormsApp/Form1.Designer.cs b/examples/Adapter/TLVWinFormsApp/Form1.Designer.cs deleted file mode 100644 index 9b3e38786..000000000 --- a/examples/Adapter/TLVWinFormsApp/Form1.Designer.cs +++ /dev/null @@ -1,201 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -namespace TLVWinFormsApp -{ - partial class Form1 - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.button1 = new System.Windows.Forms.Button(); - this.button2 = new System.Windows.Forms.Button(); - this.listBox1 = new System.Windows.Forms.ListBox(); - this.button3 = new System.Windows.Forms.Button(); - this.label1 = new System.Windows.Forms.Label(); - this.label2 = new System.Windows.Forms.Label(); - this.textBox1 = new System.Windows.Forms.TextBox(); - this.numericUpDown1 = new System.Windows.Forms.NumericUpDown(); - this.button4 = new System.Windows.Forms.Button(); - this.button5 = new System.Windows.Forms.Button(); - ((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).BeginInit(); - this.SuspendLayout(); - // - // button1 - // - this.button1.Location = new System.Drawing.Point(61, 54); - this.button1.Name = "button1"; - this.button1.Size = new System.Drawing.Size(150, 46); - this.button1.TabIndex = 0; - this.button1.Text = "启动服务器"; - this.button1.UseVisualStyleBackColor = true; - this.button1.Click += new System.EventHandler(this.button1_Click); - // - // button2 - // - this.button2.Location = new System.Drawing.Point(249, 54); - this.button2.Name = "button2"; - this.button2.Size = new System.Drawing.Size(150, 46); - this.button2.TabIndex = 1; - this.button2.Text = "客户端连接"; - this.button2.UseVisualStyleBackColor = true; - this.button2.Click += new System.EventHandler(this.button2_Click); - // - // listBox1 - // - this.listBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.listBox1.FormattingEnabled = true; - this.listBox1.ItemHeight = 31; - this.listBox1.Location = new System.Drawing.Point(12, 160); - this.listBox1.Name = "listBox1"; - this.listBox1.Size = new System.Drawing.Size(1363, 283); - this.listBox1.TabIndex = 2; - // - // button3 - // - this.button3.Location = new System.Drawing.Point(1015, 54); - this.button3.Name = "button3"; - this.button3.Size = new System.Drawing.Size(150, 46); - this.button3.TabIndex = 3; - this.button3.Text = "发送"; - this.button3.UseVisualStyleBackColor = true; - this.button3.Click += new System.EventHandler(this.button3_Click); - // - // label1 - // - this.label1.AutoSize = true; - this.label1.Location = new System.Drawing.Point(493, 62); - this.label1.Name = "label1"; - this.label1.Size = new System.Drawing.Size(56, 31); - this.label1.TabIndex = 4; - this.label1.Text = "Tag"; - // - // label2 - // - this.label2.AutoSize = true; - this.label2.Location = new System.Drawing.Point(671, 62); - this.label2.Name = "label2"; - this.label2.Size = new System.Drawing.Size(78, 31); - this.label2.TabIndex = 5; - this.label2.Text = "Value"; - // - // textBox1 - // - this.textBox1.Location = new System.Drawing.Point(770, 59); - this.textBox1.Name = "textBox1"; - this.textBox1.Size = new System.Drawing.Size(200, 38); - this.textBox1.TabIndex = 6; - // - // numericUpDown1 - // - this.numericUpDown1.Location = new System.Drawing.Point(555, 60); - this.numericUpDown1.Maximum = new decimal(new int[] { - 65535, - 0, - 0, - 0}); - this.numericUpDown1.Minimum = new decimal(new int[] { - 10, - 0, - 0, - 0}); - this.numericUpDown1.Name = "numericUpDown1"; - this.numericUpDown1.Size = new System.Drawing.Size(95, 38); - this.numericUpDown1.TabIndex = 7; - this.numericUpDown1.Value = new decimal(new int[] { - 10, - 0, - 0, - 0}); - // - // button4 - // - this.button4.Location = new System.Drawing.Point(1187, 54); - this.button4.Name = "button4"; - this.button4.Size = new System.Drawing.Size(150, 46); - this.button4.TabIndex = 8; - this.button4.Text = "Ping"; - this.button4.UseVisualStyleBackColor = true; - this.button4.Click += new System.EventHandler(this.button4_Click); - // - // button5 - // - this.button5.Location = new System.Drawing.Point(1015, 108); - this.button5.Name = "button5"; - this.button5.Size = new System.Drawing.Size(150, 46); - this.button5.TabIndex = 9; - this.button5.Text = "连续发送"; - this.button5.UseVisualStyleBackColor = true; - this.button5.Click += new System.EventHandler(this.button5_Click); - // - // Form1 - // - this.AutoScaleDimensions = new System.Drawing.SizeF(14F, 31F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(1387, 450); - this.Controls.Add(this.button5); - this.Controls.Add(this.button4); - this.Controls.Add(this.numericUpDown1); - this.Controls.Add(this.textBox1); - this.Controls.Add(this.label2); - this.Controls.Add(this.label1); - this.Controls.Add(this.button3); - this.Controls.Add(this.listBox1); - this.Controls.Add(this.button2); - this.Controls.Add(this.button1); - this.Name = "Form1"; - this.Text = "Form1"; - ((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).EndInit(); - this.ResumeLayout(false); - this.PerformLayout(); - - } - - #endregion - - private Button button1; - private Button button2; - private ListBox listBox1; - private Button button3; - private Label label1; - private Label label2; - private TextBox textBox1; - private NumericUpDown numericUpDown1; - private Button button4; - private Button button5; - } -} \ No newline at end of file diff --git a/examples/Adapter/TLVWinFormsApp/Form1.cs b/examples/Adapter/TLVWinFormsApp/Form1.cs deleted file mode 100644 index 8a41286b1..000000000 --- a/examples/Adapter/TLVWinFormsApp/Form1.cs +++ /dev/null @@ -1,130 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System.Text; -using System.Threading.Tasks; -using TouchSocket.Core; -using TouchSocket.Sockets; - -namespace TLVWinFormsApp; - -public partial class Form1 : Form -{ - public Form1() - { - this.InitializeComponent(); - Control.CheckForIllegalCrossThreadCalls = false; - } - - private void ShowMsg(string msg) - { - this.listBox1.Items.Insert(0, msg); - } - - private readonly TcpService m_tcpService = new TcpService(); - - private void button1_Click(object sender, EventArgs e) - { - //�����յ���Ϣ�¼� - this.m_tcpService.Received = (client, e) => - { - if (e.RequestInfo is TLVDataFrame frame) - { - client.Logger.Info($"�������յ�,Tag={frame.Tag},Length={frame.Length},Value={(frame.Value != null ? Encoding.UTF8.GetString(frame.Value) : string.Empty)}"); - } - return EasyTask.CompletedTask; - }; - - var config = new TouchSocketConfig(); - config.SetListenIPHosts(new IPHost[] { new IPHost(7789) }) - .ConfigureContainer(a => - { - a.AddEasyLogger(this.ShowMsg); - }) - .ConfigurePlugins(a => - { - a.Add()//ʹ�ò�����൱���Զ�����������������������ӦPing�� - .SetLengthType(FixedHeaderType.Int);//����֧�ֵ�����������ͣ���ֵ����SetMaxPackageSizeӰ�졣 - }); - - //�������� - this.m_tcpService.SetupAsync(config); - - //���� - this.m_tcpService.StartAsync(); - this.m_tcpService.Logger.Info("�������ɹ�������"); - } - - private readonly TcpClient m_client = new TcpClient(); - - private void button2_Click(object sender, EventArgs e) - { - this.m_client.SetupAsync(new TouchSocketConfig() - .SetAdapterOption(new AdapterOption() - { - MaxPackageSize = 1024 * 1024 * 10 - }) - .ConfigureContainer(a => - { - a.AddEasyLogger(this.ShowMsg); - }) - //.SetDataHandlingAdapter(() => new TLVDataHandlingAdapter(FixedHeaderType.Int, verifyFunc: null))//���ʹ��TLVPlugin������˲����ʡ�ԡ� - .ConfigurePlugins(a => - { - a.Add()//ʹ�ò�����൱���Զ�����������������������ӦPing�� - .SetLengthType(FixedHeaderType.Int);//����֧�ֵ�����������ͣ���ֵ����SetMaxPackageSizeӰ�졣 - }) - .SetRemoteIPHost(new IPHost("127.0.0.1:7789"))); - this.m_client.ConnectAsync(); - - this.m_client.Logger.Info("���ӳɹ�"); - } - - private async void button3_Click(object sender, EventArgs e) - { - try - { - await this.m_client?.SendAsync(new ValueTLVDataFrame((ushort)this.numericUpDown1.Value, Encoding.UTF8.GetBytes(this.textBox1.Text))); - } - catch (Exception ex) - { - this.m_client.Logger.Exception(ex); - } - } - - private void button4_Click(object sender, EventArgs e) - { - try - { - this.m_client.Logger.Info($"ping={this.m_client?.PingWithTLV()}"); - } - catch (Exception ex) - { - this.m_client.Logger.Exception(ex); - } - } - - private async void button5_Click(object sender, EventArgs e) - { - for (var i = 0; i < 100; i++) - { - try - { - await this.m_client?.SendAsync(new ValueTLVDataFrame((ushort)this.numericUpDown1.Value, Encoding.UTF8.GetBytes(i.ToString()))); - } - catch (Exception ex) - { - this.m_client?.Logger.Exception(ex); - } - } - } -} \ No newline at end of file diff --git a/examples/Adapter/TLVWinFormsApp/Form1.resx b/examples/Adapter/TLVWinFormsApp/Form1.resx deleted file mode 100644 index f298a7be8..000000000 --- a/examples/Adapter/TLVWinFormsApp/Form1.resx +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/examples/Adapter/TLVWinFormsApp/Program.cs b/examples/Adapter/TLVWinFormsApp/Program.cs deleted file mode 100644 index ef96b0a85..000000000 --- a/examples/Adapter/TLVWinFormsApp/Program.cs +++ /dev/null @@ -1,37 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using TouchSocket.Core; - -namespace TLVWinFormsApp; - -internal static class Program -{ - /// - /// The main entry point for the application. - /// - [STAThread] - private static void Main() - { - try - { - Enterprise.ForTest(); - } - catch - { - } - // To customize application configuration such as set high DPI settings or default font, - // see https://aka.ms/applicationconfiguration. - ApplicationConfiguration.Initialize(); - Application.Run(new Form1()); - } -} \ No newline at end of file diff --git a/examples/Adapter/TLVWinFormsApp/TLVWinFormsApp.csproj b/examples/Adapter/TLVWinFormsApp/TLVWinFormsApp.csproj deleted file mode 100644 index 7ee6b3fe9..000000000 --- a/examples/Adapter/TLVWinFormsApp/TLVWinFormsApp.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - WinExe - net9.0-windows - enable - true - enable - - - - - - - - - - - - \ No newline at end of file diff --git a/examples/AvaloniaApplication/Client/AvaloniaApplication.Browser/AvaloniaApplication.Browser.csproj b/examples/AvaloniaApplication/Client/AvaloniaApplication.Browser/AvaloniaApplication.Browser.csproj index aca3c80fe..3e01a4482 100644 --- a/examples/AvaloniaApplication/Client/AvaloniaApplication.Browser/AvaloniaApplication.Browser.csproj +++ b/examples/AvaloniaApplication/Client/AvaloniaApplication.Browser/AvaloniaApplication.Browser.csproj @@ -1,4 +1,4 @@ - + net8.0-browser Exe @@ -6,7 +6,7 @@ - + diff --git a/examples/AvaloniaApplication/Client/AvaloniaApplication.Browser/Program.cs b/examples/AvaloniaApplication/Client/AvaloniaApplication.Browser/Program.cs index 5aa934a53..b44532a8a 100644 --- a/examples/AvaloniaApplication/Client/AvaloniaApplication.Browser/Program.cs +++ b/examples/AvaloniaApplication/Client/AvaloniaApplication.Browser/Program.cs @@ -1,4 +1,4 @@ -using System.Runtime.Versioning; +using System.Runtime.Versioning; using System.Threading.Tasks; using Avalonia; diff --git a/examples/AvaloniaApplication/Client/AvaloniaApplication.Desktop/AvaloniaApplication.Desktop.csproj b/examples/AvaloniaApplication/Client/AvaloniaApplication.Desktop/AvaloniaApplication.Desktop.csproj index b204ca110..bedc7120d 100644 --- a/examples/AvaloniaApplication/Client/AvaloniaApplication.Desktop/AvaloniaApplication.Desktop.csproj +++ b/examples/AvaloniaApplication/Client/AvaloniaApplication.Desktop/AvaloniaApplication.Desktop.csproj @@ -1,4 +1,4 @@ - + WinExe diff --git a/examples/AvaloniaApplication/Client/AvaloniaApplication/ViewModels/MainViewModel.cs b/examples/AvaloniaApplication/Client/AvaloniaApplication/ViewModels/MainViewModel.cs index db42d4862..db5e2ed1f 100644 --- a/examples/AvaloniaApplication/Client/AvaloniaApplication/ViewModels/MainViewModel.cs +++ b/examples/AvaloniaApplication/Client/AvaloniaApplication/ViewModels/MainViewModel.cs @@ -1,4 +1,4 @@ -using CommunityToolkit.Mvvm.Input; +using CommunityToolkit.Mvvm.Input; using System; using System.Diagnostics; using System.Runtime.Intrinsics.Arm; diff --git a/examples/AvaloniaApplication/Client/AvaloniaApplication/ViewModels/ViewModelBase.cs b/examples/AvaloniaApplication/Client/AvaloniaApplication/ViewModels/ViewModelBase.cs index 619a659af..651abaa7f 100644 --- a/examples/AvaloniaApplication/Client/AvaloniaApplication/ViewModels/ViewModelBase.cs +++ b/examples/AvaloniaApplication/Client/AvaloniaApplication/ViewModels/ViewModelBase.cs @@ -1,4 +1,4 @@ -using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.ComponentModel; namespace AvaloniaApplication.ViewModels; diff --git a/examples/AvaloniaApplication/Client/AvaloniaApplication/Views/MainView.axaml.cs b/examples/AvaloniaApplication/Client/AvaloniaApplication/Views/MainView.axaml.cs index f3bf2901f..9fa666bd0 100644 --- a/examples/AvaloniaApplication/Client/AvaloniaApplication/Views/MainView.axaml.cs +++ b/examples/AvaloniaApplication/Client/AvaloniaApplication/Views/MainView.axaml.cs @@ -1,4 +1,4 @@ -using Avalonia.Controls; +using Avalonia.Controls; namespace AvaloniaApplication.Views; diff --git a/examples/AvaloniaApplication/Client/AvaloniaApplication/Views/MainWindow.axaml.cs b/examples/AvaloniaApplication/Client/AvaloniaApplication/Views/MainWindow.axaml.cs index af15ba0ef..b5e74a501 100644 --- a/examples/AvaloniaApplication/Client/AvaloniaApplication/Views/MainWindow.axaml.cs +++ b/examples/AvaloniaApplication/Client/AvaloniaApplication/Views/MainWindow.axaml.cs @@ -1,4 +1,4 @@ -using Avalonia.Controls; +using Avalonia.Controls; namespace AvaloniaApplication.Views; diff --git a/examples/AvaloniaApplication/Server/WebServerApplication/AssemblyInfo.cs b/examples/AvaloniaApplication/Server/WebServerApplication/AssemblyInfo.cs index 400990bad..ce024bed7 100644 --- a/examples/AvaloniaApplication/Server/WebServerApplication/AssemblyInfo.cs +++ b/examples/AvaloniaApplication/Server/WebServerApplication/AssemblyInfo.cs @@ -1,3 +1,3 @@ -using TouchSocket.Rpc; +using TouchSocket.Rpc; [assembly: GeneratorRpcServerRegister] \ No newline at end of file diff --git a/examples/AvaloniaApplication/Server/WebServerApplication/Plugins/MyDmtpPlugin.cs b/examples/AvaloniaApplication/Server/WebServerApplication/Plugins/MyDmtpPlugin.cs index 09c4fe588..2f9b787e4 100644 --- a/examples/AvaloniaApplication/Server/WebServerApplication/Plugins/MyDmtpPlugin.cs +++ b/examples/AvaloniaApplication/Server/WebServerApplication/Plugins/MyDmtpPlugin.cs @@ -1,4 +1,4 @@ -using TouchSocket.Core; +using TouchSocket.Core; namespace WebServerApplication.Plugins { diff --git a/examples/AvaloniaApplication/Server/WebServerApplication/Program.cs b/examples/AvaloniaApplication/Server/WebServerApplication/Program.cs index 74558060f..2137d0da3 100644 --- a/examples/AvaloniaApplication/Server/WebServerApplication/Program.cs +++ b/examples/AvaloniaApplication/Server/WebServerApplication/Program.cs @@ -33,7 +33,7 @@ namespace WebServerApplication .ConfigurePlugins(a => { a.UseDmtpRpc(); - //Ӳ + //��Ӳ�� a.Add(); }); }); @@ -41,7 +41,7 @@ namespace WebServerApplication var app = builder.Build(); app.UseWebSockets(); - app.UseWebSocketDmtp("/WebSocketDmtp");//WebSocketDmtpUseWebSockets֮ʹá + app.UseWebSocketDmtp("/WebSocketDmtp");//WebSocketDmtp������UseWebSockets֮��ʹ�á� app.Run("http://localhost:5043"); } diff --git a/examples/AvaloniaApplication/Server/WebServerApplication/RpcServers/MyRpcServer.cs b/examples/AvaloniaApplication/Server/WebServerApplication/RpcServers/MyRpcServer.cs index b6ed034fe..ea5c2ed72 100644 --- a/examples/AvaloniaApplication/Server/WebServerApplication/RpcServers/MyRpcServer.cs +++ b/examples/AvaloniaApplication/Server/WebServerApplication/RpcServers/MyRpcServer.cs @@ -1,4 +1,4 @@ -using RpcLibrary.Shared.RpcServers; +using RpcLibrary.Shared.RpcServers; using System.ComponentModel; using TouchSocket.Dmtp.Rpc; using TouchSocket.Rpc; diff --git a/examples/AvaloniaApplication/Server/WebServerApplication/WebServerApplication.csproj b/examples/AvaloniaApplication/Server/WebServerApplication/WebServerApplication.csproj index 9996fbad4..02b8cdb7d 100644 --- a/examples/AvaloniaApplication/Server/WebServerApplication/WebServerApplication.csproj +++ b/examples/AvaloniaApplication/Server/WebServerApplication/WebServerApplication.csproj @@ -7,7 +7,7 @@ - + diff --git a/examples/AvaloniaApplication/Shared/RpcLibrary.Shared/RpcLibrary.Shared.csproj b/examples/AvaloniaApplication/Shared/RpcLibrary.Shared/RpcLibrary.Shared.csproj index dfd43e166..a9fe383fd 100644 --- a/examples/AvaloniaApplication/Shared/RpcLibrary.Shared/RpcLibrary.Shared.csproj +++ b/examples/AvaloniaApplication/Shared/RpcLibrary.Shared/RpcLibrary.Shared.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -7,7 +7,7 @@ - + diff --git a/examples/AvaloniaApplication/Shared/RpcLibrary.Shared/RpcServers/IMyRpcServer.cs b/examples/AvaloniaApplication/Shared/RpcLibrary.Shared/RpcServers/IMyRpcServer.cs index 7255f84ca..ff96ab0b9 100644 --- a/examples/AvaloniaApplication/Shared/RpcLibrary.Shared/RpcServers/IMyRpcServer.cs +++ b/examples/AvaloniaApplication/Shared/RpcLibrary.Shared/RpcServers/IMyRpcServer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; diff --git a/examples/BlogsDemos/AccessRestrictionsConsoleApp/AccessRestrictionsConsoleApp.csproj b/examples/BlogsDemos/AccessRestrictionsConsoleApp/AccessRestrictionsConsoleApp.csproj index f18b62f22..6608859ea 100644 --- a/examples/BlogsDemos/AccessRestrictionsConsoleApp/AccessRestrictionsConsoleApp.csproj +++ b/examples/BlogsDemos/AccessRestrictionsConsoleApp/AccessRestrictionsConsoleApp.csproj @@ -8,12 +8,12 @@ - - - - - - + + + + + + diff --git a/examples/BlogsDemos/AccessRestrictionsConsoleApp/Program.cs b/examples/BlogsDemos/AccessRestrictionsConsoleApp/Program.cs index dc1b22e1e..042905c88 100644 --- a/examples/BlogsDemos/AccessRestrictionsConsoleApp/Program.cs +++ b/examples/BlogsDemos/AccessRestrictionsConsoleApp/Program.cs @@ -28,7 +28,7 @@ internal class Program service.Received = (client, e) => { //从客户端收到信息 - var mes = e.ByteBlock.Span.ToString(Encoding.UTF8); + var mes = e.Memory.Span.ToString(Encoding.UTF8); client.Logger.Info($"已从{client.Id}接收到信息:{mes}"); return Task.CompletedTask; }; diff --git a/examples/BlogsDemos/DifferentProtocolConsoleApp/DifferentProtocolConsoleApp.csproj b/examples/BlogsDemos/DifferentProtocolConsoleApp/DifferentProtocolConsoleApp.csproj index f18b62f22..6608859ea 100644 --- a/examples/BlogsDemos/DifferentProtocolConsoleApp/DifferentProtocolConsoleApp.csproj +++ b/examples/BlogsDemos/DifferentProtocolConsoleApp/DifferentProtocolConsoleApp.csproj @@ -8,12 +8,12 @@ - - - - - - + + + + + + diff --git a/examples/BlogsDemos/DifferentProtocolConsoleApp/Program.cs b/examples/BlogsDemos/DifferentProtocolConsoleApp/Program.cs index 5062304a1..3a964ec54 100644 --- a/examples/BlogsDemos/DifferentProtocolConsoleApp/Program.cs +++ b/examples/BlogsDemos/DifferentProtocolConsoleApp/Program.cs @@ -88,7 +88,7 @@ internal class DifferentProtocolPlugin : PluginBase, ITcpConnectingPlugin, ITcpR if (client is ITcpSessionClient sessionClient) { - sessionClient.Logger.Info($"{sessionClient.GetIPPort()}收到数据,服务器端口:{sessionClient.ServicePort},数据:{e.ByteBlock}"); + sessionClient.Logger.Info($"{sessionClient.GetIPPort()}收到数据,服务器端口:{sessionClient.ServicePort},数据:{e.Memory}"); } await e.InvokeNext(); diff --git a/examples/BlogsDemos/HeartbeatConsoleApp/HeartbeatConsoleApp.csproj b/examples/BlogsDemos/HeartbeatConsoleApp/HeartbeatConsoleApp.csproj index fc9dcd098..7b83a0896 100644 --- a/examples/BlogsDemos/HeartbeatConsoleApp/HeartbeatConsoleApp.csproj +++ b/examples/BlogsDemos/HeartbeatConsoleApp/HeartbeatConsoleApp.csproj @@ -1,4 +1,4 @@ - + Exe @@ -6,6 +6,6 @@ - + diff --git a/examples/BlogsDemos/HeartbeatConsoleApp/Program.cs b/examples/BlogsDemos/HeartbeatConsoleApp/Program.cs index d99fcfc1a..489adb607 100644 --- a/examples/BlogsDemos/HeartbeatConsoleApp/Program.cs +++ b/examples/BlogsDemos/HeartbeatConsoleApp/Program.cs @@ -131,8 +131,8 @@ internal class MyRequestInfo : IFixedHeaderRequestInfo public void Package(ByteBlock byteBlock) { - byteBlock.WriteUInt16((ushort)((this.Data == null ? 0 : this.Data.Length) + 1)); - byteBlock.WriteByte((byte)this.DataType); + WriterExtension.WriteValue(ref byteBlock, (ushort)((this.Data == null ? 0 : this.Data.Length) + 1)); + WriterExtension.WriteValue(ref byteBlock, (byte)this.DataType); if (this.Data != null) { byteBlock.Write(this.Data); @@ -141,7 +141,7 @@ internal class MyRequestInfo : IFixedHeaderRequestInfo public byte[] PackageAsBytes() { - using var byteBlock = new ByteBlock(1024*64); + using var byteBlock = new ByteBlock(1024 * 64); this.Package(byteBlock); return byteBlock.ToArray(); } diff --git a/examples/BlogsDemos/LimitNumberOfConnectionsConsoleApp/LimitNumberOfConnectionsConsoleApp.csproj b/examples/BlogsDemos/LimitNumberOfConnectionsConsoleApp/LimitNumberOfConnectionsConsoleApp.csproj index 3690c4a0f..11bc9a8a2 100644 --- a/examples/BlogsDemos/LimitNumberOfConnectionsConsoleApp/LimitNumberOfConnectionsConsoleApp.csproj +++ b/examples/BlogsDemos/LimitNumberOfConnectionsConsoleApp/LimitNumberOfConnectionsConsoleApp.csproj @@ -1,4 +1,4 @@ - + Exe @@ -6,12 +6,12 @@ - - - - - - + + + + + + diff --git a/examples/BlogsDemos/ThrottlingConsoleApp/Program.cs b/examples/BlogsDemos/ThrottlingConsoleApp/Program.cs index 14c9c6156..e03e20265 100644 --- a/examples/BlogsDemos/ThrottlingConsoleApp/Program.cs +++ b/examples/BlogsDemos/ThrottlingConsoleApp/Program.cs @@ -31,7 +31,7 @@ internal class Program service.Received = (client, e) => { //从客户端收到信息 - var mes = e.ByteBlock.Span.ToString(Encoding.UTF8); + var mes = e.Memory.Span.ToString(Encoding.UTF8); client.Logger.Info($"已从{client.Id}接收到信息:{mes}"); return EasyTask.CompletedTask; }; @@ -87,9 +87,9 @@ public class MyThrottlingPlugin : PluginBase, ITcpConnectedPlugin, ITcpReceiving return e.InvokeNext(); } - public async Task OnTcpReceiving(ITcpSession client, ByteBlockEventArgs e) + public async Task OnTcpReceiving(ITcpSession client, BytesReaderEventArgs e) { - await client.GetFlowGate().AddCheckWaitAsync(e.ByteBlock.Length); + await client.GetFlowGate().AddCheckWaitAsync(e.Reader.Sequence.Length); await e.InvokeNext(); } } \ No newline at end of file diff --git a/examples/BlogsDemos/ThrottlingConsoleApp/ThrottlingConsoleApp.csproj b/examples/BlogsDemos/ThrottlingConsoleApp/ThrottlingConsoleApp.csproj index 3690c4a0f..11bc9a8a2 100644 --- a/examples/BlogsDemos/ThrottlingConsoleApp/ThrottlingConsoleApp.csproj +++ b/examples/BlogsDemos/ThrottlingConsoleApp/ThrottlingConsoleApp.csproj @@ -1,4 +1,4 @@ - + Exe @@ -6,12 +6,12 @@ - - - - - - + + + + + + diff --git a/examples/BlogsDemos/TrafficCounterConsoleApp/Program.cs b/examples/BlogsDemos/TrafficCounterConsoleApp/Program.cs index b157d663d..e1ea3a7a5 100644 --- a/examples/BlogsDemos/TrafficCounterConsoleApp/Program.cs +++ b/examples/BlogsDemos/TrafficCounterConsoleApp/Program.cs @@ -92,7 +92,7 @@ internal class Program // protected override void OnReceivingData(ITcpSession client, ByteBlockEventArgs e) // { // client.SetValue(TrafficCounterEx.ReceivedTempTrafficCounterProperty, -// e.ByteBlock.Length + +client.GetValue(TrafficCounterEx.ReceivedTempTrafficCounterProperty)); +// e.Memory.Length + +client.GetValue(TrafficCounterEx.ReceivedTempTrafficCounterProperty)); // base.OnReceivingData(client, e); // } //} diff --git a/examples/BlogsDemos/TrafficCounterConsoleApp/TrafficCounterConsoleApp.csproj b/examples/BlogsDemos/TrafficCounterConsoleApp/TrafficCounterConsoleApp.csproj index 6074e652c..fe6b0d27d 100644 --- a/examples/BlogsDemos/TrafficCounterConsoleApp/TrafficCounterConsoleApp.csproj +++ b/examples/BlogsDemos/TrafficCounterConsoleApp/TrafficCounterConsoleApp.csproj @@ -1,4 +1,4 @@ - + Exe @@ -6,11 +6,11 @@ - - - - - - + + + + + + diff --git a/examples/Consul集群示例/ConsulConsoleApp/ConsulConsoleApp.csproj b/examples/Consul集群示例/ConsulConsoleApp/ConsulConsoleApp.csproj index b08beb96c..6ff43a4b2 100644 --- a/examples/Consul集群示例/ConsulConsoleApp/ConsulConsoleApp.csproj +++ b/examples/Consul集群示例/ConsulConsoleApp/ConsulConsoleApp.csproj @@ -6,13 +6,13 @@ - - - - - - - + + + + + + + diff --git a/examples/Consul集群示例/ConsulConsoleApp/Program.cs b/examples/Consul集群示例/ConsulConsoleApp/Program.cs index 1c0f8ffa6..d34a8bbe5 100644 --- a/examples/Consul集群示例/ConsulConsoleApp/Program.cs +++ b/examples/Consul集群示例/ConsulConsoleApp/Program.cs @@ -46,7 +46,7 @@ internal class Program { //从客户端收到信息 - var mes = e.ByteBlock.Span.ToString(Encoding.UTF8); + var mes = e.Memory.Span.ToString(Encoding.UTF8); client.Logger.Info($"已从{client.Id}接收到信息:{mes}"); //client.Send(mes);//将收到的信息直接返回给发送方 diff --git a/examples/Consul集群示例/TouchRpc Consul集群/ServiceConsoleApp/Program.cs b/examples/Consul集群示例/TouchRpc Consul集群/ServiceConsoleApp/Program.cs index fc6770b3a..386d82dd3 100644 --- a/examples/Consul集群示例/TouchRpc Consul集群/ServiceConsoleApp/Program.cs +++ b/examples/Consul集群示例/TouchRpc Consul集群/ServiceConsoleApp/Program.cs @@ -48,11 +48,16 @@ internal class Program .ConfigurePlugins(a => { a.UseDmtpRpc(); - a.UseXmlRpc().SetXmlRpcUrl("/xmlrpc"); + a.UseXmlRpc("/xmlrpc"); a.UseWebApi(); - a.UseWebSocket()//添加WebSocket功能 - .SetWSUrl("/ws"); + //添加WebSocket功能 + a.UseWebSocket(options => + { + options.SetUrl("/ws");//设置url直接可以连接。 + options.SetAutoPong(true);//当收到ping报文时自动回应pong + }); + a.Add();//添加WebSocket业务数据接收插件 a.Add();//添加WebSocket快捷实现,常规WS客户端发送文本“Add 10 20”即可得到30。 }) @@ -149,9 +154,9 @@ internal class MyWebSocketCommand : WebSocketCommandLinePlugin /// /// WS收到数据等业务。 /// -internal class MyWebSocketPlug : PluginBase, IWebSocketHandshakedPlugin, IWebSocketReceivedPlugin +internal class MyWebSocketPlug : PluginBase, IWebSocketConnectedPlugin, IWebSocketReceivedPlugin { - public async Task OnWebSocketHandshaked(IWebSocket client, HttpContextEventArgs e) + public async Task OnWebSocketConnected(IWebSocket client, HttpContextEventArgs e) { if (client.Client is IHttpSessionClient socketClient) { diff --git a/examples/Consul集群示例/TouchRpc Consul集群/ServiceConsoleApp/ServiceConsoleApp.csproj b/examples/Consul集群示例/TouchRpc Consul集群/ServiceConsoleApp/ServiceConsoleApp.csproj index b08beb96c..2837b700d 100644 --- a/examples/Consul集群示例/TouchRpc Consul集群/ServiceConsoleApp/ServiceConsoleApp.csproj +++ b/examples/Consul集群示例/TouchRpc Consul集群/ServiceConsoleApp/ServiceConsoleApp.csproj @@ -1,4 +1,4 @@ - + Exe @@ -6,13 +6,13 @@ - - - - - - - + + + + + + + diff --git a/examples/Consul集群示例/TouchRpc Consul集群/WinFormsApp/Form1.cs b/examples/Consul集群示例/TouchRpc Consul集群/WinFormsApp/Form1.cs index 6590df36a..373dd68ee 100644 --- a/examples/Consul集群示例/TouchRpc Consul集群/WinFormsApp/Form1.cs +++ b/examples/Consul集群示例/TouchRpc Consul集群/WinFormsApp/Form1.cs @@ -13,7 +13,6 @@ using Consul; using System; using System.Linq; -using System.Threading.Tasks; using System.Windows.Forms; using TouchSocket.Core; using TouchSocket.Dmtp; diff --git a/examples/Consul集群示例/TouchRpc Consul集群/WinFormsApp/WinFormsApp.csproj b/examples/Consul集群示例/TouchRpc Consul集群/WinFormsApp/WinFormsApp.csproj index 1b5c1eb3f..d0df5149b 100644 --- a/examples/Consul集群示例/TouchRpc Consul集群/WinFormsApp/WinFormsApp.csproj +++ b/examples/Consul集群示例/TouchRpc Consul集群/WinFormsApp/WinFormsApp.csproj @@ -1,4 +1,4 @@ - + WinExe @@ -7,13 +7,13 @@ - - - - - - - + + + + + + + \ No newline at end of file diff --git a/examples/Core/AotDynamicMethodConsoleApp/AotDynamicMethodConsoleApp.csproj b/examples/Core/AotDynamicMethodConsoleApp/AotDynamicMethodConsoleApp.csproj index 19b8dd53a..0584dd0a2 100644 --- a/examples/Core/AotDynamicMethodConsoleApp/AotDynamicMethodConsoleApp.csproj +++ b/examples/Core/AotDynamicMethodConsoleApp/AotDynamicMethodConsoleApp.csproj @@ -1,4 +1,4 @@ - + Exe @@ -13,6 +13,6 @@ - + diff --git a/examples/Core/AotDynamicMethodConsoleApp/Program.cs b/examples/Core/AotDynamicMethodConsoleApp/Program.cs index 151988ea6..9c2eec471 100644 --- a/examples/Core/AotDynamicMethodConsoleApp/Program.cs +++ b/examples/Core/AotDynamicMethodConsoleApp/Program.cs @@ -11,295 +11,346 @@ // ------------------------------------------------------------------------------ using System.Diagnostics; -using System.Reflection.Emit; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; using TouchSocket.Core; namespace AotDynamicMethodConsoleApp; internal class Program { - static async Task Main(string[] args) + private const int DefaultPerformanceTestCount = 10_000_000; + + private static async Task Main(string[] args) { var consoleAction = new ConsoleAction(); - consoleAction.OnException += ConsoleAction_OnException; - consoleAction.Add("1", "简单调用", SimpleRun); - consoleAction.Add("2", "IL调用", ILRun); - consoleAction.Add("3", "表达式树调用", ExpressionRun); - consoleAction.Add("4", "反射调用", ReflectRun); - consoleAction.Add("5", "源生成调用", SourceGeneratorRun); - consoleAction.Add("6", "性能测试", Performance); - consoleAction.Add("7", "多参数调用", MultiParameters); - consoleAction.Add("8", "自定义动态调用", CustomDynamicMethod); - consoleAction.Add("9", "TaskRun", TaskRun); - consoleAction.Add("10", "TaskObjectRun", TaskObjectRun); + consoleAction.OnException += OnException; + + // 注册所有示例命令 + RegisterCommands(consoleAction); consoleAction.ShowAll(); await consoleAction.RunCommandLineAsync(); } - private static void ConsoleAction_OnException(Exception ex) + /// + /// 注册所有示例命令 + /// + private static void RegisterCommands(ConsoleAction consoleAction) { - Console.WriteLine(ex.Message); + consoleAction.Add("1", "简单调用", SimpleRun); + consoleAction.Add("2", "性能测试", Performance); + consoleAction.Add("3", "多参数调用", MultiParameters); + consoleAction.Add("4", "自定义动态调用", CustomDynamicMethod); + consoleAction.Add("5", "异步Task调用", TaskRun); + consoleAction.Add("6", "异步Task调用", TaskObjectRun); } - static void SimpleRun() + /// + /// 全局异常处理 + /// + private static void OnException(Exception ex) { - Method method = new Method(typeof(MyClass), nameof(MyClass.Run)); - - MyClass myClass = new MyClass(); - method.Invoke(myClass); + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"错误: {ex.Message}"); + Console.ResetColor(); } - static void ILRun() + /// + /// 示例1: 简单的方法调用 + /// + private static void SimpleRun() { - Method method = new Method(typeof(MyClass), nameof(MyClass.Run), DynamicBuilderType.IL); + Console.WriteLine("=== 简单调用示例 ==="); - MyClass myClass = new MyClass(); - method.Invoke(myClass); - } - static void ExpressionRun() - { - Method method = new Method(typeof(MyClass), nameof(MyClass.Run), DynamicBuilderType.Expression); + #region 简单调用示例 + var method = new Method(typeof(MyClass), nameof(MyClass.Run)); + var instance = new MyClass(); + method.Invoke(instance); + #endregion - MyClass myClass = new MyClass(); - method.Invoke(myClass); + Console.WriteLine("调用完成!"); } - static void ReflectRun() + /// + /// 示例2: 性能测试 + /// + private static void Performance() { - Method method = new Method(typeof(MyClass), nameof(MyClass.Run), DynamicBuilderType.Reflect); + Console.WriteLine("=== 性能测试 ==="); + Console.WriteLine($"将执行 {DefaultPerformanceTestCount:N0} 次方法调用...\n"); - MyClass myClass = new MyClass(); - method.Invoke(myClass); - } + #region 性能测试代码 + var myClass = new MyClass(); + var method = new Method(typeof(MyClass), nameof(MyClass.Performance)); + var stopwatch = Stopwatch.StartNew(); - static void SourceGeneratorRun() - { - Method method = new Method(typeof(MyClass), nameof(MyClass.Run), DynamicBuilderType.SourceGenerator); - - MyClass myClass = new MyClass(); - method.Invoke(myClass); - } - - static void Performance() - { - int count = 10000000; - - MyClass myClass = new MyClass(); - - Stopwatch stopwatch = new Stopwatch(); - - var methods = GetMethods(typeof(MyClass), nameof(MyClass.Performance)); - - foreach (var item in methods) + for (var i = 0; i < 10_000_000; i++) { - stopwatch.Restart(); - try - { - var method = item; - for (int i = 0; i < count; i++) - { - method.Invoke(myClass); - } - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - stopwatch.Stop(); - Console.WriteLine($"Method BuilderType={item.DynamicBuilderType},Time={stopwatch.ElapsedMilliseconds}"); - } + method.Invoke(myClass); } + stopwatch.Stop(); + Console.WriteLine($"总耗时: {stopwatch.ElapsedMilliseconds} ms"); + #endregion + + try + { + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine($"✓ 执行完成"); + Console.WriteLine($" 总耗时: {stopwatch.ElapsedMilliseconds:N0} ms"); + Console.WriteLine($" 平均耗时: {(double)stopwatch.ElapsedMilliseconds / DefaultPerformanceTestCount:F6} ms/call"); + Console.WriteLine($" 吞吐量: {DefaultPerformanceTestCount / (stopwatch.ElapsedMilliseconds / 1000.0):N0} calls/sec"); + Console.ResetColor(); + } + catch (Exception ex) + { + stopwatch.Stop(); + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"✗ 性能测试失败: {ex.Message}"); + Console.WriteLine($" 已完成部分测试,耗时: {stopwatch.ElapsedMilliseconds:N0} ms"); + Console.ResetColor(); + } } - static void MultiParameters() + /// + /// 示例3: 多参数调用,包括 out 和 ref 参数 + /// + private static void MultiParameters() { - MyClass myClass = new MyClass(); + Console.WriteLine("=== 多参数调用示例 ==="); - var methods = GetMethods(typeof(MyClass), nameof(MyClass.MultiParameters)); + #region out和ref参数调用 + var method = new Method(typeof(MyClass), nameof(MyClass.MultiParameters)); + var instance = new MyClass(); - foreach (var item in methods) + var parameters = new object[] { "hello", 0, 200 }; + method.Invoke(instance, parameters); + + Console.WriteLine($"out参数b={parameters[1]}"); // 输出: out参数b=10 + Console.WriteLine($"ref参数c={parameters[2]}"); // 输出: ref参数c=201 + #endregion + + Console.WriteLine($"调用前参数: a=\"{parameters[0]}\", b={parameters[1]}, c={parameters[2]}"); + + try { - object[] ps = new object[] { "hello", 0, 200 }; - try + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine($"调用后参数: a=\"{parameters[0]}\", b={parameters[1]}, c={parameters[2]}"); + Console.ResetColor(); + } + catch (Exception ex) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"调用失败: {ex.Message}"); + Console.ResetColor(); + } + } + + /// + /// 示例4: 使用自定义特性的动态方法调用 + /// + private static void CustomDynamicMethod() + { + Console.WriteLine("=== 自定义动态方法调用示例 ==="); + + #region 自定义特性调用 + var method = new Method(typeof(MyClass), nameof(MyClass.CustomDynamicMethod)); + var instance = new MyClass(); + method.Invoke(instance); + #endregion + + try + { + Console.WriteLine("自定义动态方法调用成功!"); + } + catch (Exception ex) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"调用失败: {ex.Message}"); + Console.ResetColor(); + } + } + + /// + /// 示例5: 异步 Task 方法调用 + /// + private static async Task TaskRun() + { + Console.WriteLine("=== 异步Task调用示例 ==="); + + #region 异步Task调用 + var method = new Method(typeof(MyClass), nameof(MyClass.TaskRun)); + var instance = new MyClass(); + + // 判断是否为异步方法 + if (method.IsAwaitable) + { + await method.InvokeAsync(instance); + } + #endregion + + Console.WriteLine($"方法是否可等待: {method.IsAwaitable}"); + Console.WriteLine($"返回类型: {method.ReturnKind}"); + + try + { + if (method.IsAwaitable) { - var method = item; - method.Invoke(myClass, ps); + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine("异步调用完成!"); + Console.ResetColor(); } - catch (Exception ex) + else { - Console.WriteLine(ex.Message); - } - finally - { - Console.WriteLine($"Method BuilderType={item.DynamicBuilderType},ps0={ps[0]},ps1={ps[1]},ps2={ps[2]}"); + Console.WriteLine("该方法不是异步方法"); } } - + catch (Exception ex) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"异步调用失败: {ex.Message}"); + Console.ResetColor(); + } } - static void CustomDynamicMethod() + /// + /// 示例6: 异步 Task 方法调用(带返回值) + /// + private static async Task TaskObjectRun() { - MyClass myClass = new MyClass(); + Console.WriteLine("=== 异步Task调用示例 ==="); - var methods = GetMethods(typeof(MyClass), nameof(MyClass.CustomDynamicMethod)); + #region 异步Task泛型调用 + var method = new Method(typeof(MyClass), nameof(MyClass.TaskObjectRun)); + var instance = new MyClass(); - foreach (var item in methods) + if (method.ReturnKind == MethodReturnKind.AwaitableObject) { - try + var result = await method.InvokeAsync(instance); + Console.WriteLine($"返回值: {result}"); // 输出: 返回值: 10 + } + #endregion + + Console.WriteLine($"方法返回类型: {method.ReturnKind}"); + Console.WriteLine($"实际返回值类型: {method.RealReturnType?.Name ?? "void"}"); + + try + { + if (method.ReturnKind == MethodReturnKind.AwaitableObject) { - var method = item; - method.Invoke(myClass); + var result2 = await method.InvokeAsync(instance); + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine($"异步调用完成,返回值: {result2}"); + Console.ResetColor(); } - catch (Exception ex) + else { - Console.WriteLine(ex.Message); - } - finally - { - Console.WriteLine($"Method BuilderType={item.DynamicBuilderType}"); + Console.WriteLine("该方法不返回异步对象"); } } - - } - - static async Task TaskRun() - { - MyClass myClass = new MyClass(); - - var methods = GetMethods(typeof(MyClass), nameof(MyClass.TaskRun)); - - foreach (var item in methods) + catch (Exception ex) { - try - { - var method = item; - if (method.IsAwaitable) - { - await method.InvokeAsync(myClass); - } - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - Console.WriteLine($"Method BuilderType={item.DynamicBuilderType}"); - } + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"异步调用失败: {ex.Message}"); + Console.ResetColor(); } - - } - - static async Task TaskObjectRun() - { - MyClass myClass = new MyClass(); - - var methods = GetMethods(typeof(MyClass), nameof(MyClass.TaskObjectRun)); - - foreach (var item in methods) - { - try - { - var method = item; - if (method.ReturnKind == MethodReturnKind.AwaitableObject) - { - var result = await method.InvokeAsync(myClass); - Console.WriteLine($"result={result}"); - } - - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - Console.WriteLine($"Method BuilderType={item.DynamicBuilderType}"); - } - } - - } - - static List GetMethods(Type type, string name) - { - var methods = new List(); - foreach (var item in Enum.GetValues(typeof(DynamicBuilderType)).OfType()) - { - try - { - methods.Add(new Method(type, name, item)); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - } - - return methods; - } - - static bool IsDynamicCodeCompiled() - { -#if NET8_0_OR_GREATER - return RuntimeFeature.IsDynamicCodeCompiled; -#else - return true; -#endif } } +/// +/// 测试类,包含各种类型的动态方法 +/// +#region 标记方法示例 public class MyClass { + /// + /// 简单的void方法 + /// [DynamicMethod] public void Run() { - Console.WriteLine("Run"); + Console.WriteLine("Run 方法被调用"); } + #endregion + /// + /// 用于性能测试的空方法 + /// + #region 性能测试声明 [DynamicMethod] public void Performance() { - + // 空方法体,用于性能测试 } + #endregion + /// + /// 包含多种参数类型的方法:普通参数、out参数、ref参数 + /// + #region out和ref参数声明 [DynamicMethod] public void MultiParameters(string a, out int b, ref int c) { b = 10; c = c + 1; - Console.WriteLine("MultiParameters"); + Console.WriteLine($"a={a}, b={b}, c={c}"); } + #endregion + /// + /// 使用自定义特性标记的方法 + /// [MyDynamicMethod] public void CustomDynamicMethod() { - Console.WriteLine("CustomDynamicMethod"); + Console.WriteLine("CustomDynamicMethod 方法被调用(使用自定义特性)"); } + /// + /// 异步Task方法(无返回值) + /// + #region 异步Task调用声明 [DynamicMethod] public async Task TaskRun() { - Console.WriteLine("TaskRun"); - await Task.CompletedTask; + Console.WriteLine("开始执行..."); + await Task.Delay(100); + Console.WriteLine("执行完成"); } + #endregion + /// + /// 异步Task方法(有返回值) + /// + #region 异步Task泛型调用声明 [DynamicMethod] public async Task TaskObjectRun() { - Console.WriteLine("TaskObjectRun"); - await Task.CompletedTask; + await Task.Delay(100); return 10; } + #endregion } +/// +/// 自定义的动态方法特性 +/// +#region 自定义特性声明 [DynamicMethod] [AttributeUsage(AttributeTargets.Method)] public class MyDynamicMethodAttribute : Attribute { } +#endregion + +#region 缓存Method实例推荐 +/// +/// 推荐的缓存方式:将Method实例缓存为静态字段,避免重复创建 +/// +public static class MethodCache +{ + public static readonly Method RunMethod = new Method(typeof(MyClass), nameof(MyClass.Run)); + + // 使用示例: + // MethodCache.RunMethod.Invoke(instance); +} +#endregion \ No newline at end of file diff --git a/examples/Core/AotFastBinaryFormatterConsoleApp/AotFastBinaryFormatterConsoleApp.csproj b/examples/Core/AotFastBinaryFormatterConsoleApp/AotFastBinaryFormatterConsoleApp.csproj index 0a87c9d1f..fc5d9f24e 100644 --- a/examples/Core/AotFastBinaryFormatterConsoleApp/AotFastBinaryFormatterConsoleApp.csproj +++ b/examples/Core/AotFastBinaryFormatterConsoleApp/AotFastBinaryFormatterConsoleApp.csproj @@ -1,4 +1,4 @@ - + Exe @@ -10,7 +10,7 @@ - + - + diff --git a/examples/Core/FastBinaryFormatterConsoleApp/Program.cs b/examples/Core/FastBinaryFormatterConsoleApp/Program.cs index 15f1ab8df..094d67653 100644 --- a/examples/Core/FastBinaryFormatterConsoleApp/Program.cs +++ b/examples/Core/FastBinaryFormatterConsoleApp/Program.cs @@ -1,3 +1,15 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + using TouchSocket.Core; namespace FastBinaryFormatterConsoleApp; @@ -6,6 +18,8 @@ internal class Program { private static void Main(string[] args) { + SimpleUsage(); + SerializeAndDeserialize(new MyClass4()); TestMyClass2_3(); @@ -20,11 +34,20 @@ internal class Program SerializeAndDeserializeFromBytes(10); SerializeAndDeserializeFromBytes("RRQM"); - FastBinaryFormatter.AddFastBinaryConverter(typeof(MyClass5), new MyClass5FastBinaryConverter()); + AddConverter(); Console.ReadKey(); } + #region FastBinary简单使用 + public static void SimpleUsage() + { + var bytes = FastBinaryFormatter.SerializeToBytes(10); + var newObj = FastBinaryFormatter.Deserialize(bytes); + } + #endregion + + #region FastBinary兼容类型使用 public static void TestMyClass2_3() { var myClass2 = new MyClass2() @@ -36,9 +59,8 @@ internal class Program var newObj = FastBinaryFormatter.Deserialize(bytes); var ss = newObj.ToJsonString(); - } - + #endregion public static void TestMyClass1() { @@ -54,7 +76,7 @@ internal class Program SerializeAndDeserialize(myClass1); } - + #region FastBinary使用内存池块 public static void SerializeAndDeserialize(TValue value) { //申请内存块,并指定此次序列化可能使用到的最大尺寸。 @@ -64,18 +86,16 @@ internal class Program //将数据序列化到内存块 FastBinaryFormatter.Serialize(block, value); - Console.WriteLine($"ByteBlock序列化“{typeof(TValue)}”完成,数据长度={block.Length}"); - //在反序列化前,将内存块数据游标移动至正确位。 block.SeekToStart(); //反序列化 var newObj = FastBinaryFormatter.Deserialize(block); - - Console.WriteLine($"ByteBlock反序列化“{typeof(TValue)}”完成,Value={newObj.ToJsonString()}"); } } + #endregion + #region FastBinary使用值类型内存池块 public static void SerializeAndDeserializeFromValueByteBlock(TValue value) { //申请内存块,并指定此次序列化可能使用到的最大尺寸。 @@ -87,15 +107,11 @@ internal class Program //将数据序列化到内存块 FastBinaryFormatter.Serialize(ref block, value); - Console.WriteLine($"ValueByteBlock序列化“{typeof(TValue)}”完成,数据长度={block.Length}"); - //在反序列化前,将内存块数据游标移动至正确位。 block.SeekToStart(); //反序列化 var newObj = FastBinaryFormatter.Deserialize(ref block); - - Console.WriteLine($"ValueByteBlock反序列化“{typeof(TValue)}”完成,Value={newObj.ToJsonString()}"); } finally { @@ -103,20 +119,23 @@ internal class Program block.Dispose(); } } + #endregion public static void SerializeAndDeserializeFromBytes(TValue value) { var bytes = FastBinaryFormatter.SerializeToBytes(value); - - Console.WriteLine($"Bytes序列化“{typeof(TValue)}”完成,数据长度={bytes.Length}"); - - var newObj = FastBinaryFormatter.Deserialize(bytes); - - Console.WriteLine($"Bytes反序列化“{typeof(TValue)}”完成,Value={newObj.ToJsonString()}"); } + + #region FastBinary添加转换器 + public static void AddConverter() + { + FastBinaryFormatter.AddFastBinaryConverter(typeof(MyClass5), new MyClass5FastBinaryConverter()); + } + #endregion } +#region FastBinary常规配置示例类 public class MyClass1 { private int m_p5; @@ -162,7 +181,9 @@ public class MyClass1 this.P7 = value; } } +#endregion +#region FastBinary兼容类型类定义 public class MyClass2 { public int P1 { get; set; } @@ -171,9 +192,11 @@ public class MyClass2 public class MyClass3 { public int P1 { get; set; } - public string P2 { get; set; } + public string? P2 { get; set; } } +#endregion +#region FastBinary特性成员示例类 [FastSerialized(EnableIndex = true)] public class MyClass4 { @@ -183,14 +206,18 @@ public class MyClass4 [FastMember(2)] public int MyProperty2 { get; set; } } +#endregion +#region FastBinary自定义转换器示例类 [FastConverter(typeof(MyClass5FastBinaryConverter))] public class MyClass5 { public int P1 { get; set; } public int P2 { get; set; } } +#endregion +#region FastBinary自定义转换器实现 public sealed class MyClass5FastBinaryConverter : FastBinaryConverter { protected override MyClass5 Read(ref TByteBlock byteBlock, Type type) @@ -199,8 +226,8 @@ public sealed class MyClass5FastBinaryConverter : FastBinaryConverter //我们只需要把有效信息按写入的顺序,读取即可。 var myClass5 = new MyClass5(); - myClass5.P1 = byteBlock.ReadInt32(); - myClass5.P2 = byteBlock.ReadInt32(); + myClass5.P1 = ReaderExtension.ReadValue(ref byteBlock); + myClass5.P2 = ReaderExtension.ReadValue(ref byteBlock); return myClass5; } @@ -212,16 +239,17 @@ public sealed class MyClass5FastBinaryConverter : FastBinaryConverter //对于MyClass5类,只有两个属性是有效的。 //所以,依次写入属性值即可 - byteBlock.WriteInt32(obj.P1); - byteBlock.WriteInt32(obj.P2); - + WriterExtension.WriteValue(ref byteBlock, obj.P1); + WriterExtension.WriteValue(ref byteBlock, obj.P2); } } +#endregion - +#region FastBinary包模式序列化示例类 [GeneratorPackage] public partial class MyClass6 : PackageBase { public int P1 { get; set; } public int P2 { get; set; } } +#endregion diff --git a/examples/Core/IocConsoleApp/AdvancedContainer.cs b/examples/Core/IocConsoleApp/AdvancedContainer.cs new file mode 100644 index 000000000..4160a0383 --- /dev/null +++ b/examples/Core/IocConsoleApp/AdvancedContainer.cs @@ -0,0 +1,129 @@ +//------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +//------------------------------------------------------------------------------ + +using Microsoft.Extensions.DependencyInjection; +using TouchSocket.Core; +using TouchSocket.Core.AspNetCore; + +namespace IocConsoleApp; + +/// +/// 高级IOC容器示例 +/// +internal static class AdvancedContainer +{ + public static void Run() + { + UseAspNetCoreContainer(); + UseAspNetCoreContainerWithConfig(); + InterfaceMapping(); + GenericTypeRegistration(); + } + + private static void UseAspNetCoreContainer() + { + #region IOC使用AspNetCoreContainer + var aspNetCoreContainer = new AspNetCoreContainer(new ServiceCollection()); + aspNetCoreContainer.RegisterSingleton(); + aspNetCoreContainer.RegisterSingleton(); + + aspNetCoreContainer.BuildResolver(); + + var myClass1 = aspNetCoreContainer.Resolve(); + var myClass2 = aspNetCoreContainer.Resolve(); + #endregion + + Console.WriteLine("UseAspNetCoreContainer completed"); + } + + private static void UseAspNetCoreContainerWithConfig() + { + #region IOC配置中使用ServiceCollection + var config = new TouchSocketConfig() + .UseAspNetCoreContainer(new ServiceCollection()); //ServiceCollection可以使用现有的 + #endregion + + Console.WriteLine("UseAspNetCoreContainerWithConfig completed"); + } + + private static void InterfaceMapping() + { + #region IOC接口映射注册 + var container = new Container(); + // 将接口映射到实现类 + container.RegisterSingleton(); + + var service = container.Resolve(); + service.DoSomething(); + #endregion + + Console.WriteLine("InterfaceMapping completed"); + } + + private static void GenericTypeRegistration() + { + #region IOC泛型类型注册 + var container = new Container(); + // 注册泛型类型定义(使用瞬态避免单例冲突) + container.RegisterTransient(typeof(IRepository<>), typeof(Repository<>)); + + // 解析具体的泛型类型 + var userRepo = (IRepository)container.Resolve(typeof(IRepository)); + var productRepo = (IRepository)container.Resolve(typeof(IRepository)); + + userRepo.Save(new User { Name = "Alice" }); + productRepo.Save(new Product { Name = "Book" }); + #endregion + + Console.WriteLine("GenericTypeRegistration completed"); + } +} + +#region IOC高级示例类型定义 +internal interface IMyService +{ + void DoSomething(); +} + +internal class MyServiceImpl : IMyService +{ + public void DoSomething() + { + Console.WriteLine("MyServiceImpl.DoSomething"); + } +} + +internal interface IRepository +{ + void Save(T entity); +} + +internal class Repository : IRepository +{ + public void Save(T entity) + { + Console.WriteLine($"Saving {typeof(T).Name}: {entity}"); + } +} + +internal class User +{ + public string Name { get; set; } + public override string ToString() => Name; +} + +internal class Product +{ + public string Name { get; set; } + public override string ToString() => Name; +} +#endregion diff --git a/examples/Core/IocConsoleApp/IocConsoleApp.csproj b/examples/Core/IocConsoleApp/IocConsoleApp.csproj index 633c18c88..2e7f1f96a 100644 --- a/examples/Core/IocConsoleApp/IocConsoleApp.csproj +++ b/examples/Core/IocConsoleApp/IocConsoleApp.csproj @@ -7,8 +7,8 @@ - - + + diff --git a/examples/Core/IocConsoleApp/NormalContainer.cs b/examples/Core/IocConsoleApp/NormalContainer.cs index 02b1c864a..33f2d12da 100644 --- a/examples/Core/IocConsoleApp/NormalContainer.cs +++ b/examples/Core/IocConsoleApp/NormalContainer.cs @@ -23,8 +23,11 @@ internal static class NormalContainer public static void Run() { ConstructorInject(); - PropertyInject(); - MethodInject(); + SingletonLifetime(); + TransientLifetime(); + ScopedLifetime(); + RegisterWithKey(); + RegisterWithFactory(); } /// @@ -32,53 +35,124 @@ internal static class NormalContainer /// private static void ConstructorInject() { - var container = GetContainer(); + #region IOC构造函数注入 + var container = new Container(); container.RegisterSingleton(); container.RegisterSingleton(); var myClass1 = container.Resolve(); var myClass2 = container.Resolve(); + #endregion Console.WriteLine(MethodBase.GetCurrentMethod().Name); } /// - /// 属性注入 + /// 单例生命周期 /// - private static void PropertyInject() + private static void SingletonLifetime() { - var container = GetContainer(); + #region IOC单例生命周期 + var container = new Container(); container.RegisterSingleton(); - container.RegisterSingleton("key"); - container.RegisterSingleton(); - container.RegisterSingleton(); + var instance1 = container.Resolve(); + var instance2 = container.Resolve(); + // instance1 和 instance2 是同一个实例 + Console.WriteLine($"Singleton: {ReferenceEquals(instance1, instance2)}"); // True + #endregion - var myClass3 = container.Resolve(); Console.WriteLine(MethodBase.GetCurrentMethod().Name); } /// - /// 方法注入 + /// 瞬态生命周期 /// - private static void MethodInject() + private static void TransientLifetime() { - var container = GetContainer(); - container.RegisterSingleton(); - container.RegisterSingleton(); + #region IOC瞬态生命周期 + var container = new Container(); + container.RegisterTransient(); + + var instance1 = container.Resolve(); + var instance2 = container.Resolve(); + // instance1 和 instance2 是不同的实例 + Console.WriteLine($"Transient: {ReferenceEquals(instance1, instance2)}"); // False + #endregion - var myClass4 = container.Resolve(); Console.WriteLine(MethodBase.GetCurrentMethod().Name); } - private static IContainer GetContainer() + /// + /// 作用域生命周期 + /// + private static void ScopedLifetime() { - return new Container();//默认IOC容器 + #region IOC作用域生命周期 + var container = new Container(); + container.RegisterScoped(); - //return new AspNetCoreContainer(new ServiceCollection());//使用Aspnetcore的容器 + // 在同一作用域内是同一实例 + using (var scope = container.CreateScopedResolver()) + { + var instance1 = scope.Resolver.Resolve(); + var instance2 = scope.Resolver.Resolve(); + Console.WriteLine($"Scoped (same scope): {ReferenceEquals(instance1, instance2)}"); // True + } + + // 在不同作用域内是不同实例 + using (var scope1 = container.CreateScopedResolver()) + using (var scope2 = container.CreateScopedResolver()) + { + var instance1 = scope1.Resolver.Resolve(); + var instance2 = scope2.Resolver.Resolve(); + Console.WriteLine($"Scoped (different scopes): {ReferenceEquals(instance1, instance2)}"); // False + } + #endregion + + Console.WriteLine(MethodBase.GetCurrentMethod().Name); + } + + /// + /// 使用Key注册 + /// + private static void RegisterWithKey() + { + #region IOC使用Key注册 + var container = new Container(); + container.RegisterSingleton(); + container.RegisterSingleton("specialKey"); + + var defaultInstance = container.Resolve(); + var keyedInstance = container.Resolve("specialKey"); + // 不同的注册,不同的实例 + Console.WriteLine($"Keyed: {ReferenceEquals(defaultInstance, keyedInstance)}"); // False + #endregion + + Console.WriteLine(MethodBase.GetCurrentMethod().Name); + } + + /// + /// 使用工厂方法注册 + /// + private static void RegisterWithFactory() + { + #region IOC使用工厂方法注册 + var container = new Container(); + container.RegisterSingleton(resolver => + { + Console.WriteLine("Creating MyClass1 using factory"); + return new MyClass1(); + }); + + var instance = container.Resolve(); + #endregion + + Console.WriteLine(MethodBase.GetCurrentMethod().Name); } } +#region IOC定义类型 internal class MyClass1 { } @@ -92,35 +166,4 @@ internal class MyClass2 public MyClass1 MyClass1 { get; } } - -internal class MyClass3 -{ - /// - /// 直接按类型,默认方式获取 - /// - [DependencyInject] - public MyClass1 MyClass1 { get; set; } - - /// - /// 获得指定类型的对象,然后赋值到object - /// - [DependencyInject(typeof(MyClass2))] - public object MyClass2 { get; set; } - - /// - /// 按照类型+Key获取 - /// - [DependencyInject("key")] - public MyClass1 KeyMyClass1 { get; set; } -} - -internal class MyClass4 -{ - public MyClass1 MyClass1 { get; private set; } - - [DependencyInject] - public void MethodInject(MyClass1 myClass1) - { - this.MyClass1 = myClass1; - } -} \ No newline at end of file +#endregion \ No newline at end of file diff --git a/examples/Core/IocConsoleApp/Program.cs b/examples/Core/IocConsoleApp/Program.cs index 11bbf3d1a..21130d179 100644 --- a/examples/Core/IocConsoleApp/Program.cs +++ b/examples/Core/IocConsoleApp/Program.cs @@ -16,8 +16,9 @@ internal class Program { private static void Main(string[] args) { - NormalContainer.Run();// 常规Ioc容器,使用IL和反射实现,支持运行时注册和获取 - + NormalContainer.Run(); + AdvancedContainer.Run(); + Console.ReadKey(); } } \ No newline at end of file diff --git a/examples/Core/Log4netConsoleApp/Log4netConsoleApp.csproj b/examples/Core/Log4netConsoleApp/Log4netConsoleApp.csproj index 0c986496b..c96d3bd5d 100644 --- a/examples/Core/Log4netConsoleApp/Log4netConsoleApp.csproj +++ b/examples/Core/Log4netConsoleApp/Log4netConsoleApp.csproj @@ -8,13 +8,13 @@ - - - - - - - + + + + + + + diff --git a/examples/Core/Log4netConsoleApp/Program.cs b/examples/Core/Log4netConsoleApp/Program.cs index a8d2c2b92..8c47fb494 100644 --- a/examples/Core/Log4netConsoleApp/Program.cs +++ b/examples/Core/Log4netConsoleApp/Program.cs @@ -32,7 +32,7 @@ internal class Program service.Received = async (client, e) => { //从客户端收到信息 - var mes = e.ByteBlock.Span.ToString(Encoding.UTF8); + var mes = e.Memory.Span.ToString(Encoding.UTF8); service.Logger.Info($"服务器已从{client.Id}接收到信息:{mes}"); await client.SendAsync(mes);//将收到的信息直接返回给发送方 @@ -42,6 +42,22 @@ internal class Program .SetListenIPHosts(7789) .ConfigureContainer(a => { + #region 日志容器配置文件日志 + a.AddFileLogger(fileLogger => + { + fileLogger.MaxSize = 1024 * 1024; + fileLogger.LogLevel = LogLevel.Debug; + }); + #endregion + + #region 日志容器配置多日志记录器 + a.AddLogger(logger => + { + logger.AddConsoleLogger(); + logger.AddFileLogger(); + }); + #endregion + a.AddLogger(logger => { logger.AddConsoleLogger(); @@ -64,6 +80,7 @@ internal class Program } } +#region 日志Log4net自定义日志记录器 internal class Mylog4netLogger : LoggerBase { private readonly log4net.ILog m_logger; @@ -108,4 +125,5 @@ internal class Mylog4netLogger : LoggerBase break; } } -} \ No newline at end of file +} +#endregion \ No newline at end of file diff --git a/examples/Core/LoggerConsoleApp/LoggerConsoleApp.csproj b/examples/Core/LoggerConsoleApp/LoggerConsoleApp.csproj index 897fb96ef..e08d18664 100644 --- a/examples/Core/LoggerConsoleApp/LoggerConsoleApp.csproj +++ b/examples/Core/LoggerConsoleApp/LoggerConsoleApp.csproj @@ -8,6 +8,6 @@ - + diff --git a/examples/Core/LoggerConsoleApp/Program.cs b/examples/Core/LoggerConsoleApp/Program.cs index bc426fdad..e1eee1e4e 100644 --- a/examples/Core/LoggerConsoleApp/Program.cs +++ b/examples/Core/LoggerConsoleApp/Program.cs @@ -1,3 +1,15 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + using TouchSocket.Core; namespace LoggerConsoleApp; @@ -11,55 +23,89 @@ internal class Program private static void TestConsoleLogger() { + #region 日志控制台日志记录器使用 var logger = ConsoleLogger.Default; - - logger.LogLevel = LogLevel.Debug; - logger.Info("Message"); logger.Warning("Warning"); logger.Error("Error"); + #endregion } private static void TestFileLogger() { + #region 日志文件日志记录器默认配置 + var logger = new FileLogger(); + logger.Info("Message"); + logger.Warning("Warning"); + logger.Error("Error"); + #endregion + } + + private static void TestFileLogger_MaxSize() + { + #region 日志文件日志记录器配置文件大小 + var logger = new FileLogger() + { + MaxSize = 1024 * 1024 * 2 + }; + #endregion + } + + private static void TestFileLogger_Folder() + { + #region 日志文件日志记录器配置文件路径默认 + var logger = new FileLogger() + { + CreateLogFolder = (logLevel) => + { + return $"logs\\{DateTime.Now:[yyyy-MM-dd]}"; + } + }; + #endregion + } + + private static void TestFileLogger_FolderByLogLevel() + { + #region 日志文件日志记录器配置文件路径按日志类型 var logger = new FileLogger() { CreateLogFolder = (logLevel) => { return $"logs\\{DateTime.Now:[yyyy-MM-dd]}\\{logLevel}"; - }, - FileNameFormat = "", - - + } }; - logger.Info("Message"); - logger.Warning("Warning"); - logger.Error("Error"); + #endregion } private static void TestEasyLogger() { + #region 日志简易日志记录器使用 var logger = new EasyLogger(LoggerOutput); logger.Info("Message"); logger.Warning("Warning"); logger.Error("Error"); + #endregion } + #region 日志简易日志记录器回调方法 private static void LoggerOutput(string loggerString) { Console.WriteLine(loggerString); - //或者如果是winform程序,可以直接输出到TextBox + //或者如果是winform程序,可以直接输出到TextBox } + #endregion } -internal class MyLogger : ILog +#region 日志自定义日志记录器 +class MyLogger : ILog { public LogLevel LogLevel { get; set; } = LogLevel.Debug; - public string DateTimeFormat { get; set; } + public string DateTimeFormat { get; set; } = "yyyy-MM-dd HH:mm:ss ffff"; public void Log(LogLevel logLevel, object source, string message, Exception exception) { //此处可以自由实现逻辑。 } } +#endregion diff --git a/examples/Tcp/ReuseAddressServerConsoleApp/ReuseAddressServerConsoleApp.csproj b/examples/Core/OtherCoreConsoleApp/OtherCoreConsoleApp.csproj similarity index 71% rename from examples/Tcp/ReuseAddressServerConsoleApp/ReuseAddressServerConsoleApp.csproj rename to examples/Core/OtherCoreConsoleApp/OtherCoreConsoleApp.csproj index d6cfa539a..b2377b2c0 100644 --- a/examples/Tcp/ReuseAddressServerConsoleApp/ReuseAddressServerConsoleApp.csproj +++ b/examples/Core/OtherCoreConsoleApp/OtherCoreConsoleApp.csproj @@ -7,8 +7,7 @@ enable - - - - + + + diff --git a/examples/Core/OtherCoreConsoleApp/Program.cs b/examples/Core/OtherCoreConsoleApp/Program.cs new file mode 100644 index 000000000..8acdc2bc2 --- /dev/null +++ b/examples/Core/OtherCoreConsoleApp/Program.cs @@ -0,0 +1,380 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using TouchSocket.Core; + +namespace OtherCoreConsoleApp; + +internal class Program +{ + private static void Main(string[] args) + { + Console.WriteLine("=== TouchSocket 其他核心功能示例 ===\n"); + + // 一、Crc计算示例 + OtherCoreDemo.CrcExample(); + OtherCoreDemo.Crc16ValueExample(); + + // 二、时间测量器示例 + OtherCoreDemo.TimeMeasurerExample(); + OtherCoreDemo.TimeMeasurerAsyncExample(); + + // 三、MD5计算示例 + OtherCoreDemo.MD5Example(); + + // 四、16进制相关示例 + OtherCoreDemo.HexStringExample(); + + // 五、雪花Id生成示例 + OtherCoreDemo.SnowflakeIdExample(); + + // 六、数据压缩示例 + OtherCoreDemo.GZipExample(); + OtherCoreDemo.CustomDataCompressorExample(); + + // 七、短时间戳示例 + OtherCoreDemo.ShortTimestampExample(); + + // 八、读写锁using示例 + OtherCoreDemo.ReadWriteLockExample(); + + // 九、3DES示例 + OtherCoreDemo.TripleDESExample(); + + Console.WriteLine("\n=== 所有示例执行完毕 ==="); + Console.ReadKey(); + } +} + +/// +/// 其他核心功能示例类 +/// +public static class OtherCoreDemo +{ + #region 其他核心Crc计算 + /// + /// Crc计算示例 + /// + public static void CrcExample() + { + Console.WriteLine("【一、Crc计算示例】"); + + byte[] data = new byte[10]; + new Random().NextBytes(data); + + // Crc16计算 + byte[] result = Crc.Crc16(data); + + Console.WriteLine($"原始数据: {BitConverter.ToString(data)}"); + Console.WriteLine($"Crc16结果: {BitConverter.ToString(result)}"); + Console.WriteLine(); + } + #endregion + + #region 其他核心Crc16Value计算 + /// + /// Crc16Value计算示例 + /// + public static void Crc16ValueExample() + { + Console.WriteLine("【Crc16Value计算示例】"); + + byte[] data = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 }; + + // 计算Crc16Value(返回ushort) + ushort crc16Value = Crc.Crc16Value(data); + + Console.WriteLine($"原始数据: {BitConverter.ToString(data)}"); + Console.WriteLine($"Crc16Value: 0x{crc16Value:X4}"); + Console.WriteLine(); + } + #endregion + + #region 其他核心时间测量器 + /// + /// 时间测量器示例 + /// + public static void TimeMeasurerExample() + { + Console.WriteLine("【二、时间测量器示例】"); + + TimeSpan timeSpan = TimeMeasurer.Run(() => + { + Thread.Sleep(1000); + }); + + Console.WriteLine($"同步操作耗时: {timeSpan.TotalMilliseconds} 毫秒"); + Console.WriteLine(); + } + #endregion + + #region 其他核心时间测量器异步 + /// + /// 时间测量器异步示例 + /// + public static void TimeMeasurerAsyncExample() + { + Console.WriteLine("【时间测量器异步示例】"); + + var timeSpan = TimeMeasurer.Run(async () => + { + await Task.Delay(500); + }); + + Console.WriteLine($"异步操作耗时: {timeSpan.TotalMilliseconds} 毫秒"); + Console.WriteLine(); + } + #endregion + + #region 其他核心MD5计算 + /// + /// MD5计算示例 + /// + public static void MD5Example() + { + Console.WriteLine("【三、MD5计算示例】"); + + string str = MD5.GetMD5Hash("TouchSocket"); + bool b = MD5.VerifyMD5Hash("TouchSocket", str); + + Console.WriteLine($"MD5哈希值: {str}"); + Console.WriteLine($"验证结果: {b}"); + Console.WriteLine(); + } + #endregion + + #region 其他核心16进制转字节数组 + /// + /// 16进制相关示例 + /// + public static void HexStringExample() + { + Console.WriteLine("【四、16进制相关示例】"); + + // 将16进制字符串转换为字节数组 + string hexString = "01-02-03-04-05"; + byte[] bytes = hexString.ByHexStringToBytes("-"); + + Console.WriteLine($"16进制字符串: {hexString}"); + Console.WriteLine($"转换为字节数组: {BitConverter.ToString(bytes)}"); + + // 将16进制字符串转换为int32 + string hexInt = "12345678"; + int value = hexInt.ByHexStringToInt32(); + + Console.WriteLine($"16进制字符串: {hexInt}"); + Console.WriteLine($"转换为int32: 0x{value:X8}"); + Console.WriteLine(); + } + #endregion + + #region 其他核心雪花Id生成 + /// + /// 雪花Id生成示例 + /// + public static void SnowflakeIdExample() + { + Console.WriteLine("【五、雪花Id生成示例】"); + + SnowflakeIdGenerator generator = new SnowflakeIdGenerator(4); + + // 生成多个Id + for (int i = 0; i < 5; i++) + { + long id = generator.NextId(); + Console.WriteLine($"生成的雪花Id: {id}"); + } + + Console.WriteLine(); + } + #endregion + + #region 其他核心GZip压缩 + /// + /// GZip数据压缩示例 + /// + public static void GZipExample() + { + Console.WriteLine("【六、数据压缩示例】"); + + byte[] data = new byte[1024]; + new Random().NextBytes(data); + + using (ByteBlock byteBlock = new ByteBlock(1024 * 64)) + { + // 压缩 + GZip.Compress(byteBlock, data); + Console.WriteLine($"原始数据大小: {data.Length} 字节"); + Console.WriteLine($"压缩后大小: {byteBlock.Length} 字节"); + + // 解压 + var decompressData = GZip.Decompress(byteBlock.Span).ToArray(); + Console.WriteLine($"解压后大小: {decompressData.Length} 字节"); + Console.WriteLine($"数据一致性: {data.SequenceEqual(decompressData)}"); + } + + Console.WriteLine(); + } + #endregion + + #region 其他核心自定义压缩器 + /// + /// 自定义数据压缩器示例 + /// + public static void CustomDataCompressorExample() + { + Console.WriteLine("【自定义数据压缩器示例】"); + + // 使用GZip压缩器 + IDataCompressor compressor = new GZipDataCompressor(); + + byte[] data = new byte[512]; + new Random().NextBytes(data); + + // 压缩 + var compressedBlock = new ByteBlock(1024); + compressor.Compress(ref compressedBlock, data); + Console.WriteLine($"原始数据大小: {data.Length} 字节"); + Console.WriteLine($"压缩后大小: {compressedBlock.Length} 字节"); + + // 解压 + var decompressedBlock = new ByteBlock(1024); + compressor.Decompress(ref decompressedBlock, compressedBlock.Span); + Console.WriteLine($"解压后大小: {decompressedBlock.Length} 字节"); + Console.WriteLine($"数据一致性: {data.SequenceEqual(decompressedBlock.Span.ToArray())}"); + + // 释放资源 + compressedBlock.Dispose(); + decompressedBlock.Dispose(); + + Console.WriteLine(); + } + #endregion + + #region 其他核心短时间戳 + /// + /// 短时间戳示例 + /// + public static void ShortTimestampExample() + { + Console.WriteLine("【七、短时间戳示例】"); + + // 获取当前时间的短时间戳(自1970年1月1日以来的毫秒数,uint类型) + uint timestamp = DateTime.Now.ToUnsignedMillis(); + Console.WriteLine($"短时间戳(uint): {timestamp}"); + + // 使用DateTimeOffset + timestamp = DateTimeOffset.Now.ToUnsignedMillis(); + Console.WriteLine($"DateTimeOffset短时间戳: {timestamp}"); + Console.WriteLine(); + } + #endregion + + #region 其他核心读写锁Using + /// + /// 读写锁using示例 + /// + public static void ReadWriteLockExample() + { + Console.WriteLine("【八、读写锁using示例】"); + + ReaderWriterLockSlim lockSlim = new ReaderWriterLockSlim(); + int sharedResource = 0; + + // 读锁示例 + using (new ReadLock(lockSlim)) + { + Console.WriteLine($"读取共享资源: {sharedResource}"); + } + + // 写锁示例 + using (new WriteLock(lockSlim)) + { + sharedResource = 100; + Console.WriteLine($"写入共享资源: {sharedResource}"); + } + + // 再次读取 + using (new ReadLock(lockSlim)) + { + Console.WriteLine($"再次读取共享资源: {sharedResource}"); + } + + lockSlim.Dispose(); + Console.WriteLine(); + } + #endregion + + #region 其他核心3DES加密 + /// + /// 3DES加密解密示例 + /// + public static void TripleDESExample() + { + Console.WriteLine("【九、3DES加密解密示例】"); + + byte[] data = System.Text.Encoding.UTF8.GetBytes("Hello TouchSocket!"); + Console.WriteLine($"原始数据: {System.Text.Encoding.UTF8.GetString(data)}"); + + // 加密(口令长度为8) + var dataLocked = DataSecurity.EncryptDES(data, "12345678"); + Console.WriteLine($"加密后: {BitConverter.ToString(dataLocked)}"); + + // 解密 + var newData = DataSecurity.DecryptDES(dataLocked, "12345678"); + Console.WriteLine($"解密后: {System.Text.Encoding.UTF8.GetString(newData)}"); + Console.WriteLine($"数据一致性: {data.SequenceEqual(newData)}"); + Console.WriteLine(); + } + #endregion + + #region 其他核心自定义压缩器接口实现 + /// + /// 自定义压缩器实现示例 + /// + class MyDataCompressor : IDataCompressor + { + public void Compress(ref TWriter writer, ReadOnlySpan data) + where TWriter : IBytesWriter + { + //此处实现压缩 + throw new NotImplementedException(); + } + + public void Decompress(ref TWriter writer, ReadOnlySpan data) + where TWriter : IBytesWriter + { + //此处实现解压 + throw new NotImplementedException(); + } + } + #endregion + + #region 其他核心读写锁传统用法 + /// + /// 读写锁传统用法示例 + /// + public static void TraditionalReadWriteLockExample() + { + ReaderWriterLockSlim lockSlim = new ReaderWriterLockSlim(); + try + { + lockSlim.EnterReadLock(); + //do something + } + finally + { + lockSlim.ExitReadLock(); + } + } + #endregion +} diff --git a/examples/Core/PackageConsoleApp/PackageConsoleApp.csproj b/examples/Core/PackageConsoleApp/PackageConsoleApp.csproj index 591d0dcac..169bf13bc 100644 --- a/examples/Core/PackageConsoleApp/PackageConsoleApp.csproj +++ b/examples/Core/PackageConsoleApp/PackageConsoleApp.csproj @@ -6,11 +6,11 @@ enable - - - - - - + + + + + + diff --git a/examples/Core/PackageConsoleApp/Program.cs b/examples/Core/PackageConsoleApp/Program.cs index 673e780f4..c495d1ad1 100644 --- a/examples/Core/PackageConsoleApp/Program.cs +++ b/examples/Core/PackageConsoleApp/Program.cs @@ -37,12 +37,12 @@ internal class Program { 2,new MyClassModel(){ P1=DateTime.Now} } }; - var byteBlock = new ByteBlock(1024*64); + var byteBlock = new ByteBlock(1024 * 64); try { myClass.Package(ref byteBlock);//打包,相当于序列化 - byteBlock.Seek(0);//将流位置重置为0 + byteBlock.SeekToStart();//将流位置重置为0 var myNewClass = new MyPackage(); myNewClass.Unpackage(ref byteBlock);//解包,相当于反序列化 @@ -69,13 +69,13 @@ internal class Program { 2,new MyClassModel(){ P1=DateTime.Now} } }; - var byteBlock = new ByteBlock(1024*64); + var byteBlock = new ByteBlock(1024 * 64); try { myClass.Package(ref byteBlock);//打包,相当于序列化 - byteBlock.Seek(0);//将流位置重置为0 + byteBlock.SeekToStart();//将流位置重置为0 var myNewClass = new MyGeneratorPackage(); myNewClass.Unpackage(ref byteBlock);//解包,相当于反序列化 @@ -161,37 +161,37 @@ internal class MyPackage : PackageBase public override void Package(ref TByteBlock byteBlock) { //基础类型直接写入。 - byteBlock.WriteInt32(this.P1); - byteBlock.WriteString(this.P2); - byteBlock.WriteChar(this.P3); - byteBlock.WriteDouble(this.P4); + WriterExtension.WriteValue(ref byteBlock, this.P1); + WriterExtension.WriteString(ref byteBlock, this.P2); + WriterExtension.WriteValue(ref byteBlock, this.P3); + WriterExtension.WriteValue(ref byteBlock, (double)this.P4); //集合类型,可以先判断是否为null - byteBlock.WriteIsNull(this.P5); + WriterExtension.WriteIsNull(ref byteBlock, this.P5); if (this.P5 != null) { //如果不为null //就先写入集合长度 //然后遍历将每个项写入 - byteBlock.WriteInt32(this.P5.Count); + WriterExtension.WriteValue(ref byteBlock, this.P5.Count); foreach (var item in this.P5) { - byteBlock.WriteInt32(item); + WriterExtension.WriteValue(ref byteBlock, item); } } //字典类型,可以先判断是否为null - byteBlock.WriteIsNull(this.P6); + WriterExtension.WriteIsNull(ref byteBlock, this.P6); if (this.P6 != null) { //如果不为null //就先写入字典长度 //然后遍历将每个项,按键、值写入 - byteBlock.WriteInt32(this.P6.Count); + WriterExtension.WriteValue(ref byteBlock, this.P6.Count); foreach (var item in this.P6) { - byteBlock.WriteInt32(item.Key); - byteBlock.WritePackage(item.Value);//因为值MyClassModel实现了IPackage,所以可以直接写入 + WriterExtension.WriteValue(ref byteBlock, item.Key); + WriterExtension.WritePackage(ref byteBlock, item.Value);//因为值MyClassModel实现了IPackage,所以可以直接写入 } } } @@ -199,31 +199,34 @@ internal class MyPackage : PackageBase public override void Unpackage(ref TByteBlock byteBlock) { //基础类型按序读取。 - this.P1 = byteBlock.ReadInt32(); - this.P2 = byteBlock.ReadString(); - this.P3 = byteBlock.ReadChar(); - this.P4 = byteBlock.ReadDouble(); + this.P1 = ReaderExtension.ReadValue(ref byteBlock); + this.P2 = ReaderExtension.ReadString(ref byteBlock); + this.P3 = ReaderExtension.ReadValue(ref byteBlock); + this.P4 = ReaderExtension.ReadValue(ref byteBlock); - var isNull = byteBlock.ReadIsNull(); + var isNull = ReaderExtension.ReadIsNull(ref byteBlock); if (!isNull) { - var count = byteBlock.ReadInt32(); + var count = ReaderExtension.ReadValue(ref byteBlock); var list = new List(count); for (var i = 0; i < count; i++) { - list.Add(byteBlock.ReadInt32()); + list.Add(ReaderExtension.ReadValue(ref byteBlock)); } this.P5 = list; } - isNull = byteBlock.ReadIsNull();//复用前面的变量,省的重新声明 + isNull = ReaderExtension.ReadIsNull(ref byteBlock);//复用前面的变量,省的重新声明 if (!isNull) { - var count = byteBlock.ReadInt32(); + var count = ReaderExtension.ReadValue(ref byteBlock); var dic = new Dictionary(count); for (var i = 0; i < count; i++) { - dic.Add(byteBlock.ReadInt32(), byteBlock.ReadPackage()); + var key = ReaderExtension.ReadValue(ref byteBlock); + //因为值MyClassModel实现了IPackage,所以可以直接读取 + var value = ReaderExtension.ReadPackage(ref byteBlock); + dic.Add(key, value); } this.P6 = dic; } @@ -279,16 +282,16 @@ internal class RectangleConverter : FastBinaryConverter { protected override Rectangle Read(ref TByteBlock byteBlock, Type type) { - var rectangle = new Rectangle(byteBlock.ReadInt32(), byteBlock.ReadInt32(), byteBlock.ReadInt32(), byteBlock.ReadInt32()); + var rectangle = new Rectangle(ReaderExtension.ReadValue(ref byteBlock), ReaderExtension.ReadValue(ref byteBlock), ReaderExtension.ReadValue(ref byteBlock), ReaderExtension.ReadValue(ref byteBlock)); return rectangle; } protected override void Write(ref TByteBlock byteBlock, in Rectangle obj) { - byteBlock.WriteInt32(obj.X); - byteBlock.WriteInt32(obj.Y); - byteBlock.WriteInt32(obj.Width); - byteBlock.WriteInt32(obj.Height); + WriterExtension.WriteValue(ref byteBlock, obj.X); + WriterExtension.WriteValue(ref byteBlock, obj.Y); + WriterExtension.WriteValue(ref byteBlock, obj.Width); + WriterExtension.WriteValue(ref byteBlock, obj.Height); } } @@ -298,12 +301,12 @@ public class MyClassModel : PackageBase public override void Package(ref TByteBlock byteBlock) { - byteBlock.WriteDateTime(this.P1); + WriterExtension.WriteValue(ref byteBlock, this.P1); } public override void Unpackage(ref TByteBlock byteBlock) { - this.P1 = byteBlock.ReadDateTime(); + this.P1 = ReaderExtension.ReadValue(ref byteBlock); } } @@ -315,15 +318,15 @@ public class MyClass : PackageBase public override void Package(ref TByteBlock byteBlock) { //将P1与P2属性按类型依次写入 - byteBlock.WriteInt32(this.P1); - byteBlock.WriteString(this.P2); + WriterExtension.WriteValue(ref byteBlock, this.P1); + WriterExtension.WriteString(ref byteBlock, this.P2); } public override void Unpackage(ref TByteBlock byteBlock) { //将P1与P2属性按类型依次读取 - this.P1 = byteBlock.ReadInt32(); - this.P2 = byteBlock.ReadString(); + this.P1 = ReaderExtension.ReadValue(ref byteBlock); + this.P2 = ReaderExtension.ReadString(ref byteBlock); } } @@ -334,31 +337,31 @@ public class MyArrayClass : PackageBase public override void Package(ref TByteBlock byteBlock) { //集合类型,可以先判断集合是否为null - byteBlock.WriteIsNull(this.P5); + WriterExtension.WriteIsNull(ref byteBlock, this.P5); if (this.P5 != null) { //如果不为null //就先写入集合长度 //然后遍历将每个项写入 - byteBlock.WriteInt32(this.P5.Length); + WriterExtension.WriteValue(ref byteBlock, this.P5.Length); foreach (var item in this.P5) { - byteBlock.WriteInt32(item); + WriterExtension.WriteValue(ref byteBlock, item); } } } public override void Unpackage(ref TByteBlock byteBlock) { - var isNull_P5 = byteBlock.ReadIsNull(); + var isNull_P5 = ReaderExtension.ReadIsNull(ref byteBlock); if (!isNull_P5) { //有值 - var count = byteBlock.ReadInt32(); + var count = ReaderExtension.ReadValue(ref byteBlock); var array = new int[count]; for (var i = 0; i < count; i++) { - array[i] = byteBlock.ReadInt32(); + array[i] = ReaderExtension.ReadValue(ref byteBlock); } //赋值 @@ -374,31 +377,32 @@ public class MyDictionaryClass : PackageBase public override void Package(ref TByteBlock byteBlock) { //字典类型,可以先判断是否为null - byteBlock.WriteIsNull(this.P6); + WriterExtension.WriteIsNull(ref byteBlock, this.P6); if (this.P6 != null) { //如果不为null //就先写入字典长度 //然后遍历将每个项,按键、值写入 - byteBlock.WriteInt32(this.P6.Count); + WriterExtension.WriteValue(ref byteBlock, this.P6.Count); foreach (var item in this.P6) { - byteBlock.WriteInt32(item.Key); - byteBlock.WritePackage(item.Value);//因为值MyClassModel实现了IPackage,所以可以直接写入 + WriterExtension.WriteValue(ref byteBlock, item.Key); + //因为值MyClassModel实现了IPackage,所以可以直接写入 + WriterExtension.WritePackage(ref byteBlock, item.Value); } } } public override void Unpackage(ref TByteBlock byteBlock) { - var isNull_6 = byteBlock.ReadIsNull(); + var isNull_6 = ReaderExtension.ReadIsNull(ref byteBlock); if (!isNull_6) { - var count = byteBlock.ReadInt32(); + var count = ReaderExtension.ReadValue(ref byteBlock); var dic = new Dictionary(count); for (var i = 0; i < count; i++) { - dic.Add(byteBlock.ReadInt32(), byteBlock.ReadPackage()); + dic.Add(ReaderExtension.ReadValue(ref byteBlock), ReaderExtension.ReadPackage(ref byteBlock)); } this.P6 = dic; } diff --git a/examples/Core/PluginConsoleApp/PluginConsoleApp.csproj b/examples/Core/PluginConsoleApp/PluginConsoleApp.csproj index 16ff67972..33aa74222 100644 --- a/examples/Core/PluginConsoleApp/PluginConsoleApp.csproj +++ b/examples/Core/PluginConsoleApp/PluginConsoleApp.csproj @@ -1,4 +1,4 @@ - + Exe @@ -6,6 +6,6 @@ - + diff --git a/examples/Core/PluginConsoleApp/Program.cs b/examples/Core/PluginConsoleApp/Program.cs index 8e35f8020..576c51b97 100644 --- a/examples/Core/PluginConsoleApp/Program.cs +++ b/examples/Core/PluginConsoleApp/Program.cs @@ -20,20 +20,26 @@ internal class Program { private static async Task Main(string[] args) { + #region 插件系统创建插件管理器 IPluginManager pluginManager = new PluginManager(new Container()) { Enable = true//必须启用 }; + #endregion + #region 插件系统按类型添加插件 //添加插件 pluginManager.Add(); - pluginManager.Add(); - pluginManager.Add(); pluginManager.Add(); pluginManager.Add(); + #endregion + //示例:注册委托方法 + pluginManager.Add(); + //示例:源生成插件 + pluginManager.Add(); - + #region 插件系统按委托添加插件 //订阅插件,不仅可以使用声明插件的方式,还可以使用委托。 pluginManager.Add(typeof(ISayPlugin), () => { @@ -61,7 +67,9 @@ internal class Program Console.WriteLine("在Action3中获得"); await e.InvokeNext(); }); + #endregion + #region 插件系统触发插件 while (true) { Console.WriteLine("请输入hello、helloaction、hellogenerator、hi或者其他"); @@ -70,9 +78,11 @@ internal class Program Words = Console.ReadLine() }); } + #endregion } } +#region 插件系统新建插件接口及事件类 public class MyPluginEventArgs : PluginEventArgs { public string Words { get; set; } @@ -94,7 +104,9 @@ public interface ISayPlugin : IPlugin /// Task Say(object sender, MyPluginEventArgs e); } +#endregion +#region 插件系统实现插件接口 public class SayHelloPlugin : PluginBase, ISayPlugin { public async Task Say(object sender, MyPluginEventArgs e) @@ -144,7 +156,9 @@ internal class LastSayPlugin : PluginBase, ISayPlugin Console.WriteLine($"{this.GetType().Name}------Leave"); } } +#endregion +#region 插件系统注册委托方法 public class SayHelloAction : PluginBase { protected override void Loaded(IPluginManager pluginManager) @@ -171,7 +185,9 @@ public class SayHelloAction : PluginBase Console.WriteLine($"{this.GetType().Name}------Leave"); } } +#endregion +#region 插件系统源生成插件 public partial class SayHelloGenerator : PluginBase, ISayPlugin { /// @@ -195,4 +211,5 @@ public partial class SayHelloGenerator : PluginBase, ISayPlugin await e.InvokeNext(); Console.WriteLine($"{this.GetType().Name}------Leave"); } -} \ No newline at end of file +} +#endregion \ No newline at end of file diff --git a/examples/Core/TouchSocketBitConverterConsoleApp/Program.cs b/examples/Core/TouchSocketBitConverterConsoleApp/Program.cs new file mode 100644 index 000000000..aeb3a7a0c --- /dev/null +++ b/examples/Core/TouchSocketBitConverterConsoleApp/Program.cs @@ -0,0 +1,364 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.Runtime.InteropServices; +using TouchSocket.Core; + +namespace TouchSocketBitConverterConsoleApp; + +internal class Program +{ + private static void Main(string[] args) + { + TouchSocketBitConverterDemo.RunAll(); + } +} +/// +/// TouchSocketBitConverter 使用示例 +/// +public static class TouchSocketBitConverterDemo +{ + public static void RunAll() + { + BasicGetBytesAndTo(); + ChangeDefaultEndian(); + WriteBytesIntoExistingBuffer(); + ToValuesFromByteSequence(); + ConvertValuesBetweenTypes(); + ConvertBoolBitPacking(); + CrossEndianBulkConversion(); + StructReadWrite(); + } + + #region 基本GetBytes与To示例 + private static void BasicGetBytesAndTo() + { + var value = 0x12345678; + + // 按小端获取字节 + var littleBytes = TouchSocketBitConverter.LittleEndian.GetBytes(value); + // 按大端获取字节 + var bigBytes = TouchSocketBitConverter.BigEndian.GetBytes(value); + + Console.WriteLine($"小端字节: {ToHex(littleBytes.Span)}"); + Console.WriteLine($"大端字节: {ToHex(bigBytes.Span)}"); + + // 使用与写入相同的端解析 + var v1 = TouchSocketBitConverter.LittleEndian.To(littleBytes.Span); + var v2 = TouchSocketBitConverter.BigEndian.To(bigBytes.Span); + + // 交叉解析(故意错误端),会得到不同数值 + var wrong = TouchSocketBitConverter.BigEndian.To(littleBytes.Span); + + Console.WriteLine($"正确解析小端 => {v1:X8}"); + Console.WriteLine($"正确解析大端 => {v2:X8}"); + Console.WriteLine($"以大端错误解析小端数据 => {wrong:X8}"); + Console.WriteLine(); + } + #endregion + + #region 大小端转换器转换整型 + private static void ConvertInt32Example() + { + // int -> byte[] + var data = TouchSocketBitConverter.BigEndian.GetBytes(0x12345678); + + // byte[] -> int + int value = TouchSocketBitConverter.BigEndian.To(data.Span); + } + #endregion + + #region 大小端转换器转换浮点型 + private static void ConvertFloatExample() + { + // float -> byte[] + float num = 3.14f; + var bytes = TouchSocketBitConverter.LittleEndian.GetBytes(num); + + // byte[] -> float + float result = TouchSocketBitConverter.LittleEndian.To(bytes.Span); + } + #endregion + + #region 大小端转换器转换长整型 + private static void ConvertInt64Example() + { + // long -> byte[] + long bigValue = 0x123456789ABCDEF0; + var data = TouchSocketBitConverter.Default.GetBytes(bigValue); + + // byte[] -> long + long restored = TouchSocketBitConverter.Default.To(data.Span); + } + #endregion + + #region 大小端转换器转换高精度小数 + private static void ConvertDecimalExample() + { + // decimal -> byte[] + decimal money = 123456.78m; + var decimalBytes = TouchSocketBitConverter.BigEndian.GetBytes(money); + + // byte[] -> decimal + decimal result = TouchSocketBitConverter.BigEndian.To(decimalBytes.Span); + } + #endregion + + #region 大小端转换器不安全内存操作 + private static void UnsafeMemoryExample() + { + byte[] buffer = new byte[8]; + long value = 0x1122334455667788; + + // 直接内存写入 + TouchSocketBitConverter.LittleEndian.WriteBytes(buffer, value); + + // 直接内存读取 + long readValue = TouchSocketBitConverter.LittleEndian.To(buffer); + } + #endregion + + #region 大小端转换器布尔数组转换 + private static void ConvertBoolArrayExample() + { + // bool[] -> byte[] + bool[] flags = { true, false, true, true, false, true, false, true }; + var byteCount = TouchSocketBitConverter.GetConvertedLength(flags.Length); + byte[] boolBytes = new byte[byteCount]; + TouchSocketBitConverter.ConvertValues(flags, boolBytes); + + // byte[] -> bool[] + bool[] decodedFlags = new bool[flags.Length]; + TouchSocketBitConverter.ConvertValues(boolBytes, decodedFlags); + } + #endregion + + #region 大小端转换器字节序验证 + private static void CheckEndianCompatibility() + { + // 检查当前配置是否与系统字节序一致 + bool isCompatible = TouchSocketBitConverter.Default.IsSameOfSet(); + } + #endregion + + #region 大小端转换器交换端序模式 + private static void SwapEndianExample() + { + // 使用 BigSwap 模式处理网络数据包 + var swappedData = TouchSocketBitConverter.BigSwapEndian.GetBytes(0xAABBCCDD); + } + #endregion + + #region 大小端转换器网络数据包处理 + private static void NetworkPacketExample() + { + // 配置全局大端模式 + TouchSocketBitConverter.DefaultEndianType = EndianType.Big; + + // 构造数据包 + var packet = new byte[16]; + int header = 0x4D534754; // "MSGT" + float payload = 1024.5f; + + // 写入数据 + TouchSocketBitConverter.Default.WriteBytes(packet.AsSpan(0, 4), header); + TouchSocketBitConverter.Default.WriteBytes(packet.AsSpan(4, 4), payload); + + // 解析数据 + int parsedHeader = TouchSocketBitConverter.Default.To(packet.AsSpan(0, 4)); + float parsedPayload = TouchSocketBitConverter.Default.To(packet.AsSpan(4, 4)); + } + #endregion + + #region 修改默认字节序示例 + private static void ChangeDefaultEndian() + { + Console.WriteLine($"当前默认端: {TouchSocketBitConverter.DefaultEndianType}"); + TouchSocketBitConverter.DefaultEndianType = EndianType.Big; + Console.WriteLine($"修改后默认端: {TouchSocketBitConverter.DefaultEndianType}"); + + // Default引用自动指向新的实例 + var bytes = TouchSocketBitConverter.Default.GetBytes((short)0x1234); + Console.WriteLine($"默认端(大端)写出: {ToHex(bytes.Span)}"); + + // 还原 + TouchSocketBitConverter.DefaultEndianType = EndianType.Little; + Console.WriteLine($"还原默认端: {TouchSocketBitConverter.DefaultEndianType}"); + Console.WriteLine(); + } + #endregion + + #region WriteBytes写入既有缓冲区示例 + private static void WriteBytesIntoExistingBuffer() + { + var value = 0x0A0B0C0D; + Span buffer = stackalloc byte[4]; + + TouchSocketBitConverter.BigEndian.WriteBytes(buffer, value); + Console.WriteLine($"大端写入: {ToHex(buffer)}"); + + TouchSocketBitConverter.LittleEndian.WriteBytes(buffer, value); + Console.WriteLine($"小端覆盖: {ToHex(buffer)}"); + Console.WriteLine(); + } + #endregion + + #region ToValues将字节批量转为数组示例 + private static void ToValuesFromByteSequence() + { + // 准备 3 个UInt16(小端) + ushort[] data = { 0x1122, 0x3344, 0x5566 }; + var bytes = new byte[data.Length * 2]; + var conv = TouchSocketBitConverter.LittleEndian; + var span = bytes.AsSpan(); + for (var i = 0; i < data.Length; i++) + { + conv.WriteBytes(span.Slice(i * 2, 2), data[i]); + } + + Console.WriteLine($"原始字节(小端): {ToHex(bytes)}"); + + // 按小端解析 + var u16 = TouchSocketBitConverter.ToValues(bytes, EndianType.Little); + Console.WriteLine("解析结果: " + string.Join(", ", u16.Span.ToArray().Select(v => $"0x{v:X4}"))); + Console.WriteLine(); + } + #endregion + + #region ConvertValues类型批量转换示例 + private static void ConvertValuesBetweenTypes() + { + int[] ints = { 1, 2, 3, 0x10203040 }; + // int[] -> byte[] + var byteCount = TouchSocketBitConverter.GetConvertedLength(ints.Length); + var buffer = new byte[byteCount]; + TouchSocketBitConverter.ConvertValues(ints, buffer); + + Console.WriteLine($"int数组 => byte: {ToHex(buffer)}"); + + // 反向 byte -> int + var back = new int[ints.Length]; + TouchSocketBitConverter.ConvertValues(buffer, back); + Console.WriteLine("还原int: " + string.Join(", ", back.Select(v => $"0x{v:X8}"))); + Console.WriteLine(); + } + #endregion + + #region ConvertValues布尔位打包示例 + private static void ConvertBoolBitPacking() + { + bool[] bools = + { + true,false,true,true, false,false,false,true, + true,true,false,false,false,false,false,false + }; + + // bool -> byte(按位打包) + var byteLength = TouchSocketBitConverter.GetConvertedLength(bools.Length); + var packed = new byte[byteLength]; + TouchSocketBitConverter.ConvertValues(bools, packed); + Console.WriteLine($"bool打包 => {ToHex(packed)} (总位:{bools.Length})"); + + // byte -> bool + var unpacked = new bool[bools.Length]; + TouchSocketBitConverter.ConvertValues(packed, unpacked); + Console.WriteLine("解包结果: " + string.Join("", unpacked.Select(b => b ? '1' : '0'))); + Console.WriteLine(); + } + #endregion + + #region ConvertValues跨端批量转换示例 + private static void CrossEndianBulkConversion() + { + uint[] values = { 0x11223344, 0xAABBCCDD }; + // 源认为大端存储 -> 转成小端字节序的byte[] + var byteLength = TouchSocketBitConverter.GetConvertedLength(values.Length); + var littleBytes = new byte[byteLength]; + TouchSocketBitConverter.ConvertValues(values, littleBytes, EndianType.Big, EndianType.Little); + Console.WriteLine($"大端uint -> 小端byte: {ToHex(littleBytes)}"); + + // 再按小端解释为uint + var round = new uint[values.Length]; + TouchSocketBitConverter.ConvertValues(littleBytes, round, EndianType.Little, EndianType.Little); + Console.WriteLine("还原uint: " + string.Join(", ", round.Select(v => $"0x{v:X8}"))); + Console.WriteLine(); + } + #endregion + + #region 自定义结构读写示例 + [StructLayout(LayoutKind.Sequential, Pack = 1)] + private struct MyPacket + { + public ushort Id; + public int Value; + public byte Flag; + } + + private static void StructReadWrite() + { + var pkt = new MyPacket + { + Id = 0x1234, + Value = 0x11223344, + Flag = 0x5A + }; + + // 逐字段写入(控制端) + Span buffer = stackalloc byte[2 + 4 + 1]; + var big = TouchSocketBitConverter.BigEndian; + big.WriteBytes(buffer.Slice(0, 2), pkt.Id); + big.WriteBytes(buffer.Slice(2, 4), pkt.Value); + buffer[6] = pkt.Flag; + + Console.WriteLine($"MyPacket序列化(大端): {ToHex(buffer)}"); + + // 读取 + var id = big.To(buffer.Slice(0, 2)); + var val = big.To(buffer.Slice(2, 4)); + var flag = buffer[6]; + + Console.WriteLine($"解析 => Id=0x{id:X4}, Value=0x{val:X8}, Flag=0x{flag:X2}"); + Console.WriteLine(); + } + #endregion + + #region 工具方法 + private static string ToHex(ReadOnlySpan span) + { + if (span.Length == 0) + { + return string.Empty; + } + + // 每个字节需要2个十六进制字符,字节间需要1个空格(除了最后一个) + var resultLength = span.Length * 3 - 1; + Span chars = stackalloc char[resultLength]; + + const string hexChars = "0123456789ABCDEF"; + var pos = 0; + + for (var i = 0; i < span.Length; i++) + { + var b = span[i]; + chars[pos++] = hexChars[b >> 4]; // 高4位 + chars[pos++] = hexChars[b & 0x0F]; // 低4位 + + if (i < span.Length - 1) // 不是最后一个字节时添加空格 + { + chars[pos++] = ' '; + } + } + + return new string(chars); + } + #endregion +} \ No newline at end of file diff --git a/examples/Core/TouchSocketBitConverterConsoleApp/TouchSocketBitConverterConsoleApp.csproj b/examples/Core/TouchSocketBitConverterConsoleApp/TouchSocketBitConverterConsoleApp.csproj new file mode 100644 index 000000000..b2377b2c0 --- /dev/null +++ b/examples/Core/TouchSocketBitConverterConsoleApp/TouchSocketBitConverterConsoleApp.csproj @@ -0,0 +1,13 @@ + + + + Exe + net9.0 + enable + enable + + + + + + diff --git a/examples/Directory.Build.props b/examples/Directory.Build.props index c53ab6b1d..47f12524f 100644 --- a/examples/Directory.Build.props +++ b/examples/Directory.Build.props @@ -5,6 +5,5 @@ Copyright © 2024 若汝棋茗 若汝棋茗 IDE0290;IDE0090;IDE0305 - \ No newline at end of file diff --git a/examples/Directory.Packages.props b/examples/Directory.Packages.props new file mode 100644 index 000000000..c37a18d57 --- /dev/null +++ b/examples/Directory.Packages.props @@ -0,0 +1,47 @@ + + + + true + 4.0.0-rc.51 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/Dmtp/AotDmtpRpcConsoleApp/DmtpRpcConsoleApp.csproj b/examples/Dmtp/AotDmtpRpcConsoleApp/DmtpRpcConsoleApp.csproj index f6f67dfad..e2b5cc634 100644 --- a/examples/Dmtp/AotDmtpRpcConsoleApp/DmtpRpcConsoleApp.csproj +++ b/examples/Dmtp/AotDmtpRpcConsoleApp/DmtpRpcConsoleApp.csproj @@ -1,4 +1,4 @@ - + Exe @@ -9,6 +9,6 @@ - + diff --git a/examples/Dmtp/AotDmtpRpcConsoleApp/Program.cs b/examples/Dmtp/AotDmtpRpcConsoleApp/Program.cs index c8f4036e5..bb479cdc7 100644 --- a/examples/Dmtp/AotDmtpRpcConsoleApp/Program.cs +++ b/examples/Dmtp/AotDmtpRpcConsoleApp/Program.cs @@ -1,5 +1,16 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; using TouchSocket.Core; using TouchSocket.Dmtp; @@ -12,7 +23,7 @@ namespace DmtpRpcConsoleApp { internal class Program { - static async Task Main(string[] args) + private static async Task Main(string[] args) { try { @@ -29,11 +40,10 @@ namespace DmtpRpcConsoleApp continue; } - var invokeOption = new DmtpInvokeOption() + var invokeOption = new DmtpInvokeOption(5000) { FeedbackType = FeedbackType.WaitInvoke, - SerializationType = SerializationType.FastBinary, - Timeout = 5000 + SerializationType = SerializationType.FastBinary }; var result = await client.GetDmtpRpcActor().LoginAsync(strs[0], strs[1], invokeOption); @@ -47,7 +57,7 @@ namespace DmtpRpcConsoleApp } } - static async Task GetClient() + private static async Task GetClient() { var client = new TcpDmtpClient(); await client.SetupAsync(new TouchSocketConfig() @@ -58,28 +68,29 @@ namespace DmtpRpcConsoleApp .SetRemoteIPHost("127.0.0.1:7789") .ConfigurePlugins(a => { - a.UseDmtpRpc() - .ConfigureDefaultSerializationSelector(selector => + a.UseDmtpRpc(options => { - //配置Fast序列化器 - selector.FastSerializerContext = new AppFastSerializerContext(); - - //配置System.Text.Json序列化器 - selector.UseSystemTextJson(options => - { - options.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default); - }); + options.ConfigureDefaultSerializationSelector(selector => + { + //配置Fast序列化器 + selector.FastSerializerContext = new AppFastSerializerContext(); + //配置System.Text.Json序列化器 + selector.UseSystemTextJson(options => + { + options.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default); + }); + }); }); }) - .SetDmtpOption(new DmtpOption() - { - VerifyToken = "Rpc" - })); + .SetDmtpOption(options => + { + options.VerifyToken = "Rpc"; + })); await client.ConnectAsync(); client.Logger.Info($"客户端已连接"); return client; } - static async Task GetService() + private static async Task GetService() { var service = new TcpDmtpService(); var config = new TouchSocketConfig()//配置 @@ -94,22 +105,24 @@ namespace DmtpRpcConsoleApp }) .ConfigurePlugins(a => { - a.UseDmtpRpc() - .ConfigureDefaultSerializationSelector(selector => + a.UseDmtpRpc(options => { - //配置Fast序列化器 - selector.FastSerializerContext = new AppFastSerializerContext(); - - //配置System.Text.Json序列化器 - selector.UseSystemTextJson(options => + options.ConfigureDefaultSerializationSelector(selector => { - options.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default); + //配置Fast序列化器 + selector.FastSerializerContext = new AppFastSerializerContext(); + + //配置System.Text.Json序列化器 + selector.UseSystemTextJson(options => + { + options.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default); + }); }); }); }) - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options => { - VerifyToken = "Rpc" + options.VerifyToken = "Rpc"; }); await service.SetupAsync(config); @@ -124,7 +137,7 @@ namespace DmtpRpcConsoleApp [FastSerializable(typeof(MyResult))] [FastSerializable(typeof(IMyRpcServer), TypeMode.All)]//直接按类型,搜索其属性,字段,方法参数,方法返回值的类型进行注册序列化 - partial class AppFastSerializerContext : FastSerializerContext + internal partial class AppFastSerializerContext : FastSerializerContext { } diff --git a/examples/Dmtp/AotDmtpRpcPerformanceConsoleApp/DmtpRpcPerformanceConsoleApp.csproj b/examples/Dmtp/AotDmtpRpcPerformanceConsoleApp/DmtpRpcPerformanceConsoleApp.csproj index 59585b20b..5fdbe036a 100644 --- a/examples/Dmtp/AotDmtpRpcPerformanceConsoleApp/DmtpRpcPerformanceConsoleApp.csproj +++ b/examples/Dmtp/AotDmtpRpcPerformanceConsoleApp/DmtpRpcPerformanceConsoleApp.csproj @@ -1,4 +1,4 @@ - + Exe net9.0 @@ -7,6 +7,6 @@ true - + diff --git a/examples/Dmtp/AotDmtpRpcPerformanceConsoleApp/Program.cs b/examples/Dmtp/AotDmtpRpcPerformanceConsoleApp/Program.cs index b4e26ab71..adb3e7318 100644 --- a/examples/Dmtp/AotDmtpRpcPerformanceConsoleApp/Program.cs +++ b/examples/Dmtp/AotDmtpRpcPerformanceConsoleApp/Program.cs @@ -1,5 +1,16 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + using System.Text; -using System.Threading.Tasks; using TouchSocket.Core; using TouchSocket.Dmtp; using TouchSocket.Dmtp.Rpc; @@ -10,7 +21,7 @@ namespace RpcPerformanceConsoleApp { internal class Program { - static async Task Main(string[] args) + private static async Task Main(string[] args) { var consoleAction = new ConsoleAction("h|help|?");//设置帮助命令 consoleAction.OnException += ConsoleAction_OnException;//订阅执行异常输出 @@ -19,9 +30,9 @@ namespace RpcPerformanceConsoleApp var count = 100000; - consoleAction.Add("3.1", "DmtpRpc测试Sum", async () =>await StartSumClient(count)); + consoleAction.Add("3.1", "DmtpRpc测试Sum", async () => await StartSumClient(count)); consoleAction.Add("3.2", "DmtpRpc测试GetBytes", async () => await StartGetBytesClient(count)); - consoleAction.Add("3.3", "DmtpRpc测试BigString", async () =>await StartBigStringClient(count)); + consoleAction.Add("3.3", "DmtpRpc测试BigString", async () => await StartBigStringClient(count)); consoleAction.ShowAll(); @@ -45,9 +56,9 @@ namespace RpcPerformanceConsoleApp { a.UseDmtpRpc(); }) - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options=> { - VerifyToken = "Rpc"//设定连接口令,作用类似账号密码 + options.VerifyToken = "Rpc";//设定连接口令,作用类似账号密码 }); await service.SetupAsync(config); @@ -58,13 +69,13 @@ namespace RpcPerformanceConsoleApp public static async Task StartSumClient(int count) { - var client =await GetClient(); + var client = await GetClient(); var timeSpan = TimeMeasurer.Run(async () => { var actor = client.GetDmtpRpcActor(); for (var i = 0; i < count; i++) { - var rs =await actor.InvokeTAsync("Sum", InvokeOption.WaitInvoke, i, i); + var rs = await actor.InvokeTAsync("Sum", InvokeOption.WaitInvoke, i, i); if (rs != i + i) { Console.WriteLine("调用结果不一致"); @@ -87,9 +98,9 @@ namespace RpcPerformanceConsoleApp a.UseDmtpRpc(); }) .SetRemoteIPHost("127.0.0.1:7789") - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options=> { - VerifyToken = "Rpc" + options.VerifyToken = "Rpc"; })); await client.ConnectAsync(); return client; @@ -102,7 +113,7 @@ namespace RpcPerformanceConsoleApp var actor = client.GetDmtpRpcActor(); for (var i = 1; i < count; i++) { - var rs =await actor.InvokeTAsync("GetBytes", InvokeOption.WaitInvoke, i);//测试10k数据 + var rs = await actor.InvokeTAsync("GetBytes", InvokeOption.WaitInvoke, i);//测试10k数据 if (rs.Length != i) { Console.WriteLine("调用结果不一致"); @@ -118,13 +129,13 @@ namespace RpcPerformanceConsoleApp public static async Task StartBigStringClient(int count) { - var client =await GetClient(); + var client = await GetClient(); var timeSpan = TimeMeasurer.Run(async () => { var actor = client.GetDmtpRpcActor(); for (var i = 0; i < count; i++) { - var rs =await actor.InvokeTAsync("GetBigString", InvokeOption.WaitInvoke); + var rs = await actor.InvokeTAsync("GetBigString", InvokeOption.WaitInvoke); if (i % 1000 == 0) { Console.WriteLine(i); diff --git a/examples/Dmtp/CreateDmtpConsoleApp/CreateDmtpConsoleApp.csproj b/examples/Dmtp/CreateDmtpConsoleApp/CreateDmtpConsoleApp.csproj new file mode 100644 index 000000000..2bb9784a4 --- /dev/null +++ b/examples/Dmtp/CreateDmtpConsoleApp/CreateDmtpConsoleApp.csproj @@ -0,0 +1,16 @@ + + + + Exe + net9.0 + enable + enable + + + + + + + + + diff --git a/examples/Dmtp/CreateDmtpConsoleApp/Program.cs b/examples/Dmtp/CreateDmtpConsoleApp/Program.cs new file mode 100644 index 000000000..3d3020059 --- /dev/null +++ b/examples/Dmtp/CreateDmtpConsoleApp/Program.cs @@ -0,0 +1,480 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using TouchSocket.Core; +using TouchSocket.Dmtp; +using TouchSocket.NamedPipe; +using TouchSocket.Sockets; + +namespace CreateDmtpConsoleApp; + +internal class Program +{ + private static async Task Main(string[] args) + { + var tcpDmtpService = await CreateTcpDmtpService(); + } + + private static async Task CreateTcpDmtpService() + { + #region 创建TcpDmtpService + var service = new TcpDmtpService(); + var config = new TouchSocketConfig()//配置 + .SetListenIPHosts(7789) + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .ConfigurePlugins(a => + { + //添加插件 + //a.Add(); + }) + #region Dmtp服务器基础配置 + .SetDmtpOption(options => + { + options.VerifyToken = "Dmtp";//设定连接口令,作用类似账号密码 + options.VerifyTimeout = TimeSpan.FromSeconds(3);//设定账号密码验证超时时间 + }) + #endregion + ; + + await service.SetupAsync(config); + await service.StartAsync(); + #endregion + + service.Logger.Info($"{service.GetType().Name}已启动"); + return service; + } + + private static async Task CreateUdpDmtpService() + { + #region 创建UdpDmtp + var udpDmtp = new UdpDmtp(); + + var config = new TouchSocketConfig(); + config.SetBindIPHost(new IPHost(7789)) + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }); + + await udpDmtp.SetupAsync(config); + + await udpDmtp.StartAsync(); + #endregion + + udpDmtp.Logger.Info($"{udpDmtp.GetType().Name}已启动"); + return udpDmtp; + } + private static async Task CreateHttpDmtpService() + { + #region 创建HttpDmtpService + var service = new HttpDmtpService(); + var config = new TouchSocketConfig()//配置 + .SetListenIPHosts(7789) + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .SetDmtpOption(options => + { + options.VerifyToken = "Dmtp"; + }); + + await service.SetupAsync(config); + + await service.StartAsync(); + #endregion + + service.Logger.Info($"{service.GetType().Name}已启动"); + return service; + } + + private static async Task CreateNamedPipeDmtpService() + { + #region 创建NamedPipeDmtpService + var service = new NamedPipeDmtpService(); + var config = new TouchSocketConfig()//配置 + .SetPipeName("TouchSocketPipe")//设置管道名称 + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .SetDmtpOption(options => + { + options.VerifyToken = "Dmtp"; + }); + + await service.SetupAsync(config); + + await service.StartAsync(); + #endregion + + service.Logger.Info($"{service.GetType().Name}已启动"); + return service; + } + private static async Task CreateAspNetCoreWebSocketDmtpService(string[] args) + { + #region 创建WebSocket协议的Dmtp服务器 + var builder = WebApplication.CreateBuilder(args); + + #region AspNetCore统一配置容器 + builder.Services.ConfigureContainer(container => + { + container.AddAspNetCoreLogger(); + }); + #endregion + + + builder.Services.AddWebSocketDmtpService(config => + { + config.SetDmtpOption(options => + { + options.VerifyToken = "Dmtp"; + }) + .ConfigurePlugins(a => + { + }); + }); + + var app = builder.Build(); + app.UseWebSockets(); + app.UseWebSocketDmtp("/WebSocketDmtp");//WebSocketDmtp必须在UseWebSockets之后使用。 + #endregion + + await app.RunAsync(); + } + + private static async Task CreateAspNetCoreHttpDmtpService(string[] args) + { + #region 创建基于AspNetCore的Http协议的Dmtp服务器 + var builder = WebApplication.CreateBuilder(args); + + builder.Services.ConfigureContainer(container => + { + container.AddAspNetCoreLogger(); + }); + + builder.Services.AddHttpMiddlewareDmtpService(config => + { + config.SetDmtpOption(options => + { + options.VerifyToken = "Dmtp"; + }) + .ConfigurePlugins(a => + { + //添加插件 + }); + }); + + var app = builder.Build(); + app.UseHttpDmtp(); //HttpDmtp可以单独直接使用。不需要其他。 + #endregion + + } + + #region 客户端 + private static async Task CreateTcpDmtpClient() + { + #region 创建TcpDmtpClient + var client = new TcpDmtpClient(); + + //配置项目 + var config = new TouchSocketConfig(); + config.SetRemoteIPHost("tcp://127.0.0.1:7789"); + config.SetDmtpOption(options => + { + options.VerifyToken = "Dmtp"; + options.Metadata = new Metadata().Add("a", "a"); + }); + + //配置容器 + config.ConfigureContainer(a => + { + //注入日志组件 + a.AddConsoleLogger(); + }); + + //配置插件 + config.ConfigurePlugins(a => + { + //添加插件 + //a.Add(); + //a.UseDmtpRpc(); + }); + + //应用配置 + await client.SetupAsync(config); + + //连接 + await client.ConnectAsync(); + #endregion + + } + private static async Task CreateUdpDmtpClient() + { + #region 创建UdpDmtpClient + var client = new UdpDmtp(); + + //配置项目 + var config = new TouchSocketConfig(); + config.SetRemoteIPHost("udp://127.0.0.1:7797");//远程地址 + config.UseUdpReceive();//使用Udp接收 + config.SetDmtpOption(options => + { + options.VerifyToken = "Dmtp"; + options.Metadata = new Metadata().Add("a", "a"); + }); + + //配置容器 + config.ConfigureContainer(a => + { + //注入日志组件 + a.AddConsoleLogger(); + }); + + //配置插件 + config.ConfigurePlugins(a => + { + //添加插件 + //a.Add(); + //a.UseDmtpRpc(); + }); + + //应用配置 + await client.SetupAsync(config); + + //启动 + await client.StartAsync(); + #endregion + } + private static async Task CreateHttpDmtpClient() + { + #region 创建HttpDmtpClient + var client = new HttpDmtpClient(); + + //配置项目 + var config = new TouchSocketConfig(); + config.SetRemoteIPHost("http://127.0.0.1:7789"); + config.SetDmtpOption(options => + { + options.VerifyToken = "Dmtp"; + options.Metadata = new Metadata().Add("a", "a"); + }); + + //配置容器 + config.ConfigureContainer(a => + { + //注入日志组件 + a.AddConsoleLogger(); + }); + + //配置插件 + config.ConfigurePlugins(a => + { + //添加插件 + //a.Add(); + //a.UseDmtpRpc(); + }); + + //应用配置 + await client.SetupAsync(config); + + //连接 + await client.ConnectAsync(); + #endregion + } + private static async Task CreateWebSocketDmtpClient() + { + #region 创建WebSocketDmtpClient + var client = new WebSocketDmtpClient(); + + //配置项目 + var config = new TouchSocketConfig(); + config.SetRemoteIPHost("ws://127.0.0.1:7789/WebSocketDmtp"); + config.SetDmtpOption(options => + { + options.VerifyToken = "Dmtp"; + options.Metadata = new Metadata().Add("a", "a"); + }); + + //配置容器 + config.ConfigureContainer(a => + { + //注入日志组件 + a.AddConsoleLogger(); + }); + + //配置插件 + config.ConfigurePlugins(a => + { + //添加插件 + //a.Add(); + //a.UseDmtpRpc(); + }); + + //应用配置 + await client.SetupAsync(config); + + //连接 + await client.ConnectAsync(); + #endregion + } + + private static async Task CreateNamedPipeDmtpClient() + { + #region 创建NamedPipeDmtpClient + var client = new NamedPipeDmtpClient(); + + //配置项目 + var config = new TouchSocketConfig(); + config.SetPipeName("TouchSocketPipe"); + config.SetDmtpOption(options => + { + options.VerifyToken = "Dmtp"; + options.Metadata = new Metadata().Add("a", "a"); + }); + + //配置容器 + config.ConfigureContainer(a => + { + //注入日志组件 + a.AddConsoleLogger(); + }); + + //配置插件 + config.ConfigurePlugins(a => + { + //添加插件 + //a.Add(); + //a.UseDmtpRpc(); + }); + + //应用配置 + await client.SetupAsync(config); + + //连接 + await client.ConnectAsync(); + #endregion + } + private static async Task CreateTcpDmtpClientFactory() + { + #region 创建TcpDmtpClientFactory + var clientFactory = new TcpDmtpClientFactory(); + + //配置工厂 + clientFactory.GetConfig = () => + { + //配置项目 + var config = new TouchSocketConfig(); + config.SetRemoteIPHost("tcp://127.0.0.1:7789"); + config.SetDmtpOption(options => + { + options.VerifyToken = "Dmtp"; + options.Metadata = new Metadata().Add("a", "a"); + }); + + //配置容器 + config.ConfigureContainer(a => + { + //注入日志组件 + a.AddConsoleLogger(); + }); + + //配置插件 + config.ConfigurePlugins(a => + { + //添加插件 + //a.Add(); + //a.UseDmtpRpc(); + }); + + return config; + }; + + //在链接工厂获取客户端时,如果没有可用客户端时的最大等待时间。 + //超过该时间将新创建客户端。 + clientFactory.MaxWaitTime = TimeSpan.FromSeconds(1); + + clientFactory.MinCount = 2;//最小连接数 + clientFactory.MaxCount = 5;//最大连接数 + + using var cts = new CancellationTokenSource(10 * 1000); + + //获取客户端 + using (var clientFactoryResult = await clientFactory.GetClient(cts.Token)) + { + //获取到的池中的客户端 + //注意:该客户端只能在当前using作用域中使用。 + var client = clientFactoryResult.Client; + } + #endregion + } + + private static async Task CreateHttpDmtpClientFactory() + { + #region 创建HttpDmtpClientFactory + var clientFactory = new HttpDmtpClientFactory(); + + //配置工厂 + clientFactory.GetConfig = () => + { + //配置项目 + var config = new TouchSocketConfig(); + config.SetRemoteIPHost("http://127.0.0.1:7789"); + config.SetDmtpOption(options => + { + options.VerifyToken = "Dmtp"; + options.Metadata = new Metadata().Add("a", "a"); + }); + + //配置容器 + config.ConfigureContainer(a => + { + //注入日志组件 + a.AddConsoleLogger(); + }); + + //配置插件 + config.ConfigurePlugins(a => + { + //添加插件 + //a.Add(); + //a.UseDmtpRpc(); + }); + + return config; + }; + + //在链接工厂获取客户端时,如果没有可用客户端时的最大等待时间。 + //超过该时间将新创建客户端。 + clientFactory.MaxWaitTime = TimeSpan.FromSeconds(1); + + clientFactory.MinCount = 2;//最小连接数 + clientFactory.MaxCount = 5;//最大连接数 + + using var cts = new CancellationTokenSource(10 * 1000); + + //获取客户端 + using (var clientFactoryResult = await clientFactory.GetClient(cts.Token)) + { + //获取到的池中的客户端 + //注意:该客户端只能在当前using作用域中使用。 + var client = clientFactoryResult.Client; + } + #endregion + } + #endregion +} diff --git a/examples/Dmtp/CustomDmtpActorConsoleApp/CustomDmtpActorConsoleApp.csproj b/examples/Dmtp/CustomDmtpActorConsoleApp/CustomDmtpActorConsoleApp.csproj index 32cf36851..f5bdf5081 100644 --- a/examples/Dmtp/CustomDmtpActorConsoleApp/CustomDmtpActorConsoleApp.csproj +++ b/examples/Dmtp/CustomDmtpActorConsoleApp/CustomDmtpActorConsoleApp.csproj @@ -1,4 +1,4 @@ - + Exe @@ -7,6 +7,6 @@ - + diff --git a/examples/Dmtp/CustomDmtpActorConsoleApp/Program.cs b/examples/Dmtp/CustomDmtpActorConsoleApp/Program.cs index 6c4562bc2..432006637 100644 --- a/examples/Dmtp/CustomDmtpActorConsoleApp/Program.cs +++ b/examples/Dmtp/CustomDmtpActorConsoleApp/Program.cs @@ -34,7 +34,7 @@ internal class Program try { - actor.Invoke(methodName); + await actor.Invoke(methodName, CancellationToken.None); Console.WriteLine("调用成功"); } catch (Exception ex) @@ -48,9 +48,9 @@ internal class Program { var client = await new TouchSocketConfig() .SetRemoteIPHost("127.0.0.1:7789") - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options => { - VerifyToken = "File" + options.VerifyToken = "File"; }) .ConfigureContainer(a => { @@ -59,10 +59,6 @@ internal class Program .ConfigurePlugins(a => { a.UseSimpleDmtpRpc(); - - a.UseDmtpHeartbeat()//使用Dmtp心跳 - .SetTick(TimeSpan.FromSeconds(3)) - .SetMaxFailCount(3); }) .BuildClientAsync(); @@ -87,9 +83,9 @@ internal class Program a.UseSimpleDmtpRpc() .RegisterRpc(new MyServer()); }) - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options => { - VerifyToken = "File"//连接验证口令。 + options.VerifyToken = "File";//连接验证口令。 }); await service.SetupAsync(config); diff --git a/examples/Dmtp/CustomDmtpActorConsoleApp/SimpleDmtpRpc/Actor/ISimpleDmtpRpcActor.cs b/examples/Dmtp/CustomDmtpActorConsoleApp/SimpleDmtpRpc/Actor/ISimpleDmtpRpcActor.cs index 3479c41de..3775ef581 100644 --- a/examples/Dmtp/CustomDmtpActorConsoleApp/SimpleDmtpRpc/Actor/ISimpleDmtpRpcActor.cs +++ b/examples/Dmtp/CustomDmtpActorConsoleApp/SimpleDmtpRpc/Actor/ISimpleDmtpRpcActor.cs @@ -16,7 +16,7 @@ namespace CustomDmtpActorConsoleApp.SimpleDmtpRpc; internal interface ISimpleDmtpRpcActor : IActor { - void Invoke(string methodName); + Task Invoke(string methodName, CancellationToken token); - void Invoke(string targetId, string methodName); + Task Invoke(string targetId, string methodName, CancellationToken token); } \ No newline at end of file diff --git a/examples/Dmtp/CustomDmtpActorConsoleApp/SimpleDmtpRpc/Actor/SimpleDmtpRpcActor.cs b/examples/Dmtp/CustomDmtpActorConsoleApp/SimpleDmtpRpc/Actor/SimpleDmtpRpcActor.cs index b039e85cd..b91015fa0 100644 --- a/examples/Dmtp/CustomDmtpActorConsoleApp/SimpleDmtpRpc/Actor/SimpleDmtpRpcActor.cs +++ b/examples/Dmtp/CustomDmtpActorConsoleApp/SimpleDmtpRpc/Actor/SimpleDmtpRpcActor.cs @@ -15,7 +15,7 @@ using TouchSocket.Dmtp; namespace CustomDmtpActorConsoleApp.SimpleDmtpRpc; -internal class SimpleDmtpRpcActor :DisposableObject, ISimpleDmtpRpcActor +internal class SimpleDmtpRpcActor : DisposableObject, ISimpleDmtpRpcActor { private readonly ushort m_invoke_Request = 1000; private readonly ushort m_invoke_Response = 1001; @@ -30,20 +30,21 @@ internal class SimpleDmtpRpcActor :DisposableObject, ISimpleDmtpRpcActor public async Task InputReceivedData(DmtpMessage message) { - var byteBlock = message.BodyByteBlock; + var memory = message.Memory; + var reader = new BytesReader(memory); if (message.ProtocolFlags == this.m_invoke_Request) { try { var rpcPackage = new SimpleDmtpRpcPackage(); - rpcPackage.UnpackageRouter(ref byteBlock); + rpcPackage.UnpackageRouter(ref reader); if (rpcPackage.Route && this.DmtpActor.AllowRoute) { if (await this.DmtpActor.TryRouteAsync(new PackageRouterEventArgs(new RouteType("SimpleRpc"), rpcPackage))) { if (await this.DmtpActor.TryFindDmtpActor(rpcPackage.TargetId) is DmtpActor actor) { - await actor.SendAsync(this.m_invoke_Request, byteBlock.Memory); + await actor.SendAsync(this.m_invoke_Request, memory); return true; } else @@ -56,15 +57,23 @@ internal class SimpleDmtpRpcActor :DisposableObject, ISimpleDmtpRpcActor rpcPackage.Status = 3; } - byteBlock.Reset(); rpcPackage.SwitchId(); - rpcPackage.Package(ref byteBlock); - await this.DmtpActor.SendAsync(this.m_invoke_Response, byteBlock.Memory); + var byteBlock = new ByteBlock(1024); + try + { + rpcPackage.Package(ref byteBlock); + await this.DmtpActor.SendAsync(this.m_invoke_Response, byteBlock.Memory); + } + finally + { + byteBlock.Dispose(); + } + } else { - rpcPackage.UnpackageBody(ref byteBlock); + rpcPackage.UnpackageBody(ref reader); _ = Task.Factory.StartNew(this.InvokeThis, rpcPackage); } } @@ -79,18 +88,18 @@ internal class SimpleDmtpRpcActor :DisposableObject, ISimpleDmtpRpcActor try { var rpcPackage = new SimpleDmtpRpcPackage(); - rpcPackage.UnpackageRouter(ref byteBlock); + rpcPackage.UnpackageRouter(ref reader); if (this.DmtpActor.AllowRoute && rpcPackage.Route) { if (await this.DmtpActor.TryFindDmtpActor(rpcPackage.TargetId) is DmtpActor actor) { - await actor.SendAsync(this.m_invoke_Response, byteBlock.Memory); + await actor.SendAsync(this.m_invoke_Response, memory); } } else { - rpcPackage.UnpackageBody(ref byteBlock); - this.DmtpActor.WaitHandlePool.SetRun(rpcPackage); + rpcPackage.UnpackageBody(ref reader); + this.DmtpActor.WaitHandlePool.Set(rpcPackage); } } catch (Exception ex) @@ -109,7 +118,7 @@ internal class SimpleDmtpRpcActor :DisposableObject, ISimpleDmtpRpcActor var methodModel = this.TryFindMethod.Invoke(package.MethodName); if (methodModel == null) { - var byteBlock = new ByteBlock(1024*64); + var byteBlock = new ByteBlock(1024 * 64); try { package.Status = 4; @@ -127,7 +136,7 @@ internal class SimpleDmtpRpcActor :DisposableObject, ISimpleDmtpRpcActor try { methodModel.Method.Invoke(methodModel.Target, default); - var byteBlock = new ByteBlock(1024*64); + var byteBlock = new ByteBlock(1024 * 64); try { package.Status = 1; @@ -143,7 +152,7 @@ internal class SimpleDmtpRpcActor :DisposableObject, ISimpleDmtpRpcActor } catch (Exception ex) { - var byteBlock = new ByteBlock(1024*64); + var byteBlock = new ByteBlock(1024 * 64); try { package.Status = 5; @@ -176,12 +185,12 @@ internal class SimpleDmtpRpcActor :DisposableObject, ISimpleDmtpRpcActor return default; } - public void Invoke(string methodName) + public async Task Invoke(string methodName, CancellationToken token) { - this.PrivateInvoke(default, methodName); + await this.PrivateInvoke(default, methodName, token); } - public async void Invoke(string targetId, string methodName) + public async Task Invoke(string targetId, string methodName, CancellationToken token) { if (string.IsNullOrEmpty(targetId)) { @@ -195,14 +204,14 @@ internal class SimpleDmtpRpcActor :DisposableObject, ISimpleDmtpRpcActor if (this.DmtpActor.AllowRoute && await this.TryFindDmtpRpcActor(targetId) is SimpleDmtpRpcActor actor) { - actor.Invoke(methodName); + await actor.Invoke(methodName, token); return; } - this.PrivateInvoke(targetId, methodName); + await this.PrivateInvoke(targetId, methodName, token); } - private async void PrivateInvoke(string id, string methodName) + private async Task PrivateInvoke(string id, string methodName, CancellationToken token) { var package = new SimpleDmtpRpcPackage() { @@ -211,11 +220,11 @@ internal class SimpleDmtpRpcActor :DisposableObject, ISimpleDmtpRpcActor TargetId = id }; - var waitData = this.DmtpActor.WaitHandlePool.GetWaitData(package); + var waitData = this.DmtpActor.WaitHandlePool.GetWaitDataAsync(package); try { - var byteBlock = new ByteBlock(1024*64); + var byteBlock = new ByteBlock(1024 * 64); try { package.Package(ref byteBlock); @@ -226,10 +235,10 @@ internal class SimpleDmtpRpcActor :DisposableObject, ISimpleDmtpRpcActor byteBlock.Dispose(); } - switch (waitData.Wait(5000)) + switch (await waitData.WaitAsync(token)) { - case WaitDataStatus.SetRunning: - var result = (SimpleDmtpRpcPackage)waitData.WaitResult; + case WaitDataStatus.Success: + var result = (SimpleDmtpRpcPackage)waitData.CompletedData; result.CheckStatus(); return; @@ -246,7 +255,7 @@ internal class SimpleDmtpRpcActor :DisposableObject, ISimpleDmtpRpcActor } finally { - this.DmtpActor.WaitHandlePool.Destroy(package.Sign); + waitData.Dispose(); } } } \ No newline at end of file diff --git a/examples/Dmtp/CustomDmtpActorConsoleApp/SimpleDmtpRpc/Common/SimpleDmtpRpcPackage.cs b/examples/Dmtp/CustomDmtpActorConsoleApp/SimpleDmtpRpc/Common/SimpleDmtpRpcPackage.cs index c1863edd9..0fccf5e05 100644 --- a/examples/Dmtp/CustomDmtpActorConsoleApp/SimpleDmtpRpc/Common/SimpleDmtpRpcPackage.cs +++ b/examples/Dmtp/CustomDmtpActorConsoleApp/SimpleDmtpRpc/Common/SimpleDmtpRpcPackage.cs @@ -24,13 +24,13 @@ internal class SimpleDmtpRpcPackage : WaitRouterPackage public override void PackageBody(ref TByteBlock byteBlock) { base.PackageBody(ref byteBlock); - byteBlock.WriteString(this.MethodName); + WriterExtension.WriteString(ref byteBlock, this.MethodName); } public override void UnpackageBody(ref TByteBlock byteBlock) { base.UnpackageBody(ref byteBlock); - this.MethodName = byteBlock.ReadString(); + this.MethodName = ReaderExtension.ReadString(ref byteBlock); } public void CheckStatus() diff --git a/examples/Dmtp/CustomDmtpActorConsoleApp/SimpleDmtpRpc/SimpleDmtpRpcFeature.cs b/examples/Dmtp/CustomDmtpActorConsoleApp/SimpleDmtpRpc/SimpleDmtpRpcFeature.cs index 081a0477a..29f747faf 100644 --- a/examples/Dmtp/CustomDmtpActorConsoleApp/SimpleDmtpRpc/SimpleDmtpRpcFeature.cs +++ b/examples/Dmtp/CustomDmtpActorConsoleApp/SimpleDmtpRpc/SimpleDmtpRpcFeature.cs @@ -16,11 +16,11 @@ using TouchSocket.Dmtp; namespace CustomDmtpActorConsoleApp.SimpleDmtpRpc; -internal class SimpleDmtpRpcFeature : PluginBase, IDmtpHandshakingPlugin, IDmtpReceivedPlugin +internal class SimpleDmtpRpcFeature : PluginBase, IDmtpConnectingPlugin, IDmtpReceivedPlugin { private readonly Dictionary m_pairs = new Dictionary(); - public async Task OnDmtpHandshaking(IDmtpActorObject client, DmtpVerifyEventArgs e) + public async Task OnDmtpConnecting(IDmtpActorObject client, DmtpVerifyEventArgs e) { var actor = new SimpleDmtpRpcActor(client.DmtpActor) { diff --git a/examples/Dmtp/DispatchProxyDmtpRpcConsoleApp/DispatchProxyDmtpRpcConsoleApp.csproj b/examples/Dmtp/DispatchProxyDmtpRpcConsoleApp/DispatchProxyDmtpRpcConsoleApp.csproj index cd878d4fe..e09ee2012 100644 --- a/examples/Dmtp/DispatchProxyDmtpRpcConsoleApp/DispatchProxyDmtpRpcConsoleApp.csproj +++ b/examples/Dmtp/DispatchProxyDmtpRpcConsoleApp/DispatchProxyDmtpRpcConsoleApp.csproj @@ -8,6 +8,6 @@ - + diff --git a/examples/Dmtp/DispatchProxyDmtpRpcConsoleApp/Program.cs b/examples/Dmtp/DispatchProxyDmtpRpcConsoleApp/Program.cs index bc54b4df3..50d71db43 100644 --- a/examples/Dmtp/DispatchProxyDmtpRpcConsoleApp/Program.cs +++ b/examples/Dmtp/DispatchProxyDmtpRpcConsoleApp/Program.cs @@ -58,9 +58,9 @@ internal class Program a.UseDmtpRpc(); }) .SetRemoteIPHost("127.0.0.1:7789") - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options=> { - VerifyToken = "Dmtp" + options.VerifyToken = "Dmtp"; })); client.ConnectAsync(); client.Logger.Info($"连接成功,Id={client.Id}"); diff --git a/examples/Dmtp/DmtpAspnetcoreConsoleApp/DmtpAspnetcoreConsoleApp.csproj b/examples/Dmtp/DmtpAspnetcoreConsoleApp/DmtpAspnetcoreConsoleApp.csproj index 177290819..ace6afa72 100644 --- a/examples/Dmtp/DmtpAspnetcoreConsoleApp/DmtpAspnetcoreConsoleApp.csproj +++ b/examples/Dmtp/DmtpAspnetcoreConsoleApp/DmtpAspnetcoreConsoleApp.csproj @@ -8,7 +8,7 @@ - + diff --git a/examples/Dmtp/DmtpAspnetcoreConsoleApp/Program.cs b/examples/Dmtp/DmtpAspnetcoreConsoleApp/Program.cs index 2d73f4fce..da40936c0 100644 --- a/examples/Dmtp/DmtpAspnetcoreConsoleApp/Program.cs +++ b/examples/Dmtp/DmtpAspnetcoreConsoleApp/Program.cs @@ -23,9 +23,9 @@ internal class Program //WebSocketDmtpClient连接 var websocketDmtpClient = new WebSocketDmtpClient(); await websocketDmtpClient.SetupAsync(new TouchSocketConfig() - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options=> { - VerifyToken = "Dmtp" + options.VerifyToken = "Dmtp"; }) .SetRemoteIPHost("ws://localhost:5174/WebSocketDmtp")); await websocketDmtpClient.ConnectAsync(); @@ -34,9 +34,9 @@ internal class Program //HttpDmtpClient连接 var httpDmtpClient = new HttpDmtpClient(); await httpDmtpClient.SetupAsync(new TouchSocketConfig() - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options=> { - VerifyToken = "Dmtp" + options.VerifyToken = "Dmtp"; }) .SetRemoteIPHost("http://127.0.0.1:5174")); await httpDmtpClient.ConnectAsync(); diff --git a/examples/Dmtp/DmtpChannelConsoleApp/DmtpChannelConsoleApp.csproj b/examples/Dmtp/DmtpChannelConsoleApp/DmtpChannelConsoleApp.csproj index cd878d4fe..e09ee2012 100644 --- a/examples/Dmtp/DmtpChannelConsoleApp/DmtpChannelConsoleApp.csproj +++ b/examples/Dmtp/DmtpChannelConsoleApp/DmtpChannelConsoleApp.csproj @@ -8,6 +8,6 @@ - + diff --git a/examples/Dmtp/DmtpChannelConsoleApp/Program.cs b/examples/Dmtp/DmtpChannelConsoleApp/Program.cs index 6d085e1cb..2ab19efe5 100644 --- a/examples/Dmtp/DmtpChannelConsoleApp/Program.cs +++ b/examples/Dmtp/DmtpChannelConsoleApp/Program.cs @@ -38,13 +38,10 @@ internal class Program //HoldOn的使用,主要是解决同一个通道中,多个数据流传输的情况。 //1.创建通道,同时支持通道路由和元数据传递 - using (var channel =await client.CreateChannelAsync()) + using (var channel = await client.CreateChannelAsync()) { - //设置限速 - //channel.MaxSpeed = 1024 * 1024; - ConsoleLogger.Default.Info($"通道创建成功,即将写入"); - var bytes = new byte[1024]; + var bytes = new byte[100]; for (var i = 0; i < 100; i++)//循环100次 { @@ -65,73 +62,67 @@ internal class Program private static async Task RunComplete(IDmtpActorObject client) { - var count = 1024 * 1;//测试1Gb数据 + #region TcpDmtpClient创建Channel + var count = 1024; //1.创建通道,同时支持通道路由和元数据传递 - using (var channel =await client.CreateChannelAsync()) - { - //设置限速 - //channel.MaxSpeed = 1024 * 1024; - ConsoleLogger.Default.Info($"通道创建成功,即将写入{count}Mb数据"); - var bytes = new byte[1024 * 1024]; + using var cts = new CancellationTokenSource(1000 * 10); + var metadata = new Metadata(); + + using (var channel = await client.CreateChannelAsync(metadata, cts.Token)) + { + ConsoleLogger.Default.Info($"通道创建成功"); + var bytes = new byte[1000]; for (var i = 0; i < count; i++) { + if (!channel.CanWrite) + { + //通道不可写,即客户端可能已经断开连接 + break; + } + using var writeCts = new CancellationTokenSource(1000 * 5); //2.持续写入数据 - await channel.WriteAsync(bytes); + await channel.WriteAsync(bytes, writeCts.Token); } //3.在写入完成后调用终止指令。例如:Complete、Cancel、HoldOn、Dispose等 await channel.CompleteAsync("我完成了"); ConsoleLogger.Default.Info("通道写入结束"); } + #endregion + } private static async Task GetTcpDmtpClient() { var client = await new TouchSocketConfig() .SetRemoteIPHost("127.0.0.1:7789") - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options => { - VerifyToken = "Channel" + options.VerifyToken = "Channel"; }) - .SetSendTimeout(0) .ConfigureContainer(a => { a.AddConsoleLogger(); }) - .ConfigurePlugins(a => - { - a.Add(); + #region Dmtp心跳重连插件 + .ConfigurePlugins(a => + { + a.Add(); - //使用重连 - a.UseDmtpReconnection() - .UsePolling(TimeSpan.FromSeconds(3))//使用轮询,每3秒检测一次 - .SetActionForCheck(async (c, i) =>//重新定义检活策略 - { - //方法1,直接判断是否在握手状态。使用该方式,最好和心跳插件配合使用。因为如果直接断网,则检测不出来 - //await Task.CompletedTask;//消除Task - //return c.Online;//判断是否在握手状态 + //使用重连 + a.UseReconnection(options => + { + //重连间隔3秒 + options.PollingInterval = TimeSpan.FromSeconds(3); - //方法2,直接ping,如果true,则客户端必在线。如果false,则客户端不一定不在线,原因是可能当前传输正在忙 - if (await c.PingAsync()) - { - return true; - } - //返回false时可以判断,如果最近活动时间不超过3秒,则猜测客户端确实在忙,所以跳过本次重连 - else if (DateTime.Now - c.GetLastActiveTime() < TimeSpan.FromSeconds(3)) - { - return null; - } - //否则,直接重连。 - else - { - return false; - } - }); + //使用Dmtp状态、心跳等联合检测作为连接状态检查依据。 + options.UseDmtpCheckAction(); + }); + }) + #endregion - - }) .BuildClientAsync(); client.Logger.Info("连接成功"); @@ -152,9 +143,9 @@ internal class Program { a.Add(); }) - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options => { - VerifyToken = "Channel"//连接验证口令。 + options.VerifyToken = "Channel";//连接验证口令。 }); await service.SetupAsync(config); @@ -164,6 +155,7 @@ internal class Program } } +#region TcpDmtpService使用插件订阅Channel internal class MyPlugin : PluginBase, IDmtpCreatedChannelPlugin { private readonly ILog m_logger; @@ -175,30 +167,41 @@ internal class MyPlugin : PluginBase, IDmtpCreatedChannelPlugin public async Task OnDmtpCreatedChannel(IDmtpActorObject client, CreateChannelEventArgs e) { + //1.订阅通道 if (client.TrySubscribeChannel(e.ChannelId, out var channel)) { - //设定读取超时时间 - //channel.Timeout = TimeSpan.FromSeconds(30); + //2.准备处理通道数据 using (channel) { this.m_logger.Info("通道开始接收"); - //此判断主要是探测是否有Hold操作 - while (channel.CanMoveNext) + long count = 0; + + //3.持续读取数据 + while (channel.CanRead) { - long count = 0; - foreach (var byteBlock in channel) + //4.设置读取超时 + using var cts = new CancellationTokenSource(10 * 1000); + + //5.读取到的数据 + var memory = await channel.ReadAsync(cts.Token); + + //6.可以判断通道状态 + if (channel.Status == ChannelStatus.HoldOn) { - //这里处理数据 - count += byteBlock.Length; - this.m_logger.Info($"通道已接收:{count}字节"); + Console.WriteLine($"HoldOn:{channel.LastOperationMes}"); } - this.m_logger.Info($"通道接收结束,状态={channel.Status},短语={channel.LastOperationMes},共接收{count / (1048576.0):0.00}Mb字节"); + //这里处理数据 + count += memory.Length; + this.m_logger.Info($"通道已接收:{count}字节"); } + + this.m_logger.Info($"通道接收结束,状态={channel.Status},短语={channel.LastOperationMes},共接收{count / (1048576.0):0.00}Mb字节"); } } await e.InvokeNext(); } -} \ No newline at end of file +} +#endregion diff --git a/examples/Dmtp/DmtpConsoleApp/DmtpConsoleApp.csproj b/examples/Dmtp/DmtpConsoleApp/DmtpConsoleApp.csproj index 3d64cc538..1b5465cb5 100644 --- a/examples/Dmtp/DmtpConsoleApp/DmtpConsoleApp.csproj +++ b/examples/Dmtp/DmtpConsoleApp/DmtpConsoleApp.csproj @@ -7,26 +7,8 @@ enable - - - + diff --git a/examples/Dmtp/DmtpConsoleApp/Program.cs b/examples/Dmtp/DmtpConsoleApp/Program.cs index 1d0e241d7..87383443a 100644 --- a/examples/Dmtp/DmtpConsoleApp/Program.cs +++ b/examples/Dmtp/DmtpConsoleApp/Program.cs @@ -53,17 +53,17 @@ internal class Program //此处使用委托注册插件。和类插件功能一样 a.AddDmtpReceivedPlugin(async (c, e) => { - var msg = e.DmtpMessage.BodyByteBlock.ToString(); + var msg = e.DmtpMessage.Memory.Span.ToString(Encoding.UTF8); await Console.Out.WriteLineAsync($"收到服务器回信,协议{e.DmtpMessage.ProtocolFlags}收到信息,内容:{msg}"); await e.InvokeNext(); }); }) .SetRemoteIPHost("127.0.0.1:7789") - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options => { - VerifyToken = "Dmtp",//设置Token验证连接 - Id = "defaultId",//设置默认Id - Metadata = new Metadata().Add("a", "a")//设置Metadata,可以传递更多的验证信息 + options.VerifyToken = "Dmtp";//设置Token验证连接 + options.Id = "defaultId";//设置默认Id + options.Metadata = new Metadata().Add("a", "a");//设置Metadata,可以传递更多的验证信息 })); await client.ConnectAsync(); @@ -76,8 +76,12 @@ internal class Program //DmtpRpc会用[20,25)的协议。 //文件传输会用[25,35)的协议。 + + #region Dmtp发送消息 //此处使用1000,基本就不会冲突。 await client.SendAsync(1000, Encoding.UTF8.GetBytes("hello")); + #endregion + } /// @@ -86,25 +90,7 @@ internal class Program /// private static async Task Connect_2() { - using var tcpClient = new TcpClient();//创建一个普通的tcp客户端。 - tcpClient.Received = (client, e) => - { - //此处接收服务器返回的消息 - - var head = e.ByteBlock.ToArray(0, 2); - e.ByteBlock.Seek(2, SeekOrigin.Begin); - var flags = e.ByteBlock.ReadUInt16(EndianType.Big); - var length = e.ByteBlock.ReadInt32(EndianType.Big); - - var json = e.ByteBlock.Span.ToString(Encoding.UTF8); - - ConsoleLogger.Default.Info($"收到响应:flags={flags},length={length},json={json.Replace("\r\n", string.Empty).Replace(" ", string.Empty)}"); - - - return Task.CompletedTask; - }; - - #region 基础Flag协议 + #region Dmtp基础Flag协议 Console.WriteLine($"{nameof(DmtpActor.P0_Close)}-flag-->{DmtpActor.P0_Close}"); Console.WriteLine($"{nameof(DmtpActor.P1_Handshake_Request)}-flag-->{DmtpActor.P1_Handshake_Request}"); @@ -119,7 +105,26 @@ internal class Program #endregion 基础Flag协议 - #region 连接 + #region Dmtp以普通Tcp模拟连接 + using var tcpClient = new TcpClient();//创建一个普通的tcp客户端。 + tcpClient.Received = (client, e) => + { + //此处接收服务器返回的消息 + + var span = e.Memory.Span; + var head = span[..2]; + + span = span[2..]; + var flags = span.ReadValue(EndianType.Big); + var length = span.ReadValue(EndianType.Big); + + var json = e.Memory.Span.ToString(Encoding.UTF8); + + ConsoleLogger.Default.Info($"收到响应:flags={flags},length={length},json={json.Replace("\r\n", string.Empty).Replace(" ", string.Empty)}"); + + + return Task.CompletedTask; + }; //开始链接服务器 await tcpClient.ConnectAsync("127.0.0.1:7789"); @@ -133,36 +138,38 @@ internal class Program //将json转为utf-8编码。 var jsonBytes = Encoding.UTF8.GetBytes(json); - using (var byteBlock = new ByteBlock(1024*64)) + using (var byteBlock = new ByteBlock(1024 * 64)) { //按照Head+Flags+Length+Data的格式。 byteBlock.Write(Encoding.ASCII.GetBytes("dm")); - byteBlock.Write(TouchSocketBitConverter.BigEndian.GetBytes((ushort)1)); - byteBlock.Write(TouchSocketBitConverter.BigEndian.GetBytes(jsonBytes.Length)); + byteBlock.WriteValue((ushort)1, EndianType.Big); + byteBlock.WriteValue(jsonBytes.Length, EndianType.Big); byteBlock.Write(jsonBytes); await tcpClient.SendAsync(byteBlock.Memory); } - #endregion 连接 + #endregion - #region Ping + #region Dmtp以普通Tcp模拟Ping json = "{\"Sign\":2,\"Route\":false,\"SourceId\":null,\"TargetId\":null}"; jsonBytes = Encoding.UTF8.GetBytes(json); - using (var byteBlock = new ByteBlock(1024*64)) + using (var byteBlock = new ByteBlock(1024 * 64)) { //按照Head+Flags+Length+Data的格式。 byteBlock.Write(Encoding.ASCII.GetBytes("dm")); - byteBlock.Write(TouchSocketBitConverter.BigEndian.GetBytes((ushort)5)); - byteBlock.Write(TouchSocketBitConverter.BigEndian.GetBytes(jsonBytes.Length)); + byteBlock.WriteValue((ushort)5, EndianType.Big); + byteBlock.WriteValue(jsonBytes.Length, EndianType.Big); byteBlock.Write(jsonBytes); await tcpClient.SendAsync(byteBlock.Memory); } - #endregion Ping + #endregion + + await Task.Delay(2000); } @@ -182,11 +189,11 @@ internal class Program a.AddConsoleLogger(); }) .SetRemoteIPHost("127.0.0.1:7789") - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options => { - VerifyToken = "Dmtp",//设置Token验证连接 - Id = "defaultId",//设置默认Id - Metadata = new Metadata().Add("a", "a")//设置Metadata,可以传递更多的验证信息 + options.VerifyToken = "Dmtp";//设置Token验证连接 + options.Id = "defaultId";//设置默认Id + options.Metadata = new Metadata().Add("a", "a");//设置Metadata,可以传递更多的验证信息 })); await client.ConnectAsync(); @@ -209,9 +216,9 @@ internal class Program a.Add(); a.Add(); }) - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options => { - VerifyToken = "Dmtp"//设定连接口令,作用类似账号密码 + options.VerifyToken = "Dmtp";//设定连接口令,作用类似账号密码 }); await service.SetupAsync(config); @@ -220,11 +227,28 @@ internal class Program service.Logger.Info($"{service.GetType().Name}已启动"); return service; } + + private static async Task CreateConfig() + { + var config = new TouchSocketConfig(); + + #region Dmtp配置 + config.SetDmtpOption(options => + { + options.Id = Guid.NewGuid().ToString();//仅当在客户端,连接时,如果指定Id,则该链接将使用设定的id。 + options.VerifyToken = "Dmtp";//连接口令,作用类似账号密码 + options.VerifyTimeout = TimeSpan.FromSeconds(3);//仅当在服务器,验证连接的超时时间 + options.Metadata = new Metadata().Add("a", "a");//仅当在客户端,连接时,可以传递更多的验证信息 + }); + #endregion + + } } -internal class MyVerifyPlugin : PluginBase, IDmtpHandshakingPlugin +#region Dmtp动态验证连接 +internal class MyVerifyPlugin : PluginBase, IDmtpConnectingPlugin { - public async Task OnDmtpHandshaking(IDmtpActorObject client, DmtpVerifyEventArgs e) + public async Task OnDmtpConnecting(IDmtpActorObject client, DmtpVerifyEventArgs e) { if (e.Metadata["a"] != "a") { @@ -235,23 +259,26 @@ internal class MyVerifyPlugin : PluginBase, IDmtpHandshakingPlugin } if (e.Token == "Dmtp") { - e.IsPermitOperation = true; - e.Handled = true; + e.IsPermitOperation = true;//允许连接 + e.Handled = true;//表示该消息已在此处处理。 return; } await e.InvokeNext(); } } +#endregion + +#region Dmtp接收数据 internal class MyFlagsPlugin : PluginBase, IDmtpReceivedPlugin { public async Task OnDmtpReceived(IDmtpActorObject client, DmtpMessageEventArgs e) { if (e.DmtpMessage.ProtocolFlags == 1000) { - //判断完协议以后,从 e.DmtpMessage.BodyByteBlock可以拿到实际的数据 - var msg = e.DmtpMessage.BodyByteBlock.ToString(); + //判断完协议以后,从 e.DmtpMessage.Memory可以拿到实际的数据 + var msg = e.DmtpMessage.Memory.Span.ToUtf8String(); await Console.Out.WriteLineAsync($"从协议{e.DmtpMessage.ProtocolFlags}收到信息,内容:{msg}"); //向客户端回发消息 @@ -262,4 +289,5 @@ internal class MyFlagsPlugin : PluginBase, IDmtpReceivedPlugin //flags不满足,调用下一个插件 await e.InvokeNext(); } -} \ No newline at end of file +} +#endregion diff --git a/examples/Dmtp/DmtpRedisConsoleApp/DmtpRedisConsoleApp.csproj b/examples/Dmtp/DmtpRedisConsoleApp/DmtpRedisConsoleApp.csproj index 177290819..ace6afa72 100644 --- a/examples/Dmtp/DmtpRedisConsoleApp/DmtpRedisConsoleApp.csproj +++ b/examples/Dmtp/DmtpRedisConsoleApp/DmtpRedisConsoleApp.csproj @@ -8,7 +8,7 @@ - + diff --git a/examples/Dmtp/DmtpRedisConsoleApp/Program.cs b/examples/Dmtp/DmtpRedisConsoleApp/Program.cs index 91516f823..82861230a 100644 --- a/examples/Dmtp/DmtpRedisConsoleApp/Program.cs +++ b/examples/Dmtp/DmtpRedisConsoleApp/Program.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 // 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 // CSDN博客:https://blog.csdn.net/qq_40374647 @@ -24,16 +24,18 @@ internal class Program var service = await GetTcpDmtpService(); var client = await GetTcpDmtpClient(); + #region Redis基本使用 + //获取Redis var redis = client.GetDmtpRedisActor(); //执行Set - var result = await redis.SetAsync("1", "1"); + var result = await redis.SetAsync("1", "1", 60 * 1000, CancellationToken.None); client.Logger.Info($"Set result={result}"); client.Logger.Info($"ContainsCache result={await redis.ContainsCacheAsync("1")}"); //执行Get - var result1 = await redis.GetAsync("1"); + var result1 = await redis.GetAsync("1", CancellationToken.None); client.Logger.Info($"Get result={result}"); //执行Remove @@ -41,17 +43,21 @@ internal class Program client.Logger.Info($"Get result={result}"); await redis.ClearCacheAsync(); + #endregion Redis基本使用 + Console.ReadKey(); } private static async Task GetTcpDmtpClient() { + #region Redis客户端配置 + var client = new TcpDmtpClient(); await client.SetupAsync(new TouchSocketConfig() .SetRemoteIPHost("127.0.0.1:7789") - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options => { - VerifyToken = "Dmtp" + options.VerifyToken = "Dmtp"; }) .ConfigureContainer(a => { @@ -63,10 +69,14 @@ internal class Program })); await client.ConnectAsync(); return client; + + #endregion Redis客户端配置 } private static async Task GetTcpDmtpService() { + #region Redis服务器配置 + var service = await new TouchSocketConfig()//配置 .SetListenIPHosts(new IPHost[] { new IPHost(7789) }) .ConfigureContainer(a => @@ -75,15 +85,44 @@ internal class Program }) .ConfigurePlugins(a => { - a.UseDmtpRedis()//必须添加Redis访问插件 - .SetCache(new MemoryCache());//这里可以设置缓存持久化,此处仍然是使用内存缓存。 + a.UseDmtpRedis(); }) - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options => { - VerifyToken = "Dmtp"//连接验证口令。 + options.VerifyToken = "Dmtp";//连接验证口令。 }) .BuildServiceAsync();//此处build相当于new TcpDmtpService,然后SetupAsync,然后StartAsync。 service.Logger.Info("服务器成功启动"); return service; + + #endregion Redis服务器配置 } -} \ No newline at end of file + + private static async Task GetTcpDmtpServiceWithCache() + { + #region Redis缓存持久化配置 + + var service = await new TouchSocketConfig()//配置 + .SetListenIPHosts(new IPHost[] { new IPHost(7789) }) + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .ConfigurePlugins(a => + { + a.UseDmtpRedis(options => + { + options.Cache = new MemoryCache>();//这里可以设置缓存持久化,此处仍然是使用内存缓存。 + }); + }) + .SetDmtpOption(options => + { + options.VerifyToken = "Dmtp";//连接验证口令。 + }) + .BuildServiceAsync();//此处build相当于new TcpDmtpService,然后SetupAsync,然后StartAsync。 + service.Logger.Info("服务器成功启动"); + return service; + + #endregion Redis缓存持久化配置 + } +} diff --git a/examples/Dmtp/DmtpRpcClientApp/DmtpRpcClientApp.csproj b/examples/Dmtp/DmtpRpcClientApp/DmtpRpcClientApp.csproj index 4a7641c2b..867bd928f 100644 --- a/examples/Dmtp/DmtpRpcClientApp/DmtpRpcClientApp.csproj +++ b/examples/Dmtp/DmtpRpcClientApp/DmtpRpcClientApp.csproj @@ -1,4 +1,4 @@ - + Exe net9.0-windows @@ -6,11 +6,11 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/examples/Dmtp/DmtpRpcClientApp/Form1.cs b/examples/Dmtp/DmtpRpcClientApp/Form1.cs index d7ef9c5e2..61fea3590 100644 --- a/examples/Dmtp/DmtpRpcClientApp/Form1.cs +++ b/examples/Dmtp/DmtpRpcClientApp/Form1.cs @@ -12,7 +12,6 @@ using RpcProxy; using System; -using System.Threading.Tasks; using System.Windows.Forms; using TouchSocket.Core; using TouchSocket.Dmtp; @@ -36,7 +35,7 @@ public partial class Form1 : Form //直接调用时,第一个参数为调用键,服务类全名+方法名(必须全小写) //第二个参数为调用配置参数,可设置调用超时时间,取消调用等功能。 //后续参数为调用参数。 - var result =await this.m_client.GetDmtpRpcActor().InvokeTAsync("Login", InvokeOption.WaitInvoke, this.textBox1.Text, this.textBox2.Text); + var result = await this.m_client.GetDmtpRpcActor().InvokeTAsync("Login", InvokeOption.WaitInvoke, this.textBox1.Text, this.textBox2.Text); MessageBox.Show(result.ToString()); } @@ -72,41 +71,17 @@ public partial class Form1 : Form { a.UseDmtpRpc(); - //使用心跳保活,或者避免异常连接。达到最大失败次数会断开,不会重连。 - a.UseDmtpHeartbeat() - .SetTick(TimeSpan.FromSeconds(3)) - .SetMaxFailCount(3); - //使用重连 - a.UseDmtpReconnection() - .UsePolling(TimeSpan.FromSeconds(3)) - .SetActionForCheck(async (c, i) =>//重新定义检活策略 + a.UseReconnection(options => { - //方法1,直接判断是否在握手状态。使用该方式,最好和心跳插件配合使用 - //await Task.CompletedTask;//消除Task - //return c.IsHandshaked;//判断是否在握手状态 + options.PollingInterval = TimeSpan.FromSeconds(3); - //方法2,直接ping,如果true,则客户端必在线。如果false,则客户端不一定不在线,原因是可能当前传输正在忙 - if (await c.PingAsync()) - { - return true; - } - //返回false时可以判断,如果最近活动时间不超过3秒,则猜测客户端确实在忙,所以跳过本次重连 - else if (DateTime.Now - c.GetLastActiveTime() < TimeSpan.FromSeconds(3)) - { - return null; - } - //否则,直接重连。 - else - { - return false; - } }); }) - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options => { - VerifyToken = "Rpc", - Id = "asdasd" + options.VerifyToken = "Rpc"; + options.Id = "asdasd"; })); this.m_client.ConnectAsync(); diff --git a/examples/Dmtp/DmtpRpcClientApp/Program.cs b/examples/Dmtp/DmtpRpcClientApp/Program.cs index 6e8419082..529fb2a4c 100644 --- a/examples/Dmtp/DmtpRpcClientApp/Program.cs +++ b/examples/Dmtp/DmtpRpcClientApp/Program.cs @@ -55,11 +55,11 @@ internal static class Program }) .ConfigurePlugins(a => { - a.Add(typeof(IDmtpHandshakingPlugin), async (c, e) => + a.Add(typeof(IDmtpConnectingPlugin), async (c, e) => { await e.InvokeNext(); }); - a.Add(typeof(IDmtpHandshakedPlugin), async (c, e) => + a.Add(typeof(IDmtpConnectedPlugin), async (c, e) => { await e.InvokeNext(); }); @@ -70,9 +70,9 @@ internal static class Program { a.AddConsoleLogger(); }) - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options=> { - VerifyToken = "Rpc" + options.VerifyToken = "Rpc"; }); await service.SetupAsync(config); diff --git a/examples/Dmtp/DmtpRpcClientApp/RpcProxy.cs b/examples/Dmtp/DmtpRpcClientApp/RpcProxy.cs index c5c61dbfa..96bf780c2 100644 --- a/examples/Dmtp/DmtpRpcClientApp/RpcProxy.cs +++ b/examples/Dmtp/DmtpRpcClientApp/RpcProxy.cs @@ -32,14 +32,14 @@ namespace RpcProxy /// 调用超时 /// Rpc调用异常 /// 其他异常 - System.Boolean Login(System.String account, System.String password, IInvokeOption invokeOption = default); + System.Boolean Login(System.String account, System.String password, InvokeOption invokeOption = default); /// ///登录 /// /// 调用超时 /// Rpc调用异常 /// 其他异常 - Task LoginAsync(System.String account, System.String password, IInvokeOption invokeOption = default); + Task LoginAsync(System.String account, System.String password, InvokeOption invokeOption = default); /// ///注册 @@ -47,14 +47,14 @@ namespace RpcProxy /// 调用超时 /// Rpc调用异常 /// 其他异常 - System.Boolean Register(RegisterModel register, IInvokeOption invokeOption = default); + System.Boolean Register(RegisterModel register, InvokeOption invokeOption = default); /// ///注册 /// /// 调用超时 /// Rpc调用异常 /// 其他异常 - Task RegisterAsync(RegisterModel register, IInvokeOption invokeOption = default); + Task RegisterAsync(RegisterModel register, InvokeOption invokeOption = default); /// ///性能测试 @@ -62,14 +62,14 @@ namespace RpcProxy /// 调用超时 /// Rpc调用异常 /// 其他异常 - System.Int32 Performance(System.Int32 a, IInvokeOption invokeOption = default); + System.Int32 Performance(System.Int32 a, InvokeOption invokeOption = default); /// ///性能测试 /// /// 调用超时 /// Rpc调用异常 /// 其他异常 - Task PerformanceAsync(System.Int32 a, IInvokeOption invokeOption = default); + Task PerformanceAsync(System.Int32 a, InvokeOption invokeOption = default); /// ///测试out @@ -77,14 +77,14 @@ namespace RpcProxy /// 调用超时 /// Rpc调用异常 /// 其他异常 - System.Boolean OutBytes(System.Byte[] bytes, IInvokeOption invokeOption = default); + System.Boolean OutBytes(System.Byte[] bytes, InvokeOption invokeOption = default); /// ///测试out /// /// 调用超时 /// Rpc调用异常 /// 其他异常 - Task OutBytesAsync(System.Byte[] bytes, IInvokeOption invokeOption = default); + Task OutBytesAsync(System.Byte[] bytes, InvokeOption invokeOption = default); } public class MyRpcServer : IMyRpcServer @@ -100,7 +100,7 @@ namespace RpcProxy /// 调用超时 /// Rpc调用异常 /// 其他异常 - public System.Boolean Login(System.String account, System.String password, IInvokeOption invokeOption = default) + public System.Boolean Login(System.String account, System.String password, InvokeOption invokeOption = default) { if (this.Client == null) { @@ -113,7 +113,7 @@ namespace RpcProxy /// ///登录 /// - public async Task LoginAsync(System.String account, System.String password, IInvokeOption invokeOption = default) + public async Task LoginAsync(System.String account, System.String password, InvokeOption invokeOption = default) { if (this.Client == null) { @@ -129,7 +129,7 @@ namespace RpcProxy /// 调用超时 /// Rpc调用异常 /// 其他异常 - public System.Boolean Register(RegisterModel register, IInvokeOption invokeOption = default) + public System.Boolean Register(RegisterModel register, InvokeOption invokeOption = default) { if (this.Client == null) { @@ -142,7 +142,7 @@ namespace RpcProxy /// ///注册 /// - public async Task RegisterAsync(RegisterModel register, IInvokeOption invokeOption = default) + public async Task RegisterAsync(RegisterModel register, InvokeOption invokeOption = default) { if (this.Client == null) { @@ -158,7 +158,7 @@ namespace RpcProxy /// 调用超时 /// Rpc调用异常 /// 其他异常 - public System.Int32 Performance(System.Int32 a, IInvokeOption invokeOption = default) + public System.Int32 Performance(System.Int32 a, InvokeOption invokeOption = default) { if (this.Client == null) { @@ -171,7 +171,7 @@ namespace RpcProxy /// ///性能测试 /// - public async Task PerformanceAsync(System.Int32 a, IInvokeOption invokeOption = default) + public async Task PerformanceAsync(System.Int32 a, InvokeOption invokeOption = default) { if (this.Client == null) { @@ -187,7 +187,7 @@ namespace RpcProxy /// 调用超时 /// Rpc调用异常 /// 其他异常 - public System.Boolean OutBytes(System.Byte[] bytes, IInvokeOption invokeOption = default) + public System.Boolean OutBytes(System.Byte[] bytes, InvokeOption invokeOption = default) { if (this.Client == null) { @@ -200,7 +200,7 @@ namespace RpcProxy /// ///测试out /// - public async Task OutBytesAsync(System.Byte[] bytes, IInvokeOption invokeOption = default) + public async Task OutBytesAsync(System.Byte[] bytes, InvokeOption invokeOption = default) { if (this.Client == null) { @@ -219,7 +219,7 @@ namespace RpcProxy /// 调用超时 /// Rpc调用异常 /// 其他异常 - public static System.Boolean Login(this TClient client, System.String account, System.String password, IInvokeOption invokeOption = default) where TClient : + public static System.Boolean Login(this TClient client, System.String account, System.String password, InvokeOption invokeOption = default) where TClient : TouchSocket.Rpc.IRpcClient { object[] @_parameters = new object[] { account, password }; @@ -229,7 +229,7 @@ namespace RpcProxy /// ///登录 /// - public static async Task LoginAsync(this TClient client, System.String account, System.String password, IInvokeOption invokeOption = default) where TClient : + public static async Task LoginAsync(this TClient client, System.String account, System.String password, InvokeOption invokeOption = default) where TClient : TouchSocket.Rpc.IRpcClient { object[] parameters = new object[] { account, password }; @@ -242,7 +242,7 @@ namespace RpcProxy /// 调用超时 /// Rpc调用异常 /// 其他异常 - public static System.Boolean Register(this TClient client, RegisterModel register, IInvokeOption invokeOption = default) where TClient : + public static System.Boolean Register(this TClient client, RegisterModel register, InvokeOption invokeOption = default) where TClient : TouchSocket.Rpc.IRpcClient { object[] @_parameters = new object[] { register }; @@ -252,7 +252,7 @@ namespace RpcProxy /// ///注册 /// - public static async Task RegisterAsync(this TClient client, RegisterModel register, IInvokeOption invokeOption = default) where TClient : + public static async Task RegisterAsync(this TClient client, RegisterModel register, InvokeOption invokeOption = default) where TClient : TouchSocket.Rpc.IRpcClient { object[] parameters = new object[] { register }; @@ -265,7 +265,7 @@ namespace RpcProxy /// 调用超时 /// Rpc调用异常 /// 其他异常 - public static System.Int32 Performance(this TClient client, System.Int32 a, IInvokeOption invokeOption = default) where TClient : + public static System.Int32 Performance(this TClient client, System.Int32 a, InvokeOption invokeOption = default) where TClient : TouchSocket.Rpc.IRpcClient { object[] @_parameters = new object[] { a }; @@ -275,7 +275,7 @@ namespace RpcProxy /// ///性能测试 /// - public static async Task PerformanceAsync(this TClient client, System.Int32 a, IInvokeOption invokeOption = default) where TClient : + public static async Task PerformanceAsync(this TClient client, System.Int32 a, InvokeOption invokeOption = default) where TClient : TouchSocket.Rpc.IRpcClient { object[] parameters = new object[] { a }; @@ -288,7 +288,7 @@ namespace RpcProxy /// 调用超时 /// Rpc调用异常 /// 其他异常 - public static System.Boolean OutBytes(this TClient client, System.Byte[] bytes, IInvokeOption invokeOption = default) where TClient : + public static System.Boolean OutBytes(this TClient client, System.Byte[] bytes, InvokeOption invokeOption = default) where TClient : TouchSocket.Rpc.IRpcClient { object[] @_parameters = new object[] { bytes }; @@ -298,7 +298,7 @@ namespace RpcProxy /// ///测试out /// - public static async Task OutBytesAsync(this TClient client, System.Byte[] bytes, IInvokeOption invokeOption = default) where TClient : + public static async Task OutBytesAsync(this TClient client, System.Byte[] bytes, InvokeOption invokeOption = default) where TClient : TouchSocket.Rpc.IRpcClient { object[] parameters = new object[] { bytes }; diff --git a/examples/Dmtp/DmtpRpcClientConsoleApp/DmtpRpcClientConsoleApp.csproj b/examples/Dmtp/DmtpRpcClientConsoleApp/DmtpRpcClientConsoleApp.csproj index d5ec6da5e..e3c1adb40 100644 --- a/examples/Dmtp/DmtpRpcClientConsoleApp/DmtpRpcClientConsoleApp.csproj +++ b/examples/Dmtp/DmtpRpcClientConsoleApp/DmtpRpcClientConsoleApp.csproj @@ -12,6 +12,7 @@ - + + diff --git a/examples/Dmtp/DmtpRpcClientConsoleApp/Program.cs b/examples/Dmtp/DmtpRpcClientConsoleApp/Program.cs index 0a868b7a5..edf7495eb 100644 --- a/examples/Dmtp/DmtpRpcClientConsoleApp/Program.cs +++ b/examples/Dmtp/DmtpRpcClientConsoleApp/Program.cs @@ -10,6 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ +using MemoryPack; using RpcProxy; using TouchSocket.Core; using TouchSocket.Dmtp; @@ -68,11 +69,10 @@ internal class Program //创建一个指定时间可取消令箭源,可用于取消Rpc的调用。 using (var tokenSource = new CancellationTokenSource(5000)) { - var invokeOption = new DmtpInvokeOption()//调用配置 + var invokeOption = new DmtpInvokeOption(5000)//调用配置 { FeedbackType = FeedbackType.WaitInvoke,//调用反馈类型 SerializationType = SerializationType.FastBinary,//序列化类型 - Timeout = 5000,//调用超时设置 Token = tokenSource.Token//配置可取消令箭 }; @@ -110,33 +110,64 @@ internal class Program { var client = await GetTcpDmtpClient(); + #region DmtpRpc直接调用 //设置调用配置 - var tokenSource = new CancellationTokenSource();//可取消令箭源,可用于取消Rpc的调用 + using var cts = new CancellationTokenSource(5000);//可取消令箭源,可用于取消Rpc的调用 var invokeOption = new DmtpInvokeOption()//调用配置 { FeedbackType = FeedbackType.WaitInvoke,//调用反馈类型 SerializationType = SerializationType.FastBinary,//序列化类型 - Timeout = 5000,//调用超时设置 - Token = tokenSource.Token//配置可取消令箭 + Token = cts.Token//配置可取消令箭 }; + //获取RpcActor,用于后续的rpc调用 + var rpcActor = client.GetDmtpRpcActor(); - var sum = await client.GetDmtpRpcActor().InvokeTAsync("Add", invokeOption, 10, 20); + //调用Add方法 + var sum = await rpcActor.InvokeTAsync("Add", invokeOption, 10, 20); client.Logger.Info($"调用Add方法成功,结果:{sum}"); + #endregion + } + private static async Task RunInvokeWithProxy() + { + using var client = await GetTcpDmtpClient(); + + #region DmtpRpc代理调用 + //设置调用配置 + using var cts = new CancellationTokenSource(5000);//可取消令箭源,可用于取消Rpc的调用 + var invokeOption = new DmtpInvokeOption()//调用配置 + { + FeedbackType = FeedbackType.WaitInvoke,//调用反馈类型 + SerializationType = SerializationType.FastBinary,//序列化类型 + Token = cts.Token//配置可取消令箭 + }; + //获取RpcActor,用于后续的rpc调用 + var rpcActor = client.GetDmtpRpcActor(); + + //调用Add方法 + var sum = await rpcActor.AddAsync(10, 20, invokeOption); + client.Logger.Info($"调用Add方法成功,结果:{sum}"); + #endregion + + } + + #region DmtpRpc客户端请求流数据 private static async Task RunRpcPullChannel() { using var client = await GetTcpDmtpClient(); var status = ChannelStatus.Default; var size = 0; - var channel =await client.CreateChannelAsync();//创建通道 - var task = Task.Run(() =>//这里必须用异步 + var channel = await client.CreateChannelAsync();//创建通道 + var task = Task.Run(async () =>//这里必须用异步 { using (channel) { - foreach (var currentByteBlock in channel) + while (channel.CanRead) { - size += currentByteBlock.Length;//此处可以处理传递来的流数据 + using var cts = new CancellationTokenSource(10 * 1000); + var memory = await channel.ReadAsync(cts.Token); + size += memory.Length; } status = channel.Status;//最后状态 } @@ -145,7 +176,9 @@ internal class Program await task;//等待异步接收完成 Console.WriteLine($"状态:{status},size={size}"); } + #endregion + #region DmtpRpc客户端推送流数据 private static async Task RunRpcPushChannel() { using var client = await GetTcpDmtpClient(); @@ -168,33 +201,57 @@ internal class Program await task;//等待异步接收完成 Console.WriteLine($"状态:{status},result={result}"); } + #endregion + + private static async Task CreateDmtpRpcClient() + { + #region 创建DmtpRpc客户端 + var client = new TcpDmtpClient(); + await client.SetupAsync(new TouchSocketConfig() + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .ConfigurePlugins(a => + { + //启用dmtp rpc插件 + a.UseDmtpRpc(); + }) + .SetRemoteIPHost("127.0.0.1:7789") + .SetDmtpOption(options => + { + options.VerifyToken = "Dmtp"; + })); + await client.ConnectAsync(); + #endregion + } private static async Task GetTcpDmtpClient() { var client = new TcpDmtpClient(); await client.SetupAsync(new TouchSocketConfig() - .ConfigureContainer(a => - { - a.AddConsoleLogger(); - a.AddRpcStore(store => - { - store.RegisterServer(); - }); - }) + + #region 客户端注册反向DmtpRpc服务 + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + a.AddRpcStore(store => + { + store.RegisterServer(); + }); + }) + #endregion .ConfigurePlugins(a => { - a.UseDmtpRpc() - //.SetSerializationSelector(new MySerializationSelector())//自定义序列化器 - .SetCreateDmtpRpcActor((actor, serverprovider, dispatcher) => new MyDmtpRpcActor(actor, serverprovider, dispatcher)); - - a.UseDmtpHeartbeat() - .SetTick(TimeSpan.FromSeconds(3)) - .SetMaxFailCount(3); + a.UseDmtpRpc(options => + { + options.SetCreateDmtpRpcActor((actor, serverprovider, dispatcher) => new MyDmtpRpcActor(actor, serverprovider, dispatcher)); + }); }) .SetRemoteIPHost("127.0.0.1:7789") - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options => { - VerifyToken = "Dmtp" + options.VerifyToken = "Dmtp"; })); await client.ConnectAsync(); @@ -206,6 +263,7 @@ internal class Program } } +#region DmtpRpc限制代理接口声明 internal interface IRpcClient1 : IDmtpRpcActor { } @@ -220,7 +278,44 @@ internal class MyDmtpRpcActor : DmtpRpcActor, IRpcClient1, IRpcClient2 { } } +#endregion +#region DmtpRpc限制代理接口配置 +internal class LimitInterfaceExample +{ + public async Task ConfigLimitInterface() + { + var client = new TcpDmtpClient(); + await client.SetupAsync(new TouchSocketConfig() + .ConfigurePlugins(a => + { + a.UseDmtpRpc(options => + { + options.SetCreateDmtpRpcActor((actor, serverprovider, dispatcher) => new MyDmtpRpcActor(actor, serverprovider, dispatcher)); + }); + }) + .SetRemoteIPHost("127.0.0.1:7789") + .SetDmtpOption(options => + { + options.VerifyToken = "Rpc";//连接验证口令。 + })); + await client.ConnectAsync(); + } +} +#endregion + +#region DmtpRpc限制代理接口使用 +internal class UseLimitInterfaceExample +{ + public void UseLimitInterface(TcpDmtpClient client) + { + IRpcClient1 rpcClient1 = client.GetDmtpRpcActor(); + IRpcClient2 rpcClient2 = client.GetDmtpRpcActor(); + } +} +#endregion + +#region 声明反向DmtpRpc服务 internal partial class MyClientRpcServer : SingletonRpcServer { private readonly ILog m_logger; @@ -238,18 +333,206 @@ internal partial class MyClientRpcServer : SingletonRpcServer } } +#endregion + /// /// 序列化选择器 /// public class MySerializationSelector : ISerializationSelector { - public object DeserializeParameter(ref TByteBlock byteBlock, SerializationType serializationType, Type parameterType) where TByteBlock : IByteBlock + public object DeserializeParameter(ref TByteBlock byteBlock, SerializationType serializationType, Type parameterType) where TByteBlock : IBytesReader { throw new NotImplementedException(); } - public void SerializeParameter(ref TByteBlock byteBlock, SerializationType serializationType, in object parameter) where TByteBlock : IByteBlock + public void SerializeParameter(ref TByteBlock byteBlock, SerializationType serializationType, in object parameter) where TByteBlock : IBytesWriter { throw new NotImplementedException(); } -} \ No newline at end of file +} + +#region DmtpRpc配置路由 +internal class ConfigureRouteExample +{ + public void ConfigureRoute() + { + var service = new TcpDmtpService(); + var config = new TouchSocketConfig() + .ConfigureContainer(a => + { + a.AddDmtpRouteService(); + a.AddConsoleLogger(); + }); + } +} +#endregion + +#region DmtpRpc设置最大包大小 +internal class SetMaxPackageSizeExample +{ + public void ConfigMaxPackageSize() + { + var config = new TouchSocketConfig();//配置 + config.SetAdapterOption(options => + { + options.MaxPackageSize = 10 * 1024 * 1024;//设置最大包大小为10MB + }); + } +} +#endregion + +#region DmtpRpc配置序列化选择器 +internal class ConfigureSerializationSelectorExample +{ + public void ConfigSerializationSelector() + { + var config = new TouchSocketConfig() + .ConfigurePlugins(a => + { + a.UseDmtpRpc(options => + { + options.ConfigureDefaultSerializationSelector(selector => + { + selector.JsonSerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented; + selector.FastSerializerContext = default; + }); + }); + }); + } +} +#endregion + +#region DmtpRpc自定义序列化配置 +internal class CustomSerializationExample +{ + public void ConfigCustomSerialization() + { + var config = new TouchSocketConfig() + .ConfigurePlugins(a => + { + a.UseDmtpRpc(options => + { + options.SerializationSelector = new MemoryPackSerializationSelector(); + }); + }); + } +} + +public class MemoryPackSerializationSelector : ISerializationSelector +{ + public object DeserializeParameter(ref TByteBlock byteBlock, SerializationType serializationType, Type parameterType) where TByteBlock : IBytesReader + { + var len = ReaderExtension.ReadValue(ref byteBlock); + var span = ReaderExtension.ReadToSpan(ref byteBlock, len); + return MemoryPackSerializer.Deserialize(parameterType, span); + } + + public void SerializeParameter(ref TByteBlock byteBlock, SerializationType serializationType, in object parameter) where TByteBlock : IBytesWriter + { + var writerAnchor = new WriterAnchor(ref byteBlock, 4); + + var memoryPackWriter = new MemoryPackWriter(ref byteBlock, null); + + MemoryPackSerializer.Serialize(parameter.GetType(), ref memoryPackWriter, parameter); + + var span = writerAnchor.Rewind(ref byteBlock, out var len); + span.WriteValue(memoryPackWriter.WrittenCount); + } +} +#endregion + +#region DmtpRpc使用自定义序列化类型 +internal class UseCustomSerializationTypeExample +{ + public async Task UseCustomSerializationType() + { + var client = new TcpDmtpClient(); + var invokeOption = new DmtpInvokeOption()//调用配置 + { + FeedbackType = FeedbackType.WaitInvoke,//调用反馈类型 + SerializationType = (SerializationType)4,//序列化类型 + Timeout = 5000,//调用超时设置 + }; + } +} +#endregion + +#region DmtpRpc使用Metadata +internal partial class MetadataRpcServer : SingletonRpcServer +{ + [DmtpRpc(MethodInvoke = true)] + public Metadata CallContextMetadata(IDmtpRpcCallContext callContext) + { + return callContext.Metadata; + } +} +#endregion + +#region DmtpRpc客户端使用Metadata +internal class ClientUseMetadataExample +{ + public async Task UseMetadata() + { + var client = new TcpDmtpClient(); + var invokeOption = new DmtpInvokeOption()//调用配置 + { + FeedbackType = FeedbackType.WaitInvoke,//调用反馈类型 + SerializationType = SerializationType.FastBinary,//序列化类型 + Timeout = 5000,//调用超时设置 + Metadata = new Metadata() { { "a", "a" } } + }; + + var metadata = await client.GetDmtpRpcActor().InvokeTAsync("CallContextMetadata", invokeOption); + } +} +#endregion + +#region DmtpRpc使用CancellationToken +internal class UseCancellationTokenExample +{ + public async Task UseCancellationToken() + { + var client = new TcpDmtpClient(); + + //设置调用配置 + var tokenSource = new CancellationTokenSource();//可取消令箭源,可用于取消Rpc的调用 + var invokeOption = new DmtpInvokeOption()//调用配置 + { + FeedbackType = FeedbackType.WaitInvoke,//调用反馈类型 + SerializationType = SerializationType.FastBinary,//序列化类型 + Timeout = 5000,//调用超时设置 + Token = tokenSource.Token//配置可取消令箭 + }; + + var sum = await client.GetDmtpRpcActor().InvokeTAsync("Add", invokeOption, 10, 20); + client.Logger.Info($"调用Add方法成功,结果:{sum}"); + } +} +#endregion + +#region DmtpRpc客户端互Call示例 +internal class ClientToClientCallExample +{ + public async Task CallBetweenClients() + { + var client1 = new TcpDmtpClient(); + var client2 = new TcpDmtpClient(); + + await client1.GetDmtpRpcActor().InvokeTAsync(client2.Id, "Notice", InvokeOption.WaitInvoke, "Hello"); + } +} +#endregion + +#region DmtpRpc客户端互Call使用目标RpcActor +internal class ClientToClientCallWithTargetExample +{ + public async Task CallWithTarget() + { + var client1 = new TcpDmtpClient(); + var client2 = new TcpDmtpClient(); + + var targetRpcClient = client1.CreateTargetDmtpRpcActor(client2.Id); + await targetRpcClient.InvokeTAsync("Notice", InvokeOption.WaitInvoke, "Hello"); + } +} +#endregion diff --git a/examples/Dmtp/DmtpRpcDelayPerformanceConsoleApp/DmtpRpcDelayPerformanceConsoleApp.csproj b/examples/Dmtp/DmtpRpcDelayPerformanceConsoleApp/DmtpRpcDelayPerformanceConsoleApp.csproj index 6e94fdf8e..9e67fd647 100644 --- a/examples/Dmtp/DmtpRpcDelayPerformanceConsoleApp/DmtpRpcDelayPerformanceConsoleApp.csproj +++ b/examples/Dmtp/DmtpRpcDelayPerformanceConsoleApp/DmtpRpcDelayPerformanceConsoleApp.csproj @@ -8,6 +8,6 @@ - + diff --git a/examples/Dmtp/DmtpRpcDelayPerformanceConsoleApp/Program.cs b/examples/Dmtp/DmtpRpcDelayPerformanceConsoleApp/Program.cs index 51aebc81c..d3b91e777 100644 --- a/examples/Dmtp/DmtpRpcDelayPerformanceConsoleApp/Program.cs +++ b/examples/Dmtp/DmtpRpcDelayPerformanceConsoleApp/Program.cs @@ -1,6 +1,17 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + using System.ComponentModel; using System.Diagnostics; -using System.Threading.Tasks; using TouchSocket.Core; using TouchSocket.Dmtp; using TouchSocket.Dmtp.Rpc; @@ -11,13 +22,13 @@ namespace RpcDelayPerConsoleApp { internal class Program { - static async Task Main(string[] args) + private static async Task Main(string[] args) { var service = await GetService(); var client = await GetClient(); - List tasks = new List(); + var tasks = new List(); Console.WriteLine("按任意键开始"); Console.ReadKey(); @@ -41,7 +52,7 @@ namespace RpcDelayPerConsoleApp } - static async Task GetService() + private static async Task GetService() { var service = new TcpDmtpService(); var config = new TouchSocketConfig()//配置 @@ -57,9 +68,9 @@ namespace RpcDelayPerConsoleApp { a.UseDmtpRpc(); }) - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options=> { - VerifyToken = "Rpc" + options.VerifyToken = "Rpc"; }); await service.SetupAsync(config); @@ -69,7 +80,7 @@ namespace RpcDelayPerConsoleApp return service; } - static async Task GetClient() + private static async Task GetClient() { var client = new TcpDmtpClient(); await client.SetupAsync(new TouchSocketConfig() @@ -78,9 +89,9 @@ namespace RpcDelayPerConsoleApp { a.UseDmtpRpc(); }) - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options=> { - VerifyToken = "Rpc" + options.VerifyToken = "Rpc"; })); await client.ConnectAsync(); @@ -91,23 +102,23 @@ namespace RpcDelayPerConsoleApp public partial class MyRpcServer : SingletonRpcServer { - Timer m_timer; + private readonly Timer m_timer; public MyRpcServer() { - m_timer = new Timer((s) => + this.m_timer = new Timer((s) => { - Console.WriteLine(m_count); + Console.WriteLine(this.m_count); }, default, 1000, 1000); } - int m_count = 0; + private int m_count = 0; [Description("登录")]//服务描述,在生成代理时,会变成注释。 [DmtpRpc(InvokeKey = "Login")]//服务注册的函数键,此处为显式指定。默认不传参的时候,为该函数类全名+方法名的全小写。 public async Task Login(string account, string password) { await Task.Delay(1000 * 3); - Interlocked.Increment(ref m_count); + Interlocked.Increment(ref this.m_count); if (account == "123" && password == "abc") { return true; diff --git a/examples/Dmtp/DmtpRpcPerformanceConsoleApp/DmtpRpcPerformanceConsoleApp.csproj b/examples/Dmtp/DmtpRpcPerformanceConsoleApp/DmtpRpcPerformanceConsoleApp.csproj index 773848ccd..ed2a63ce7 100644 --- a/examples/Dmtp/DmtpRpcPerformanceConsoleApp/DmtpRpcPerformanceConsoleApp.csproj +++ b/examples/Dmtp/DmtpRpcPerformanceConsoleApp/DmtpRpcPerformanceConsoleApp.csproj @@ -1,4 +1,4 @@ - + Exe @@ -8,16 +8,16 @@ - - - + + + - + diff --git a/examples/Dmtp/FileTransferConsoleApp/Program.cs b/examples/Dmtp/FileTransferConsoleApp/Program.cs index 4fcf2539d..be4af98fe 100644 --- a/examples/Dmtp/FileTransferConsoleApp/Program.cs +++ b/examples/Dmtp/FileTransferConsoleApp/Program.cs @@ -54,11 +54,103 @@ internal class Program consoleAction.Add("9", "测试客户端工厂向服务器请求文件", async () => await MultithreadingClientPullFileFromService()); consoleAction.Add("10", "测试客户端工厂向服务器推送文件", async () => await MultithreadingClientPushFileFromService()); + consoleAction.Add("11", "测试Token取消传输", async () => await TestCancelWithToken()); + consoleAction.Add("12", "测试CancellationFileOperator取消传输", async () => await TestCancelWithCancellationFileOperator()); + consoleAction.ShowAll(); await consoleAction.RunCommandLineAsync(); } + /// + /// 测试使用Token取消传输 + /// + private static async Task TestCancelWithToken() + { + var client = await GetTcpDmtpClient(); + ConsoleLogger.Default.Info("开始测试Token取消传输"); + + var filePath = "TestCancelWithToken.Test"; + var saveFilePath = "SaveTestCancelWithToken.Test"; + if (!File.Exists(filePath)) + { + using (var stream = File.OpenWrite(filePath)) + { + stream.SetLength(FileLength); + } + } + + #region 文件传输Token取消 + var tokenSource = new CancellationTokenSource(); + + _ = Task.Run(async () => + { + //此处模拟五秒后自动取消传输 + await Task.Delay(5000); + tokenSource.Cancel(); + }); + + var metadata = new Metadata(); + var fileOperator = new FileOperator + { + SavePath = saveFilePath, + ResourcePath = filePath, + Metadata = metadata, + TryCount = 10, + FileSectionSize = 1024 * 512, + Token = tokenSource.Token + }; + + fileOperator.MaxSpeed = MaxSpeed; + + var result = await client.GetDmtpFileTransferActor().PullFileAsync(fileOperator); + #endregion + + ConsoleLogger.Default.Info($"Token取消传输测试结束,结果:{result}"); + File.Delete(filePath); + File.Delete(saveFilePath); + } + + /// + /// 测试使用CancellationFileOperator取消传输 + /// + private static async Task TestCancelWithCancellationFileOperator() + { + var client = await GetTcpDmtpClient(); + ConsoleLogger.Default.Info("开始测试CancellationFileOperator取消传输"); + + var filePath = "TestCancelWithCancellationFileOperator.Test"; + var saveFilePath = "SaveTestCancelWithCancellationFileOperator.Test"; + if (!File.Exists(filePath)) + { + using (var stream = File.OpenWrite(filePath)) + { + stream.SetLength(FileLength); + } + } + + #region 文件传输CancellationFileOperator取消 + var fileOperator = new CancellationFileOperator + { + SavePath = saveFilePath, + ResourcePath = filePath, + TryCount = 10, + FileSectionSize = 1024 * 512 + }; + + fileOperator.MaxSpeed = MaxSpeed; + + //模拟五秒后自动取消传输,或者直接Cancel + fileOperator.CancelAfter(TimeSpan.FromSeconds(5)); + + var result = await client.GetDmtpFileTransferActor().PullFileAsync(fileOperator); + #endregion + + ConsoleLogger.Default.Info($"CancellationFileOperator取消传输测试结束,结果:{result}"); + File.Delete(filePath); + File.Delete(saveFilePath); + } + /// /// 多线程推送文件 /// @@ -81,6 +173,7 @@ internal class Program } /****此处的逻辑是在程序运行目录下创建一个空内容,但是有长度的文件,用于测试****/ + #region 文件传输多线程推送文件 var metadata = new Metadata();//传递到服务器的元数据 metadata.Add("1", "1"); metadata.Add("2", "2"); @@ -90,7 +183,6 @@ internal class Program SavePath = saveFilePath,//客户端本地保存路径 ResourcePath = filePath,//请求文件的资源路径 Metadata = metadata,//传递到服务器的元数据 - Timeout = TimeSpan.FromSeconds(60),//传输超时时长 TryCount = 10,//当遇到失败时,尝试次数 FileSectionSize = 1024 * 512,//分包大小,当网络较差时,应该适当减小该值 MultithreadingCount = 10//多线程数量 @@ -111,7 +203,8 @@ internal class Program _ = loopAction.RunAsync(); //此方法会等待,直到传输结束 - IResult result = await clientFactory.PushFileAsync(fileOperator); + var result = await clientFactory.PushFileAsync(fileOperator); + #endregion ConsoleLogger.Default.Info($"向服务器推送文件结束,{result}"); @@ -141,6 +234,7 @@ internal class Program } /****此处的逻辑是在程序运行目录下创建一个空内容,但是有长度的文件,用于测试****/ + #region 文件传输多线程请求文件 var metadata = new Metadata();//传递到服务器的元数据 metadata.Add("1", "1"); metadata.Add("2", "2"); @@ -150,7 +244,6 @@ internal class Program SavePath = saveFilePath,//客户端本地保存路径 ResourcePath = filePath,//请求文件的资源路径 Metadata = metadata,//传递到服务器的元数据 - Timeout = TimeSpan.FromSeconds(60),//传输超时时长 TryCount = 10,//当遇到失败时,尝试次数 FileSectionSize = 1024 * 512,//分包大小,当网络较差时,应该适当减小该值 MultithreadingCount = 10//多线程数量 @@ -171,7 +264,8 @@ internal class Program _ = loopAction.RunAsync(); //此方法会等待,直到传输结束 - IResult result = await clientFactory.PullFileAsync(fileOperator); + var result = await clientFactory.PullFileAsync(fileOperator); + #endregion ConsoleLogger.Default.Info($"从服务器下载文件结束,{result}"); @@ -197,16 +291,18 @@ internal class Program } } + #region 文件传输推送小文件 var metadata = new Metadata();//传递到服务器的元数据 metadata.Add("1", "1"); metadata.Add("2", "2"); //此方法会阻塞,直到传输结束,也可以使用PullSmallFileAsync - var result = await client.GetDmtpFileTransferActor().PushSmallFileAsync(saveFilePath, new FileInfo(filePath), metadata); + var result = await client.GetDmtpFileTransferActor().PushSmallFileAsync(saveFilePath, new FileInfo(filePath), metadata, CancellationToken.None); if (result.IsSuccess) { //成功 } + #endregion ConsoleLogger.Default.Info($"从服务器下载小文件结束,结果:{result}"); client.Logger.Info(result.ToString()); @@ -236,14 +332,16 @@ internal class Program } /****此处的逻辑是在程序运行目录下创建一个空内容,但是有长度的文件,用于测试****/ + #region 文件传输拉取小文件 var metadata = new Metadata();//传递到服务器的元数据 metadata.Add("1", "1"); metadata.Add("2", "2"); //此方法会阻塞,直到传输结束,也可以使用PullSmallFileAsync - var result = await client.GetDmtpFileTransferActor().PullSmallFileAsync(filePath, metadata); + var result = await client.GetDmtpFileTransferActor().PullSmallFileAsync(filePath, metadata, CancellationToken.None); var data = result.Value;//此处即是下载的小文件的实际数据 - result.Save(saveFilePath, overwrite: true);//将数据保存到指定路径。 + await result.SaveAsync(saveFilePath, overwrite: true);//将数据保存到指定路径。 + #endregion ConsoleLogger.Default.Info("从服务器下载小文件结束"); client.Logger.Info(result.ToString()); @@ -289,7 +387,6 @@ internal class Program SavePath = saveFilePath,//客户端本地保存路径 ResourcePath = filePath,//请求文件的资源路径 Metadata = metadata,//传递到服务器的元数据 - Timeout = TimeSpan.FromSeconds(60),//传输超时时长 TryCount = 10,//当遇到失败时,尝试次数 FileSectionSize = 1024 * 512//分包大小,当网络较差时,应该适当减小该值 }; @@ -309,7 +406,7 @@ internal class Program _ = loopAction.RunAsync(); //此方法会阻塞,直到传输结束,也可以使用PullFileAsync - IResult result = await client1.GetDmtpFileTransferActor().PushFileAsync(client2.Id, fileOperator); + var result = await client1.GetDmtpFileTransferActor().PushFileAsync(client2.Id, fileOperator); ConsoleLogger.Default.Info("从其他客户端下载文件结束"); client1.Logger.Info(result.ToString()); @@ -350,7 +447,6 @@ internal class Program SavePath = saveFilePath,//客户端本地保存路径 ResourcePath = filePath,//请求文件的资源路径 Metadata = metadata,//传递到服务器的元数据 - Timeout = TimeSpan.FromSeconds(60),//传输超时时长 TryCount = 10,//当遇到失败时,尝试次数 FileSectionSize = 1024 * 512//分包大小,当网络较差时,应该适当减小该值 }; @@ -370,7 +466,7 @@ internal class Program _ = loopAction.RunAsync(); //此方法会阻塞,直到传输结束,也可以使用PullFileAsync - IResult result = await client1.GetDmtpFileTransferActor().PullFileAsync(client2.Id, fileOperator); + var result = await client1.GetDmtpFileTransferActor().PullFileAsync(client2.Id, fileOperator); ConsoleLogger.Default.Info("从其他客户端下载文件结束"); client1.Logger.Info(result.ToString()); @@ -387,6 +483,7 @@ internal class Program /// 服务器要请求的客户端Id private static async Task ServicePushFileFromClient(TcpDmtpService service, string targetId) { + #region 文件传输服务器推送文件 if (!service.TryGetClient(targetId, out var socketClient)) { throw new Exception($"没有找到Id={targetId}的客户端"); @@ -415,7 +512,6 @@ internal class Program SavePath = saveFilePath,//客户端本地保存路径 ResourcePath = filePath,//服务器文件的资源路径 Metadata = metadata,//传递到客户端的元数据 - Timeout = TimeSpan.FromSeconds(60),//传输超时时长 TryCount = 10,//当遇到失败时,尝试次数 FileSectionSize = 1024 * 512//分包大小,当网络较差时,应该适当减小该值 }; @@ -435,7 +531,8 @@ internal class Program _ = loopAction.RunAsync(); //此方法会阻塞,直到传输结束,也可以使用PushFileAsync - IResult result = await socketClient.GetDmtpFileTransferActor().PushFileAsync(fileOperator); + var result = await socketClient.GetDmtpFileTransferActor().PushFileAsync(fileOperator); + #endregion ConsoleLogger.Default.Info("服务器主动推送客户端文件结束"); socketClient.Logger.Info(result.ToString()); @@ -452,6 +549,7 @@ internal class Program /// 服务器要请求的客户端Id private static async Task ServicePullFileFromClient(TcpDmtpService service, string targetId) { + #region 文件传输服务器请求文件 if (!service.TryGetClient(targetId, out var socketClient)) { throw new Exception($"没有找到Id={targetId}的客户端"); @@ -480,7 +578,6 @@ internal class Program SavePath = saveFilePath,//服务器本地保存路径 ResourcePath = filePath,//请求客户端文件的资源路径 Metadata = metadata,//传递到客户端的元数据 - Timeout = TimeSpan.FromSeconds(60),//传输超时时长 TryCount = 10,//当遇到失败时,尝试次数 FileSectionSize = 1024 * 512//分包大小,当网络较差时,应该适当减小该值 }; @@ -500,7 +597,8 @@ internal class Program _ = loopAction.RunAsync(); //此方法会阻塞,直到传输结束,也可以使用PullFileAsync - IResult result = await socketClient.GetDmtpFileTransferActor().PullFileAsync(fileOperator); + var result = await socketClient.GetDmtpFileTransferActor().PullFileAsync(fileOperator); + #endregion ConsoleLogger.Default.Info("从客户端下载文件结束"); socketClient.Logger.Info(result.ToString()); @@ -528,8 +626,9 @@ internal class Program stream.SetLength(FileLength); } } - /****此处的逻辑是在程序运行目录下创建一个空内容,但是有长度的文件,用于测试****/ + /****此处的逻辑是在程序运行目录下创建一个空内容,但是有长度的文件,用于测试****/ + #region 文件传输客户端请求文件 var metadata = new Metadata();//传递到服务器的元数据 metadata.Add("1", "1"); metadata.Add("2", "2"); @@ -539,7 +638,6 @@ internal class Program SavePath = saveFilePath,//客户端本地保存路径 ResourcePath = filePath,//请求文件的资源路径 Metadata = metadata,//传递到服务器的元数据 - Timeout = TimeSpan.FromSeconds(60),//传输超时时长 TryCount = 10,//当遇到失败时,尝试次数 FileSectionSize = 1024 * 512//分包大小,当网络较差时,应该适当减小该值 }; @@ -559,8 +657,10 @@ internal class Program _ = loopAction.RunAsync(); //此方法会阻塞,直到传输结束,也可以使用PullFileAsync - IResult result = await client.GetDmtpFileTransferActor().PullFileAsync(fileOperator); + var result = await client.GetDmtpFileTransferActor().PullFileAsync(fileOperator); + #endregion + #region 文件传输断点续传持久化 ////关于断点续传 ////在执行完PullFile(fileOperator)或PushFile(fileOperator)时。只要返回的结果不是Success。 ////那么就意味着传输没有完成。 @@ -584,6 +684,7 @@ internal class Program // var resourceInfo = new FileResourceInfo(byteBlock); // //然后把resourceInfo赋值给新建的FileOperator的ResourceInfo属性。 //} + #endregion ConsoleLogger.Default.Info("从服务器下载文件结束"); client.Logger.Info(result.ToString()); @@ -613,6 +714,7 @@ internal class Program } /****此处的逻辑是在程序运行目录下创建一个空内容,但是有长度的文件,用于测试****/ + #region 文件传输客户端推送文件 var metadata = new Metadata();//传递到服务器的元数据 metadata.Add("1", "1"); metadata.Add("2", "2"); @@ -622,7 +724,6 @@ internal class Program SavePath = saveFilePath,//服务器本地保存路径 ResourcePath = filePath,//客户端本地即将上传文件的资源路径 Metadata = metadata,//传递到服务器的元数据 - Timeout = TimeSpan.FromSeconds(60),//传输超时时长 TryCount = 10,//当遇到失败时,尝试次数 FileSectionSize = 1024 * 512//分包大小,当网络较差时,应该适当减小该值 }; @@ -642,7 +743,8 @@ internal class Program _ = loopAction.RunAsync(); //此方法会阻塞,直到传输结束,也可以使用PushFileAsync - IResult result = await client.GetDmtpFileTransferActor().PushFileAsync(fileOperator); + var result = await client.GetDmtpFileTransferActor().PushFileAsync(fileOperator); + #endregion ConsoleLogger.Default.Info("上传文件到服务器结束"); client.Logger.Info(result.ToString()); @@ -656,9 +758,9 @@ internal class Program { var client = await new TouchSocketConfig() .SetRemoteIPHost("127.0.0.1:7789") - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options => { - VerifyToken = "File" + options.VerifyToken = "File"; }) .ConfigureContainer(a => { @@ -669,10 +771,6 @@ internal class Program a.UseDmtpFileTransfer();//必须添加文件传输插件 a.Add(); - - a.UseDmtpHeartbeat()//使用Dmtp心跳 - .SetTick(TimeSpan.FromSeconds(3)) - .SetMaxFailCount(3); }) .BuildClientAsync(); @@ -684,25 +782,29 @@ internal class Program { var service = new TcpDmtpService(); + #region 文件传输配置插件 var config = new TouchSocketConfig()//配置 .SetListenIPHosts(new IPHost[] { new IPHost(7789) }) .ConfigureContainer(a => { a.AddConsoleLogger(); - + #region 文件传输添加路由策略 a.AddDmtpRouteService();//添加路由策略 + #endregion }) .ConfigurePlugins(a => { - a.UseDmtpFileTransfer()//必须添加文件传输插件 - //.SetRootPath("C:\\新建文件夹")//设置RootPath - .SetMaxSmallFileLength(1024 * 1024);//设置小文件的最大限制长度 + a.UseDmtpFileTransfer(options => + { + options.MaxSmallFileLength = 1024 * 1024 * 2;//设置小文件的最大限制长度 + }); a.Add(); }) - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options => { - VerifyToken = "File"//连接验证口令。 + options.VerifyToken = "File";//连接验证口令。 }); + #endregion await service.SetupAsync(config); await service.StartAsync(); @@ -720,6 +822,8 @@ internal class Program { ConsoleLogger.Default.Exception(ex); } + + #region 文件传输多线程配置 var clientFactory = new TcpDmtpClientFactory() { MinCount = 5, @@ -728,9 +832,9 @@ internal class Program { return new TouchSocketConfig() .SetRemoteIPHost("127.0.0.1:7789") - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options => { - VerifyToken = "File" + options.VerifyToken = "File"; }) .ConfigurePlugins(a => { @@ -738,7 +842,9 @@ internal class Program }); } }; + #endregion + #region 文件传输多线程获取目标传输客户端Id clientFactory.SetFindTransferIds((targetId) => { //此处的操作不唯一,可能需要rpc实现。 @@ -747,10 +853,13 @@ internal class Program return new string[] { targetId };//此处为模拟结果。 }); + #endregion + return clientFactory; } } +#region 文件传输插件定义 internal class MyPlugin : PluginBase, IDmtpFileTransferringPlugin, IDmtpFileTransferredPlugin, IDmtpRoutingPlugin { private readonly ILog m_logger; @@ -811,6 +920,7 @@ internal class MyPlugin : PluginBase, IDmtpFileTransferringPlugin, IDmtpFileTran await e.InvokeNext(); } + #region 文件传输同意路由 public async Task OnDmtpRouting(IDmtpActorObject client, PackageRouterEventArgs e) { e.IsPermitOperation = true;//允许路由 @@ -818,4 +928,6 @@ internal class MyPlugin : PluginBase, IDmtpFileTransferringPlugin, IDmtpFileTran await e.InvokeNext(); } -} \ No newline at end of file + #endregion +} +#endregion \ No newline at end of file diff --git a/examples/Dmtp/GeneratorRpcProxyConsoleApp/GeneratorRpcProxyConsoleApp.csproj b/examples/Dmtp/GeneratorRpcProxyConsoleApp/GeneratorRpcProxyConsoleApp.csproj index 8328d5ea7..f769ddcae 100644 --- a/examples/Dmtp/GeneratorRpcProxyConsoleApp/GeneratorRpcProxyConsoleApp.csproj +++ b/examples/Dmtp/GeneratorRpcProxyConsoleApp/GeneratorRpcProxyConsoleApp.csproj @@ -8,12 +8,12 @@ - - - - - - + + + + + + diff --git a/examples/Dmtp/GeneratorRpcProxyConsoleApp/Program.cs b/examples/Dmtp/GeneratorRpcProxyConsoleApp/Program.cs index dff394a96..2e0f1e5ef 100644 --- a/examples/Dmtp/GeneratorRpcProxyConsoleApp/Program.cs +++ b/examples/Dmtp/GeneratorRpcProxyConsoleApp/Program.cs @@ -11,7 +11,6 @@ //------------------------------------------------------------------------------ using System.ComponentModel; -using System.Threading.Tasks; using TouchSocket.Core; using TouchSocket.Dmtp; using TouchSocket.Dmtp.Rpc; @@ -41,9 +40,9 @@ internal class Program { a.UseDmtpRpc(); }) - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options=> { - VerifyToken = "Dmtp"//设定连接口令,作用类似账号密码 + options.VerifyToken = "Dmtp";//设定连接口令,作用类似账号密码 }); await service.SetupAsync(config); @@ -59,9 +58,9 @@ internal class Program { a.UseDmtpRpc(); }) - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options=> { - VerifyToken = "Dmtp" + options.VerifyToken = "Dmtp"; })); await client.ConnectAsync(); diff --git a/examples/Dmtp/NamedPipeDmtpConsoleApp/NamedPipeDmtpConsoleApp.csproj b/examples/Dmtp/NamedPipeDmtpConsoleApp/NamedPipeDmtpConsoleApp.csproj index eeadb366b..1b5465cb5 100644 --- a/examples/Dmtp/NamedPipeDmtpConsoleApp/NamedPipeDmtpConsoleApp.csproj +++ b/examples/Dmtp/NamedPipeDmtpConsoleApp/NamedPipeDmtpConsoleApp.csproj @@ -8,7 +8,7 @@ - + diff --git a/examples/Dmtp/NamedPipeDmtpConsoleApp/Program.cs b/examples/Dmtp/NamedPipeDmtpConsoleApp/Program.cs index 61402e723..79360da19 100644 --- a/examples/Dmtp/NamedPipeDmtpConsoleApp/Program.cs +++ b/examples/Dmtp/NamedPipeDmtpConsoleApp/Program.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; using TouchSocket.Core; using TouchSocket.Dmtp; using TouchSocket.NamedPipe; @@ -45,9 +44,9 @@ internal class Program a.AddConsoleLogger(); }) .SetPipeName("TouchSocketPipe")//设置管道名称 - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options=> { - VerifyToken = "Dmtp" + options.VerifyToken = "Dmtp"; })); await client.ConnectAsync(); @@ -64,9 +63,9 @@ internal class Program { a.AddConsoleLogger(); }) - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options=> { - VerifyToken = "Dmtp"//设定连接口令,作用类似账号密码 + options.VerifyToken = "Dmtp";//设定连接口令,作用类似账号密码 }); await service.SetupAsync(config); diff --git a/examples/Dmtp/RealityProxyDmtpRpcConsoleApp/Program.cs b/examples/Dmtp/RealityProxyDmtpRpcConsoleApp/Program.cs deleted file mode 100644 index 3ce1f1018..000000000 --- a/examples/Dmtp/RealityProxyDmtpRpcConsoleApp/Program.cs +++ /dev/null @@ -1,89 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; -using TouchSocket.Core; -using TouchSocket.Dmtp; -using TouchSocket.Dmtp.Rpc; -using TouchSocket.Sockets; - -namespace RealityProxyDmtpRpcConsoleApp; - -/// -/// 调用前先启动DmtpRpcServerConsoleApp项目 -/// -internal class Program -{ - private static void Main(string[] args) - { - var myDmtpRpcRealityProxy = new MyDmtpRpcRealityProxy(); - - var myRpcServer = myDmtpRpcRealityProxy.GetTransparentProxy(); - - var result = myRpcServer.Add(10, 20); - Console.WriteLine(result); - Console.ReadKey(); - } -} - -/// -/// 新建一个类,按照需要,继承DmtpRpcRealityProxy,亦或者RpcRealityProxy基类。 -/// 然后实现抽象方法,主要是能获取到调用的IRpcClient派生接口。 -/// -internal class MyDmtpRpcRealityProxy : DmtpRpcRealityProxy -{ - private readonly TcpDmtpClient m_client; - - public MyDmtpRpcRealityProxy() - { - this.m_client = GetTcpDmtpClient(); - } - - private static TcpDmtpClient GetTcpDmtpClient() - { - var client = new TcpDmtpClient(); - client.SetupAsync(new TouchSocketConfig() - .ConfigureContainer(a => - { - a.AddConsoleLogger(); - }) - .ConfigurePlugins(a => - { - a.UseDmtpRpc(); - }) - .SetRemoteIPHost("127.0.0.1:7789") - .SetDmtpOption(new DmtpOption() - { - VerifyToken = "Dmtp" - })); - client.ConnectAsync().GetFalseAwaitResult(); - client.Logger.Info($"连接成功,Id={client.Id}"); - return client; - } - - public override IDmtpRpcActor GetClient() - { - return this.m_client.GetDmtpRpcActor(); - } -} - -internal interface IMyRpcServer -{ - /// - /// 将两个数相加 - /// - /// - /// - /// - [DmtpRpc(MethodInvoke = true)]//使用函数名直接调用 - int Add(int a, int b); -} \ No newline at end of file diff --git a/examples/Dmtp/RealityProxyDmtpRpcConsoleApp/RealityProxyDmtpRpcConsoleApp.csproj b/examples/Dmtp/RealityProxyDmtpRpcConsoleApp/RealityProxyDmtpRpcConsoleApp.csproj deleted file mode 100644 index 1c0f326a4..000000000 --- a/examples/Dmtp/RealityProxyDmtpRpcConsoleApp/RealityProxyDmtpRpcConsoleApp.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - Exe - net461 - - - - - - - diff --git a/examples/Dmtp/RecommendRpcConsoleApp/Program.cs b/examples/Dmtp/RecommendRpcConsoleApp/Program.cs index 810f165a9..d677583ab 100644 --- a/examples/Dmtp/RecommendRpcConsoleApp/Program.cs +++ b/examples/Dmtp/RecommendRpcConsoleApp/Program.cs @@ -43,9 +43,9 @@ internal class Program { a.UseDmtpRpc(); }) - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options=> { - VerifyToken = "Dmtp" + options.VerifyToken = "Dmtp"; });//设定连接口令,作用类似账号密码 await service.SetupAsync(config); @@ -60,14 +60,14 @@ internal class Program { a.UseDmtpRpc(); }) - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options=> { - VerifyToken = "Dmtp" + options.VerifyToken = "Dmtp"; })); await client.ConnectAsync(); //Login即为在RpcClassLibrary中自动生成的项目 - var response =await client.GetDmtpRpcActor().LoginAsync(new RpcClassLibrary.Models.LoginRequest() { Account = "Account", Password = "Account" }); + var response = await client.GetDmtpRpcActor().LoginAsync(new RpcClassLibrary.Models.LoginRequest() { Account = "Account", Password = "Account" }); Console.WriteLine(response.Result); Console.ReadKey(); } diff --git a/examples/Dmtp/RecommendRpcConsoleApp/RecommendRpcConsoleApp.csproj b/examples/Dmtp/RecommendRpcConsoleApp/RecommendRpcConsoleApp.csproj index fc6b617b5..c7cefb7b3 100644 --- a/examples/Dmtp/RecommendRpcConsoleApp/RecommendRpcConsoleApp.csproj +++ b/examples/Dmtp/RecommendRpcConsoleApp/RecommendRpcConsoleApp.csproj @@ -8,7 +8,7 @@ - + diff --git a/examples/Dmtp/RemoteAccessApp/Form1.cs b/examples/Dmtp/RemoteAccessApp/Form1.cs index b3ed5dd3d..c68a8d0ab 100644 --- a/examples/Dmtp/RemoteAccessApp/Form1.cs +++ b/examples/Dmtp/RemoteAccessApp/Form1.cs @@ -28,11 +28,12 @@ public partial class Form1 : Form private void Form1_Load(object? sender, EventArgs e) { + #region 远程文件系统配置客户端 this.m_client.SetupAsync(new TouchSocketConfig() .SetRemoteIPHost("127.0.0.1:7789") - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options=> { - VerifyToken = "Dmtp" + options.VerifyToken = "Dmtp"; }) .ConfigureContainer(a => { @@ -45,9 +46,10 @@ public partial class Form1 : Form { a.UseDmtpRemoteAccess(); })); + #endregion this.m_client.ConnectAsync(); - this.m_client.Logger.Info("�ɹ�����"); + this.m_client.Logger.Info("成功连接"); } private readonly TcpDmtpClient m_client = new TcpDmtpClient(); @@ -58,7 +60,7 @@ public partial class Form1 : Form { if (this.textBox1.Text.IsNullOrEmpty()) { - this.m_client.Logger.Warning("·������Ϊ�ա�"); + this.m_client.Logger.Warning("路径不能为空。"); return; } var result = await this.m_client.GetRemoteAccessActor().CreateDirectoryAsync(this.textBox1.Text, millisecondsTimeout: 30 * 1000); @@ -76,7 +78,7 @@ public partial class Form1 : Form { if (this.textBox1.Text.IsNullOrEmpty()) { - this.m_client.Logger.Warning("·������Ϊ�ա�"); + this.m_client.Logger.Warning("路径不能为空。"); return; } var result = await this.m_client.GetRemoteAccessActor().DeleteDirectoryAsync(this.textBox1.Text, millisecondsTimeout: 30 * 1000); @@ -94,11 +96,13 @@ public partial class Form1 : Form { if (this.textBox1.Text.IsNullOrEmpty()) { - this.m_client.Logger.Warning("·������Ϊ�ա�"); + this.m_client.Logger.Warning("路径不能为空。"); return; } + #region 远程文件系统获取目录信息 var result = await this.m_client.GetRemoteAccessActor().GetDirectoryInfoAsync(this.textBox1.Text, millisecondsTimeout: 30 * 1000); - this.m_client.Logger.Info($"�����{result.ResultCode}����Ϣ��{result.Message}��������Ϣ����Ի�á�"); + #endregion + this.m_client.Logger.Info($"结果:{result.ResultCode},信息:{result.Message},详细信息请在对话框获取。"); } catch (Exception ex) { @@ -112,7 +116,7 @@ public partial class Form1 : Form { if (this.textBox1.Text.IsNullOrEmpty()) { - this.m_client.Logger.Warning("·������Ϊ�ա�"); + this.m_client.Logger.Warning("路径不能为空。"); return; } var result = await this.m_client.GetRemoteAccessActor().DeleteFileAsync(this.textBox1.Text, millisecondsTimeout: 30 * 1000); @@ -130,11 +134,11 @@ public partial class Form1 : Form { if (this.textBox1.Text.IsNullOrEmpty()) { - this.m_client.Logger.Warning("·������Ϊ�ա�"); + this.m_client.Logger.Warning("路径不能为空。"); return; } var result = await this.m_client.GetRemoteAccessActor().GetFileInfoAsync(this.textBox1.Text, millisecondsTimeout: 30 * 1000); - this.m_client.Logger.Info($"�����{result.ResultCode}����Ϣ��{result.Message}������Ϣ����Ի�á�"); + this.m_client.Logger.Info($"结果:{result.ResultCode},信息:{result.Message},详细信息请在对话框获取。"); } catch (Exception ex) { diff --git a/examples/Dmtp/RemoteAccessApp/Program.cs b/examples/Dmtp/RemoteAccessApp/Program.cs index 548d8e44a..f7df6145f 100644 --- a/examples/Dmtp/RemoteAccessApp/Program.cs +++ b/examples/Dmtp/RemoteAccessApp/Program.cs @@ -43,36 +43,39 @@ internal static class Program private static async Task GetTcpDmtpService() { - var service = await new TouchSocketConfig()//���� + var service = await new TouchSocketConfig()//配置 .SetListenIPHosts(new IPHost[] { new IPHost(7789) }) .ConfigureContainer(a => { a.AddConsoleLogger(); }) + #region 远程文件系统配置插件 .ConfigurePlugins(a => { - a.UseDmtpRemoteAccess();//��������Զ�̷��ʲ�� + a.UseDmtpRemoteAccess();//使用Dmtp远程访问插件 a.Add(); }) - .SetDmtpOption(new DmtpOption() + #endregion + .SetDmtpOption(options=> { - VerifyToken = "Dmtp"//������֤��� + options.VerifyToken = "Dmtp";//连接验证口令 }) - .BuildServiceAsync();//�˴�build�൱��new TcpDmtpService��Ȼ��SetupAsync��Ȼ��StartAsync�� - service.Logger.Info("�������ɹ�����"); + .BuildServiceAsync();//此处build相当于new TcpDmtpService,然后SetupAsync,然后StartAsync。 + service.Logger.Info("服务器成功启动"); return service; } + #region 远程文件系统响应端插件 public class MyRemoteAccessPlugin : PluginBase, IDmtpRemoteAccessingPlugin { public async Task OnRemoteAccessing(IDmtpActorObject client, RemoteAccessingEventArgs e) { - //Console.WriteLine($"�пͻ�����������Զ�̲���"); - //Console.WriteLine($"���ͣ�{e.AccessType}��ģʽ��{e.AccessMode}"); - //Console.WriteLine($"����·����{e.Path}"); - //Console.WriteLine($"Ŀ��·����{e.TargetPath}"); + //Console.WriteLine($"有客户端正在请求远程操作"); + //Console.WriteLine($"类型:{e.AccessType},模式:{e.AccessMode}"); + //Console.WriteLine($"源路径:{e.Path}"); + //Console.WriteLine($"目标路径:{e.TargetPath}"); - //Console.WriteLine("������y/n�����Ƿ����������?"); + //Console.WriteLine("请输入y/n,表示是否允许该操作?"); //var input = Console.ReadLine(); //if (input == "y") @@ -81,8 +84,9 @@ internal static class Program // return; //} - //�����ǰ����޷�������ת����һ����� + //如果当前插件无法处理,请转至下一个插件 await e.InvokeNext(); } } + #endregion } \ No newline at end of file diff --git a/examples/Dmtp/RemoteAccessApp/RemoteAccessApp.csproj b/examples/Dmtp/RemoteAccessApp/RemoteAccessApp.csproj index e6c039ba4..7b9f9271a 100644 --- a/examples/Dmtp/RemoteAccessApp/RemoteAccessApp.csproj +++ b/examples/Dmtp/RemoteAccessApp/RemoteAccessApp.csproj @@ -1,4 +1,4 @@ - + Exe @@ -10,11 +10,11 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/examples/Dmtp/RemoteStreamConsoleApp/Program.cs b/examples/Dmtp/RemoteStreamConsoleApp/Program.cs index 388b5ba03..f11e793cd 100644 --- a/examples/Dmtp/RemoteStreamConsoleApp/Program.cs +++ b/examples/Dmtp/RemoteStreamConsoleApp/Program.cs @@ -34,16 +34,22 @@ internal class Program var client = await GetTcpDmtpClient(); + #region 远程流映射请求端 //元数据可以传递一些字符串数据 var metadata = new Metadata(); metadata.Add("tag", "tag1"); - var remoteStream = await client.GetDmtpRemoteStreamActor().LoadRemoteStreamAsync(metadata); + var remoteStream = await client.GetDmtpRemoteStreamActor().LoadRemoteStreamAsync(metadata, CancellationToken.None); + #endregion client.Logger.Info("已经成功载入流,请输入任意字符"); //可以持续写入流,但在此处只写入了一次 - await remoteStream.WriteAsync(Encoding.UTF8.GetBytes(Console.ReadLine())); + var input = Console.ReadLine(); + if (input != null) + { + await remoteStream.WriteAsync(Encoding.UTF8.GetBytes(input)); + } //可以使用下列代码完成持续读流 //while (true) @@ -71,9 +77,9 @@ internal class Program var client = new TcpDmtpClient(); await client.SetupAsync(new TouchSocketConfig() .SetRemoteIPHost("127.0.0.1:7789") - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options => { - VerifyToken = "Dmtp" + options.VerifyToken = "Dmtp"; }) .ConfigureContainer(a => { @@ -82,10 +88,6 @@ internal class Program .ConfigurePlugins(a => { a.UseDmtpRemoteStream(); - - a.UseDmtpHeartbeat()//使用Dmtp心跳 - .SetTick(TimeSpan.FromSeconds(3)) - .SetMaxFailCount(3); })); await client.ConnectAsync(); return client; @@ -101,19 +103,40 @@ internal class Program }) .ConfigurePlugins(a => { + #region 远程流映射启用插件 a.UseDmtpRemoteStream();//必须添加远程流访问插件 + #endregion a.Add(); }) - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options => { - VerifyToken = "Dmtp"//连接验证口令。 + options.VerifyToken = "Dmtp";//连接验证口令。 }) .BuildServiceAsync();//此处build相当于new TcpDmtpService,然后SetupAsync,然后StartAsync。 service.Logger.Info("服务器成功启动"); return service; } + + #region 远程流映射读写操作 + private static async Task RemoteStreamReadWrite(TcpDmtpClient client) + { + var metadata = new Metadata(); + metadata.Add("tag", "tag1"); + var remoteStream = await client.GetDmtpRemoteStreamActor().LoadRemoteStreamAsync(metadata, CancellationToken.None); + + byte[] data = new byte[] { 0, 1, 2, 3, 4 }; + await remoteStream.WriteAsync(data); + + remoteStream.Position = 0; + byte[] buffer = new byte[5]; + await remoteStream.ReadAsync(buffer); + + remoteStream.Dispose(); + } + #endregion } +#region 远程流映射响应端插件 internal class MyRemoteStreamPlugin : PluginBase, IDmtpRemoteStreamPlugin { private readonly ILog m_logger; @@ -133,7 +156,7 @@ internal class MyRemoteStreamPlugin : PluginBase, IDmtpRemoteStreamPlugin //此处加载的是一个内存流,实际上只要是Stream,都可以,例如:FileStream using (var stream = new MemoryStream()) { - await e.WaitingLoadStreamAsync(stream, TimeSpan.FromSeconds(60)); + await e.WaitingLoadStreamAsync(stream, CancellationToken.None); this.m_logger.Info($"载入的流已被释放,流中信息:{Encoding.UTF8.GetString(stream.ToArray())}"); } @@ -144,4 +167,5 @@ internal class MyRemoteStreamPlugin : PluginBase, IDmtpRemoteStreamPlugin //如果不满足,调用下一个插件 await e.InvokeNext(); } -} \ No newline at end of file +} +#endregion \ No newline at end of file diff --git a/examples/Dmtp/RemoteStreamConsoleApp/RemoteStreamConsoleApp.csproj b/examples/Dmtp/RemoteStreamConsoleApp/RemoteStreamConsoleApp.csproj index eeadb366b..1b5465cb5 100644 --- a/examples/Dmtp/RemoteStreamConsoleApp/RemoteStreamConsoleApp.csproj +++ b/examples/Dmtp/RemoteStreamConsoleApp/RemoteStreamConsoleApp.csproj @@ -8,7 +8,7 @@ - + diff --git a/examples/Dmtp/ReverseRpcConsoleApp/Program.cs b/examples/Dmtp/ReverseRpcConsoleApp/Program.cs index 7ca7e00a8..a7f69cddd 100644 --- a/examples/Dmtp/ReverseRpcConsoleApp/Program.cs +++ b/examples/Dmtp/ReverseRpcConsoleApp/Program.cs @@ -52,9 +52,9 @@ internal class Program { a.UseDmtpRpc(); }) - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options=> { - VerifyToken = "Dmtp" + options.VerifyToken = "Dmtp"; }); service.SetupAsync(config); @@ -81,9 +81,9 @@ internal class Program { a.UseDmtpRpc(); }) - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options=> { - VerifyToken = "Dmtp" + options.VerifyToken = "Dmtp"; })); client.ConnectAsync(); diff --git a/examples/Dmtp/ReverseRpcConsoleApp/ReverseRpcConsoleApp.csproj b/examples/Dmtp/ReverseRpcConsoleApp/ReverseRpcConsoleApp.csproj index e61b4ab6f..c5ca9ba39 100644 --- a/examples/Dmtp/ReverseRpcConsoleApp/ReverseRpcConsoleApp.csproj +++ b/examples/Dmtp/ReverseRpcConsoleApp/ReverseRpcConsoleApp.csproj @@ -6,11 +6,11 @@ - - - - - - + + + + + + diff --git a/examples/Dmtp/RouterPackageConsoleApp/Program.cs b/examples/Dmtp/RouterPackageConsoleApp/Program.cs index 7270cb345..0a2fd8a0b 100644 --- a/examples/Dmtp/RouterPackageConsoleApp/Program.cs +++ b/examples/Dmtp/RouterPackageConsoleApp/Program.cs @@ -30,7 +30,7 @@ internal class Program { ConsoleLogger.Default.Info(ex.Message); } - var service =await GetTcpDmtpService(); + var service = await GetTcpDmtpService(); var consoleAction = new ConsoleAction(); consoleAction.OnException += ConsoleAction_OnException; @@ -56,7 +56,7 @@ internal class Program }; //发起请求,然后等待一个自定义的响应包。 - var response = await client.GetDmtpRouterPackageActor().RequestAsync(requestPackage); + var response = await client.GetDmtpRouterPackageActor().RequestAsync(requestPackage, CancellationToken.None); client.Logger.Info($"自定义响应成功,{response}"); } @@ -65,6 +65,7 @@ internal class Program private static async Task RequestMyResponsePackage() { using var client = await GetTcpDmtpClient(); + #region 路由包请求端 using (var byteBlock = new ByteBlock(1024 * 512)) { //此处模拟一个大数据块,实际情况中请使用write写入实际数据。 @@ -76,10 +77,11 @@ internal class Program }; //发起请求,然后等待一个自定义的响应包。 - var response = await client.GetDmtpRouterPackageActor().RequestAsync(requestPackage); + var response = await client.GetDmtpRouterPackageActor().RequestAsync(requestPackage, CancellationToken.None); client.Logger.Info($"自定义响应成功,{response.Message}"); } + #endregion } private static void ConsoleAction_OnException(Exception obj) @@ -89,11 +91,12 @@ internal class Program private static async Task GetTcpDmtpClient() { + #region 路由包添加插件 var client = await new TouchSocketConfig() .SetRemoteIPHost("127.0.0.1:7789") - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options=> { - VerifyToken = "Dmtp" + options.VerifyToken = "Dmtp"; }) .ConfigureContainer(a => { @@ -104,6 +107,7 @@ internal class Program a.UseDmtpRouterPackage();//添加路由包功能插件 }) .BuildClientAsync(); + #endregion client.Logger.Info("连接成功"); return client; @@ -128,9 +132,9 @@ internal class Program a.Add(); a.Add(); }) - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options=> { - VerifyToken = "Dmtp"//连接验证口令。 + options.VerifyToken = "Dmtp";//连接验证口令。 }); await service.SetupAsync(config); @@ -142,10 +146,11 @@ internal class Program /// /// 定义请求包 /// + #region 路由包定义请求包 private class MyRequestPackage : DmtpRouterPackage { /// - /// 包尺寸大小。此值并非需要精准数值,只需要估计数据即可。其作用为申请内存池。所以数据应当大小合适。 + /// 包尺寸大小。此值并非需要精准数值,只需要估计数据即可。其作用为申请内存池。所以数据应当大小合适。 /// public override int PackageSize => 1024 * 1024; @@ -157,19 +162,22 @@ internal class Program public override void PackageBody(ref TByteBlock byteBlock) { base.PackageBody(ref byteBlock); - byteBlock.WriteByteBlock(this.ByteBlock); + WriterExtension.WriteByteBlock(ref byteBlock, this.ByteBlock); } public override void UnpackageBody(ref TByteBlock byteBlock) { base.UnpackageBody(ref byteBlock); - this.ByteBlock = byteBlock.ReadByteBlock(); + + this.ByteBlock = ReaderExtension.ReadByteBlock(ref byteBlock); } } + #endregion /// /// 定义响应包 /// + #region 路由包定义响应包 private class MyResponsePackage : DmtpRouterPackage { /// @@ -177,7 +185,9 @@ internal class Program /// public override int PackageSize => 1024; } + #endregion + #region 路由包响应端插件 private class MyPlugin1 : PluginBase, IDmtpRouterPackagePlugin { private readonly ILog m_logger; @@ -209,6 +219,7 @@ internal class Program await e.InvokeNext(); } } + #endregion private class MyPlugin2 : PluginBase, IDmtpRouterPackagePlugin { diff --git a/examples/Dmtp/RouterPackageConsoleApp/RouterPackageConsoleApp.csproj b/examples/Dmtp/RouterPackageConsoleApp/RouterPackageConsoleApp.csproj index 117ad74df..276ed914e 100644 --- a/examples/Dmtp/RouterPackageConsoleApp/RouterPackageConsoleApp.csproj +++ b/examples/Dmtp/RouterPackageConsoleApp/RouterPackageConsoleApp.csproj @@ -1,4 +1,4 @@ - + Exe @@ -7,6 +7,6 @@ - + diff --git a/examples/Dmtp/RpcClassLibrary/RpcClassLibrary.csproj b/examples/Dmtp/RpcClassLibrary/RpcClassLibrary.csproj index 1bc660f1b..09a2d53d5 100644 --- a/examples/Dmtp/RpcClassLibrary/RpcClassLibrary.csproj +++ b/examples/Dmtp/RpcClassLibrary/RpcClassLibrary.csproj @@ -5,7 +5,7 @@ - + diff --git a/examples/Dmtp/RpcFactoryConsoleApp/Program.cs b/examples/Dmtp/RpcFactoryConsoleApp/Program.cs index 8cc4c18b0..0891d2ce1 100644 --- a/examples/Dmtp/RpcFactoryConsoleApp/Program.cs +++ b/examples/Dmtp/RpcFactoryConsoleApp/Program.cs @@ -23,7 +23,8 @@ internal class Program { var clientFactory = CreateTcpClientFactory(); - using (var clientFactoryResult = await clientFactory.GetClient()) + var cts = new CancellationTokenSource(1000 * 10); + using (var clientFactoryResult = await clientFactory.GetClient(cts.Token)) { //这里可以让得到的通讯单体进行业务交流 var client = clientFactoryResult.Client; diff --git a/examples/Dmtp/RpcFactoryConsoleApp/RpcFactoryConsoleApp.csproj b/examples/Dmtp/RpcFactoryConsoleApp/RpcFactoryConsoleApp.csproj index 8328d5ea7..f769ddcae 100644 --- a/examples/Dmtp/RpcFactoryConsoleApp/RpcFactoryConsoleApp.csproj +++ b/examples/Dmtp/RpcFactoryConsoleApp/RpcFactoryConsoleApp.csproj @@ -8,12 +8,12 @@ - - - - - - + + + + + + diff --git a/examples/Dmtp/RpcImplementationClassLibrary/RpcImplementationClassLibrary.csproj b/examples/Dmtp/RpcImplementationClassLibrary/RpcImplementationClassLibrary.csproj index 7bb4ca9db..f7f7d462c 100644 --- a/examples/Dmtp/RpcImplementationClassLibrary/RpcImplementationClassLibrary.csproj +++ b/examples/Dmtp/RpcImplementationClassLibrary/RpcImplementationClassLibrary.csproj @@ -5,7 +5,7 @@ - + diff --git a/examples/Dmtp/RpcStreamConsoleApp/Program.cs b/examples/Dmtp/RpcStreamConsoleApp/Program.cs index 5bb3da851..d2f764ceb 100644 --- a/examples/Dmtp/RpcStreamConsoleApp/Program.cs +++ b/examples/Dmtp/RpcStreamConsoleApp/Program.cs @@ -40,9 +40,9 @@ internal class Program { a.UseDmtpRpc(); }) - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options => { - VerifyToken = "Dmtp" + options.VerifyToken = "Dmtp"; })); client.ConnectAsync(); return client; @@ -65,9 +65,9 @@ internal class Program { a.UseDmtpRpc(); }) - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options => { - VerifyToken = "Rpc" + options.VerifyToken = "Rpc"; }); service.SetupAsync(config); @@ -84,20 +84,23 @@ internal class Program var client = CreateClient(); //测试客户端持续请求数据 var size = 0; - var channel =await client.CreateChannelAsync();//创建通道 - var task = Task.Run(() =>//这里必须用异步 + var channel = await client.CreateChannelAsync();//创建通道 + var task = Task.Run(async () =>//这里必须用异步 { using (channel) { - foreach (var byteBlock in channel) + while (channel.CanRead) { - size += byteBlock.Length; + using var cts = new CancellationTokenSource(10 * 1000); + var memory = await channel.ReadAsync(cts.Token); + //这里处理数据 + size += memory.Length; } } }); //此处是直接调用,真正使用时,可以生成代理调用。 - var result =await client.GetDmtpRpcActor().InvokeTAsync("RpcPullChannel", InvokeOption.WaitInvoke, channel.Id); + var result = await client.GetDmtpRpcActor().InvokeTAsync("RpcPullChannel", InvokeOption.WaitInvoke, channel.Id); await task;//等待异步接收完成 Console.WriteLine($"客户端接收结束,状态:{channel.Status},size={size}"); //测试客户端持续请求数据 @@ -111,7 +114,7 @@ internal class Program var client = CreateClient(); var size = 0; var package = 1024 * 1024; - var channel =await client.CreateChannelAsync();//创建通道 + var channel = await client.CreateChannelAsync();//创建通道 var task = Task.Run(async () =>//这里必须用异步 { for (var i = 0; i < Program.Count; i++) @@ -123,7 +126,7 @@ internal class Program }); //此处是直接调用,真正使用时,可以生成代理调用。 - var result =await client.GetDmtpRpcActor().InvokeTAsync("RpcPushChannel", InvokeOption.WaitInvoke, channel.Id); + var result = await client.GetDmtpRpcActor().InvokeTAsync("RpcPushChannel", InvokeOption.WaitInvoke, channel.Id); await task;//等待异步接收完成 channel.Dispose(); @@ -165,7 +168,7 @@ internal class Program /// [Description("测试ServiceToClient创建通道,从而实现流数据的传输")] [DmtpRpc(MethodInvoke = true)]//此处设置直接使用方法名调用 - public int RpcPushChannel(ICallContext callContext, int channelID) + public async Task RpcPushChannel(ICallContext callContext, int channelID) { var size = 0; @@ -173,9 +176,12 @@ internal class Program { if (socketClient.TrySubscribeChannel(channelID, out var channel)) { - foreach (var byteBlock in channel) + while (channel.CanRead) { - size += byteBlock.Length; + using var cts = new CancellationTokenSource(10 * 1000); + var memory = await channel.ReadAsync(cts.Token); + //这里处理数据 + size += memory.Length; } Console.WriteLine($"服务器接收结束,状态:{channel.Status},长度:{size}"); } diff --git a/examples/Dmtp/RpcStreamConsoleApp/RpcStreamConsoleApp.csproj b/examples/Dmtp/RpcStreamConsoleApp/RpcStreamConsoleApp.csproj index f18b62f22..6608859ea 100644 --- a/examples/Dmtp/RpcStreamConsoleApp/RpcStreamConsoleApp.csproj +++ b/examples/Dmtp/RpcStreamConsoleApp/RpcStreamConsoleApp.csproj @@ -8,12 +8,12 @@ - - - - - - + + + + + + diff --git a/examples/Dmtp/SerializationSelectorClassLibrary/MemoryPackSerializationSelector.cs b/examples/Dmtp/SerializationSelectorClassLibrary/MemoryPackSerializationSelector.cs index b48aa6b40..d2022286c 100644 --- a/examples/Dmtp/SerializationSelectorClassLibrary/MemoryPackSerializationSelector.cs +++ b/examples/Dmtp/SerializationSelectorClassLibrary/MemoryPackSerializationSelector.cs @@ -11,190 +11,60 @@ //------------------------------------------------------------------------------ using MemoryPack; -using Newtonsoft.Json; using System; -using System.IO; using TouchSocket.Core; using TouchSocket.Dmtp.Rpc; -using TouchSocket.Rpc; namespace SerializationSelectorClassLibrary; public class MemoryPackSerializationSelector : ISerializationSelector { - public object DeserializeParameter(ref TByteBlock byteBlock, SerializationType serializationType, Type parameterType) where TByteBlock : IByteBlock + public object DeserializeParameter(ref TByteBlock byteBlock, SerializationType serializationType, Type parameterType) where TByteBlock : IBytesReader { - var len = byteBlock.ReadInt32(); - var span = byteBlock.ReadToSpan(len); + var len = ReaderExtension.ReadValue(ref byteBlock); + var span = ReaderExtension.ReadToSpan(ref byteBlock, len); return MemoryPackSerializer.Deserialize(parameterType, span); } - public void SerializeParameter(ref TByteBlock byteBlock, SerializationType serializationType, in object parameter) where TByteBlock : IByteBlock + public void SerializeParameter(ref TByteBlock byteBlock, SerializationType serializationType, in object parameter) where TByteBlock : IBytesWriter { - var pos = byteBlock.Position; - byteBlock.Seek(4, SeekOrigin.Current); + var writerAnchor = new WriterAnchor(ref byteBlock, 4); + var memoryPackWriter = new MemoryPackWriter(ref byteBlock, null); MemoryPackSerializer.Serialize(parameter.GetType(), ref memoryPackWriter, parameter); - var newPos = byteBlock.Position; - byteBlock.Position = pos; - byteBlock.WriteInt32(memoryPackWriter.WrittenCount); - byteBlock.Position = newPos; + var span = writerAnchor.Rewind(ref byteBlock, out var len); + span.WriteValue(memoryPackWriter.WrittenCount); } } -internal sealed class DefaultSerializationSelector : ISerializationSelector +internal sealed class MyDefaultSerializationSelector : DefaultSerializationSelector { - /// - /// 根据指定的序列化类型反序列化字节块中的数据。 - /// - /// 包含序列化数据的字节块。 - /// 指定的序列化类型。 - /// 预期反序列化出的对象类型。 - /// 反序列化后的对象。 - /// 抛出当未识别序列化类型时。 - public object DeserializeParameter(ref TByteBlock byteBlock, SerializationType serializationType, Type parameterType) where TByteBlock : IByteBlock + public override object DeserializeParameter(ref TReader reader, SerializationType serializationType, Type parameterType) { - // 根据序列化类型选择不同的反序列化方式 - switch (serializationType) + if ((byte)serializationType == 4) { - case SerializationType.FastBinary: - // 使用FastBinary格式进行反序列化 - return FastBinaryFormatter.Deserialize(ref byteBlock, parameterType); - case SerializationType.SystemBinary: - // 检查字节块是否为null - if (byteBlock.ReadIsNull()) - { - // 如果为null,则返回该类型的默认值 - return parameterType.GetDefault(); - } - - // 使用SystemBinary格式进行反序列化 - using (var block = byteBlock.ReadByteBlock()) - { - // 将字节块转换为流并进行反序列化 - return SerializeConvert.BinaryDeserialize(block.AsStream()); - } - case SerializationType.Json: - // 检查字节块是否为null - if (byteBlock.ReadIsNull()) - { - // 如果为null,则返回该类型的默认值 - return parameterType.GetDefault(); - } - - // 使用Json格式进行反序列化 - return JsonConvert.DeserializeObject(byteBlock.ReadString(), parameterType); - - case SerializationType.Xml: - // 检查字节块是否为null - if (byteBlock.ReadIsNull()) - { - // 如果为null,则返回该类型的默认值 - return parameterType.GetDefault(); - } - // 使用Xml格式进行反序列化 - return SerializeConvert.XmlDeserializeFromBytes(byteBlock.ReadBytesPackage(), parameterType); - case (SerializationType)4: - { - var len = byteBlock.ReadInt32(); - var span = byteBlock.ReadToSpan(len); - return MemoryPackSerializer.Deserialize(parameterType, span); - } - default: - // 如果序列化类型未识别,则抛出异常 - throw new RpcException("未指定的反序列化方式"); + var len = ReaderExtension.ReadValue(ref reader); + var span = ReaderExtension.ReadToSpan(ref reader, len); + return MemoryPackSerializer.Deserialize(parameterType, span); } + return base.DeserializeParameter(ref reader, serializationType, parameterType); } - /// - /// 序列化参数 - /// - /// 字节块引用,用于存储序列化后的数据 - /// 序列化类型,决定了使用哪种方式序列化 - /// 待序列化的参数对象 - /// 字节块类型,必须实现IByteBlock接口 - public void SerializeParameter(ref TByteBlock byteBlock, SerializationType serializationType, in object parameter) where TByteBlock : IByteBlock + public override void SerializeParameter(ref TWriter writer, SerializationType serializationType, in object parameter) { - // 根据序列化类型选择不同的序列化方法 - switch (serializationType) + if ((byte)serializationType == 4) { - case SerializationType.FastBinary: - { - // 使用FastBinaryFormatter进行序列化 - FastBinaryFormatter.Serialize(ref byteBlock, parameter); - break; - } - case SerializationType.SystemBinary: - { - // 参数为null时,写入空值标记 - if (parameter is null) - { - byteBlock.WriteNull(); - } - else - { - // 参数不为null时,标记并序列化参数 - byteBlock.WriteNotNull(); - using (var block = new ByteBlock(1024 * 64)) - { - // 使用System.Runtime.Serialization.BinaryFormatter进行序列化 - SerializeConvert.BinarySerialize(block.AsStream(), parameter); - // 将序列化后的字节块写入byteBlock - byteBlock.WriteByteBlock(block); - } - } - break; - } - case SerializationType.Json: - { - // 参数为null时,写入空值标记 - if (parameter is null) - { - byteBlock.WriteNull(); - } - else - { - // 参数不为null时,标记并转换为JSON字符串 - byteBlock.WriteNotNull(); - byteBlock.WriteString(JsonConvert.SerializeObject(parameter)); - } - break; - } - case SerializationType.Xml: - { - // 参数为null时,写入空值标记 - if (parameter is null) - { - byteBlock.WriteNull(); - } - else - { - // 参数不为null时,标记并转换为Xml字节 - byteBlock.WriteNotNull(); - byteBlock.WriteBytesPackage(SerializeConvert.XmlSerializeToBytes(parameter)); - } - break; - } - case (SerializationType)4: - { - var pos = byteBlock.Position; - byteBlock.Seek(4, SeekOrigin.Current); - var memoryPackWriter = new MemoryPackWriter(ref byteBlock, null); + var writerAnchor = new WriterAnchor(ref writer, 4); - MemoryPackSerializer.Serialize(parameter.GetType(), ref memoryPackWriter, parameter); + var memoryPackWriter = new MemoryPackWriter(ref writer, null); - var newPos = byteBlock.Position; - byteBlock.Position = pos; - byteBlock.WriteInt32(memoryPackWriter.WrittenCount); - byteBlock.Position = newPos; + MemoryPackSerializer.Serialize(parameter.GetType(), ref memoryPackWriter, parameter); - break; - } - default: - // 抛出异常,提示未指定的序列化方式 - throw new RpcException("未指定的序列化方式"); + var span = writerAnchor.Rewind(ref writer, out var len); + span.WriteValue(memoryPackWriter.WrittenCount); } + base.SerializeParameter(ref writer, serializationType, parameter); } } \ No newline at end of file diff --git a/examples/Dmtp/SerializationSelectorClassLibrary/SerializationSelectorClassLibrary.csproj b/examples/Dmtp/SerializationSelectorClassLibrary/SerializationSelectorClassLibrary.csproj index a43de4651..b3fab3ddf 100644 --- a/examples/Dmtp/SerializationSelectorClassLibrary/SerializationSelectorClassLibrary.csproj +++ b/examples/Dmtp/SerializationSelectorClassLibrary/SerializationSelectorClassLibrary.csproj @@ -5,13 +5,13 @@ - - - - - - - + + + + + + + diff --git a/examples/Dmtp/SerializationSelectorConsoleApp/Program.cs b/examples/Dmtp/SerializationSelectorConsoleApp/Program.cs index ff828a4b9..d6e4101a2 100644 --- a/examples/Dmtp/SerializationSelectorConsoleApp/Program.cs +++ b/examples/Dmtp/SerializationSelectorConsoleApp/Program.cs @@ -29,11 +29,10 @@ internal class Program var client = await CreateClient(); - InvokeOption invokeOption = new DmtpInvokeOption() + InvokeOption invokeOption = new DmtpInvokeOption(1000 * 10) { FeedbackType = FeedbackType.WaitInvoke, - SerializationType = (SerializationType)4, - Timeout = 1000 * 10 + SerializationType = (SerializationType)4 }; var msg = await client.GetDmtpRpcActor().LoginAsync(new LoginModel() { Account = "Account", Password = "Password" }, invokeOption); @@ -48,8 +47,10 @@ internal class Program .SetRemoteIPHost("127.0.0.1:7789") .ConfigurePlugins(a => { - a.UseDmtpRpc() - .SetSerializationSelector(new MemoryPackSerializationSelector()); + a.UseDmtpRpc(options => + { + options.SerializationSelector = new MemoryPackSerializationSelector(); + }); //a.UseDmtpRpc() // .SetSerializationSelector(new DefaultSerializationSelector() @@ -60,9 +61,9 @@ internal class Program // SerializationBinder = default, // }); }) - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options => { - VerifyToken = "Dmtp" + options.VerifyToken = "Dmtp"; })); await client.ConnectAsync(); return client; @@ -75,8 +76,10 @@ internal class Program .SetListenIPHosts(new IPHost[] { new IPHost(7789) }) .ConfigurePlugins(a => { - a.UseDmtpRpc() - .SetSerializationSelector(new MemoryPackSerializationSelector()); + a.UseDmtpRpc(options => + { + options.SerializationSelector = new MemoryPackSerializationSelector(); + }); }) .ConfigureContainer(a => { @@ -86,9 +89,9 @@ internal class Program store.RegisterServer(); }); }) - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options => { - VerifyToken = "Dmtp" + options.VerifyToken = "Dmtp"; }); await service.SetupAsync(config); diff --git a/examples/Dmtp/SerializationSelectorConsoleApp/SerializationSelectorConsoleApp.csproj b/examples/Dmtp/SerializationSelectorConsoleApp/SerializationSelectorConsoleApp.csproj index 9a6c6e7ef..d7c3f04f6 100644 --- a/examples/Dmtp/SerializationSelectorConsoleApp/SerializationSelectorConsoleApp.csproj +++ b/examples/Dmtp/SerializationSelectorConsoleApp/SerializationSelectorConsoleApp.csproj @@ -1,4 +1,4 @@ - + Exe @@ -7,12 +7,12 @@ enable - - - - - - + + + + + + diff --git a/examples/Examples-All.sln b/examples/Examples-All.sln index b09dc5c52..c9cc3dbd3 100644 --- a/examples/Examples-All.sln +++ b/examples/Examples-All.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.3.32922.545 +# Visual Studio Version 18 +VisualStudioVersion = 18.0.11018.127 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tcp", "Tcp", "{FB46E926-0EEC-488C-B950-A16D2E7F694B}" EndProject @@ -116,7 +116,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DmtpWebApplication", "Dmtp\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RemoteStreamConsoleApp", "Dmtp\RemoteStreamConsoleApp\RemoteStreamConsoleApp.csproj", "{4019C53F-2185-4ACF-A816-C659607B05E5}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Dmtp", "Dmtp", "{57B547B6-D725-4C3E-82A7-39ED03BD438B}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "创建Dmtp", "创建Dmtp", "{57B547B6-D725-4C3E-82A7-39ED03BD438B}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DmtpConsoleApp", "Dmtp\DmtpConsoleApp\DmtpConsoleApp.csproj", "{44AE2CE8-5EE2-42F6-A1CA-E562EC697445}" EndProject @@ -166,10 +166,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TcpServiceForWebApi", "Tcp\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TcpCommandLineConsoleApp", "Tcp\TcpCommandLineConsoleApp\TcpCommandLineConsoleApp.csproj", "{01EB9E70-4A13-41E3-8609-8DFD9C2BC0A4}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "同步请求", "同步请求", "{50312210-D0AC-4E25-B819-BE2F1CC70581}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TcpWaitingClientWinFormsApp", "Tcp\TcpWaitingClientWinFormsApp\TcpWaitingClientWinFormsApp.csproj", "{DF384D76-50A5-4D7B-A1D2-15CA00B844D4}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HttpServiceConsoleApp", "Http\HttpServiceConsoleApp\HttpServiceConsoleApp.csproj", "{FADFD5E7-A5AD-49D9-A03F-70DB8E98AD38}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HttpClientConsoleApp", "Http\HttpClientConsoleApp\HttpClientConsoleApp.csproj", "{9F96589A-C7F8-4E33-BD6E-3BDA40205D07}" @@ -184,12 +180,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Rpc", "Rpc", "{D01CF5F0-67D EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GenerateProxyFromServerConsoleApp", "Rpc\GenerateProxyFromServerConsoleApp\GenerateProxyFromServerConsoleApp.csproj", "{09ABC5EB-0896-4693-A057-A03B35E0294C}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AdapterConsoleApp", "Adapter\AdapterConsoleApp\AdapterConsoleApp.csproj", "{5F141D56-D6ED-45C7-A355-63EC641246DB}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AdapterTesterConsoleApp", "Adapter\AdapterTesterConsoleApp\AdapterTesterConsoleApp.csproj", "{E1C1B35D-6336-413B-B7DE-25BC24AE401A}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TLVWinFormsApp", "Adapter\TLVWinFormsApp\TLVWinFormsApp.csproj", "{E05EFE74-1D74-44BF-8DE6-AA4B0DC4BC97}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DmtpChannel", "DmtpChannel", "{01B7AC92-32D4-4864-B5CF-02752C391C9B}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DmtpChannelConsoleApp", "Dmtp\DmtpChannelConsoleApp\DmtpChannelConsoleApp.csproj", "{3D418431-3CD2-439A-AE5F-2BBC7FF977C6}" @@ -230,10 +222,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DispatchProxyJsonRpcClientC EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DispatchProxyXmlRpcClientConsoleApp", "XmlRpc\DispatchProxyXmlRpcClientConsoleApp\DispatchProxyXmlRpcClientConsoleApp.csproj", "{8AFB1C99-7E16-4912-B662-91C5328C6BAE}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "RealityProxy", "RealityProxy", "{995183D4-74FD-4EDD-8FDF-8B6BC0410A06}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RealityProxyDmtpRpcConsoleApp", "Dmtp\RealityProxyDmtpRpcConsoleApp\RealityProxyDmtpRpcConsoleApp.csproj", "{C94B9E69-3B48-4AAB-B3C3-80AA190640C2}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Hosting", "Hosting", "{0E116F60-01DE-4648-B4EE-5B944EED42AA}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HostingWorkerService", "Hosting\HostingWorkerService\HostingWorkerService.csproj", "{866798F5-FD69-49D2-B4FA-279DC7188A54}" @@ -256,8 +244,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RpcRateLimitingConsoleApp", EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PluginPassingParameters", "PluginPassingParameters", "{B9AD57A2-89FB-459B-8D58-41C770CCA89B}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReuseAddressServerConsoleApp", "Tcp\ReuseAddressServerConsoleApp\ReuseAddressServerConsoleApp.csproj", "{1E65E919-C7D8-4678-8BFD-1434CC8293BF}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DmtpAspnetcoreConsoleApp", "Dmtp\DmtpAspnetcoreConsoleApp\DmtpAspnetcoreConsoleApp.csproj", "{7208999A-BC28-4476-BBFD-572C8D43E35E}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "基于NamedPipe的Dmtp", "基于NamedPipe的Dmtp", "{1B6092F9-4776-4502-818F-B35D153D19D6}" @@ -267,6 +253,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".Solutions", ".Solutions", "{043D35EB-9819-4E9A-BDC7-2974A122195D}" ProjectSection(SolutionItems) = preProject Directory.Build.props = Directory.Build.props + Directory.Packages.props = Directory.Packages.props EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TcpServiceReadAsyncConsoleApp", "Tcp\TcpServiceReadAsyncConsoleApp\TcpServiceReadAsyncConsoleApp.csproj", "{03EE9539-60C6-4BB5-97D7-3F9B1A077234}" @@ -351,6 +338,32 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlcBridgesConsoleApp", "Plc EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModbusPlcBridgeConsoleApp", "PlcBridges\ModbusPlcBridgeConsoleApp\ModbusPlcBridgeConsoleApp.csproj", "{449F3FDA-0AF3-4ACE-B584-A38037C803E3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RawAdapterConsoleApp", "Adapter\RawAdapterConsoleApp\RawAdapterConsoleApp.csproj", "{A90C21AE-9552-E1EF-028A-309CB5F72843}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TouchSocketBitConverterConsoleApp", "Core\TouchSocketBitConverterConsoleApp\TouchSocketBitConverterConsoleApp.csproj", "{896858AE-13F4-473B-A5D5-B214CD04FDEF}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WaitingClient", "WaitingClient", "{432C7F0A-B01D-4113-8DF3-C588B147A4C6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TcpWaitingClientWinFormsApp", "WaitingClient\TcpWaitingClientWinFormsApp\TcpWaitingClientWinFormsApp.csproj", "{90FE56EA-94C2-820D-6E26-012E0BC1BDA9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WaitingClientConsoleApp", "WaitingClient\WaitingClientConsoleApp\WaitingClientConsoleApp.csproj", "{9CC9DEE5-C96B-454E-8CBF-67A359055121}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MultipacketAdapterConsoleApp", "Adapter\MultipacketAdapterConsoleApp\MultipacketAdapterConsoleApp.csproj", "{3B17545F-6B5B-C98E-9EA2-27E6FCFFE9AF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CreateDmtpConsoleApp", "Dmtp\CreateDmtpConsoleApp\CreateDmtpConsoleApp.csproj", "{D791EA02-3EC8-44A0-9877-614DE92BA9B5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleActionConsoleApp", "Core\ConsoleActionConsoleApp\ConsoleActionConsoleApp.csproj", "{F646A400-15A5-9DFE-D66B-76BC09579811}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppMessengerConsoleApp", "Core\AppMessengerConsoleApp\AppMessengerConsoleApp.csproj", "{E69CACF0-FE0E-01E3-0B31-0804EABFFF4D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DependencyPropertyConsoleApp", "Core\DependencyPropertyConsoleApp\DependencyPropertyConsoleApp.csproj", "{FCBCA3E2-22D3-4311-A1B7-AE029B961953}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RpcRegisterConsoleApp", "Rpc\RpcRegisterConsoleApp\RpcRegisterConsoleApp.csproj", "{F72A1BA0-541A-40CD-89EB-24C4F010DAC0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RpcAopConsoleApp", "Rpc\RpcAopConsoleApp\RpcAopConsoleApp.csproj", "{3F974BD3-A55A-4D13-AB83-C8FE94CE0041}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RpcCallContextConsoleApp", "Rpc\RpcCallContextConsoleApp\RpcCallContextConsoleApp.csproj", "{58069766-C855-4B2D-B547-6C256E725215}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -529,10 +542,6 @@ Global {01EB9E70-4A13-41E3-8609-8DFD9C2BC0A4}.Debug|Any CPU.Build.0 = Debug|Any CPU {01EB9E70-4A13-41E3-8609-8DFD9C2BC0A4}.Release|Any CPU.ActiveCfg = Release|Any CPU {01EB9E70-4A13-41E3-8609-8DFD9C2BC0A4}.Release|Any CPU.Build.0 = Release|Any CPU - {DF384D76-50A5-4D7B-A1D2-15CA00B844D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DF384D76-50A5-4D7B-A1D2-15CA00B844D4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DF384D76-50A5-4D7B-A1D2-15CA00B844D4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DF384D76-50A5-4D7B-A1D2-15CA00B844D4}.Release|Any CPU.Build.0 = Release|Any CPU {FADFD5E7-A5AD-49D9-A03F-70DB8E98AD38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FADFD5E7-A5AD-49D9-A03F-70DB8E98AD38}.Debug|Any CPU.Build.0 = Debug|Any CPU {FADFD5E7-A5AD-49D9-A03F-70DB8E98AD38}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -553,18 +562,10 @@ Global {09ABC5EB-0896-4693-A057-A03B35E0294C}.Debug|Any CPU.Build.0 = Debug|Any CPU {09ABC5EB-0896-4693-A057-A03B35E0294C}.Release|Any CPU.ActiveCfg = Release|Any CPU {09ABC5EB-0896-4693-A057-A03B35E0294C}.Release|Any CPU.Build.0 = Release|Any CPU - {5F141D56-D6ED-45C7-A355-63EC641246DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5F141D56-D6ED-45C7-A355-63EC641246DB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5F141D56-D6ED-45C7-A355-63EC641246DB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5F141D56-D6ED-45C7-A355-63EC641246DB}.Release|Any CPU.Build.0 = Release|Any CPU {E1C1B35D-6336-413B-B7DE-25BC24AE401A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E1C1B35D-6336-413B-B7DE-25BC24AE401A}.Debug|Any CPU.Build.0 = Debug|Any CPU {E1C1B35D-6336-413B-B7DE-25BC24AE401A}.Release|Any CPU.ActiveCfg = Release|Any CPU {E1C1B35D-6336-413B-B7DE-25BC24AE401A}.Release|Any CPU.Build.0 = Release|Any CPU - {E05EFE74-1D74-44BF-8DE6-AA4B0DC4BC97}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E05EFE74-1D74-44BF-8DE6-AA4B0DC4BC97}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E05EFE74-1D74-44BF-8DE6-AA4B0DC4BC97}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E05EFE74-1D74-44BF-8DE6-AA4B0DC4BC97}.Release|Any CPU.Build.0 = Release|Any CPU {3D418431-3CD2-439A-AE5F-2BBC7FF977C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3D418431-3CD2-439A-AE5F-2BBC7FF977C6}.Debug|Any CPU.Build.0 = Debug|Any CPU {3D418431-3CD2-439A-AE5F-2BBC7FF977C6}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -629,10 +630,6 @@ Global {8AFB1C99-7E16-4912-B662-91C5328C6BAE}.Debug|Any CPU.Build.0 = Debug|Any CPU {8AFB1C99-7E16-4912-B662-91C5328C6BAE}.Release|Any CPU.ActiveCfg = Release|Any CPU {8AFB1C99-7E16-4912-B662-91C5328C6BAE}.Release|Any CPU.Build.0 = Release|Any CPU - {C94B9E69-3B48-4AAB-B3C3-80AA190640C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C94B9E69-3B48-4AAB-B3C3-80AA190640C2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C94B9E69-3B48-4AAB-B3C3-80AA190640C2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C94B9E69-3B48-4AAB-B3C3-80AA190640C2}.Release|Any CPU.Build.0 = Release|Any CPU {866798F5-FD69-49D2-B4FA-279DC7188A54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {866798F5-FD69-49D2-B4FA-279DC7188A54}.Debug|Any CPU.Build.0 = Debug|Any CPU {866798F5-FD69-49D2-B4FA-279DC7188A54}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -661,10 +658,6 @@ Global {BAEBA822-4EBF-4211-A92F-797D529C2DE3}.Debug|Any CPU.Build.0 = Debug|Any CPU {BAEBA822-4EBF-4211-A92F-797D529C2DE3}.Release|Any CPU.ActiveCfg = Release|Any CPU {BAEBA822-4EBF-4211-A92F-797D529C2DE3}.Release|Any CPU.Build.0 = Release|Any CPU - {1E65E919-C7D8-4678-8BFD-1434CC8293BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1E65E919-C7D8-4678-8BFD-1434CC8293BF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1E65E919-C7D8-4678-8BFD-1434CC8293BF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1E65E919-C7D8-4678-8BFD-1434CC8293BF}.Release|Any CPU.Build.0 = Release|Any CPU {7208999A-BC28-4476-BBFD-572C8D43E35E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7208999A-BC28-4476-BBFD-572C8D43E35E}.Debug|Any CPU.Build.0 = Debug|Any CPU {7208999A-BC28-4476-BBFD-572C8D43E35E}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -809,6 +802,54 @@ Global {449F3FDA-0AF3-4ACE-B584-A38037C803E3}.Debug|Any CPU.Build.0 = Debug|Any CPU {449F3FDA-0AF3-4ACE-B584-A38037C803E3}.Release|Any CPU.ActiveCfg = Release|Any CPU {449F3FDA-0AF3-4ACE-B584-A38037C803E3}.Release|Any CPU.Build.0 = Release|Any CPU + {A90C21AE-9552-E1EF-028A-309CB5F72843}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A90C21AE-9552-E1EF-028A-309CB5F72843}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A90C21AE-9552-E1EF-028A-309CB5F72843}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A90C21AE-9552-E1EF-028A-309CB5F72843}.Release|Any CPU.Build.0 = Release|Any CPU + {896858AE-13F4-473B-A5D5-B214CD04FDEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {896858AE-13F4-473B-A5D5-B214CD04FDEF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {896858AE-13F4-473B-A5D5-B214CD04FDEF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {896858AE-13F4-473B-A5D5-B214CD04FDEF}.Release|Any CPU.Build.0 = Release|Any CPU + {90FE56EA-94C2-820D-6E26-012E0BC1BDA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90FE56EA-94C2-820D-6E26-012E0BC1BDA9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90FE56EA-94C2-820D-6E26-012E0BC1BDA9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90FE56EA-94C2-820D-6E26-012E0BC1BDA9}.Release|Any CPU.Build.0 = Release|Any CPU + {9CC9DEE5-C96B-454E-8CBF-67A359055121}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9CC9DEE5-C96B-454E-8CBF-67A359055121}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9CC9DEE5-C96B-454E-8CBF-67A359055121}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9CC9DEE5-C96B-454E-8CBF-67A359055121}.Release|Any CPU.Build.0 = Release|Any CPU + {3B17545F-6B5B-C98E-9EA2-27E6FCFFE9AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3B17545F-6B5B-C98E-9EA2-27E6FCFFE9AF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3B17545F-6B5B-C98E-9EA2-27E6FCFFE9AF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3B17545F-6B5B-C98E-9EA2-27E6FCFFE9AF}.Release|Any CPU.Build.0 = Release|Any CPU + {D791EA02-3EC8-44A0-9877-614DE92BA9B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D791EA02-3EC8-44A0-9877-614DE92BA9B5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D791EA02-3EC8-44A0-9877-614DE92BA9B5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D791EA02-3EC8-44A0-9877-614DE92BA9B5}.Release|Any CPU.Build.0 = Release|Any CPU + {F646A400-15A5-9DFE-D66B-76BC09579811}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F646A400-15A5-9DFE-D66B-76BC09579811}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F646A400-15A5-9DFE-D66B-76BC09579811}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F646A400-15A5-9DFE-D66B-76BC09579811}.Release|Any CPU.Build.0 = Release|Any CPU + {E69CACF0-FE0E-01E3-0B31-0804EABFFF4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E69CACF0-FE0E-01E3-0B31-0804EABFFF4D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E69CACF0-FE0E-01E3-0B31-0804EABFFF4D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E69CACF0-FE0E-01E3-0B31-0804EABFFF4D}.Release|Any CPU.Build.0 = Release|Any CPU + {FCBCA3E2-22D3-4311-A1B7-AE029B961953}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FCBCA3E2-22D3-4311-A1B7-AE029B961953}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FCBCA3E2-22D3-4311-A1B7-AE029B961953}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FCBCA3E2-22D3-4311-A1B7-AE029B961953}.Release|Any CPU.Build.0 = Release|Any CPU + {F72A1BA0-541A-40CD-89EB-24C4F010DAC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F72A1BA0-541A-40CD-89EB-24C4F010DAC0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F72A1BA0-541A-40CD-89EB-24C4F010DAC0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F72A1BA0-541A-40CD-89EB-24C4F010DAC0}.Release|Any CPU.Build.0 = Release|Any CPU + {3F974BD3-A55A-4D13-AB83-C8FE94CE0041}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3F974BD3-A55A-4D13-AB83-C8FE94CE0041}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3F974BD3-A55A-4D13-AB83-C8FE94CE0041}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3F974BD3-A55A-4D13-AB83-C8FE94CE0041}.Release|Any CPU.Build.0 = Release|Any CPU + {58069766-C855-4B2D-B547-6C256E725215}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {58069766-C855-4B2D-B547-6C256E725215}.Debug|Any CPU.Build.0 = Debug|Any CPU + {58069766-C855-4B2D-B547-6C256E725215}.Release|Any CPU.ActiveCfg = Release|Any CPU + {58069766-C855-4B2D-B547-6C256E725215}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -880,16 +921,12 @@ Global {CC8FCD95-0DDF-4AEA-9F8C-3D3950317B27} = {DB89FCB6-E4F9-4BD4-93F3-4E857741D749} {92F5AD36-4100-47B6-8660-8938EF7705F3} = {97C928E3-54D6-4FAE-A314-775BE9E25A14} {01EB9E70-4A13-41E3-8609-8DFD9C2BC0A4} = {5B8D8451-E920-42E5-9F6A-D6D1238A24B4} - {50312210-D0AC-4E25-B819-BE2F1CC70581} = {FB46E926-0EEC-488C-B950-A16D2E7F694B} - {DF384D76-50A5-4D7B-A1D2-15CA00B844D4} = {50312210-D0AC-4E25-B819-BE2F1CC70581} {FADFD5E7-A5AD-49D9-A03F-70DB8E98AD38} = {EFB33E23-9E98-4B85-99E4-865705D5ACD2} {9F96589A-C7F8-4E33-BD6E-3BDA40205D07} = {EFB33E23-9E98-4B85-99E4-865705D5ACD2} {E7FE662C-24E4-4915-9847-26AB4038540D} = {5126C261-9A81-4FBC-8653-7710C1D1D735} {89C3B730-4024-4C76-8CB0-FAA8BF0945B9} = {5126C261-9A81-4FBC-8653-7710C1D1D735} {09ABC5EB-0896-4693-A057-A03B35E0294C} = {D01CF5F0-67D0-44E3-B7CE-0BAD4F475C0E} - {5F141D56-D6ED-45C7-A355-63EC641246DB} = {F8C07F29-7233-4FD3-A3DC-45963071E20A} {E1C1B35D-6336-413B-B7DE-25BC24AE401A} = {F8C07F29-7233-4FD3-A3DC-45963071E20A} - {E05EFE74-1D74-44BF-8DE6-AA4B0DC4BC97} = {F8C07F29-7233-4FD3-A3DC-45963071E20A} {01B7AC92-32D4-4864-B5CF-02752C391C9B} = {BF701694-E1CE-4FD1-97BA-DA832F6F3D45} {3D418431-3CD2-439A-AE5F-2BBC7FF977C6} = {01B7AC92-32D4-4864-B5CF-02752C391C9B} {2535B065-655A-4799-BF19-B57A8FABA0AD} = {8B9374B5-42D7-48C9-84C7-A560167F0D47} @@ -910,8 +947,6 @@ Global {3AEFF66C-2896-4D53-8066-4D2AFE6AE780} = {5836A018-B0F3-4D6F-8C45-FC1E9C85D51A} {0C74F825-F173-4ABC-AC1C-A0094FBBF4A6} = {969A695E-BEB5-45B8-93D8-B31640FA495D} {8AFB1C99-7E16-4912-B662-91C5328C6BAE} = {94F46729-90A8-415A-92E0-2D996D3210B4} - {995183D4-74FD-4EDD-8FDF-8B6BC0410A06} = {63EE2247-0A5B-47F9-B8AA-AED5BC8FC873} - {C94B9E69-3B48-4AAB-B3C3-80AA190640C2} = {995183D4-74FD-4EDD-8FDF-8B6BC0410A06} {866798F5-FD69-49D2-B4FA-279DC7188A54} = {0E116F60-01DE-4648-B4EE-5B944EED42AA} {EBB7E090-7309-49D6-A0C9-CD704E7FEA31} = {8911AC2E-B951-436C-A76D-7E994017069B} {3043062B-A48A-419F-8F78-9ED234383103} = {0F95EA5A-0E08-4516-89D4-C3BFC88789F9} @@ -919,7 +954,6 @@ Global {74C98BF5-D663-4CCA-B970-D9A1258B6BB9} = {0F95EA5A-0E08-4516-89D4-C3BFC88789F9} {12A8A263-EF41-4B20-97A8-3D050FF1B326} = {EFB33E23-9E98-4B85-99E4-865705D5ACD2} {BAEBA822-4EBF-4211-A92F-797D529C2DE3} = {D01CF5F0-67D0-44E3-B7CE-0BAD4F475C0E} - {1E65E919-C7D8-4678-8BFD-1434CC8293BF} = {FB46E926-0EEC-488C-B950-A16D2E7F694B} {7208999A-BC28-4476-BBFD-572C8D43E35E} = {5C17B127-95DD-480C-BDCC-7FDF5E645B0C} {1B6092F9-4776-4502-818F-B35D153D19D6} = {BF701694-E1CE-4FD1-97BA-DA832F6F3D45} {5AB49F06-014A-4CAF-9A92-F5B3783E51C6} = {1B6092F9-4776-4502-818F-B35D153D19D6} @@ -961,6 +995,18 @@ Global {8849C272-4073-1AF3-0EF1-8E7517A13F38} = {5836A018-B0F3-4D6F-8C45-FC1E9C85D51A} {BE9B0F56-849A-4BCE-A54E-6CEE7B52AB12} = {CDED01F8-D581-2FF7-E03E-F4B04DDFAC03} {449F3FDA-0AF3-4ACE-B584-A38037C803E3} = {CDED01F8-D581-2FF7-E03E-F4B04DDFAC03} + {A90C21AE-9552-E1EF-028A-309CB5F72843} = {F8C07F29-7233-4FD3-A3DC-45963071E20A} + {896858AE-13F4-473B-A5D5-B214CD04FDEF} = {A228384F-7DF9-4144-B84E-AC8BF2F69D1B} + {90FE56EA-94C2-820D-6E26-012E0BC1BDA9} = {432C7F0A-B01D-4113-8DF3-C588B147A4C6} + {9CC9DEE5-C96B-454E-8CBF-67A359055121} = {432C7F0A-B01D-4113-8DF3-C588B147A4C6} + {3B17545F-6B5B-C98E-9EA2-27E6FCFFE9AF} = {F8C07F29-7233-4FD3-A3DC-45963071E20A} + {D791EA02-3EC8-44A0-9877-614DE92BA9B5} = {57B547B6-D725-4C3E-82A7-39ED03BD438B} + {F646A400-15A5-9DFE-D66B-76BC09579811} = {A228384F-7DF9-4144-B84E-AC8BF2F69D1B} + {E69CACF0-FE0E-01E3-0B31-0804EABFFF4D} = {A228384F-7DF9-4144-B84E-AC8BF2F69D1B} + {FCBCA3E2-22D3-4311-A1B7-AE029B961953} = {A228384F-7DF9-4144-B84E-AC8BF2F69D1B} + {F72A1BA0-541A-40CD-89EB-24C4F010DAC0} = {D01CF5F0-67D0-44E3-B7CE-0BAD4F475C0E} + {3F974BD3-A55A-4D13-AB83-C8FE94CE0041} = {D01CF5F0-67D0-44E3-B7CE-0BAD4F475C0E} + {58069766-C855-4B2D-B547-6C256E725215} = {D01CF5F0-67D0-44E3-B7CE-0BAD4F475C0E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {DB787235-A13A-4A3D-B5A8-5DFEB6511EEE} diff --git a/examples/Hosting/HostingWorkerService/HostingWorkerService.csproj b/examples/Hosting/HostingWorkerService/HostingWorkerService.csproj index 17c8bff0d..605dcee96 100644 --- a/examples/Hosting/HostingWorkerService/HostingWorkerService.csproj +++ b/examples/Hosting/HostingWorkerService/HostingWorkerService.csproj @@ -8,8 +8,8 @@ - - - + + + diff --git a/examples/Hosting/HostingWorkerService/Program.cs b/examples/Hosting/HostingWorkerService/Program.cs index 2ea8c8251..27b74e3db 100644 --- a/examples/Hosting/HostingWorkerService/Program.cs +++ b/examples/Hosting/HostingWorkerService/Program.cs @@ -10,6 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ +using TouchSocket.Core; using TouchSocket.NamedPipe; using TouchSocket.Sockets; @@ -23,14 +24,13 @@ public class Program builder.ConfigureServices(services => { - //����TcpService�� + services.AddTcpService(config => { config.SetListenIPHosts(7789); }); - //����TcpClient - //ע�⣬Client��ķ�������������StartAsyncʱ������ִ��ConnectAsync��������Ҫ�������ӣ�������������ֵ������ + services.AddSingletonTcpClient(config => { config.SetRemoteIPHost("127.0.0.1:7789"); @@ -41,7 +41,7 @@ public class Program config.SetListenIPHosts(7790); }); - //���������ܵ����� + services.AddServiceHostedService(config => { config.SetPipeName("pipe7789"); diff --git a/examples/Http/HttpClientConsoleApp/HttpClientConsoleApp.csproj b/examples/Http/HttpClientConsoleApp/HttpClientConsoleApp.csproj index 778dcbb2a..4149e5504 100644 --- a/examples/Http/HttpClientConsoleApp/HttpClientConsoleApp.csproj +++ b/examples/Http/HttpClientConsoleApp/HttpClientConsoleApp.csproj @@ -1,4 +1,4 @@ - + Exe @@ -7,7 +7,7 @@ - + - + diff --git a/examples/Http/HttpServiceConsoleApp/Program.cs b/examples/Http/HttpServiceConsoleApp/Program.cs index b6399034d..c6ac55839 100644 --- a/examples/Http/HttpServiceConsoleApp/Program.cs +++ b/examples/Http/HttpServiceConsoleApp/Program.cs @@ -10,9 +10,12 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ +using Newtonsoft.Json; using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.IO; +using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -32,8 +35,9 @@ internal class Program //最后客户端需要先安装证书。 var service = new HttpService(); - await service.SetupAsync(new TouchSocketConfig()//加载配置 - .SetListenIPHosts(7789) + + var config = new TouchSocketConfig(); + config.SetListenIPHosts(7789) .ConfigureContainer(a => { a.AddConsoleLogger(); @@ -51,23 +55,16 @@ internal class Program a.Add(); a.Add(); - a.UseHttpStaticPage() - .SetNavigateAction(request => - { - //此处可以设置重定向 - return request.RelativeURL; - }) - .SetResponseAction(response => - { - //可以设置响应头 - }) - .AddFolder("api/");//添加静态页面文件夹 - //default插件应该最后添加,其作用是 //1、为找不到的路由返回404 //2、处理 header 为Option的探视跨域请求。 a.UseDefaultHttpServicePlugin(); - })); + }); + + ConfigureStaticPage(config); + + await service.SetupAsync(new TouchSocketConfig()//加载配置 + ); await service.StartAsync(); Console.WriteLine("Http服务器已启动"); @@ -78,6 +75,92 @@ internal class Program Console.WriteLine("Post访问 http://127.0.0.1:7789/uploadfile 上传文件"); Console.ReadKey(); } + + private static void ConfigureStaticPage(TouchSocketConfig config) + { + #region Http服务器启用静态页面插件 + config.ConfigurePlugins(a => + { + a.UseHttpStaticPage(options => + { + //添加静态页面文件夹 + options.AddFolder("api/"); + + #region 静态页面请求资源定向 + options.SetNavigateAction(request => + { + //此处可以设置重定向 + return request.RelativeURL; + }); + #endregion + + #region 静态页面响应设置 + options.SetResponseAction(response => + { + //可以设置响应头 + }); + #endregion + + #region 静态页面配置ContentType + options.SetContentTypeProvider(provider => + { + provider.Add(".txt", "text/plain"); + }); + #endregion + + }); + }); + #endregion + } + + private static async Task CreateHttpService() + { + #region 创建Http服务器 {10} + var service = new HttpService(); + await service.SetupAsync(new TouchSocketConfig()//加载配置 + .SetListenIPHosts(7789) + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .ConfigurePlugins(a => + { + a.Add(); + //default插件应该最后添加,其作用是 + //1、为找不到的路由返回404 + //2、处理 header 为Option的探视跨域请求。 + a.UseDefaultHttpServicePlugin(); + })); + await service.StartAsync(); + #endregion + } + + private static async Task CreateHttpService1() + { + #region 创建Ssl的Http服务器 {4-8} + var service = new HttpService(); + await service.SetupAsync(new TouchSocketConfig()//加载配置 + .SetListenIPHosts(7789) + .SetServiceSslOption(options => + { + options.Certificate = new X509Certificate2("TouchSocketTestCert.pfx", "123456"); + options.SslProtocols = System.Security.Authentication.SslProtocols.Tls12; + }) + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .ConfigurePlugins(a => + { + a.Add(); + //default插件应该最后添加,其作用是 + //1、为找不到的路由返回404 + //2、处理 header 为Option的探视跨域请求。 + a.UseDefaultHttpServicePlugin(); + })); + await service.StartAsync(); + #endregion + } } public class MyBigWriteHttpPlug : PluginBase, IHttpPlugin @@ -137,7 +220,7 @@ public class MyUploadBigFileHttpPlugin : PluginBase, IHttpPlugin try { var fileName = e.Context.Request.Headers["FileName"]; - if (fileName.IsNullOrEmpty()) + if (fileName.IsEmpty) { await e.Context.Response .SetStatus(502, "fileName is null") @@ -180,32 +263,40 @@ public class MyHttpPlugin4 : PluginBase, IHttpPlugin var content = await e.Context.Request.GetContentAsync(); //情况2,当数据太大时,可持续读取 + #region Http服务器持续读取Body内容 while (true) { var buffer = new byte[1024 * 64]; using (var blockResult = await e.Context.Request.ReadAsync()) { + var memory = blockResult.Memory;//本次读取的数据 + + //把本次读取的数据写入缓冲区,或者直接处理 + memory.CopyTo(buffer); + if (blockResult.IsCompleted) { + //读取完毕 break; } - - //这里可以一直处理读到的数据。 - blockResult.Memory.CopyTo(buffer); } } + #endregion //情况3,或者把数据读到流 + #region Http服务器获取Body持续写入Stream中 using (var stream = new MemoryStream()) { // await e.Context.Request.ReadCopyToAsync(stream); } + #endregion + //情况4,接收小文件。 - + #region Http服务器获取Body小文件集合 if (e.Context.Request.ContentLength > 1024 * 1024 * 100)//全部数据体超过100Mb则直接拒绝接收。 { await e.Context.Response @@ -229,12 +320,14 @@ public class MyHttpPlugin4 : PluginBase, IHttpPlugin var data = file.Data; //开始保存数据到磁盘 - using (var fileStream = File.OpenWrite(file.Name)) + using (var fileStream = File.Create(file.Name)) { await fileStream.WriteAsync(data); await fileStream.FlushAsync(); } } + #endregion + await e.Context.Response .SetStatusWithSuccess() @@ -255,6 +348,7 @@ public class MyHttpPlug3 : PluginBase, IHttpPlugin { public async Task OnHttpRequest(IHttpSessionClient client, HttpContextEventArgs e) { + #region Http服务器响应页面请求 if (e.Context.Request.IsGet()) { if (e.Context.Request.UrlEquals("/html")) @@ -284,11 +378,12 @@ public class MyHttpPlug3 : PluginBase, IHttpPlugin return; } } - + #endregion await e.InvokeNext(); } } +#region Http服务器响应文件 public class MyHttpPlug2 : PluginBase, IHttpPlugin { public async Task OnHttpRequest(IHttpSessionClient client, HttpContextEventArgs e) @@ -299,14 +394,45 @@ public class MyHttpPlug2 : PluginBase, IHttpPlugin { try { + //假设这是一个很大的文件 + var fileInfo = new FileInfo(@"D:\System\Windows.iso"); + + //可以重新命名下载的文件名,不设置则为本地文件名 + string fileName = null; + + //是否自动启用gzip压缩,前提是HttpRequest请求了gzip + var autoGzip = true; + + //流式传输操作,可以控制流速、获取当前流速、进度等 + var httpFlowOperator = new HttpFlowOperator(); + + //流速控制,当前设置为最大512KB/秒 + httpFlowOperator.MaxSpeed = 1024 * 512; + + //每次读取文件长度,数值越大,效率越高,但对内存要求也越高 + httpFlowOperator.BlockSize = 1024 * 8; + + //可以设置可取消令箭操作 + using var cts = new CancellationTokenSource(); + httpFlowOperator.Token = cts.Token; + + using var timer = new Timer(state => + { + //每秒打印当前流速、进度等信息 + Console.WriteLine($"当前流速:{httpFlowOperator.Speed() / 1024.0}KB/s,进度:{httpFlowOperator.Progress}"); + }, null, 1000, 1000); + //直接回应文件。 await e.Context.Response .SetStatusWithSuccess()//必须要有状态 - .FromFileAsync(new FileInfo(@"D:\System\Windows.iso"), e.Context.Request); + .FromFileAsync(fileInfo, httpFlowOperator, e.Context.Request, fileName, autoGzip); } catch (Exception ex) { - await e.Context.Response.SetStatus(403, ex.Message).FromText(ex.Message).AnswerAsync(); + await e.Context.Response + .SetStatus(403, ex.Message) + .FromText(ex.Message) + .AnswerAsync(); } return; @@ -315,13 +441,18 @@ public class MyHttpPlug2 : PluginBase, IHttpPlugin await e.InvokeNext(); } } +#endregion + +#region Http服务器使用插件 public class MyHttpPlug1 : PluginBase, IHttpPlugin { public async Task OnHttpRequest(IHttpSessionClient client, HttpContextEventArgs e) { + #region HttpContext生命周期 var request = e.Context.Request;//http请求体 var response = e.Context.Response;//http响应体 + #endregion if (request.IsGet() && request.UrlEquals("/success")) { @@ -339,7 +470,126 @@ public class MyHttpPlug1 : PluginBase, IHttpPlugin await e.InvokeNext(); } } +#endregion +public class MyHttpPlug11 : PluginBase, IHttpPlugin +{ + public async Task OnHttpRequest(IHttpSessionClient client, HttpContextEventArgs e) + { + var request = e.Context.Request;//http请求体 + var response = e.Context.Response;//http响应体 + + #region Http服务器获取Query参数 + var name = e.Context.Request.Query.Get("name"); + //或者 + var name2 = e.Context.Request.Query["name"]; + //或者 + if (e.Context.Request.Query.TryGetValue("name", out var nameValue)) + { + //获取成功 + } + #endregion + + #region Http服务器获取Header参数 + var nameHeader1 = e.Context.Request.Headers.Get("name"); + //或者 + var name2Header1 = e.Context.Request.Headers["name"]; + //或者 + if (e.Context.Request.Headers.TryGetValue("name", out var nameValueHeader1)) + { + //获取成功 + } + #endregion + + #region Http服务器从预设获取Header参数 + var nameHeader2 = e.Context.Request.Headers.Get(HttpHeaders.Authorization); + //或者 + var name2Header2 = e.Context.Request.Headers[HttpHeaders.Authorization]; + //或者 + if (e.Context.Request.Headers.TryGetValue(HttpHeaders.Authorization, out var nameValueHeader2)) + { + //获取成功 + } + #endregion + + #region Http服务器获取字符串Body内容 + var bodyString = await e.Context.Request.GetBodyAsync(); + #endregion + + #region Http服务器获取内存Body内容 + var content = await e.Context.Request.GetContentAsync(); + #endregion + + if (request.IsGet() && request.UrlEquals("/success")) + { + #region Http服务器设置响应状态 + e.Context.Response.SetStatus(200, "success"); + + //或者 + e.Context.Response.SetStatusWithSuccess(); + #endregion + + #region Http服务器设置响应Header + e.Context.Response.Headers.Add("1", "1"); + + //或者 + e.Context.Response.Headers["1"] = "1"; + + //或者 + e.Context.Response.Headers.TryAdd("1", "1"); + + //或者 + e.Context.Response.AddHeader("1", "1"); + + //或者 + e.Context.Response.AddHeader(HttpHeaders.Authorization, "Authorization"); + #endregion + + #region Http服务器设置响应内容 + e.Context.Response.SetContent("Hello World"); + + //或者 + e.Context.Response.FromText("Hello World"); + + //或者 + e.Context.Response.SetContent(Encoding.UTF8.GetBytes("Hello World")); + #endregion + + #region Http服务器设置响应Json内容 + var obj = new + { + Name = "TouchSocket", + Author = "若汝棋茗", + Blog = "https://blog.csdn.net/qq_40374647" + }; + e.Context.Response.FromJson(JsonConvert.SerializeObject(obj)); + #endregion + + #region Http服务器设置响应Xml内容 + var xml = @" + + + TouchSocket + 若汝棋茗 + https://blog.csdn.net/qq_40374647 +"; + e.Context.Response.FromXml(xml); + #endregion + + #region Http服务器执行响应 + //直接响应文字 + await e.Context.Response.AnswerAsync(); + #endregion + + Console.WriteLine("处理/success"); + return; + } + + + //无法处理,调用下一个插件 + await e.InvokeNext(); + } +} public class TestFormPlugin : PluginBase, IHttpPlugin { public async Task OnHttpRequest(IHttpSessionClient client, HttpContextEventArgs e) @@ -348,15 +598,21 @@ public class TestFormPlugin : PluginBase, IHttpPlugin { if (e.Context.Request.UrlEquals("/form")) { + #region Http服务器获取Form参数 var formCollection = await e.Context.Request.GetFormCollectionAsync(); foreach (var item in formCollection) { Console.WriteLine($"{item.Key}={item.Value}"); } + #endregion + + #region Http服务器链式响应 await e.Context.Response .SetStatusWithSuccess() .FromText("Ok") .AnswerAsync(); + #endregion + } } await e.InvokeNext(); @@ -365,11 +621,11 @@ public class TestFormPlugin : PluginBase, IHttpPlugin public class MyCustomDownloadHttpPlug : PluginBase, IHttpPlugin { - private readonly ILog logger; + private readonly ILog m_logger; public MyCustomDownloadHttpPlug(ILog logger) { - this.logger = logger; + this.m_logger = logger; } public async Task OnHttpRequest(IHttpSessionClient client, HttpContextEventArgs e) { @@ -415,15 +671,15 @@ public class MyCustomDownloadHttpPlug : PluginBase, IHttpPlugin break; } - await response.WriteAsync(buffer.Slice(0, readLen)); + await response.WriteAsync(buffer[..readLen]); } } - this.logger.Info("Success"); + this.m_logger.Info("Success"); } catch (Exception ex) { - this.logger.Exception(ex); + this.m_logger.Exception(ex); //await response.SetStatus(403, ex.Message) // .AnswerAsync(); } @@ -431,13 +687,13 @@ public class MyCustomDownloadHttpPlug : PluginBase, IHttpPlugin } } -class MyDelayResponsePlugin : PluginBase, IHttpPlugin +internal class MyDelayResponsePlugin : PluginBase, IHttpPlugin { - private ConcurrentQueue> m_queue = new(); + private readonly ConcurrentQueue> m_queue = new(); public MyDelayResponsePlugin() { - Task.Factory.StartNew(HandleQueue, TaskCreationOptions.LongRunning); + Task.Factory.StartNew(this.HandleQueue, TaskCreationOptions.LongRunning); } private async Task HandleQueue() @@ -448,7 +704,7 @@ class MyDelayResponsePlugin : PluginBase, IHttpPlugin await Task.Delay(3000); //处理队列 - while (m_queue.TryDequeue(out var tcs)) + while (this.m_queue.TryDequeue(out var tcs)) { //返回结果 tcs.SetResult(Guid.NewGuid().ToString()); @@ -463,7 +719,7 @@ class MyDelayResponsePlugin : PluginBase, IHttpPlugin if (request.UrlEquals("/delay")) { var tcs = new TaskCompletionSource(); - m_queue.Enqueue(tcs); + this.m_queue.Enqueue(tcs); var result = await tcs.Task; await response @@ -475,13 +731,13 @@ class MyDelayResponsePlugin : PluginBase, IHttpPlugin } } -class MyDelayResponsePlugin2 : PluginBase, IHttpPlugin +internal class MyDelayResponsePlugin2 : PluginBase, IHttpPlugin { - ConcurrentQueue m_queue = new(); + private readonly ConcurrentQueue m_queue = new(); public MyDelayResponsePlugin2() { - Task.Factory.StartNew(HandleQueue, TaskCreationOptions.LongRunning); + Task.Factory.StartNew(this.HandleQueue, TaskCreationOptions.LongRunning); } private async Task HandleQueue() @@ -492,7 +748,7 @@ class MyDelayResponsePlugin2 : PluginBase, IHttpPlugin await Task.Delay(3000); //处理队列 - while (m_queue.TryDequeue(out var tcs)) + while (this.m_queue.TryDequeue(out var tcs)) { //返回结果 @@ -502,9 +758,9 @@ class MyDelayResponsePlugin2 : PluginBase, IHttpPlugin response.SetStatus(200, "success"); response.IsChunk = true; - for (int i = 0; i < 5; i++) + for (var i = 0; i < 5; i++) { - await response.WriteAsync(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString()+"\r\n")); + await response.WriteAsync(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString() + "\r\n")); await Task.Delay(1000); } @@ -522,14 +778,14 @@ class MyDelayResponsePlugin2 : PluginBase, IHttpPlugin if (request.UrlEquals("/delay2")) { var tcs = new MyTaskCompletionSource(client, e.Context); - m_queue.Enqueue(tcs); + this.m_queue.Enqueue(tcs); var result = await tcs.Task; //不做任何处理 } await e.InvokeNext(); } - class MyTaskCompletionSource : TaskCompletionSource + private class MyTaskCompletionSource : TaskCompletionSource { public MyTaskCompletionSource(IHttpSessionClient client, HttpContext context) { @@ -540,4 +796,59 @@ class MyDelayResponsePlugin2 : PluginBase, IHttpPlugin public IHttpSessionClient Client { get; } public HttpContext Context { get; } } -} \ No newline at end of file +} + +#region Http服务器响应有长度大数据 {10,13,18} +internal class MyHttpPlugin22 : PluginBase, IHttpPlugin +{ + public async Task OnHttpRequest(IHttpSessionClient client, HttpContextEventArgs e) + { + var request = e.Context.Request;//http请求体 + var response = e.Context.Response;//http响应体 + if (e.Context.Request.IsGet() && e.Context.Request.UrlEquals("/data1")) + { + //1.先设置需要响应的相关配置 + response.SetStatusWithSuccess(); + + //2.然后设置数据总长度 + response.ContentLength = 1024 * 1024; + + for (var i = 0; i < 1024; i++) + { + //3.将数据持续写入 + await response.WriteAsync(new byte[1024]); + } + } + await e.InvokeNext(); + } +} +#endregion + +#region Http服务器响应无长度大数据 {10,13,18,22} +internal class MyHttpPlugin33 : PluginBase, IHttpPlugin +{ + public async Task OnHttpRequest(IHttpSessionClient client, HttpContextEventArgs e) + { + var request = e.Context.Request;//http请求体 + var response = e.Context.Response;//http响应体 + if (e.Context.Request.IsGet() && e.Context.Request.UrlEquals("/data2")) + { + //1.先设置需要响应的相关配置 + response.SetStatusWithSuccess(); + + //2.设置使用Chunk模式 + response.IsChunk = true; + + for (var i = 0; i < 1024; i++) + { + //3.将数据持续写入 + await response.WriteAsync(new byte[1024]); + } + + //4.在正式数据传输完成后,调用此方法,客户端才知道数据结束了 + await response.CompleteChunkAsync(); + } + await e.InvokeNext(); + } +} +#endregion diff --git a/examples/Http/HttpServiceForCorsConsoleApp/HttpServiceForCorsConsoleApp.csproj b/examples/Http/HttpServiceForCorsConsoleApp/HttpServiceForCorsConsoleApp.csproj index 1099a07d7..eb2eeb8d7 100644 --- a/examples/Http/HttpServiceForCorsConsoleApp/HttpServiceForCorsConsoleApp.csproj +++ b/examples/Http/HttpServiceForCorsConsoleApp/HttpServiceForCorsConsoleApp.csproj @@ -8,7 +8,7 @@ - + - - - - - - \ No newline at end of file diff --git a/examples/Udp/UdpBroadcastConsoleApp/Program.cs b/examples/Udp/UdpBroadcastConsoleApp/Program.cs index b639ea597..9b0c25c62 100644 --- a/examples/Udp/UdpBroadcastConsoleApp/Program.cs +++ b/examples/Udp/UdpBroadcastConsoleApp/Program.cs @@ -12,7 +12,6 @@ using System.Net; using System.Text; -using System.Threading.Tasks; using TouchSocket.Core; using TouchSocket.Sockets; @@ -26,14 +25,14 @@ internal class Program var udpService = new UdpSession(); await udpService.SetupAsync(new TouchSocketConfig() .SetBindIPHost(new IPHost(7789)) - .UseBroadcast() + .SetEnableBroadcast(true) .ConfigurePlugins(a => { a.Add(); a.Add(); a.Add(); }) - .SetUdpDataHandlingAdapter(() => new NormalUdpDataHandlingAdapter())); + ); await udpService.StartAsync(); //加入组播组 @@ -43,8 +42,8 @@ internal class Program await udpClient.SetupAsync(new TouchSocketConfig() //.UseUdpReceive()//作为客户端时,如果需要接收数据,那么需要绑定端口。要么使用SetBindIPHost指定端口,要么调用UseUdpReceive绑定随机端口。 .SetBindIPHost(new IPHost(7788)) - .UseBroadcast()//该配置在广播时是必须的 - .SetUdpDataHandlingAdapter(() => new NormalUdpDataHandlingAdapter())); + .SetEnableBroadcast(true)//该配置在广播时是必须的 + ); await udpClient.StartAsync(); while (true) @@ -64,7 +63,7 @@ internal class Program { public async Task OnUdpReceived(IUdpSessionBase client, UdpReceivedDataEventArgs e) { - var msg = e.ByteBlock.ToString(); + var msg = e.Memory.Span.ToUtf8String(); if (msg == "hello") { Console.WriteLine("已处理Hello"); @@ -81,7 +80,7 @@ internal class Program { public async Task OnUdpReceived(IUdpSessionBase client, UdpReceivedDataEventArgs e) { - var msg = e.ByteBlock.ToString(); + var msg = e.Memory.Span.ToUtf8String(); if (msg == "hi") { Console.WriteLine("已处理Hi"); @@ -98,7 +97,7 @@ internal class Program { public async Task OnUdpReceived(IUdpSessionBase client, UdpReceivedDataEventArgs e) { - var msg = e.ByteBlock.ToString(); + var msg = e.Memory.Span.ToUtf8String(); Console.WriteLine(msg); await Task.CompletedTask; } diff --git a/examples/Udp/UdpBroadcastConsoleApp/UdpBroadcastConsoleApp.csproj b/examples/Udp/UdpBroadcastConsoleApp/UdpBroadcastConsoleApp.csproj index f18b62f22..6608859ea 100644 --- a/examples/Udp/UdpBroadcastConsoleApp/UdpBroadcastConsoleApp.csproj +++ b/examples/Udp/UdpBroadcastConsoleApp/UdpBroadcastConsoleApp.csproj @@ -8,12 +8,12 @@ - - - - - - + + + + + + diff --git a/examples/Udp/UdpDemoApp/Form1.cs b/examples/Udp/UdpDemoApp/Form1.cs index 2c84cf7f9..6b8a718a6 100644 --- a/examples/Udp/UdpDemoApp/Form1.cs +++ b/examples/Udp/UdpDemoApp/Form1.cs @@ -12,7 +12,6 @@ using System; using System.Text; -using System.Threading.Tasks; using System.Windows.Forms; using TouchSocket.Core; using TouchSocket.Sockets; @@ -33,14 +32,14 @@ public partial class Form1 : Form { this.m_udpSession.Received = async (remote, e) => { - if (e.ByteBlock.Length > 1024) + if (e.Memory.Length > 1024) { - this.m_udpSession.Logger.Info($"收到:{e.ByteBlock.Length}长度的数据。"); + this.m_udpSession.Logger.Info($"收到:{e.Memory.Length}长度的数据。"); await this.m_udpSession.SendAsync("收到"); } else { - this.m_udpSession.Logger.Info($"收到:{e.ByteBlock.Span.ToString(Encoding.UTF8)}"); + this.m_udpSession.Logger.Info($"收到:{e.Memory.Span.ToString(Encoding.UTF8)}"); } var endPoint = e.EndPoint; }; @@ -48,7 +47,7 @@ public partial class Form1 : Form await this.m_udpSession.SetupAsync(new TouchSocketConfig() .SetBindIPHost(new IPHost(this.textBox2.Text)) .SetRemoteIPHost(new IPHost(this.textBox3.Text)) - .UseBroadcast() + .SetEnableBroadcast(true) .SetUdpDataHandlingAdapter(() => { if (this.checkBox1.Checked) @@ -57,7 +56,7 @@ public partial class Form1 : Form } else { - return new NormalUdpDataHandlingAdapter(); + return default; } }) .ConfigureContainer(a => @@ -105,8 +104,8 @@ public partial class Form1 : Form }); //然后使用SendThenReturn。 - var returnData = await waitClient.SendThenReturnAsync(Encoding.UTF8.GetBytes("RRQM")); - this.ShowMsg($"收到回应消息:{Encoding.UTF8.GetString(returnData)}"); + using var returnData = await waitClient.SendThenResponseAsync(Encoding.UTF8.GetBytes("RRQM")); + this.ShowMsg($"收到回应消息:{Encoding.UTF8.GetString(returnData.Memory.Span)}"); ////同时,如果适配器收到数据后,返回的并不是字节,而是IRequestInfo对象时,可以使用SendThenResponse. //ResponsedData responsedData = waitClient.SendThenResponse(Encoding.UTF8.GetBytes("RRQM")); diff --git a/examples/Udp/UdpDemoApp/UdpDemoApp.csproj b/examples/Udp/UdpDemoApp/UdpDemoApp.csproj index 2e5f4e936..f2f2eead4 100644 --- a/examples/Udp/UdpDemoApp/UdpDemoApp.csproj +++ b/examples/Udp/UdpDemoApp/UdpDemoApp.csproj @@ -1,4 +1,4 @@ - + WinExe @@ -7,11 +7,11 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/examples/Udp/UdpDemoApp/UdpSessionDocExamples.cs b/examples/Udp/UdpDemoApp/UdpSessionDocExamples.cs new file mode 100644 index 000000000..593b14d0c --- /dev/null +++ b/examples/Udp/UdpDemoApp/UdpSessionDocExamples.cs @@ -0,0 +1,233 @@ +//------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +//------------------------------------------------------------------------------ + +using System; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using TouchSocket.Core; +using TouchSocket.Sockets; + +namespace UdpDemoApp; + +#region UdpSession作为服务器使用 +internal class UdpSessionServerExample +{ + public async Task ServerExample() + { + var udpService = new UdpSession(); + udpService.Received = (c, e) => + { + Console.WriteLine(e.Memory.ToString()); + return EasyTask.CompletedTask; + }; + await udpService.SetupAsync(new TouchSocketConfig() + .SetBindIPHost(new IPHost(7789))); + await udpService.StartAsync(); + Console.WriteLine("等待接收"); + } +} +#endregion + +#region UdpSession作为客户端使用 +internal class UdpSessionClientExample +{ + public async Task ClientExample() + { + var udpClient = new UdpSession(); + await udpClient.SetupAsync(new TouchSocketConfig() + //.UseUdpReceive()//作为客户端时,如果需要接收数据,那么需要绑定端口。要么使用SetBindIPHost指定端口,要么调用UseUdpReceive绑定随机端口。 + .SetBindIPHost(new IPHost(7788))); + await udpClient.StartAsync(); + } +} +#endregion + +#region UdpSession发送到接收数据的地址 +internal class MyPluginClass : PluginBase, IUdpReceivedPlugin +{ + public async Task OnUdpReceived(IUdpSessionBase client, UdpReceivedDataEventArgs e) + { + if (client is IUdpSession udpSession) + { + await udpSession.SendAsync(e.EndPoint, Encoding.UTF8.GetBytes("RRQM")); + } + await e.InvokeNext(); + } +} +#endregion + +#region UdpSession委托接收 +internal class UdpSessionDelegateReceiveExample +{ + public async Task DelegateReceiveExample() + { + var udpService = new UdpSession(); + udpService.Received = (c, e) => + { + Console.WriteLine(e.Memory.ToString()); + return EasyTask.CompletedTask; + }; + await udpService.StartAsync(7789); + } +} +#endregion + +#region UdpSession插件接收声明插件1 +internal class MyPluginClass1 : PluginBase, IUdpReceivedPlugin +{ + public async Task OnUdpReceived(IUdpSessionBase client, UdpReceivedDataEventArgs e) + { + var msg = e.Memory.ToString(); + if (msg == "hello") + { + Console.WriteLine("已处理Hello"); + } + else + { + //如果判断逻辑发现此处无法处理,即可转到下一个插件 + await e.InvokeNext(); + } + } +} +#endregion + +#region UdpSession插件接收声明插件2 +internal class MyPluginClass2 : PluginBase, IUdpReceivedPlugin +{ + public async Task OnUdpReceived(IUdpSessionBase client, UdpReceivedDataEventArgs e) + { + var msg = e.Memory.ToString(); + if (msg == "hi") + { + Console.WriteLine("已处理Hi"); + } + else + { + //如果判断逻辑发现此处无法处理,即可转到下一个插件 + await e.InvokeNext(); + } + } +} +#endregion + +#region UdpSession插件接收使用插件 +internal class UdpSessionPluginReceiveExample +{ + public async Task PluginReceiveExample() + { + var udpService = new UdpSession(); + await udpService.SetupAsync(new TouchSocketConfig() + .SetBindIPHost(new IPHost(7789)) + .ConfigurePlugins(a => + { + a.Add(); + a.Add(); + })); + await udpService.StartAsync(); + } +} +#endregion + +#region UdpSession创建组播服务器 +internal class UdpSessionMulticastServerExample +{ + public async Task MulticastServerExample() + { + //创建udpService + var udpService = new UdpSession(); + udpService.Received = (remote, e) => + { + Console.WriteLine(e.Memory.ToString()); + return EasyTask.CompletedTask; + }; + await udpService.SetupAsync(new TouchSocketConfig() + .SetBindIPHost(new IPHost(7789)) + .SetEnableBroadcast(true)); + await udpService.StartAsync(); + + //加入组播组 + udpService.JoinMulticastGroup(IPAddress.Parse("224.5.6.7")); + } +} +#endregion + +#region UdpSession发送组播数据 +internal class UdpSessionMulticastSendExample +{ + public async Task MulticastSendExample() + { + var udpClient = new UdpSession(); + await udpClient.SetupAsync(new TouchSocketConfig() + .SetBindIPHost(new IPHost(7788))); + await udpClient.StartAsync(); + + await udpClient.SendAsync(new IPEndPoint(IPAddress.Parse("224.5.6.7"), 7789), Encoding.UTF8.GetBytes("我是组播")); + } +} +#endregion + +#region UdpSession创建广播服务器 +internal class UdpSessionBroadcastServerExample +{ + public async Task BroadcastServerExample() + { + //创建udpService + var udpService = new UdpSession(); + udpService.Received = (remote, e) => + { + Console.WriteLine(e.Memory.ToString()); + return EasyTask.CompletedTask; + }; + await udpService.SetupAsync(new TouchSocketConfig() + .SetBindIPHost(new IPHost(7789)) + .SetEnableBroadcast(true)); + await udpService.StartAsync(); + } +} +#endregion + +#region UdpSession发送广播数据 +internal class UdpSessionBroadcastSendExample +{ + public async Task BroadcastSendExample() + { + var udpClient = new UdpSession(); + await udpClient.SetupAsync(new TouchSocketConfig() + .SetEnableBroadcast(true)//该配置在发送广播时是必须的 + .SetBindIPHost(new IPHost(7788))); + await udpClient.StartAsync(); + + await udpClient.SendAsync(new IPEndPoint(IPAddress.Parse("255.255.255.255"), 7789), Encoding.UTF8.GetBytes("我是广播")); + } +} +#endregion + +#region UdpSession传输大于64K的数据 +internal class UdpSessionBigDataExample +{ + public async Task BigDataExample() + { + var udpSession = new UdpSession(); + udpSession.Received = (endpoint, e) => + { + // 处理接收到的数据 + return EasyTask.CompletedTask; + }; + + await udpSession.SetupAsync(new TouchSocketConfig() + .SetBindIPHost(new IPHost("127.0.0.1:7789")) + .SetUdpDataHandlingAdapter(() => new UdpPackageAdapter()));//加载配置 + await udpSession.StartAsync();//启动 + } +} +#endregion diff --git a/examples/Udp/UdpScreenCapture/ScreenUdpReceiver/Form1.cs b/examples/Udp/UdpScreenCapture/ScreenUdpReceiver/Form1.cs index 2ea2ab453..9e93a5f57 100644 --- a/examples/Udp/UdpScreenCapture/ScreenUdpReceiver/Form1.cs +++ b/examples/Udp/UdpScreenCapture/ScreenUdpReceiver/Form1.cs @@ -11,7 +11,9 @@ //------------------------------------------------------------------------------ using System; +using System.Buffers; using System.Drawing; +using System.IO; using System.Windows.Forms; using TouchSocket.Core; using TouchSocket.Sockets; @@ -19,7 +21,7 @@ using TouchSocket.Sockets; namespace ScreenUdpReceiver; /// -/// 本程序源码由网友“木南白水”提供。 +/// 本程序源码由网友"木南白水"提供。 /// public partial class Form1 : Form { @@ -38,7 +40,30 @@ public partial class Form1 : Form this.udpSession.Received = (c, e) => { - this.pictureBox1.Image = Image.FromStream(e.ByteBlock.AsStream(false)); + + try + { + using var stream = new ReadOnlyMemoryStream(e.Memory); + + // Update UI on the main thread + this.Invoke(() => + { + try + { + this.pictureBox1.Image = Image.FromStream(stream); + } + catch (Exception ex) + { + // Handle image conversion errors silently or log them + Console.WriteLine($"Error converting image: {ex.Message}"); + } + }); + } + catch (Exception ex) + { + } + + return EasyTask.CompletedTask; }; this.udpSession.SetupAsync(new TouchSocketConfig() diff --git a/examples/Udp/UdpScreenCapture/ScreenUdpReceiver/ScreenUdpReceiver.csproj b/examples/Udp/UdpScreenCapture/ScreenUdpReceiver/ScreenUdpReceiver.csproj index 7cd9717f4..81ec51bea 100644 --- a/examples/Udp/UdpScreenCapture/ScreenUdpReceiver/ScreenUdpReceiver.csproj +++ b/examples/Udp/UdpScreenCapture/ScreenUdpReceiver/ScreenUdpReceiver.csproj @@ -1,4 +1,4 @@ - + WinExe @@ -7,7 +7,7 @@ - + \ No newline at end of file diff --git a/examples/Udp/UdpScreenCapture/ScreenUdpSender/Form1.cs b/examples/Udp/UdpScreenCapture/ScreenUdpSender/Form1.cs index f3810f0d5..27f8d2648 100644 --- a/examples/Udp/UdpScreenCapture/ScreenUdpSender/Form1.cs +++ b/examples/Udp/UdpScreenCapture/ScreenUdpSender/Form1.cs @@ -40,8 +40,10 @@ public partial class Form1 : Form { while (true) { - var byteArray = this.ImageToByte(this.getScreen()); - using var bb = new ByteBlock(byteArray); + using var bb = new ByteBlock(1024 * 1024 * 5); + using var img = this.getScreen(); + this.ImageToByte(bb.AsStream(), img); + await this.udpSession.SendAsync(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 7790), bb.Memory); await Task.Delay((int)(1000.0 / (int)this.numericUpDown1.Value)); } @@ -76,7 +78,7 @@ public partial class Form1 : Form if (height == -1) height = SystemInformation.VirtualScreen.Height; var tmp = new Bitmap(width, height); //按指定大小创建位图 - var g = Graphics.FromImage(tmp); //从位图创建Graphics对象 + using var g = Graphics.FromImage(tmp); //从位图创建Graphics对象 g.CopyFromScreen(x, y, 0, 0, new Size(width, height)); //绘制 // 绘制鼠标 @@ -90,7 +92,7 @@ public partial class Form1 : Form var cur = new System.Windows.Forms.Cursor(pci.hCursor); cur.Draw(g, new Rectangle(pci.ptScreenPos.x, pci.ptScreenPos.y, cur.Size.Width, cur.Size.Height)); } - catch (Exception ex) { } // 若获取鼠标异常则不显示 + catch (Exception) { } // 若获取鼠标异常则不显示 } //Size halfSize = new Size((int)(tmp.Size.Width * 0.8), (int)(tmp.Size.Height * 0.8)); // 按一半尺寸存储图像 @@ -103,25 +105,16 @@ public partial class Form1 : Form #region 格式转换 - private byte[] ImageToByte(Image Picture) + private void ImageToByte(Stream stream, Image Picture) { - var ms = new MemoryStream(); if (Picture == null) - return new byte[ms.Length]; - Picture.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg); - var BPicture = new byte[ms.Length]; - BPicture = ms.GetBuffer(); - return BPicture; + { + return; + } + + Picture.Save(stream, System.Drawing.Imaging.ImageFormat.Jpeg); } - private Image ByteToImage(byte[] btImage) - { - if (btImage.Length == 0) - return null; - var ms = new System.IO.MemoryStream(btImage); - var image = System.Drawing.Image.FromStream(ms); - return image; - } #endregion 格式转换 @@ -134,6 +127,7 @@ public partial class Form1 : Form this.udpSession.SetupAsync( new TouchSocketConfig() + .SetUdpConnReset(true) .SetBindIPHost(new IPHost(7789)) .SetUdpDataHandlingAdapter(() => { return new UdpPackageAdapter() { MaxPackageSize = 1024 * 1024, MTU = 1024 * 10 }; })); this.udpSession.StartAsync(); diff --git a/examples/Udp/UdpScreenCapture/ScreenUdpSender/ScreenUdpSender.csproj b/examples/Udp/UdpScreenCapture/ScreenUdpSender/ScreenUdpSender.csproj index 7cd9717f4..81ec51bea 100644 --- a/examples/Udp/UdpScreenCapture/ScreenUdpSender/ScreenUdpSender.csproj +++ b/examples/Udp/UdpScreenCapture/ScreenUdpSender/ScreenUdpSender.csproj @@ -1,4 +1,4 @@ - + WinExe @@ -7,7 +7,7 @@ - + \ No newline at end of file diff --git a/examples/Unity3d/UnityPackage/由于unity包体积较大,以后版本均在qq群(234762506)文件获取.txt b/examples/Unity3d/UnityPackage/由于unity包体积较大,以后版本均在qq群(234762506)文件获取.txt new file mode 100644 index 000000000..7921185eb --- /dev/null +++ b/examples/Unity3d/UnityPackage/由于unity包体积较大,以后版本均在qq群(234762506)文件获取.txt @@ -0,0 +1 @@ +由于unity包体积较大,以后版本均在qq群(234762506)文件获取 \ No newline at end of file diff --git a/examples/Unity3d/UnityServerConsoleApp_2D/Program.cs b/examples/Unity3d/UnityServerConsoleApp_2D/Program.cs index 9a140f87a..8d21d8199 100644 --- a/examples/Unity3d/UnityServerConsoleApp_2D/Program.cs +++ b/examples/Unity3d/UnityServerConsoleApp_2D/Program.cs @@ -1,3 +1,15 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + using UnityServerConsoleApp_2D.TouchServer; namespace UnityServerConsoleApp_2D; diff --git a/examples/Unity3d/UnityServerConsoleApp_2D/RPCStore/Reverse2DSquareRpcServer.cs b/examples/Unity3d/UnityServerConsoleApp_2D/RPCStore/Reverse2DSquareRpcServer.cs index 7c4c8dfc1..70bcd035a 100644 --- a/examples/Unity3d/UnityServerConsoleApp_2D/RPCStore/Reverse2DSquareRpcServer.cs +++ b/examples/Unity3d/UnityServerConsoleApp_2D/RPCStore/Reverse2DSquareRpcServer.cs @@ -1,3 +1,15 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + /* 此代码由Rpc工具直接生成,非必要请不要修改此处代码 */ @@ -20,14 +32,14 @@ namespace UnityRpcProxy /// 调用超时 /// Rpc调用异常 /// 其他异常 - void UpdatePosition(System.Int32 id, System.Numerics.Vector3 vector3, System.Int64 time, IInvokeOption invokeOption = default); + void UpdatePosition(System.Int32 id, System.Numerics.Vector3 vector3, System.Int64 time, InvokeOption invokeOption = default); /// ///更新位置 /// /// 调用超时 /// Rpc调用异常 /// 其他异常 - Task UpdatePositionAsync(System.Int32 id, System.Numerics.Vector3 vector3, System.Int64 time, IInvokeOption invokeOption = default); + Task UpdatePositionAsync(System.Int32 id, System.Numerics.Vector3 vector3, System.Int64 time, InvokeOption invokeOption = default); /// ///创建新的NPC @@ -35,14 +47,14 @@ namespace UnityRpcProxy /// 调用超时 /// Rpc调用异常 /// 其他异常 - void NewNPC(System.Int32 id, System.Numerics.Vector3 vector3, IInvokeOption invokeOption = default); + void NewNPC(System.Int32 id, System.Numerics.Vector3 vector3, InvokeOption invokeOption = default); /// ///创建新的NPC /// /// 调用超时 /// Rpc调用异常 /// 其他异常 - Task NewNPCAsync(System.Int32 id, System.Numerics.Vector3 vector3, IInvokeOption invokeOption = default); + Task NewNPCAsync(System.Int32 id, System.Numerics.Vector3 vector3, InvokeOption invokeOption = default); /// ///玩家离线 @@ -50,14 +62,14 @@ namespace UnityRpcProxy /// 调用超时 /// Rpc调用异常 /// 其他异常 - void Offline(System.Int32 id, IInvokeOption invokeOption = default); + void Offline(System.Int32 id, InvokeOption invokeOption = default); /// ///玩家离线 /// /// 调用超时 /// Rpc调用异常 /// 其他异常 - Task OfflineAsync(System.Int32 id, IInvokeOption invokeOption = default); + Task OfflineAsync(System.Int32 id, InvokeOption invokeOption = default); /// ///玩家登陆 @@ -65,14 +77,14 @@ namespace UnityRpcProxy /// 调用超时 /// Rpc调用异常 /// 其他异常 - void PlayerLogin(System.Int32 id, IInvokeOption invokeOption = default); + void PlayerLogin(System.Int32 id, InvokeOption invokeOption = default); /// ///玩家登陆 /// /// 调用超时 /// Rpc调用异常 /// 其他异常 - Task PlayerLoginAsync(System.Int32 id, IInvokeOption invokeOption = default); + Task PlayerLoginAsync(System.Int32 id, InvokeOption invokeOption = default); } public class Reverse2DSquareRpcServer : IReverse2DSquareRpcServer @@ -88,7 +100,7 @@ namespace UnityRpcProxy /// 调用超时 /// Rpc调用异常 /// 其他异常 - public void UpdatePosition(System.Int32 id, System.Numerics.Vector3 vector3, System.Int64 time, IInvokeOption invokeOption = default) + public void UpdatePosition(System.Int32 id, System.Numerics.Vector3 vector3, System.Int64 time, InvokeOption invokeOption = default) { if (this.Client == null) { @@ -101,7 +113,7 @@ namespace UnityRpcProxy /// ///更新位置 /// - public Task UpdatePositionAsync(System.Int32 id, System.Numerics.Vector3 vector3, System.Int64 time, IInvokeOption invokeOption = default) + public Task UpdatePositionAsync(System.Int32 id, System.Numerics.Vector3 vector3, System.Int64 time, InvokeOption invokeOption = default) { if (this.Client == null) { @@ -118,7 +130,7 @@ namespace UnityRpcProxy /// 调用超时 /// Rpc调用异常 /// 其他异常 - public void NewNPC(System.Int32 id, System.Numerics.Vector3 vector3, IInvokeOption invokeOption = default) + public void NewNPC(System.Int32 id, System.Numerics.Vector3 vector3, InvokeOption invokeOption = default) { if (this.Client == null) { @@ -131,7 +143,7 @@ namespace UnityRpcProxy /// ///创建新的NPC /// - public Task NewNPCAsync(System.Int32 id, System.Numerics.Vector3 vector3, IInvokeOption invokeOption = default) + public Task NewNPCAsync(System.Int32 id, System.Numerics.Vector3 vector3, InvokeOption invokeOption = default) { if (this.Client == null) { @@ -148,7 +160,7 @@ namespace UnityRpcProxy /// 调用超时 /// Rpc调用异常 /// 其他异常 - public void Offline(System.Int32 id, IInvokeOption invokeOption = default) + public void Offline(System.Int32 id, InvokeOption invokeOption = default) { if (this.Client == null) { @@ -161,7 +173,7 @@ namespace UnityRpcProxy /// ///玩家离线 /// - public Task OfflineAsync(System.Int32 id, IInvokeOption invokeOption = default) + public Task OfflineAsync(System.Int32 id, InvokeOption invokeOption = default) { if (this.Client == null) { @@ -178,7 +190,7 @@ namespace UnityRpcProxy /// 调用超时 /// Rpc调用异常 /// 其他异常 - public void PlayerLogin(System.Int32 id, IInvokeOption invokeOption = default) + public void PlayerLogin(System.Int32 id, InvokeOption invokeOption = default) { if (this.Client == null) { @@ -191,7 +203,7 @@ namespace UnityRpcProxy /// ///玩家登陆 /// - public Task PlayerLoginAsync(System.Int32 id, IInvokeOption invokeOption = default) + public Task PlayerLoginAsync(System.Int32 id, InvokeOption invokeOption = default) { if (this.Client == null) { @@ -211,7 +223,7 @@ namespace UnityRpcProxy /// 调用超时 /// Rpc调用异常 /// 其他异常 - public static void UpdatePosition(this TClient client, System.Int32 id, System.Numerics.Vector3 vector3, System.Int64 time, IInvokeOption invokeOption = default) where TClient : + public static void UpdatePosition(this TClient client, System.Int32 id, System.Numerics.Vector3 vector3, System.Int64 time, InvokeOption invokeOption = default) where TClient : TouchSocket.JsonRpc.IJsonRpcClient { object[] @_parameters = new object[] { id, vector3, time }; @@ -221,7 +233,7 @@ namespace UnityRpcProxy /// ///更新位置 /// - public static Task UpdatePositionAsync(this TClient client, System.Int32 id, System.Numerics.Vector3 vector3, System.Int64 time, IInvokeOption invokeOption = default) where TClient : + public static Task UpdatePositionAsync(this TClient client, System.Int32 id, System.Numerics.Vector3 vector3, System.Int64 time, InvokeOption invokeOption = default) where TClient : TouchSocket.JsonRpc.IJsonRpcClient { object[] parameters = new object[] { id, vector3, time }; @@ -235,7 +247,7 @@ namespace UnityRpcProxy /// 调用超时 /// Rpc调用异常 /// 其他异常 - public static void NewNPC(this TClient client, System.Int32 id, System.Numerics.Vector3 vector3, IInvokeOption invokeOption = default) where TClient : + public static void NewNPC(this TClient client, System.Int32 id, System.Numerics.Vector3 vector3, InvokeOption invokeOption = default) where TClient : TouchSocket.JsonRpc.IJsonRpcClient { object[] @_parameters = new object[] { id, vector3 }; @@ -245,7 +257,7 @@ namespace UnityRpcProxy /// ///创建新的NPC /// - public static Task NewNPCAsync(this TClient client, System.Int32 id, System.Numerics.Vector3 vector3, IInvokeOption invokeOption = default) where TClient : + public static Task NewNPCAsync(this TClient client, System.Int32 id, System.Numerics.Vector3 vector3, InvokeOption invokeOption = default) where TClient : TouchSocket.JsonRpc.IJsonRpcClient { object[] parameters = new object[] { id, vector3 }; @@ -259,7 +271,7 @@ namespace UnityRpcProxy /// 调用超时 /// Rpc调用异常 /// 其他异常 - public static void Offline(this TClient client, System.Int32 id, IInvokeOption invokeOption = default) where TClient : + public static void Offline(this TClient client, System.Int32 id, InvokeOption invokeOption = default) where TClient : TouchSocket.JsonRpc.IJsonRpcClient { object[] @_parameters = new object[] { id }; @@ -269,7 +281,7 @@ namespace UnityRpcProxy /// ///玩家离线 /// - public static Task OfflineAsync(this TClient client, System.Int32 id, IInvokeOption invokeOption = default) where TClient : + public static Task OfflineAsync(this TClient client, System.Int32 id, InvokeOption invokeOption = default) where TClient : TouchSocket.JsonRpc.IJsonRpcClient { object[] parameters = new object[] { id }; @@ -283,7 +295,7 @@ namespace UnityRpcProxy /// 调用超时 /// Rpc调用异常 /// 其他异常 - public static void PlayerLogin(this TClient client, System.Int32 id, IInvokeOption invokeOption = default) where TClient : + public static void PlayerLogin(this TClient client, System.Int32 id, InvokeOption invokeOption = default) where TClient : TouchSocket.JsonRpc.IJsonRpcClient { object[] @_parameters = new object[] { id }; @@ -293,7 +305,7 @@ namespace UnityRpcProxy /// ///玩家登陆 /// - public static Task PlayerLoginAsync(this TClient client, System.Int32 id, IInvokeOption invokeOption = default) where TClient : + public static Task PlayerLoginAsync(this TClient client, System.Int32 id, InvokeOption invokeOption = default) where TClient : TouchSocket.JsonRpc.IJsonRpcClient { object[] parameters = new object[] { id }; diff --git a/examples/Unity3d/UnityServerConsoleApp_2D/RPCStore/UnityRpcProxy_Json_HttpDmtp_2D.cs b/examples/Unity3d/UnityServerConsoleApp_2D/RPCStore/UnityRpcProxy_Json_HttpDmtp_2D.cs index cbc263d15..33e27271e 100644 --- a/examples/Unity3d/UnityServerConsoleApp_2D/RPCStore/UnityRpcProxy_Json_HttpDmtp_2D.cs +++ b/examples/Unity3d/UnityServerConsoleApp_2D/RPCStore/UnityRpcProxy_Json_HttpDmtp_2D.cs @@ -1,96 +1,110 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + /* 此代码由Rpc工具直接生成,非必要请不要修改此处代码 */ #pragma warning disable using System; -using TouchSocket.Core; -using TouchSocket.Sockets; -using TouchSocket.Rpc; using System.Collections.Generic; using System.Diagnostics; using System.Text; using System.Threading.Tasks; +using TouchSocket.Core; +using TouchSocket.Rpc; +using TouchSocket.Sockets; namespace UnityRpcProxy_Json_HttpDmtp_2D { -public interface IUnityRpcStore:TouchSocket.Rpc.IRemoteServer -{ -/// -///单位移动 -/// -/// 调用超时 -/// Rpc调用异常 -/// 其他异常 -void JsonRpc_UnitMovement(System.Numerics.Vector3 vector3,IInvokeOption invokeOption = default); -/// -///单位移动 -/// -/// 调用超时 -/// Rpc调用异常 -/// 其他异常 -Task JsonRpc_UnitMovementAsync(System.Numerics.Vector3 vector3,IInvokeOption invokeOption = default); + public interface IUnityRpcStore : TouchSocket.Rpc.IRemoteServer + { + /// + ///单位移动 + /// + /// 调用超时 + /// Rpc调用异常 + /// 其他异常 + void JsonRpc_UnitMovement(System.Numerics.Vector3 vector3, InvokeOption invokeOption = default); + /// + ///单位移动 + /// + /// 调用超时 + /// Rpc调用异常 + /// 其他异常 + Task JsonRpc_UnitMovementAsync(System.Numerics.Vector3 vector3, InvokeOption invokeOption = default); -} -public class UnityRpcStore :IUnityRpcStore -{ -public UnityRpcStore(IRpcClient client) -{ -this.Client=client; -} -public IRpcClient Client{get;private set; } -/// -///单位移动 -/// -/// 调用超时 -/// Rpc调用异常 -/// 其他异常 -public void JsonRpc_UnitMovement(System.Numerics.Vector3 vector3,IInvokeOption invokeOption = default) -{ -if(this.Client==null) -{ -throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); -} -object[] @_parameters = new object[]{vector3}; -this.Client.Invoke("JsonRpc_UnitMovement",null,invokeOption, @_parameters); + } + public class UnityRpcStore : IUnityRpcStore + { + public UnityRpcStore(IRpcClient client) + { + this.Client = client; + } + public IRpcClient Client { get; private set; } + /// + ///单位移动 + /// + /// 调用超时 + /// Rpc调用异常 + /// 其他异常 + public void JsonRpc_UnitMovement(System.Numerics.Vector3 vector3, InvokeOption invokeOption = default) + { + if (this.Client == null) + { + throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); + } + object[] @_parameters = new object[] { vector3 }; + this.Client.Invoke("JsonRpc_UnitMovement", null, invokeOption, @_parameters); -} -/// -///单位移动 -/// -public Task JsonRpc_UnitMovementAsync(System.Numerics.Vector3 vector3,IInvokeOption invokeOption = default) -{ -if(this.Client==null) -{ -throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); -} -object[] parameters = new object[]{vector3}; -return this.Client.InvokeAsync("JsonRpc_UnitMovement",null,invokeOption, parameters); + } + /// + ///单位移动 + /// + public Task JsonRpc_UnitMovementAsync(System.Numerics.Vector3 vector3, InvokeOption invokeOption = default) + { + if (this.Client == null) + { + throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); + } + object[] parameters = new object[] { vector3 }; + return this.Client.InvokeAsync("JsonRpc_UnitMovement", null, invokeOption, parameters); -} + } -} -public static class UnityRpcStoreExtensions -{ -/// -///单位移动 -/// -/// 调用超时 -/// Rpc调用异常 -/// 其他异常 -public static void JsonRpc_UnitMovement(this TClient client,System.Numerics.Vector3 vector3,IInvokeOption invokeOption = default) where TClient: -TouchSocket.JsonRpc.IJsonRpcClient{ -object[] @_parameters = new object[]{vector3}; -client.Invoke("JsonRpc_UnitMovement",null,invokeOption, @_parameters); + } + public static class UnityRpcStoreExtensions + { + /// + ///单位移动 + /// + /// 调用超时 + /// Rpc调用异常 + /// 其他异常 + public static void JsonRpc_UnitMovement(this TClient client, System.Numerics.Vector3 vector3, InvokeOption invokeOption = default) where TClient : + TouchSocket.JsonRpc.IJsonRpcClient + { + object[] @_parameters = new object[] { vector3 }; + client.Invoke("JsonRpc_UnitMovement", null, invokeOption, @_parameters); -} -/// -///单位移动 -/// -public static Task JsonRpc_UnitMovementAsync(this TClient client,System.Numerics.Vector3 vector3,IInvokeOption invokeOption = default) where TClient: -TouchSocket.JsonRpc.IJsonRpcClient{ -object[] parameters = new object[]{vector3}; -return client.InvokeAsync("JsonRpc_UnitMovement",null,invokeOption, parameters); + } + /// + ///单位移动 + /// + public static Task JsonRpc_UnitMovementAsync(this TClient client, System.Numerics.Vector3 vector3, InvokeOption invokeOption = default) where TClient : + TouchSocket.JsonRpc.IJsonRpcClient + { + object[] parameters = new object[] { vector3 }; + return client.InvokeAsync("JsonRpc_UnitMovement", null, invokeOption, parameters); -} + } -} + } } diff --git a/examples/Unity3d/UnityServerConsoleApp_2D/TouchServer/BaseTouchServer.cs b/examples/Unity3d/UnityServerConsoleApp_2D/TouchServer/BaseTouchServer.cs index 8d1f8ae7e..6863ab57d 100644 --- a/examples/Unity3d/UnityServerConsoleApp_2D/TouchServer/BaseTouchServer.cs +++ b/examples/Unity3d/UnityServerConsoleApp_2D/TouchServer/BaseTouchServer.cs @@ -1,3 +1,15 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + namespace UnityServerConsoleApp_2D.TouchServer; /// diff --git a/examples/Unity3d/UnityServerConsoleApp_2D/TouchServer/Touch_JsonWebSocket_2D.cs b/examples/Unity3d/UnityServerConsoleApp_2D/TouchServer/Touch_JsonWebSocket_2D.cs index 060e39647..10f09c684 100644 --- a/examples/Unity3d/UnityServerConsoleApp_2D/TouchServer/Touch_JsonWebSocket_2D.cs +++ b/examples/Unity3d/UnityServerConsoleApp_2D/TouchServer/Touch_JsonWebSocket_2D.cs @@ -1,3 +1,15 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + using System.Numerics; using TouchSocket.Core; using TouchSocket.Dmtp; @@ -36,12 +48,20 @@ public class Touch_JsonWebSocket_2D : BaseTouchServer }) .ConfigurePlugins(a => { - a.UseWebSocket() - .SetWSUrl("/ws"); + //添加WebSocket功能 + a.UseWebSocket(options => + { + options.SetUrl("/ws");//设置url直接可以连接。 + options.SetAutoPong(true);//当收到ping报文时自动回应pong + }); //启用json rpc插件 - a.UseWebSocketJsonRpc() - .SetAllowJsonRpc((websocket, context) => true);//让所有请求WebSocket都加载JsonRpc插件 + a.UseWebSocketJsonRpc(options => + { + options.SetAllowJsonRpc((websocket, context) => true);//让所有请求WebSocket都加载JsonRpc插件 + }); + + a.Add(); @@ -58,7 +78,7 @@ public class Touch_JsonWebSocket_2D : BaseTouchServer /// /// 状态日志打印插件 /// -internal class Touch_JsonWebSocket_Log_Plguin : PluginBase, IWebSocketHandshakedPlugin, IWebSocketClosedPlugin +internal class Touch_JsonWebSocket_Log_Plguin : PluginBase, IWebSocketConnectedPlugin, IWebSocketClosedPlugin { private readonly ILog m_log; public Touch_JsonWebSocket_Log_Plguin(ILog Log) @@ -87,7 +107,7 @@ internal class Touch_JsonWebSocket_Log_Plguin : PluginBase, IWebSocketHandshaked await e.InvokeNext(); } - public async Task OnWebSocketHandshaked(IWebSocket webSocket, HttpContextEventArgs e) + public async Task OnWebSocketConnected(IWebSocket webSocket, HttpContextEventArgs e) { if (webSocket.Client is JsonHttpSessionClient client) diff --git a/examples/Unity3d/UnityServerConsoleApp_2D/TouchServer/UnityRpcStore.cs b/examples/Unity3d/UnityServerConsoleApp_2D/TouchServer/UnityRpcStore.cs index 77b1a67d4..9b19982e0 100644 --- a/examples/Unity3d/UnityServerConsoleApp_2D/TouchServer/UnityRpcStore.cs +++ b/examples/Unity3d/UnityServerConsoleApp_2D/TouchServer/UnityRpcStore.cs @@ -1,6 +1,17 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + using System.ComponentModel; using System.Numerics; -using System.Threading.Tasks; using TouchSocket.Core; using TouchSocket.JsonRpc; using TouchSocket.Rpc; @@ -43,12 +54,12 @@ internal class UnityRpcStore : SingletonRpcServer //通知除开玩家的其他所有客户端 if (jsonsession != clientItem) { - await clientItem.GetJsonRpcActionClient().UpdatePositionAsync(jsonsession.ID, jsonsession.Postion, ToTimestamp(DateTime.Now)); + await clientItem.GetJsonRpcActionClient().UpdatePositionAsync(jsonsession.ID, jsonsession.Postion, ToTimestamp(DateTime.Now)); } } - m_logger.Info($"玩家{jsonsession.ID}移动到{vector3}"); + this.m_logger.Info($"玩家{jsonsession.ID}移动到{vector3}"); } } diff --git a/examples/Unity3d/UnityServerConsoleApp_2D/UnityServerConsoleApp_2D.csproj b/examples/Unity3d/UnityServerConsoleApp_2D/UnityServerConsoleApp_2D.csproj index 75aa9b331..ac08ba95a 100644 --- a/examples/Unity3d/UnityServerConsoleApp_2D/UnityServerConsoleApp_2D.csproj +++ b/examples/Unity3d/UnityServerConsoleApp_2D/UnityServerConsoleApp_2D.csproj @@ -1,4 +1,4 @@ - + Exe @@ -7,8 +7,8 @@ enable - - + + diff --git a/examples/Unity3d/UnityServerConsoleApp_All/Program.cs b/examples/Unity3d/UnityServerConsoleApp_All/Program.cs index bc9857225..80926126e 100644 --- a/examples/Unity3d/UnityServerConsoleApp_All/Program.cs +++ b/examples/Unity3d/UnityServerConsoleApp_All/Program.cs @@ -1,3 +1,15 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + using UnityServerConsoleApp_All.TouchServer; namespace UnityServerConsoleApp_All; diff --git a/examples/Unity3d/UnityServerConsoleApp_All/RPCStore/UnityRpcProxy_Client_HttpDmtp.cs b/examples/Unity3d/UnityServerConsoleApp_All/RPCStore/UnityRpcProxy_Client_HttpDmtp.cs index aa41683eb..bf6798a1b 100644 --- a/examples/Unity3d/UnityServerConsoleApp_All/RPCStore/UnityRpcProxy_Client_HttpDmtp.cs +++ b/examples/Unity3d/UnityServerConsoleApp_All/RPCStore/UnityRpcProxy_Client_HttpDmtp.cs @@ -1,3 +1,15 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + /* 此代码由Rpc工具直接生成,非必要请不要修改此处代码 */ @@ -20,14 +32,14 @@ namespace UnityRpcProxy /// 调用超时 /// Rpc调用异常 /// 其他异常 - System.Int32 RandomNumber(System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default); + System.Int32 RandomNumber(System.Int32 a, System.Int32 b, InvokeOption invokeOption = default); /// ///无注释信息 /// /// 调用超时 /// Rpc调用异常 /// 其他异常 - Task RandomNumberAsync(System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default); + Task RandomNumberAsync(System.Int32 a, System.Int32 b, InvokeOption invokeOption = default); } public class Touch_HttpDmtp_Client_UnityRpcStore : ITouch_HttpDmtp_Client_UnityRpcStore @@ -43,7 +55,7 @@ namespace UnityRpcProxy /// 调用超时 /// Rpc调用异常 /// 其他异常 - public System.Int32 RandomNumber(System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default) + public System.Int32 RandomNumber(System.Int32 a, System.Int32 b, InvokeOption invokeOption = default) { if (this.Client == null) { @@ -57,7 +69,7 @@ namespace UnityRpcProxy /// ///无注释信息 /// - public async Task RandomNumberAsync(System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default) + public async Task RandomNumberAsync(System.Int32 a, System.Int32 b, InvokeOption invokeOption = default) { if (this.Client == null) { @@ -77,7 +89,7 @@ namespace UnityRpcProxy /// 调用超时 /// Rpc调用异常 /// 其他异常 - public static System.Int32 RandomNumber(this TClient client, System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default) where TClient : + public static System.Int32 RandomNumber(this TClient client, System.Int32 a, System.Int32 b, InvokeOption invokeOption = default) where TClient : TouchSocket.Dmtp.Rpc.IDmtpRpcActor { object[] @_parameters = new object[] { a, b }; @@ -88,7 +100,7 @@ namespace UnityRpcProxy /// ///无注释信息 /// - public static async Task RandomNumberAsync(this TClient client, System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default) where TClient : + public static async Task RandomNumberAsync(this TClient client, System.Int32 a, System.Int32 b, InvokeOption invokeOption = default) where TClient : TouchSocket.Dmtp.Rpc.IDmtpRpcActor { object[] parameters = new object[] { a, b }; diff --git a/examples/Unity3d/UnityServerConsoleApp_All/RPCStore/UnityRpcProxy_Client_JsonRPCDmtp.cs b/examples/Unity3d/UnityServerConsoleApp_All/RPCStore/UnityRpcProxy_Client_JsonRPCDmtp.cs index 5b121b1b8..c4b126f4f 100644 --- a/examples/Unity3d/UnityServerConsoleApp_All/RPCStore/UnityRpcProxy_Client_JsonRPCDmtp.cs +++ b/examples/Unity3d/UnityServerConsoleApp_All/RPCStore/UnityRpcProxy_Client_JsonRPCDmtp.cs @@ -1,3 +1,15 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + /* 此代码由Rpc工具直接生成,非必要请不要修改此处代码 */ @@ -20,14 +32,14 @@ namespace UnityRpcProxy /// 调用超时 /// Rpc调用异常 /// 其他异常 - System.Int32 Add(System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default); + System.Int32 Add(System.Int32 a, System.Int32 b, InvokeOption invokeOption = default); /// ///无注释信息 /// /// 调用超时 /// Rpc调用异常 /// 其他异常 - Task AddAsync(System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default); + Task AddAsync(System.Int32 a, System.Int32 b, InvokeOption invokeOption = default); } public class ReverseJsonRpcServer : IReverseJsonRpcServer @@ -43,7 +55,7 @@ namespace UnityRpcProxy /// 调用超时 /// Rpc调用异常 /// 其他异常 - public System.Int32 Add(System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default) + public System.Int32 Add(System.Int32 a, System.Int32 b, InvokeOption invokeOption = default) { if (this.Client == null) { @@ -57,7 +69,7 @@ namespace UnityRpcProxy /// ///无注释信息 /// - public async Task AddAsync(System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default) + public async Task AddAsync(System.Int32 a, System.Int32 b, InvokeOption invokeOption = default) { if (this.Client == null) { @@ -77,7 +89,7 @@ namespace UnityRpcProxy /// 调用超时 /// Rpc调用异常 /// 其他异常 - public static System.Int32 Add(this TClient client, System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default) where TClient : + public static System.Int32 Add(this TClient client, System.Int32 a, System.Int32 b, InvokeOption invokeOption = default) where TClient : TouchSocket.JsonRpc.IJsonRpcClient { object[] @_parameters = new object[] { a, b }; @@ -88,7 +100,7 @@ namespace UnityRpcProxy /// ///无注释信息 /// - public static async Task AddAsync(this TClient client, System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default) where TClient : + public static async Task AddAsync(this TClient client, System.Int32 a, System.Int32 b, InvokeOption invokeOption = default) where TClient : TouchSocket.JsonRpc.IJsonRpcClient { object[] parameters = new object[] { a, b }; diff --git a/examples/Unity3d/UnityServerConsoleApp_All/RPCStore/UnityRpcProxy_HttpDmtp.cs b/examples/Unity3d/UnityServerConsoleApp_All/RPCStore/UnityRpcProxy_HttpDmtp.cs index 509434e37..b2c655624 100644 --- a/examples/Unity3d/UnityServerConsoleApp_All/RPCStore/UnityRpcProxy_HttpDmtp.cs +++ b/examples/Unity3d/UnityServerConsoleApp_All/RPCStore/UnityRpcProxy_HttpDmtp.cs @@ -20,14 +20,15 @@ public interface IUnityRpcStore:TouchSocket.Rpc.IRemoteServer /// 调用超时 /// Rpc调用异常 /// 其他异常 -LoginModelResult DmtpRpc_Login(LoginModel model,IInvokeOption invokeOption = default); +[AsyncToSyncWarning] +LoginModelResult DmtpRpc_Login(LoginModel model,InvokeOption invokeOption = default); /// ///登录 /// /// 调用超时 /// Rpc调用异常 /// 其他异常 -Task DmtpRpc_LoginAsync(LoginModel model,IInvokeOption invokeOption = default); +Task DmtpRpc_LoginAsync(LoginModel model,InvokeOption invokeOption = default); /// ///性能测试 @@ -35,14 +36,15 @@ Task DmtpRpc_LoginAsync(LoginModel model,IInvokeOption invokeO /// 调用超时 /// Rpc调用异常 /// 其他异常 -System.Int32 DmtpRpc_Performance(System.Int32 i,IInvokeOption invokeOption = default); +[AsyncToSyncWarning] +System.Int32 DmtpRpc_Performance(System.Int32 i,InvokeOption invokeOption = default); /// ///性能测试 /// /// 调用超时 /// Rpc调用异常 /// 其他异常 -Task DmtpRpc_PerformanceAsync(System.Int32 i,IInvokeOption invokeOption = default); +Task DmtpRpc_PerformanceAsync(System.Int32 i,InvokeOption invokeOption = default); } public class UnityRpcStore :IUnityRpcStore @@ -58,7 +60,8 @@ public IRpcClient Client{get;private set; } /// 调用超时 /// Rpc调用异常 /// 其他异常 -public LoginModelResult DmtpRpc_Login(LoginModel model,IInvokeOption invokeOption = default) +[AsyncToSyncWarning] +public LoginModelResult DmtpRpc_Login(LoginModel model,InvokeOption invokeOption = default) { if(this.Client==null) { @@ -72,7 +75,7 @@ return returnData; /// ///登录 /// -public async Task DmtpRpc_LoginAsync(LoginModel model,IInvokeOption invokeOption = default) +public async Task DmtpRpc_LoginAsync(LoginModel model,InvokeOption invokeOption = default) { if(this.Client==null) { @@ -89,7 +92,8 @@ return (LoginModelResult) await this.Client.InvokeAsync("DmtpRpc_Login",typeof(L /// 调用超时 /// Rpc调用异常 /// 其他异常 -public System.Int32 DmtpRpc_Performance(System.Int32 i,IInvokeOption invokeOption = default) +[AsyncToSyncWarning] +public System.Int32 DmtpRpc_Performance(System.Int32 i,InvokeOption invokeOption = default) { if(this.Client==null) { @@ -103,7 +107,7 @@ return returnData; /// ///性能测试 /// -public async Task DmtpRpc_PerformanceAsync(System.Int32 i,IInvokeOption invokeOption = default) +public async Task DmtpRpc_PerformanceAsync(System.Int32 i,InvokeOption invokeOption = default) { if(this.Client==null) { @@ -123,7 +127,8 @@ public static class UnityRpcStoreExtensions /// 调用超时 /// Rpc调用异常 /// 其他异常 -public static LoginModelResult DmtpRpc_Login(this TClient client,LoginModel model,IInvokeOption invokeOption = default) where TClient: +[AsyncToSyncWarning] +public static LoginModelResult DmtpRpc_Login(this TClient client,LoginModel model,InvokeOption invokeOption = default) where TClient: TouchSocket.Dmtp.Rpc.IDmtpRpcActor{ object[] @_parameters = new object[]{model}; LoginModelResult returnData=(LoginModelResult)client.Invoke("DmtpRpc_Login",typeof(LoginModelResult),invokeOption, @_parameters); @@ -133,7 +138,7 @@ return returnData; /// ///登录 /// -public static async Task DmtpRpc_LoginAsync(this TClient client,LoginModel model,IInvokeOption invokeOption = default) where TClient: +public static async Task DmtpRpc_LoginAsync(this TClient client,LoginModel model,InvokeOption invokeOption = default) where TClient: TouchSocket.Dmtp.Rpc.IDmtpRpcActor{ object[] parameters = new object[]{model}; return (LoginModelResult) await client.InvokeAsync("DmtpRpc_Login",typeof(LoginModelResult),invokeOption, parameters); @@ -146,7 +151,8 @@ return (LoginModelResult) await client.InvokeAsync("DmtpRpc_Login",typeof(LoginM /// 调用超时 /// Rpc调用异常 /// 其他异常 -public static System.Int32 DmtpRpc_Performance(this TClient client,System.Int32 i,IInvokeOption invokeOption = default) where TClient: +[AsyncToSyncWarning] +public static System.Int32 DmtpRpc_Performance(this TClient client,System.Int32 i,InvokeOption invokeOption = default) where TClient: TouchSocket.Dmtp.Rpc.IDmtpRpcActor{ object[] @_parameters = new object[]{i}; System.Int32 returnData=(System.Int32)client.Invoke("DmtpRpc_Performance",typeof(System.Int32),invokeOption, @_parameters); @@ -156,7 +162,7 @@ return returnData; /// ///性能测试 /// -public static async Task DmtpRpc_PerformanceAsync(this TClient client,System.Int32 i,IInvokeOption invokeOption = default) where TClient: +public static async Task DmtpRpc_PerformanceAsync(this TClient client,System.Int32 i,InvokeOption invokeOption = default) where TClient: TouchSocket.Dmtp.Rpc.IDmtpRpcActor{ object[] parameters = new object[]{i}; return (System.Int32) await client.InvokeAsync("DmtpRpc_Performance",typeof(System.Int32),invokeOption, parameters); @@ -164,6 +170,12 @@ return (System.Int32) await client.InvokeAsync("DmtpRpc_Performance",typeof(Syst } } +public class LoginModelResult +{ +public TouchSocket.Core.ResultCode ResultCode { get; set; } +public System.String Message { get; set; } +} + public class LoginModel { public System.String Token { get; set; } @@ -171,10 +183,4 @@ public System.String Account { get; set; } public System.String Password { get; set; } } -public class LoginModelResult -{ -public TouchSocket.Core.ResultCode ResultCode { get; set; } -public System.String Message { get; set; } -} - } diff --git a/examples/Unity3d/UnityServerConsoleApp_All/RPCStore/UnityRpcProxy_Json_HttpDmtp.cs b/examples/Unity3d/UnityServerConsoleApp_All/RPCStore/UnityRpcProxy_Json_HttpDmtp.cs index d1d961d35..9ba24d372 100644 --- a/examples/Unity3d/UnityServerConsoleApp_All/RPCStore/UnityRpcProxy_Json_HttpDmtp.cs +++ b/examples/Unity3d/UnityServerConsoleApp_All/RPCStore/UnityRpcProxy_Json_HttpDmtp.cs @@ -20,14 +20,15 @@ public interface IUnityRpcStore:TouchSocket.Rpc.IRemoteServer /// 调用超时 /// Rpc调用异常 /// 其他异常 -LoginModelResult JsonRpc_Login(LoginModel model,IInvokeOption invokeOption = default); +[AsyncToSyncWarning] +LoginModelResult JsonRpc_Login(LoginModel model,InvokeOption invokeOption = default); /// ///登录 /// /// 调用超时 /// Rpc调用异常 /// 其他异常 -Task JsonRpc_LoginAsync(LoginModel model,IInvokeOption invokeOption = default); +Task JsonRpc_LoginAsync(LoginModel model,InvokeOption invokeOption = default); /// ///性能测试 @@ -35,14 +36,15 @@ Task JsonRpc_LoginAsync(LoginModel model,IInvokeOption invokeO /// 调用超时 /// Rpc调用异常 /// 其他异常 -System.Int32 JsonRpc_Performance(System.Int32 i,IInvokeOption invokeOption = default); +[AsyncToSyncWarning] +System.Int32 JsonRpc_Performance(System.Int32 i,InvokeOption invokeOption = default); /// ///性能测试 /// /// 调用超时 /// Rpc调用异常 /// 其他异常 -Task JsonRpc_PerformanceAsync(System.Int32 i,IInvokeOption invokeOption = default); +Task JsonRpc_PerformanceAsync(System.Int32 i,InvokeOption invokeOption = default); } public class UnityRpcStore :IUnityRpcStore @@ -58,7 +60,8 @@ public IRpcClient Client{get;private set; } /// 调用超时 /// Rpc调用异常 /// 其他异常 -public LoginModelResult JsonRpc_Login(LoginModel model,IInvokeOption invokeOption = default) +[AsyncToSyncWarning] +public LoginModelResult JsonRpc_Login(LoginModel model,InvokeOption invokeOption = default) { if(this.Client==null) { @@ -72,7 +75,7 @@ return returnData; /// ///登录 /// -public async Task JsonRpc_LoginAsync(LoginModel model,IInvokeOption invokeOption = default) +public async Task JsonRpc_LoginAsync(LoginModel model,InvokeOption invokeOption = default) { if(this.Client==null) { @@ -89,7 +92,8 @@ return (LoginModelResult) await this.Client.InvokeAsync("JsonRpc_Login",typeof(L /// 调用超时 /// Rpc调用异常 /// 其他异常 -public System.Int32 JsonRpc_Performance(System.Int32 i,IInvokeOption invokeOption = default) +[AsyncToSyncWarning] +public System.Int32 JsonRpc_Performance(System.Int32 i,InvokeOption invokeOption = default) { if(this.Client==null) { @@ -103,7 +107,7 @@ return returnData; /// ///性能测试 /// -public async Task JsonRpc_PerformanceAsync(System.Int32 i,IInvokeOption invokeOption = default) +public async Task JsonRpc_PerformanceAsync(System.Int32 i,InvokeOption invokeOption = default) { if(this.Client==null) { @@ -123,7 +127,8 @@ public static class UnityRpcStoreExtensions /// 调用超时 /// Rpc调用异常 /// 其他异常 -public static LoginModelResult JsonRpc_Login(this TClient client,LoginModel model,IInvokeOption invokeOption = default) where TClient: +[AsyncToSyncWarning] +public static LoginModelResult JsonRpc_Login(this TClient client,LoginModel model,InvokeOption invokeOption = default) where TClient: TouchSocket.JsonRpc.IJsonRpcClient{ object[] @_parameters = new object[]{model}; LoginModelResult returnData=(LoginModelResult)client.Invoke("JsonRpc_Login",typeof(LoginModelResult),invokeOption, @_parameters); @@ -133,7 +138,7 @@ return returnData; /// ///登录 /// -public static async Task JsonRpc_LoginAsync(this TClient client,LoginModel model,IInvokeOption invokeOption = default) where TClient: +public static async Task JsonRpc_LoginAsync(this TClient client,LoginModel model,InvokeOption invokeOption = default) where TClient: TouchSocket.JsonRpc.IJsonRpcClient{ object[] parameters = new object[]{model}; return (LoginModelResult) await client.InvokeAsync("JsonRpc_Login",typeof(LoginModelResult),invokeOption, parameters); @@ -146,7 +151,8 @@ return (LoginModelResult) await client.InvokeAsync("JsonRpc_Login",typeof(LoginM /// 调用超时 /// Rpc调用异常 /// 其他异常 -public static System.Int32 JsonRpc_Performance(this TClient client,System.Int32 i,IInvokeOption invokeOption = default) where TClient: +[AsyncToSyncWarning] +public static System.Int32 JsonRpc_Performance(this TClient client,System.Int32 i,InvokeOption invokeOption = default) where TClient: TouchSocket.JsonRpc.IJsonRpcClient{ object[] @_parameters = new object[]{i}; System.Int32 returnData=(System.Int32)client.Invoke("JsonRpc_Performance",typeof(System.Int32),invokeOption, @_parameters); @@ -156,7 +162,7 @@ return returnData; /// ///性能测试 /// -public static async Task JsonRpc_PerformanceAsync(this TClient client,System.Int32 i,IInvokeOption invokeOption = default) where TClient: +public static async Task JsonRpc_PerformanceAsync(this TClient client,System.Int32 i,InvokeOption invokeOption = default) where TClient: TouchSocket.JsonRpc.IJsonRpcClient{ object[] parameters = new object[]{i}; return (System.Int32) await client.InvokeAsync("JsonRpc_Performance",typeof(System.Int32),invokeOption, parameters); @@ -164,6 +170,12 @@ return (System.Int32) await client.InvokeAsync("JsonRpc_Performance",typeof(Syst } } +public class LoginModelResult +{ +public TouchSocket.Core.ResultCode ResultCode { get; set; } +public System.String Message { get; set; } +} + public class LoginModel { public System.String Token { get; set; } @@ -171,10 +183,4 @@ public System.String Account { get; set; } public System.String Password { get; set; } } -public class LoginModelResult -{ -public TouchSocket.Core.ResultCode ResultCode { get; set; } -public System.String Message { get; set; } -} - } diff --git a/examples/Unity3d/UnityServerConsoleApp_All/RPCStore/UnityRpcProxy_TcpDmtp.cs b/examples/Unity3d/UnityServerConsoleApp_All/RPCStore/UnityRpcProxy_TcpDmtp.cs index 7ae8fce8e..25e1855c4 100644 --- a/examples/Unity3d/UnityServerConsoleApp_All/RPCStore/UnityRpcProxy_TcpDmtp.cs +++ b/examples/Unity3d/UnityServerConsoleApp_All/RPCStore/UnityRpcProxy_TcpDmtp.cs @@ -20,14 +20,15 @@ public interface IUnityRpcStore:TouchSocket.Rpc.IRemoteServer /// 调用超时 /// Rpc调用异常 /// 其他异常 -LoginModelResult DmtpRpc_Login(LoginModel model,IInvokeOption invokeOption = default); +[AsyncToSyncWarning] +LoginModelResult DmtpRpc_Login(LoginModel model,InvokeOption invokeOption = default); /// ///登录 /// /// 调用超时 /// Rpc调用异常 /// 其他异常 -Task DmtpRpc_LoginAsync(LoginModel model,IInvokeOption invokeOption = default); +Task DmtpRpc_LoginAsync(LoginModel model,InvokeOption invokeOption = default); /// ///性能测试 @@ -35,14 +36,15 @@ Task DmtpRpc_LoginAsync(LoginModel model,IInvokeOption invokeO /// 调用超时 /// Rpc调用异常 /// 其他异常 -System.Int32 DmtpRpc_Performance(System.Int32 i,IInvokeOption invokeOption = default); +[AsyncToSyncWarning] +System.Int32 DmtpRpc_Performance(System.Int32 i,InvokeOption invokeOption = default); /// ///性能测试 /// /// 调用超时 /// Rpc调用异常 /// 其他异常 -Task DmtpRpc_PerformanceAsync(System.Int32 i,IInvokeOption invokeOption = default); +Task DmtpRpc_PerformanceAsync(System.Int32 i,InvokeOption invokeOption = default); } public class UnityRpcStore :IUnityRpcStore @@ -58,7 +60,8 @@ public IRpcClient Client{get;private set; } /// 调用超时 /// Rpc调用异常 /// 其他异常 -public LoginModelResult DmtpRpc_Login(LoginModel model,IInvokeOption invokeOption = default) +[AsyncToSyncWarning] +public LoginModelResult DmtpRpc_Login(LoginModel model,InvokeOption invokeOption = default) { if(this.Client==null) { @@ -72,7 +75,7 @@ return returnData; /// ///登录 /// -public async Task DmtpRpc_LoginAsync(LoginModel model,IInvokeOption invokeOption = default) +public async Task DmtpRpc_LoginAsync(LoginModel model,InvokeOption invokeOption = default) { if(this.Client==null) { @@ -89,7 +92,8 @@ return (LoginModelResult) await this.Client.InvokeAsync("DmtpRpc_Login",typeof(L /// 调用超时 /// Rpc调用异常 /// 其他异常 -public System.Int32 DmtpRpc_Performance(System.Int32 i,IInvokeOption invokeOption = default) +[AsyncToSyncWarning] +public System.Int32 DmtpRpc_Performance(System.Int32 i,InvokeOption invokeOption = default) { if(this.Client==null) { @@ -103,7 +107,7 @@ return returnData; /// ///性能测试 /// -public async Task DmtpRpc_PerformanceAsync(System.Int32 i,IInvokeOption invokeOption = default) +public async Task DmtpRpc_PerformanceAsync(System.Int32 i,InvokeOption invokeOption = default) { if(this.Client==null) { @@ -123,7 +127,8 @@ public static class UnityRpcStoreExtensions /// 调用超时 /// Rpc调用异常 /// 其他异常 -public static LoginModelResult DmtpRpc_Login(this TClient client,LoginModel model,IInvokeOption invokeOption = default) where TClient: +[AsyncToSyncWarning] +public static LoginModelResult DmtpRpc_Login(this TClient client,LoginModel model,InvokeOption invokeOption = default) where TClient: TouchSocket.Dmtp.Rpc.IDmtpRpcActor{ object[] @_parameters = new object[]{model}; LoginModelResult returnData=(LoginModelResult)client.Invoke("DmtpRpc_Login",typeof(LoginModelResult),invokeOption, @_parameters); @@ -133,7 +138,7 @@ return returnData; /// ///登录 /// -public static async Task DmtpRpc_LoginAsync(this TClient client,LoginModel model,IInvokeOption invokeOption = default) where TClient: +public static async Task DmtpRpc_LoginAsync(this TClient client,LoginModel model,InvokeOption invokeOption = default) where TClient: TouchSocket.Dmtp.Rpc.IDmtpRpcActor{ object[] parameters = new object[]{model}; return (LoginModelResult) await client.InvokeAsync("DmtpRpc_Login",typeof(LoginModelResult),invokeOption, parameters); @@ -146,7 +151,8 @@ return (LoginModelResult) await client.InvokeAsync("DmtpRpc_Login",typeof(LoginM /// 调用超时 /// Rpc调用异常 /// 其他异常 -public static System.Int32 DmtpRpc_Performance(this TClient client,System.Int32 i,IInvokeOption invokeOption = default) where TClient: +[AsyncToSyncWarning] +public static System.Int32 DmtpRpc_Performance(this TClient client,System.Int32 i,InvokeOption invokeOption = default) where TClient: TouchSocket.Dmtp.Rpc.IDmtpRpcActor{ object[] @_parameters = new object[]{i}; System.Int32 returnData=(System.Int32)client.Invoke("DmtpRpc_Performance",typeof(System.Int32),invokeOption, @_parameters); @@ -156,7 +162,7 @@ return returnData; /// ///性能测试 /// -public static async Task DmtpRpc_PerformanceAsync(this TClient client,System.Int32 i,IInvokeOption invokeOption = default) where TClient: +public static async Task DmtpRpc_PerformanceAsync(this TClient client,System.Int32 i,InvokeOption invokeOption = default) where TClient: TouchSocket.Dmtp.Rpc.IDmtpRpcActor{ object[] parameters = new object[]{i}; return (System.Int32) await client.InvokeAsync("DmtpRpc_Performance",typeof(System.Int32),invokeOption, parameters); @@ -164,6 +170,12 @@ return (System.Int32) await client.InvokeAsync("DmtpRpc_Performance",typeof(Syst } } +public class LoginModelResult +{ +public TouchSocket.Core.ResultCode ResultCode { get; set; } +public System.String Message { get; set; } +} + public class LoginModel { public System.String Token { get; set; } @@ -171,10 +183,4 @@ public System.String Account { get; set; } public System.String Password { get; set; } } -public class LoginModelResult -{ -public TouchSocket.Core.ResultCode ResultCode { get; set; } -public System.String Message { get; set; } -} - } diff --git a/examples/Unity3d/UnityServerConsoleApp_All/TouchServer/BaseTouchServer.cs b/examples/Unity3d/UnityServerConsoleApp_All/TouchServer/BaseTouchServer.cs index db33fe1b7..66f88a1f0 100644 --- a/examples/Unity3d/UnityServerConsoleApp_All/TouchServer/BaseTouchServer.cs +++ b/examples/Unity3d/UnityServerConsoleApp_All/TouchServer/BaseTouchServer.cs @@ -1,3 +1,15 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + namespace UnityServerConsoleApp_All.TouchServer; /// diff --git a/examples/Unity3d/UnityServerConsoleApp_All/TouchServer/Touch_HttpDmtp.cs b/examples/Unity3d/UnityServerConsoleApp_All/TouchServer/Touch_HttpDmtp.cs index f786a175c..a434b6647 100644 --- a/examples/Unity3d/UnityServerConsoleApp_All/TouchServer/Touch_HttpDmtp.cs +++ b/examples/Unity3d/UnityServerConsoleApp_All/TouchServer/Touch_HttpDmtp.cs @@ -1,3 +1,15 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + using TouchSocket.Core; using TouchSocket.Dmtp; using TouchSocket.Dmtp.Rpc; @@ -40,9 +52,9 @@ public class Touch_HttpDmtp : BaseTouchServer a.Add(); }) - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options => { - VerifyToken = "Dmtp"//设置验证token + options.VerifyToken = "Dmtp";//设置验证token }); await this.dmtpService.SetupAsync(config); @@ -55,7 +67,7 @@ public class Touch_HttpDmtp : BaseTouchServer /// /// 状态日志打印插件 /// - internal class Touch_Dmtp_Log_Plguin : PluginBase, IDmtpHandshakedPlugin, IDmtpClosedPlugin, IDmtpCreatedChannelPlugin + internal class Touch_Dmtp_Log_Plguin : PluginBase, IDmtpConnectedPlugin, IDmtpClosedPlugin, IDmtpCreatedChannelPlugin { public async Task OnDmtpClosed(IDmtpActorObject client, ClosedEventArgs e) { @@ -76,26 +88,22 @@ public class Touch_HttpDmtp : BaseTouchServer using (channel) { client.DmtpActor.Logger.Info("通道开始接收"); - //此判断主要是探测是否有Hold操作 - while (channel.CanMoveNext) + long count = 0; + while (channel.CanRead) { - long count = 0; - foreach (var byteBlock in channel) - { - //这里处理数据 - count += byteBlock.Length; - client.DmtpActor.Logger.Info($"通道已接收:{count}字节"); - } - - client.DmtpActor.Logger.Info($"通道接收结束,状态={channel.Status},短语={channel.LastOperationMes},共接收{count / (1048576.0):0.00}Mb字节"); + using var cts = new CancellationTokenSource(10 * 1000); + var memory = await channel.ReadAsync(cts.Token); + //这里处理数据 + count += memory.Length; } + client.DmtpActor.Logger.Info($"通道接收结束,状态={channel.Status},短语={channel.LastOperationMes},共接收{count / (1048576.0):0.00}Mb字节"); } } await e.InvokeNext(); } - public async Task OnDmtpHandshaked(IDmtpActorObject client, DmtpVerifyEventArgs e) + public async Task OnDmtpConnected(IDmtpActorObject client, DmtpVerifyEventArgs e) { if (client is HttpDmtpSessionClient clientSession) { @@ -150,7 +158,7 @@ public class Touch_HttpDmtp : BaseTouchServer this.Logger.Info("客户端计算数据不对"); } } - catch (Exception e) + catch (Exception) { this.StopReverseRPC(); } diff --git a/examples/Unity3d/UnityServerConsoleApp_All/TouchServer/Touch_JsonWebSocket.cs b/examples/Unity3d/UnityServerConsoleApp_All/TouchServer/Touch_JsonWebSocket.cs index 00ea240da..1ac2e1d24 100644 --- a/examples/Unity3d/UnityServerConsoleApp_All/TouchServer/Touch_JsonWebSocket.cs +++ b/examples/Unity3d/UnityServerConsoleApp_All/TouchServer/Touch_JsonWebSocket.cs @@ -1,3 +1,15 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + using TouchSocket.Core; using TouchSocket.Http; using TouchSocket.Http.WebSockets; @@ -35,12 +47,18 @@ public class Touch_JsonWebSocket : BaseTouchServer }) .ConfigurePlugins(a => { - a.UseWebSocket() - .SetWSUrl("/ws"); + //添加WebSocket功能 + a.UseWebSocket(options => + { + options.SetUrl("/ws");//设置url直接可以连接。 + options.SetAutoPong(true);//当收到ping报文时自动回应pong + }); //启用json rpc插件 - a.UseWebSocketJsonRpc() - .SetAllowJsonRpc((websocket, context) => true);//让所有请求WebSocket都加载JsonRpc插件 + a.UseWebSocketJsonRpc(options => + { + options.SetAllowJsonRpc((websocket, context) => true);//让所有请求WebSocket都加载JsonRpc插件 + }); a.Add(); @@ -57,7 +75,7 @@ public class Touch_JsonWebSocket : BaseTouchServer /// /// 状态日志打印插件 /// -internal class Touch_JsonWebSocket_Log_Plguin : PluginBase, IWebSocketHandshakedPlugin, IWebSocketClosedPlugin +internal class Touch_JsonWebSocket_Log_Plguin : PluginBase, IWebSocketConnectedPlugin, IWebSocketClosedPlugin //,IWebSocketReceivedPlugin { @@ -67,7 +85,7 @@ internal class Touch_JsonWebSocket_Log_Plguin : PluginBase, IWebSocketHandshaked await e.InvokeNext(); } - public async Task OnWebSocketHandshaked(IWebSocket webSocket, HttpContextEventArgs e) + public async Task OnWebSocketConnected(IWebSocket webSocket, HttpContextEventArgs e) { webSocket.Client.Logger.Info($"TCP_WebSocket:客户端{webSocket.Client.IP}已连接"); await e.InvokeNext(); diff --git a/examples/Unity3d/UnityServerConsoleApp_All/TouchServer/Touch_TCP.cs b/examples/Unity3d/UnityServerConsoleApp_All/TouchServer/Touch_TCP.cs index 659beab97..f46a14a00 100644 --- a/examples/Unity3d/UnityServerConsoleApp_All/TouchServer/Touch_TCP.cs +++ b/examples/Unity3d/UnityServerConsoleApp_All/TouchServer/Touch_TCP.cs @@ -1,3 +1,15 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + using System.Text; using TouchSocket.Core; using TouchSocket.Sockets; @@ -48,11 +60,11 @@ internal class Touch_TCP_Log_Plguin : PluginBase, ITcpConnectedPlugin, ITcpClose public async Task OnTcpReceived(ITcpSession client, ReceivedDataEventArgs e) { - client.Logger.Info($"TCP:接收到信息:{e.ByteBlock.Span.ToString(Encoding.UTF8)}"); + client.Logger.Info($"TCP:接收到信息:{e.Memory.Span.ToString(Encoding.UTF8)}"); if (client is ITcpSessionClient sessionClient) { - await sessionClient.SendAsync($"TCP:服务器已收到你发送的消息:{e.ByteBlock.ToString()}"); + await sessionClient.SendAsync($"TCP:服务器已收到你发送的消息:{e.Memory.Span.ToUtf8String()}"); } await e.InvokeNext(); } diff --git a/examples/Unity3d/UnityServerConsoleApp_All/TouchServer/Touch_TcpDmtp.cs b/examples/Unity3d/UnityServerConsoleApp_All/TouchServer/Touch_TcpDmtp.cs index 5bc9aa8d8..afa702f7e 100644 --- a/examples/Unity3d/UnityServerConsoleApp_All/TouchServer/Touch_TcpDmtp.cs +++ b/examples/Unity3d/UnityServerConsoleApp_All/TouchServer/Touch_TcpDmtp.cs @@ -1,3 +1,15 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + using TouchSocket.Core; using TouchSocket.Dmtp; using TouchSocket.Dmtp.Rpc; @@ -40,9 +52,9 @@ public class Touch_TcpDmtp : BaseTouchServer a.Add(); }) - .SetDmtpOption(new DmtpOption() + .SetDmtpOption(options => { - VerifyToken = "Dmtp"//设置验证token + options.VerifyToken = "Dmtp";//设置验证token }); await this.dmtpService.SetupAsync(config); @@ -55,7 +67,7 @@ public class Touch_TcpDmtp : BaseTouchServer /// /// 状态日志打印插件 /// - internal class Touch_Dmtp_Log_Plguin : PluginBase, IDmtpHandshakedPlugin, IDmtpClosedPlugin, IDmtpCreatedChannelPlugin + internal class Touch_Dmtp_Log_Plguin : PluginBase, IDmtpConnectedPlugin, IDmtpClosedPlugin, IDmtpCreatedChannelPlugin { public async Task OnDmtpClosed(IDmtpActorObject client, ClosedEventArgs e) { @@ -76,26 +88,23 @@ public class Touch_TcpDmtp : BaseTouchServer using (channel) { client.DmtpActor.Logger.Info("通道开始接收"); - //此判断主要是探测是否有Hold操作 - while (channel.CanMoveNext) + long count = 0; + while (channel.CanRead) { - long count = 0; - foreach (var byteBlock in channel) - { - //这里处理数据 - count += byteBlock.Length; - client.DmtpActor.Logger.Info($"通道已接收:{count}字节"); - } - - client.DmtpActor.Logger.Info($"通道接收结束,状态={channel.Status},短语={channel.LastOperationMes},共接收{count / (1048576.0):0.00}Mb字节"); + using var cts = new CancellationTokenSource(10 * 1000); + var memory = await channel.ReadAsync(cts.Token); + //这里处理数据 + count += memory.Length; } + + client.DmtpActor.Logger.Info($"通道接收结束,状态={channel.Status},短语={channel.LastOperationMes},共接收{count / (1048576.0):0.00}Mb字节"); } } await e.InvokeNext(); } - public async Task OnDmtpHandshaked(IDmtpActorObject client, DmtpVerifyEventArgs e) + public async Task OnDmtpConnected(IDmtpActorObject client, DmtpVerifyEventArgs e) { if (client is TcpDmtpSessionClient clientSession) { @@ -150,7 +159,7 @@ public class Touch_TcpDmtp : BaseTouchServer this.Logger.Info("客户端计算数据不对"); } } - catch (Exception e) + catch (Exception) { this.StopReverseRPC(); } diff --git a/examples/Unity3d/UnityServerConsoleApp_All/TouchServer/Touch_UDP.cs b/examples/Unity3d/UnityServerConsoleApp_All/TouchServer/Touch_UDP.cs index 1cb36c728..7acfbf9de 100644 --- a/examples/Unity3d/UnityServerConsoleApp_All/TouchServer/Touch_UDP.cs +++ b/examples/Unity3d/UnityServerConsoleApp_All/TouchServer/Touch_UDP.cs @@ -1,3 +1,15 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + using System.Text; using TouchSocket.Core; using TouchSocket.Sockets; @@ -15,8 +27,8 @@ public class Touch_UDP : BaseTouchServer { await this.udpService.SetupAsync(new TouchSocketConfig() .SetBindIPHost(new IPHost(port)) - .SetUdpDataHandlingAdapter(() => new NormalUdpDataHandlingAdapter())//常规udp - //.SetUdpDataHandlingAdapter(() => new UdpPackageAdapter())//Udp包模式,支持超过64k数据。 + //常规udp + //.SetUdpDataHandlingAdapter(() => new UdpPackageAdapter())//Udp包模式,支持超过64k数据。 .ConfigurePlugins(a => { a.Add();//此处可以添加插件 @@ -36,10 +48,10 @@ public class Touch_UDP : BaseTouchServer { public async Task OnUdpReceived(IUdpSessionBase client, UdpReceivedDataEventArgs e) { - client.Logger.Info($"UDP:收到:{e.ByteBlock.Span.ToString(Encoding.UTF8)}"); + client.Logger.Info($"UDP:收到:{e.Memory.Span.ToString(Encoding.UTF8)}"); if (client is UdpSession session) { - await session.SendAsync(e.EndPoint, "UDP:" + e.EndPoint.ToString() + "收到了你的消息:" + e.ByteBlock.Span.ToString(Encoding.UTF8)); + await session.SendAsync(e.EndPoint, "UDP:" + e.EndPoint.ToString() + "收到了你的消息:" + e.Memory.Span.ToString(Encoding.UTF8)); } await e.InvokeNext(); } diff --git a/examples/Unity3d/UnityServerConsoleApp_All/TouchServer/Touch_WebSocket.cs b/examples/Unity3d/UnityServerConsoleApp_All/TouchServer/Touch_WebSocket.cs index c21c0af0d..e33e54bdc 100644 --- a/examples/Unity3d/UnityServerConsoleApp_All/TouchServer/Touch_WebSocket.cs +++ b/examples/Unity3d/UnityServerConsoleApp_All/TouchServer/Touch_WebSocket.cs @@ -1,3 +1,15 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + using TouchSocket.Core; using TouchSocket.Dmtp; using TouchSocket.Http; @@ -23,8 +35,12 @@ public class Touch_WebSocket : BaseTouchServer }) .ConfigurePlugins(a => { - a.UseWebSocket() - .SetWSUrl("/ws"); + //添加WebSocket功能 + a.UseWebSocket(options => + { + options.SetUrl("/ws");//设置url直接可以连接。 + options.SetAutoPong(true);//当收到ping报文时自动回应pong + }); a.Add(); @@ -40,7 +56,7 @@ public class Touch_WebSocket : BaseTouchServer /// /// 状态日志打印插件 /// -internal class Touch_WebSocket_Log_Plguin : PluginBase, IWebSocketHandshakedPlugin, IWebSocketReceivedPlugin, IWebSocketClosedPlugin +internal class Touch_WebSocket_Log_Plguin : PluginBase, IWebSocketConnectedPlugin, IWebSocketReceivedPlugin, IWebSocketClosedPlugin { public async Task OnWebSocketClosed(IWebSocket webSocket, ClosedEventArgs e) @@ -49,7 +65,7 @@ internal class Touch_WebSocket_Log_Plguin : PluginBase, IWebSocketHandshakedPlug await e.InvokeNext(); } - public async Task OnWebSocketHandshaked(IWebSocket webSocket, HttpContextEventArgs e) + public async Task OnWebSocketConnected(IWebSocket webSocket, HttpContextEventArgs e) { webSocket.Client.Logger.Info($"TCP_WebSocket:客户端{webSocket.Client.IP}已连接"); await e.InvokeNext(); @@ -62,7 +78,7 @@ internal class Touch_WebSocket_Log_Plguin : PluginBase, IWebSocketHandshakedPlug switch (e.DataFrame.Opcode) { case WSDataType.Cont: - m_logger.Info($"TCP_WebSocket:收到中间数据,长度为:{e.DataFrame.PayloadLength}"); + m_logger.Info($"TCP_WebSocket:收到中间数据,长度为:{e.DataFrame.PayloadData.Length}"); return; @@ -78,11 +94,11 @@ internal class Touch_WebSocket_Log_Plguin : PluginBase, IWebSocketHandshakedPlug case WSDataType.Binary: if (e.DataFrame.FIN) { - m_logger.Info($"TCP_WebSocket:收到二进制数据,长度为:{e.DataFrame.PayloadLength}"); + m_logger.Info($"TCP_WebSocket:收到二进制数据,长度为:{e.DataFrame.PayloadData.Length}"); } else { - m_logger.Info($"TCP_WebSocket:收到未结束的二进制数据,长度为:{e.DataFrame.PayloadLength}"); + m_logger.Info($"TCP_WebSocket:收到未结束的二进制数据,长度为:{e.DataFrame.PayloadData.Length}"); } return; diff --git a/examples/Unity3d/UnityServerConsoleApp_All/TouchServer/UnityRpcStore.cs b/examples/Unity3d/UnityServerConsoleApp_All/TouchServer/UnityRpcStore.cs index d4d039849..3cef80a08 100644 --- a/examples/Unity3d/UnityServerConsoleApp_All/TouchServer/UnityRpcStore.cs +++ b/examples/Unity3d/UnityServerConsoleApp_All/TouchServer/UnityRpcStore.cs @@ -1,3 +1,15 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + using System.ComponentModel; using TouchSocket.Core; using TouchSocket.Dmtp.Rpc; diff --git a/examples/Unity3d/UnityServerConsoleApp_All/UnityServerConsoleApp_All.csproj b/examples/Unity3d/UnityServerConsoleApp_All/UnityServerConsoleApp_All.csproj index 75aa9b331..ac08ba95a 100644 --- a/examples/Unity3d/UnityServerConsoleApp_All/UnityServerConsoleApp_All.csproj +++ b/examples/Unity3d/UnityServerConsoleApp_All/UnityServerConsoleApp_All.csproj @@ -1,4 +1,4 @@ - + Exe @@ -7,8 +7,8 @@ enable - - + + diff --git a/examples/UpdateProjectReferences.ps1 b/examples/UpdateProjectReferences.ps1 new file mode 100644 index 000000000..75cb0d59d --- /dev/null +++ b/examples/UpdateProjectReferences.ps1 @@ -0,0 +1,23 @@ +# PowerShell script to update all project files to use centralized package management +# This script removes Version attributes from PackageReference elements + +$projectFiles = Get-ChildItem -Path "." -Recurse -Filter "*.csproj" + +foreach ($project in $projectFiles) { + Write-Host "Processing: $($project.FullName)" + + $content = Get-Content $project.FullName -Raw + + # Remove Version attributes from PackageReference elements + $updatedContent = $content -replace '()', '$1$3' + + # Only update the file if changes were made + if ($content -ne $updatedContent) { + Set-Content -Path $project.FullName -Value $updatedContent -NoNewline + Write-Host "Updated: $($project.Name)" -ForegroundColor Green + } else { + Write-Host "No changes needed: $($project.Name)" -ForegroundColor Yellow + } +} + +Write-Host "All project files have been processed!" -ForegroundColor Cyan \ No newline at end of file diff --git a/examples/Tcp/TcpWaitingClientWinFormsApp/Form1.Designer.cs b/examples/WaitingClient/TcpWaitingClientWinFormsApp/Form1.Designer.cs similarity index 100% rename from examples/Tcp/TcpWaitingClientWinFormsApp/Form1.Designer.cs rename to examples/WaitingClient/TcpWaitingClientWinFormsApp/Form1.Designer.cs diff --git a/examples/Tcp/TcpWaitingClientWinFormsApp/Form1.cs b/examples/WaitingClient/TcpWaitingClientWinFormsApp/Form1.cs similarity index 83% rename from examples/Tcp/TcpWaitingClientWinFormsApp/Form1.cs rename to examples/WaitingClient/TcpWaitingClientWinFormsApp/Form1.cs index 532509320..deaaa0219 100644 --- a/examples/Tcp/TcpWaitingClientWinFormsApp/Form1.cs +++ b/examples/WaitingClient/TcpWaitingClientWinFormsApp/Form1.cs @@ -11,7 +11,6 @@ //------------------------------------------------------------------------------ using System.Text; -using System.Threading.Tasks; using TouchSocket.Core; using TouchSocket.Sockets; @@ -19,19 +18,18 @@ namespace TcpWaitingClientWinFormsApp; public partial class Form1 : Form { + private CancellationTokenSource cts; + + private TcpClient m_tcpClient; + + private TcpService m_tcpService; + public Form1() { this.InitializeComponent(); this.Load += this.Form1_Load; } - private TcpService m_tcpService; - private async void Form1_Load(object? sender, EventArgs e) - { - this.m_tcpService = await CreateService(); - - this.UpdateServiceButtonUI(); - } private static async Task CreateService() { var service = new TcpService(); @@ -53,48 +51,9 @@ public partial class Form1 : Form return service; } - private TcpClient m_tcpClient; - - private async Task IsConnected() + private async void button1_Click(object sender, EventArgs e) { - try - { - if (this.m_tcpClient?.Online == true) - { - return; - } - this.m_tcpClient.SafeDispose(); - this.m_tcpClient = new TcpClient(); - - this.m_tcpClient.Received = async (client, e) => - { - //此处不能await,否则也会导致死锁 - _ = Task.Run(async () => - { - var waitingClient = client.CreateWaitingClient(new WaitingOptions()); - - var bytes = await waitingClient.SendThenReturnAsync("hello"); - }); - - await Task.CompletedTask; - }; - - await this.m_tcpClient.SetupAsync(new TouchSocketConfig() - .ConfigurePlugins(a => - { - a.Add(typeof(ITcpReceivedPlugin), (ReceivedDataEventArgs e) => - { - Console.WriteLine($"PluginReceivedData:{e.ByteBlock.Span.ToString(Encoding.UTF8)}"); - }); - }) - .SetRemoteIPHost(this.textBox1.Text)); - - await this.m_tcpClient.ConnectAsync(); - } - catch (Exception ex) - { - MessageBox.Show(ex.Message); - } + await this.m_tcpClient?.CloseAsync(); } private async void button2_Click(object sender, EventArgs e) @@ -105,11 +64,8 @@ public partial class Form1 : Form var waitingClient = this.m_tcpClient.CreateWaitingClient(new WaitingOptions()); this.cts = new CancellationTokenSource(5000); - var bytes = await waitingClient.SendThenReturnAsync(this.textBox2.Text.ToUtf8Bytes(), this.cts.Token); - if (bytes != null) - { - MessageBox.Show($"message:{Encoding.UTF8.GetString(bytes)}"); - } + using var responsedData = await waitingClient.SendThenResponseAsync(this.textBox2.Text.ToUtf8Bytes(), this.cts.Token); + MessageBox.Show($"message:{Encoding.UTF8.GetString(responsedData.Memory.Span)}"); } catch (Exception ex) { @@ -126,12 +82,12 @@ public partial class Form1 : Form { FilterFuncAsync = async (response) => { - var byteBlock = response.ByteBlock; + var memory = response.Memory; var requestInfo = response.RequestInfo; - if (byteBlock != null) + if (!memory.IsEmpty) { - var str = byteBlock.Span.ToString(Encoding.UTF8); + var str = memory.Span.ToString(Encoding.UTF8); if (str.Contains(this.textBox4.Text)) { return true; @@ -142,7 +98,7 @@ public partial class Form1 : Form //如果需要在插件中继续处理,在此处触发插件 - await this.m_tcpClient.PluginManager.RaiseAsync(typeof(ITcpReceivedPlugin), this.m_tcpClient, new ReceivedDataEventArgs(byteBlock, requestInfo)).ConfigureAwait(false); + await this.m_tcpClient.PluginManager.RaiseAsync(typeof(ITcpReceivedPlugin), this.m_tcpClient, new ReceivedDataEventArgs(memory, requestInfo)).ConfigureAwait(false); } } return false; @@ -150,12 +106,9 @@ public partial class Form1 : Form }); this.cts = new CancellationTokenSource(500000); - var bytes = await waitingClient.SendThenReturnAsync(this.textBox3.Text.ToUtf8Bytes(), this.cts.Token); + using var responsedData = await waitingClient.SendThenResponseAsync(this.textBox3.Text.ToUtf8Bytes(), this.cts.Token); - if (bytes != null) - { - MessageBox.Show($"message:{Encoding.UTF8.GetString(bytes)}"); - } + MessageBox.Show($"message:{Encoding.UTF8.GetString(responsedData.Memory.Span)}"); } catch (Exception ex) { @@ -163,17 +116,6 @@ public partial class Form1 : Form } } - private async void button1_Click(object sender, EventArgs e) - { - await this.m_tcpClient?.CloseAsync(); - } - - private CancellationTokenSource cts; - private void button5_Click(object sender, EventArgs e) - { - this.cts?.Cancel(); - } - private void button4_Click(object sender, EventArgs e) { this.m_tcpService?.Dispose(); @@ -181,6 +123,60 @@ public partial class Form1 : Form this.UpdateServiceButtonUI(); } + private void button5_Click(object sender, EventArgs e) + { + this.cts?.Cancel(); + } + + private async void Form1_Load(object? sender, EventArgs e) + { + this.m_tcpService = await CreateService(); + + this.UpdateServiceButtonUI(); + } + + private async Task IsConnected() + { + try + { + if (this.m_tcpClient?.Online == true) + { + return; + } + await this.m_tcpClient.CloseAsync(); + this.m_tcpClient = new TcpClient(); + + this.m_tcpClient.Received = async (client, e) => + { + //此处不能await,否则也会导致死锁 + _ = Task.Run(async () => + { + var waitingClient = client.CreateWaitingClient(new WaitingOptions()); + + using var bytes = await waitingClient.SendThenResponseAsync("hello"); + }); + + await Task.CompletedTask; + }; + + await this.m_tcpClient.SetupAsync(new TouchSocketConfig() + .ConfigurePlugins(a => + { + a.Add(typeof(ITcpReceivedPlugin), (ReceivedDataEventArgs e) => + { + Console.WriteLine($"PluginReceivedData:{e.Memory.Span.ToString(Encoding.UTF8)}"); + }); + }) + .SetRemoteIPHost(this.textBox1.Text)); + + await this.m_tcpClient.ConnectAsync(); + } + catch (Exception ex) + { + MessageBox.Show(ex.Message); + } + } + private void UpdateServiceButtonUI() { if (this.m_tcpService == null) @@ -205,11 +201,11 @@ internal class MyPlugin1 : PluginBase, ITcpReceivedPlugin public async Task OnTcpReceived(ITcpSession client, ReceivedDataEventArgs e) { - this.m_logger.Info($"Plugin:{e.ByteBlock.ToString()}"); + this.m_logger.Info($"Plugin:{e.Memory.Span.ToUtf8String()}"); if (client is ITcpSessionClient sessionClient) { - await sessionClient.SendAsync(e.ByteBlock.Memory); + await sessionClient.SendAsync(e.Memory); } } } \ No newline at end of file diff --git a/examples/Tcp/TcpWaitingClientWinFormsApp/Form1.resx b/examples/WaitingClient/TcpWaitingClientWinFormsApp/Form1.resx similarity index 100% rename from examples/Tcp/TcpWaitingClientWinFormsApp/Form1.resx rename to examples/WaitingClient/TcpWaitingClientWinFormsApp/Form1.resx diff --git a/examples/Tcp/TcpWaitingClientWinFormsApp/Program.cs b/examples/WaitingClient/TcpWaitingClientWinFormsApp/Program.cs similarity index 100% rename from examples/Tcp/TcpWaitingClientWinFormsApp/Program.cs rename to examples/WaitingClient/TcpWaitingClientWinFormsApp/Program.cs diff --git a/examples/WaitingClient/TcpWaitingClientWinFormsApp/TcpWaitingClientWinFormsApp.csproj b/examples/WaitingClient/TcpWaitingClientWinFormsApp/TcpWaitingClientWinFormsApp.csproj new file mode 100644 index 000000000..092f6a341 --- /dev/null +++ b/examples/WaitingClient/TcpWaitingClientWinFormsApp/TcpWaitingClientWinFormsApp.csproj @@ -0,0 +1,15 @@ + + + + Exe + net9.0-windows + enable + true + enable + + + + + + + \ No newline at end of file diff --git a/examples/WaitingClient/WaitingClientConsoleApp/Program.cs b/examples/WaitingClient/WaitingClientConsoleApp/Program.cs new file mode 100644 index 000000000..4c2ae9d62 --- /dev/null +++ b/examples/WaitingClient/WaitingClientConsoleApp/Program.cs @@ -0,0 +1,172 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.Text; +using TouchSocket.Core; +using TouchSocket.Sockets; + +namespace WaitingClientConsoleApp; + +internal class Program +{ + private static void Main(string[] args) + { + Console.WriteLine("Hello, World!"); + } + + + + private static async Task TcpService_WaitingClient() + { + #region Tcp服务器WaitingClient示例 + var service = new TcpService(); + await service.StartAsync(7789);//启动服务器 + + //在服务器中,找到指定Id的会话客户端 + if (service.TryGetClient("targetId", out var tcpSessionClient)) + { + //调用CreateWaitingClient获取到IWaitingClient的对象。 + var waitClient = tcpSessionClient.CreateWaitingClient(new WaitingOptions() + { + FilterFunc = response => //设置用于筛选的fun委托,当返回为true时,才会响应返回 + { + return true; + } + }); + + //发送数据,并等待响应 + using (var responsedData = await waitClient.SendThenResponseAsync(Encoding.UTF8.GetBytes("RRQM"))) + { + var memory = responsedData.Memory; + Console.WriteLine(memory.Span.ToString(Encoding.UTF8)); + + var requestInfo = responsedData.RequestInfo;//如果适配器收到数据后,返回的并不是字节,而是IRequestInfo对象时 + } + } + #endregion + } + private static async Task TcpClient_WaitingClient() + { + #region Tcp客户端WaitingClient示例 + var client = new TcpClient(); + await client.ConnectAsync("tcp://127.0.0.1:7789"); + + //调用CreateWaitingClient获取到IWaitingClient的对象。 + + #region WaitingClient设置筛选函数 + var waitingClient = client.CreateWaitingClient(new WaitingOptions() + { + FilterFunc = response => //设置用于筛选的fun委托,当返回为true时,才会响应返回 + { + var requestInfo = response.RequestInfo; + var memory = response.Memory; + + //这里可以根据服务端返回的信息,判断是否响应 + return true; + } + }); + #endregion + + + + //发送数据,并等待响应 + using (var responsedData = await waitingClient.SendThenResponseAsync(Encoding.UTF8.GetBytes("RRQM"))) + { + var memory = responsedData.Memory; + Console.WriteLine(memory.Span.ToString(Encoding.UTF8)); + + var requestInfo = responsedData.RequestInfo;//如果适配器收到数据后,返回的并不是字节,而是IRequestInfo对象时 + } + #endregion + + #region WaitingClient默认超时示例 + using var responsed = await waitingClient.SendThenResponseAsync("hello");//默认5秒超时 + #endregion + + #region WaitingClient指定超时取消示例 + var cts = new CancellationTokenSource(); + + _ = Task.Run(async () => + { + await Task.Delay(5000); + cts.Cancel();//5秒后取消等待,不再等待服务端的消息。这里模拟的是客户端主动取消等待 + }); + using var responsed2 = await waitingClient.SendThenResponseAsync("hello", cts.Token); + #endregion + + } + + private static async Task WaitingClient_FilterFuncRetrigger() + { + var m_tcpClient = new TcpClient(); + await m_tcpClient.ConnectAsync("tcp://127.0.0.1:7789"); + + #region WaitingClient筛选函数重新触发插件 + var waitingClient = m_tcpClient.CreateWaitingClient(new WaitingOptions() + { + FilterFuncAsync = async (response) => + { + var byteBlock = response.Memory; + var requestInfo = response.RequestInfo; + + if (true)//如果满足某个条件,则响应WaitingClient + { + return true; + } + else + { + //否则 + //数据不符合要求,waitingClient继续等待 + //如果需要在插件中继续处理,在此处触发插件 + + await m_tcpClient.PluginManager.RaiseAsync(typeof(ITcpReceivedPlugin), m_tcpClient, new ReceivedDataEventArgs(byteBlock, requestInfo)); + + return false; + } + } + }); + #endregion + } + + private static void WaitingClient_WrongUsageInReceived() + { + #region WaitingClient错误使用时机示例 + var tcpClient = new TcpClient(); + + tcpClient.Received = async (client, e) => + { + var waitingClient = client.CreateWaitingClient(new WaitingOptions()); + + //这里将导致死锁 + await waitingClient.SendThenResponseAsync("hello"); + }; + #endregion + } + + private static void WaitingClient_CorrectUsageInReceived() + { + var m_tcpClient = new TcpClient(); + + #region WaitingClient正确使用时机示例 + m_tcpClient.Received = async (client, e) => + { + //此处不能await,否则也会导致死锁 + _ = Task.Run(async () => + { + var waitingClient = client.CreateWaitingClient(new WaitingOptions()); + + await waitingClient.SendThenResponseAsync("hello"); + }); + }; + #endregion + } +} diff --git a/examples/WaitingClient/WaitingClientConsoleApp/WaitingClientConsoleApp.csproj b/examples/WaitingClient/WaitingClientConsoleApp/WaitingClientConsoleApp.csproj new file mode 100644 index 000000000..e08d18664 --- /dev/null +++ b/examples/WaitingClient/WaitingClientConsoleApp/WaitingClientConsoleApp.csproj @@ -0,0 +1,13 @@ + + + + Exe + net9.0 + enable + enable + + + + + + diff --git a/examples/WebApi/AotWebApiConsoleApp/AotWebApiConsoleApp.csproj b/examples/WebApi/AotWebApiConsoleApp/AotWebApiConsoleApp.csproj index 6b9763acd..85f27cf1a 100644 --- a/examples/WebApi/AotWebApiConsoleApp/AotWebApiConsoleApp.csproj +++ b/examples/WebApi/AotWebApiConsoleApp/AotWebApiConsoleApp.csproj @@ -1,4 +1,4 @@ - + net9.0 @@ -10,10 +10,10 @@ - - - - + + + + diff --git a/examples/WebApi/AotWebApiConsoleApp/Program.cs b/examples/WebApi/AotWebApiConsoleApp/Program.cs index fa3923f55..1e5f94fd6 100644 --- a/examples/WebApi/AotWebApiConsoleApp/Program.cs +++ b/examples/WebApi/AotWebApiConsoleApp/Program.cs @@ -1,10 +1,21 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + using System.Text.Json.Serialization; using TouchSocket.Core; using TouchSocket.Http; using TouchSocket.Rpc; using TouchSocket.Sockets; using TouchSocket.WebApi; -using TouchSocket.WebApi.Swagger; namespace WebApiConsoleApp { @@ -14,7 +25,7 @@ namespace WebApiConsoleApp { var builder = Host.CreateApplicationBuilder(args); - builder.Services.ConfigureContainer(a => + builder.Services.ConfigureContainer(a => { a.AddRpcStore(store => { @@ -31,13 +42,15 @@ namespace WebApiConsoleApp { a.UseTcpSessionCheckClear(); - a.UseWebApi() - .ConfigureConverter(converter => + a.UseWebApi(options => { - converter.Clear(); - converter.AddSystemTextJsonSerializerFormatter(options => + options.ConfigureConverter(converter => { - options.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default); + converter.Clear(); + converter.AddSystemTextJsonSerializerFormatter(options => + { + options.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default); + }); }); }); @@ -95,7 +108,7 @@ namespace WebApiConsoleApp [WebApi(Method = HttpMethodType.Post)] public MySum TestPost(MyClass myClass) { - m_logger.Info("TestPost"); + this.m_logger.Info("TestPost"); return new MySum() { A = myClass.A, B = myClass.B, Sum = myClass.A + myClass.B }; } diff --git a/examples/WebApi/DispatchProxyWebApiConsoleApp/DispatchProxyWebApiConsoleApp.csproj b/examples/WebApi/DispatchProxyWebApiConsoleApp/DispatchProxyWebApiConsoleApp.csproj index cf26d6c94..69accf85a 100644 --- a/examples/WebApi/DispatchProxyWebApiConsoleApp/DispatchProxyWebApiConsoleApp.csproj +++ b/examples/WebApi/DispatchProxyWebApiConsoleApp/DispatchProxyWebApiConsoleApp.csproj @@ -8,6 +8,6 @@ - + diff --git a/examples/WebApi/DispatchProxyWebApiConsoleApp/Program.cs b/examples/WebApi/DispatchProxyWebApiConsoleApp/Program.cs index 4caaf6c1f..cee80fca3 100644 --- a/examples/WebApi/DispatchProxyWebApiConsoleApp/Program.cs +++ b/examples/WebApi/DispatchProxyWebApiConsoleApp/Program.cs @@ -18,6 +18,7 @@ namespace DispatchProxyWebApiConsoleApp; internal class Program { + #region WebApi客户端DispatchProxy代理调用 /// /// 使用DispatchProxy生成调用代理 /// @@ -37,8 +38,10 @@ internal class Program Console.WriteLine(sum); } } + #endregion } +#region WebApi客户端DispatchProxy代理类实现 /// /// 新建一个类,继承WebApiDispatchProxy,亦或者RpcDispatchProxy基类。 /// 然后实现抽象方法,主要是能获取到调用的IRpcClient派生接口。 @@ -59,7 +62,7 @@ internal class MyWebApiDispatchProxy : WebApiDispatchProxy .SetRemoteIPHost("127.0.0.1:7789") .ConfigurePlugins(a => { - a.UseTcpReconnection(); + a.UseReconnection(); })); client.ConnectAsync(); Console.WriteLine("连接成功"); @@ -71,11 +74,14 @@ internal class MyWebApiDispatchProxy : WebApiDispatchProxy return this.m_client; } } +#endregion +#region WebApi客户端DispatchProxy代理接口定义 internal interface IApiServer { [Router("ApiServer/[action]ab")] [Router("ApiServer/[action]")] [WebApi(Method = HttpMethodType.Get)] int Sum(int a, int b); -} \ No newline at end of file +} +#endregion \ No newline at end of file diff --git a/examples/WebApi/WebApiClientApp/Program.cs b/examples/WebApi/WebApiClientApp/Program.cs index 67e060cb7..d17fe3502 100644 --- a/examples/WebApi/WebApiClientApp/Program.cs +++ b/examples/WebApi/WebApiClientApp/Program.cs @@ -12,6 +12,7 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using TouchSocket.Core; using TouchSocket.Http; @@ -29,12 +30,12 @@ internal class Program await TestHttpClient(); //此处预设一个30秒超时的请求设定。 - var invokeOption_30s = new InvokeOption() + var invokeOption_30s = new InvokeOption(30 * 1000) { - FeedbackType = FeedbackType.WaitInvoke, - Timeout = 30 * 1000 + FeedbackType = FeedbackType.WaitInvoke }; + #region WebApi客户端GET调用 { var client = await CreateWebApiClient(); @@ -42,50 +43,166 @@ internal class Program request.Method = HttpMethodType.Get; request.Querys = new KeyValuePair[] { new KeyValuePair("a", "10"), new KeyValuePair("b", "20") }; - var sum1 =await client.InvokeTAsync("/ApiServer/Sum", invokeOption_30s, request); + var sum1 = await client.InvokeTAsync("/ApiServer/Sum", invokeOption_30s, request); Console.WriteLine($"Get调用成功,结果:{sum1}"); + } + #endregion + #region WebApi客户端POST调用 + { + var client = await CreateWebApiClient(); var requestForPost = new WebApiRequest(); requestForPost.Method = HttpMethodType.Post; requestForPost.Body = new MyClass() { A = 10, B = 20 }; - var sum2 =await client.InvokeTAsync("/ApiServer/TestPost", invokeOption_30s, requestForPost); + var sum2 = await client.InvokeTAsync("/ApiServer/TestPost", invokeOption_30s, requestForPost); Console.WriteLine($"Post调用成功,结果:{sum2}"); + } + #endregion - var sum3 = client.TestPost(new MyClass() { A = 10, B = 20 }, invokeOption_30s); + #region WebApi客户端使用代理调用 + { + var client = await CreateWebApiClient(); + + var sum3 = await client.TestPostAsync(new MyClass() { A = 10, B = 20 }, invokeOption_30s); Console.WriteLine($"代理调用成功,结果:{sum3}"); } + #endregion + #region WebApi客户端字符串模板调用 { var client = await CreateWebApiClientSlim(); - var sum1 =await client.InvokeTAsync("GET:/ApiServer/Sum?a={0}&b={1}", invokeOption_30s, 10, 20); + var sum1 = await client.InvokeTAsync("GET:/ApiServer/Sum?a={0}&b={1}", invokeOption_30s, 10, 20); Console.WriteLine($"Get调用成功,结果:{sum1}"); - var sum2 =await client.InvokeTAsync("POST:/ApiServer/TestPost", invokeOption_30s, new MyClass() { A = 10, B = 20 }); + var sum2 = await client.InvokeTAsync("POST:/ApiServer/TestPost", invokeOption_30s, new MyClass() { A = 10, B = 20 }); Console.WriteLine($"Post调用成功,结果:{sum2}"); - var sum3 = client.TestPost(new MyClass() { A = 10, B = 20 }, invokeOption_30s); + var sum3 = client.TestPostAsync(new MyClass() { A = 10, B = 20 }, invokeOption_30s); Console.WriteLine($"代理调用成功,结果:{sum3}"); } + #endregion Console.ReadKey(); } + #region WebApi客户端使用原生HttpClient + private static async Task UseNativeHttpClient() + { + using var httpClient = new HttpClient(); + var result = await httpClient.GetStringAsync("http://localhost:7789/apiserver/sum?a=10&b=20"); + Console.WriteLine(result); // 输出: 30 + } + #endregion + + #region WebApi客户端代码生成使用代理 + private static async Task UseGeneratedProxy() + { + var client = new WebApiClient(); + await client.SetupAsync(new TouchSocketConfig() + .SetRemoteIPHost("127.0.0.1:7789")); + await client.ConnectAsync(); + + // 直接使用生成的扩展方法 + var sum = await client.SumAsync(10, 20); + Console.WriteLine($"结果: {sum}"); + } + #endregion + + #region WebApi客户端使用插件 + private static async Task UsePluginExample() + { + var client = new WebApiClient(); + await client.SetupAsync(new TouchSocketConfig() + .SetRemoteIPHost("127.0.0.1:7789") + .ConfigurePlugins(a => + { + a.Add(); + })); + } + #endregion + + #region WebApi客户端异常处理 + private static async Task ExceptionHandlingExample() + { + try + { + var client = new WebApiClient(); + await client.SetupAsync(new TouchSocketConfig() + .SetRemoteIPHost("127.0.0.1:7789")); + await client.ConnectAsync(); + + var request = new WebApiRequest(); + request.Method = HttpMethodType.Get; + + var result = await client.InvokeTAsync("/apiserver/sum?a=10&b=20", null, request); + Console.WriteLine($"结果: {result}"); + } + catch (TimeoutException) + { + Console.WriteLine("请求超时"); + } + catch (Exception ex) + { + Console.WriteLine($"请求失败: {ex.Message}"); + } + } + #endregion + + #region WebApi客户端设置超时 + private static async Task SetTimeoutExample() + { + var client = new WebApiClient(); + await client.SetupAsync(new TouchSocketConfig() + .SetRemoteIPHost("127.0.0.1:7789")); + await client.ConnectAsync(); + + // 为单次调用设置超时 + var invokeOption = new InvokeOption(30 * 1000) + { + FeedbackType = FeedbackType.WaitInvoke + }; + + var request = new WebApiRequest(); + request.Method = HttpMethodType.Get; + var result = await client.InvokeTAsync("/apiserver/sum", invokeOption, request); + } + #endregion + + #region WebApi客户端使用CancellationToken + private static async Task UseCancellationTokenExample() + { + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + + try + { + var httpClient = new HttpClient(); + var result = await httpClient.GetStringAsync("/apiserver/sum?a=10&b=20", cts.Token); + } + catch (OperationCanceledException) + { + Console.WriteLine("操作已取消"); + } + } + #endregion + private static async Task TestHttpClient() { - var client = new HttpClient(); + using var client = new HttpClient(); await client.ConnectAsync("127.0.0.1:7789"); Console.WriteLine("连接成功"); - var responseString = await client.GetStringAsync("/ApiServer/Sum?a=10&b=20"); + using var cts = new CancellationTokenSource(1000 * 10); + var responseString = await client.GetStringAsync("/ApiServer/Sum?a=10&b=20", cts.Token); return client; } + #region WebApi客户端创建WebApiClient private static async Task CreateWebApiClient() { - var client = new WebApiClient(); + using var client = new WebApiClient(); await client.SetupAsync(new TouchSocketConfig() .SetRemoteIPHost("127.0.0.1:7789") .ConfigurePlugins(a => @@ -96,10 +213,12 @@ internal class Program Console.WriteLine("连接成功"); return client; } + #endregion + #region WebApi客户端创建WebApiClientSlim private static async Task CreateWebApiClientSlim() { - var client = new WebApiClientSlim(new System.Net.Http.HttpClient()); + using var client = new WebApiClientSlim(new System.Net.Http.HttpClient()); await client.SetupAsync(new TouchSocketConfig() .SetRemoteIPHost("http://127.0.0.1:7789") .ConfigurePlugins(a => @@ -109,7 +228,9 @@ internal class Program return client; } + #endregion + #region WebApi客户端插件拦截 /// /// 此处可以做WebApi的请求之前和之后的拦截。 /// @@ -137,4 +258,5 @@ internal class Program await e.InvokeNext(); } } + #endregion } \ No newline at end of file diff --git a/examples/WebApi/WebApiClientApp/WebApiClientApp.csproj b/examples/WebApi/WebApiClientApp/WebApiClientApp.csproj index ca4468d08..e0bb5af14 100644 --- a/examples/WebApi/WebApiClientApp/WebApiClientApp.csproj +++ b/examples/WebApi/WebApiClientApp/WebApiClientApp.csproj @@ -1,4 +1,4 @@ - + Exe net9.0 @@ -7,6 +7,6 @@ - + \ No newline at end of file diff --git a/examples/WebApi/WebApiPerformanceConsoleApp/Controllers/ApiServer.cs b/examples/WebApi/WebApiPerformanceConsoleApp/Controllers/ApiServer.cs index 2a869837f..aa25febdd 100644 --- a/examples/WebApi/WebApiPerformanceConsoleApp/Controllers/ApiServer.cs +++ b/examples/WebApi/WebApiPerformanceConsoleApp/Controllers/ApiServer.cs @@ -1,3 +1,15 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + using TouchSocket.Rpc; using TouchSocket.WebApi; diff --git a/examples/WebApi/WebApiPerformanceConsoleApp/Controllers/ApiServerController.cs b/examples/WebApi/WebApiPerformanceConsoleApp/Controllers/ApiServerController.cs index 1eae6cab0..6f9b1d1b5 100644 --- a/examples/WebApi/WebApiPerformanceConsoleApp/Controllers/ApiServerController.cs +++ b/examples/WebApi/WebApiPerformanceConsoleApp/Controllers/ApiServerController.cs @@ -1,3 +1,15 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + using Microsoft.AspNetCore.Mvc; namespace WebApplication2.Controllers diff --git a/examples/WebApi/WebApiPerformanceConsoleApp/Controllers/MyFastEndpoint.cs b/examples/WebApi/WebApiPerformanceConsoleApp/Controllers/MyFastEndpoint.cs index af8e52378..d7c6dadae 100644 --- a/examples/WebApi/WebApiPerformanceConsoleApp/Controllers/MyFastEndpoint.cs +++ b/examples/WebApi/WebApiPerformanceConsoleApp/Controllers/MyFastEndpoint.cs @@ -1,9 +1,16 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + using FastEndpoints; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace HttpPerformanceConsoleApp.Controllers { @@ -11,16 +18,16 @@ namespace HttpPerformanceConsoleApp.Controllers { public override void Configure() { - Get("/ApiServer/Add"); - AllowAnonymous(); + this.Get("/ApiServer/Add"); + this.AllowAnonymous(); } public override async Task HandleAsync(MyRequest req, CancellationToken ct) { - await this.Send.OkAsync(new MyResponse + await this.SendAsync(new() { Result = req.A + req.B - }, ct); + }); } } diff --git a/examples/WebApi/WebApiPerformanceConsoleApp/Program.cs b/examples/WebApi/WebApiPerformanceConsoleApp/Program.cs index 49761d776..2748dd69a 100644 --- a/examples/WebApi/WebApiPerformanceConsoleApp/Program.cs +++ b/examples/WebApi/WebApiPerformanceConsoleApp/Program.cs @@ -1,3 +1,15 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + using FastEndpoints; using HttpPerformanceConsoleApp.Controllers; using Microsoft.AspNetCore.Builder; @@ -13,7 +25,7 @@ namespace HttpPerformanceConsoleApp { internal class Program { - static void Main(string[] args) + private static void Main(string[] args) { StartAspnetHttp(); StartTouchSokcetHttp(); @@ -21,9 +33,9 @@ namespace HttpPerformanceConsoleApp Console.ReadKey(); } - static void StartTouchSokcetHttp() + private static void StartTouchSokcetHttp() { - IHost host = Host.CreateDefaultBuilder() + var host = Host.CreateDefaultBuilder() .ConfigureServices(services => { services.AddServiceHostedService(config => @@ -56,7 +68,7 @@ namespace HttpPerformanceConsoleApp ConsoleLogger.Default.Info($"TouchSokcetHttp已启动,请求连接:http://127.0.0.1:7790/ApiServer/Add?a=10&b=20"); } - static void StartAspnetHttp() + private static void StartAspnetHttp() { var builder = WebApplication.CreateBuilder(); @@ -73,7 +85,7 @@ namespace HttpPerformanceConsoleApp } - static void StartFastEndpoints() + private static void StartFastEndpoints() { var builder = WebApplication.CreateBuilder(); builder.Logging.ClearProviders(); diff --git a/examples/WebApi/WebApiPerformanceConsoleApp/WebApiPerformanceConsoleApp.csproj b/examples/WebApi/WebApiPerformanceConsoleApp/WebApiPerformanceConsoleApp.csproj index 6a5990ead..4d1925130 100644 --- a/examples/WebApi/WebApiPerformanceConsoleApp/WebApiPerformanceConsoleApp.csproj +++ b/examples/WebApi/WebApiPerformanceConsoleApp/WebApiPerformanceConsoleApp.csproj @@ -1,4 +1,4 @@ - + Exe @@ -13,10 +13,10 @@ - - - - - + + + + + diff --git a/examples/WebApi/WebApiServer/ApiServer.cs b/examples/WebApi/WebApiServer/ApiServer.cs new file mode 100644 index 000000000..613269bbe --- /dev/null +++ b/examples/WebApi/WebApiServer/ApiServer.cs @@ -0,0 +1,368 @@ +//------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +//------------------------------------------------------------------------------ + +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using TouchSocket.Core; +using TouchSocket.Http; +using TouchSocket.Rpc; +using TouchSocket.Sockets; +using TouchSocket.WebApi; + +namespace WebApiServerApp; + +[CustomResponse] +public partial class ApiServer : SingletonRpcServer +{ + private readonly ILog m_logger; + + public ApiServer(ILog logger) + { + this.m_logger = logger; + } + + #region 多路由配置 + + [Router("[api]/[action]ab")]//此路由会以"/ApiServer/Sumab"实现 + [Router("[api]/[action]")]//此路由会以"/ApiServer/Sum"实现 + [WebApi(Method = HttpMethodType.Get)] + public int Sum(int a, int b) + { + return a + b; + } + + #endregion 多路由配置 + + #region 自定义路由路径 + + [Router("/api/custom/calculate")] + [WebApi(Method = HttpMethodType.Get)] + public int Calculate(int x, int y) + { + return x * y; + } + + #endregion 自定义路由路径 + + #region 类级别路由 + + // 注意:此示例展示类级别路由的概念 + // 实际使用时应在类声明上添加 [Router] 特性 + + #endregion 类级别路由 + + #region 正则路由示例 + + [RegexRouter(@"^/products/\d+$")] + [WebApi(Method = HttpMethodType.Get)] + public string GetProductById(IWebApiCallContext callContext) + { + var url = callContext.HttpContext.Request.RelativeURL; + var id = url.Split('/').Last(); + return $"Product ID: {id}"; + } + + #endregion 正则路由示例 + + #region 使用调用上下文 + + [WebApi(Method = HttpMethodType.Get)] + public int GetCallContext(IWebApiCallContext callContext, int a, int b) + { + if (callContext.Caller is IHttpSessionClient httpSessionClient) + { + Console.WriteLine($"IP:{httpSessionClient.IP}"); + Console.WriteLine($"Port:{httpSessionClient.Port}"); + Console.WriteLine($"Id:{httpSessionClient.Id}"); + } + + //http内容 + var httpContext = callContext.HttpContext; + + //http请求 + var request = httpContext.Request; + //http响应 + var response = httpContext.Response; + return a + b; + } + + #endregion 使用调用上下文 + + #region 返回对象 + + [WebApi(Method = HttpMethodType.Get)] + public MyClass GetMyClass() + { + return new MyClass() + { + A = 1, + B = 2 + }; + } + + #endregion 返回对象 + + #region Post传参 + + [WebApi(Method = HttpMethodType.Post)] + public int TestPost(MyClass myClass) + { + return myClass.A + myClass.B; + } + + #endregion Post传参 + + #region FromQuery传参 + + [WebApi(Method = HttpMethodType.Get)] + public int SumFromQuery([FromQuery(Name = "aa")] int a, [FromQuery] int b) + { + return a + b; + } + + #endregion FromQuery传参 + + #region FromForm传参 + + [WebApi(Method = HttpMethodType.Get)] + public int SumFromForm([FromForm] int a, [FromForm] int b) + { + return a + b; + } + + #endregion FromForm传参 + + #region FromHeader传参 + + [WebApi(Method = HttpMethodType.Get)] + public int SumFromHeader([FromHeader] int a, [FromHeader] int b) + { + return a + b; + } + + #endregion FromHeader传参 + + #region 启用跨域 + + [EnableCors("cors")]//使用跨域 + [WebApi(Method = HttpMethodType.Get)] + public int SumCallContext(IWebApiCallContext callContext, int a, int b) + { + return a + b; + } + + #endregion 启用跨域 + + #region 文件下载 + + /// + /// 使用调用上下文,响应文件下载。 + /// + /// + [WebApi(Method = HttpMethodType.Get)] + public async Task DownloadFile(IWebApiCallContext callContext, string id) + { + if (id == "rrqm") + { + await callContext.HttpContext.Response.FromFileAsync(new FileInfo(@"D:\System\Windows.iso"), callContext.HttpContext.Request); + return "ok"; + } + return "id不正确。"; + } + + #endregion 文件下载 + + #region 获取请求体 + + /// + /// 使用调用上下文,获取实际请求体。 + /// + /// + [WebApi(Method = HttpMethodType.Post)] + [Router("[api]/[action]")] + public async Task PostContent(IWebApiCallContext callContext) + { + if (callContext.Caller is IHttpSessionClient socketClient) + { + this.m_logger.Info($"IP:{socketClient.IP},Port:{socketClient.Port}");//获取Ip和端口 + } + + var content = await callContext.HttpContext.Request.GetContentAsync(); + this.m_logger.Info($"共计:{content.Length}"); + + return "ok"; + } + + #endregion 获取请求体 + + #region 上传多个小文件 + + /// + /// 使用调用上下文,上传多个小文件。 + /// + /// + [WebApi(Method = HttpMethodType.Post)] + public async Task UploadMultiFile(IWebApiCallContext callContext, string id) + { + var formFiles = await callContext.HttpContext.Request.GetFormCollectionAsync(); + if (formFiles != null) + { + foreach (var item in formFiles.Files) + { + Console.WriteLine($"fileName={item.FileName},name={item.Name}"); + + //写入实际数据 + File.WriteAllBytes(item.FileName, item.Data.ToArray()); + } + } + return "ok"; + } + + #endregion 上传多个小文件 + + #region 上传大文件 + + /// + /// 使用调用上下文,上传大文件。 + /// + /// + [WebApi(Method = HttpMethodType.Post)] + public async Task UploadBigFile(IWebApiCallContext callContext) + { + using (var stream = File.Create("text.file")) + { + await callContext.HttpContext.Request.ReadCopyToAsync(stream); + } + Console.WriteLine("ok"); + return "ok"; + } + + #endregion 上传大文件 + + #region WebApi调用上下文自定义响应状态码 + + [WebApi(Method = HttpMethodType.Get)] + public async Task CustomResponse(IWebApiCallContext callContext) + { + var response = callContext.HttpContext.Response; + + response.SetStatus(201, "Created"); + response.SetContent("Resource created successfully"); + + await response.AnswerAsync(); + } + + #endregion WebApi调用上下文自定义响应状态码 + + #region WebApi调用上下文设置响应头 + + [WebApi(Method = HttpMethodType.Get)] + public string SetHeaders(IWebApiCallContext callContext) + { + var response = callContext.HttpContext.Response; + + response.Headers["X-Custom-Header"] = "CustomValue"; + response.Headers["X-Timestamp"] = DateTime.Now.ToString(); + + return "Headers set"; + } + + #endregion WebApi调用上下文设置响应头 + + #region WebApi调用上下文设置ContentType + + [WebApi(Method = HttpMethodType.Get)] + public string PlainText(IWebApiCallContext callContext) + { + callContext.HttpContext.Response.ContentType = "text/plain"; + return "This is plain text"; + } + + #endregion WebApi调用上下文设置ContentType + + #region WebApi调用上下文流式响应 + + [WebApi(Method = HttpMethodType.Get)] + public async Task StreamResponse(IWebApiCallContext callContext) + { + var response = callContext.HttpContext.Response; + + response.ContentType = "text/plain"; + response.SetStatus(200, "OK"); + + // 流式写入数据 + for (int i = 0; i < 100; i++) + { + var data = $"Line {i}\n"; + await response.WriteAsync(System.Text.Encoding.UTF8.GetBytes(data)); + await Task.Delay(100); // 模拟延迟 + } + } + + #endregion WebApi调用上下文流式响应 + + #region WebApi调用上下文获取客户端信息和请求详情 + + [WebApi(Method = HttpMethodType.Get)] + public object GetRequestInfo(IWebApiCallContext callContext) + { + if (callContext.Caller is IHttpSessionClient httpSessionClient) + { + var request = callContext.HttpContext.Request; + + return new + { + ClientIp = httpSessionClient.IP, + ClientPort = httpSessionClient.Port, + ClientId = httpSessionClient.Id, + Method = request.Method, + Url = request.URL, + RelativeUrl = request.RelativeURL, + UserAgent = request.Headers["User-Agent"], + ContentType = request.Headers["Content-Type"] + }; + } + return "无法获取客户端信息"; + } + + #endregion WebApi调用上下文获取客户端信息和请求详情 + + #region WebApi调用上下文条件响应 + + [WebApi(Method = HttpMethodType.Get)] + public async Task ConditionalResponse(IWebApiCallContext callContext, string type) + { + var response = callContext.HttpContext.Response; + + if (type == "json") + { + response.ContentType = "application/json"; + response.SetContent("{\"message\":\"Hello JSON\"}"); + } + else if (type == "xml") + { + response.ContentType = "application/xml"; + response.SetContent("Hello XML"); + } + else + { + response.SetStatus(400, "Bad Request"); + response.SetContent("Invalid type parameter"); + } + + await response.AnswerAsync(); + } + + #endregion WebApi调用上下文条件响应 +} \ No newline at end of file diff --git a/examples/WebApi/WebApiServer/AuthenticationPlugin.cs b/examples/WebApi/WebApiServer/AuthenticationPlugin.cs new file mode 100644 index 000000000..2db4b4979 --- /dev/null +++ b/examples/WebApi/WebApiServer/AuthenticationPlugin.cs @@ -0,0 +1,42 @@ +//------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +//------------------------------------------------------------------------------ + +using System.Threading.Tasks; +using TouchSocket.Core; +using TouchSocket.Http; + +namespace WebApiServerApp; + +/// +/// 鉴权插件 +/// +internal class AuthenticationPlugin : PluginBase, IHttpPlugin +{ + public async Task OnHttpRequest(IHttpSessionClient client, HttpContextEventArgs e) + { + //string aut = e.Context.Request.Headers["Authorization"]; + //if (aut.IsNullOrEmpty())//授权header为空 + //{ + // await e.Context.Response + // .SetStatus(401, "授权失败") + // .AnswerAsync(); + // return; + //} + + //伪代码,假设使用jwt解码成功。那就执行下一个插件。 + //if (jwt.Encode(aut)) + //{ + // 此处可以做一些授权相关的。 + //} + await e.InvokeNext(); + } +} diff --git a/examples/WebApi/WebApiServer/CustomResponseAttribute.cs b/examples/WebApi/WebApiServer/CustomResponseAttribute.cs new file mode 100644 index 000000000..dcb73b9cd --- /dev/null +++ b/examples/WebApi/WebApiServer/CustomResponseAttribute.cs @@ -0,0 +1,56 @@ +//------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +//------------------------------------------------------------------------------ + +using System; +using System.Threading.Tasks; +using TouchSocket.Rpc; + +namespace WebApiServerApp; + +public class CustomResponseAttribute : RpcActionFilterAttribute +{ + public override async Task ExecutedAsync(ICallContext callContext, object[] parameters, InvokeResult invokeResult, Exception exception) + { + if (invokeResult.Status == InvokeStatus.Success) + { + //正常情况,直接返回数据 + return invokeResult; + } + else + { + //非正常情况,可以获取到错误信息 + var errorMsg = invokeResult.Message; + //和异常 + var errorException = exception; + + return new InvokeResult() + { + Status = InvokeStatus.Success, + Result = exception?.Message ?? "自定义结果" + }; + } + + + //if (callContext is IWebApiCallContext webApiCallContext) + //{ + // var response = webApiCallContext.HttpContext.Response; + // if (!response.Responsed) + // { + // response.SetStatus(500, "自定义状态码"); + // await response.AnswerAsync(); + // } + + //} + + //return invokeResult; + } +} diff --git a/examples/WebApi/WebApiServer/DemoApiServer.cs b/examples/WebApi/WebApiServer/DemoApiServer.cs new file mode 100644 index 000000000..28deca0e1 --- /dev/null +++ b/examples/WebApi/WebApiServer/DemoApiServer.cs @@ -0,0 +1,42 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using TouchSocket.Core; +using TouchSocket.Rpc; +using TouchSocket.WebApi; + +namespace WebApiServerApp; + +#region 定义WebApi服务 + +public partial class DemoApiServer : SingletonRpcServer +{ + private readonly ILog m_logger; + + public DemoApiServer(ILog logger) + { + this.m_logger = logger; + } + + [WebApi(Method = HttpMethodType.Get)] + public int Sum(int a, int b) + { + return a + b; + } +} + +#endregion 定义WebApi服务 + + + + + diff --git a/src/TouchSocket.Core/Reflection/InstanceCreater.cs b/examples/WebApi/WebApiServer/MyActionFilterAttribute.cs similarity index 63% rename from src/TouchSocket.Core/Reflection/InstanceCreater.cs rename to examples/WebApi/WebApiServer/MyActionFilterAttribute.cs index 3608eef1c..d5fecaa5a 100644 --- a/src/TouchSocket.Core/Reflection/InstanceCreater.cs +++ b/examples/WebApi/WebApiServer/MyActionFilterAttribute.cs @@ -11,22 +11,16 @@ //------------------------------------------------------------------------------ using System; +using System.Threading.Tasks; +using TouchSocket.Rpc; -namespace TouchSocket.Core; +namespace WebApiServerApp; -/// -/// 实例生成 -/// -public static class InstanceCreater +internal class MyActionFilterAttribute : RpcActionFilterAttribute { - /// - /// 根据对象类型创建对象实例 - /// - /// 对象类型 - /// - /// - public static object Create(Type key, object[] args) + public override Task ExecutedAsync(ICallContext callContext, object[] parameters, InvokeResult invokeResult, Exception exception) { - return Activator.CreateInstance(key, args); + Console.WriteLine(invokeResult.Message); + return base.ExecutedAsync(callContext, parameters, invokeResult, exception); } } \ No newline at end of file diff --git a/examples/WebApi/WebApiServer/MyApiServer.cs b/examples/WebApi/WebApiServer/MyApiServer.cs new file mode 100644 index 000000000..6a26e491d --- /dev/null +++ b/examples/WebApi/WebApiServer/MyApiServer.cs @@ -0,0 +1,77 @@ +//------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +//------------------------------------------------------------------------------ + +using System; +using System.Threading; +using System.Threading.Tasks; +using TouchSocket.Core; +using TouchSocket.Http; +using TouchSocket.Rpc; +using TouchSocket.Sockets; +using TouchSocket.WebApi; + +namespace WebApiServerApp; + +public class MyApiServer : SingletonRpcServer +{ + private readonly ILog m_logger; + + public MyApiServer(ILog logger) + { + this.m_logger = logger; + } + + #region WebApi调用上下文WebSocket升级 + + [Router("/[api]/[action]")] + [WebApi(Method = HttpMethodType.Get)] + public async Task ConnectWS(IWebApiCallContext callContext) + { + if (callContext.Caller is HttpSessionClient sessionClient) + { + var result = await sessionClient.SwitchProtocolToWebSocketAsync(callContext.HttpContext); + if (!result.IsSuccess) + { + Console.WriteLine(result.Message); + return; + } + + this.m_logger.Info("WS通过WebApi连接"); + var webSocket = sessionClient.WebSocket; + + webSocket.AllowAsyncRead = true; + + while (true) + { + using (var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30))) + { + using (var receiveResult = await webSocket.ReadAsync(tokenSource.Token)) + { + if (receiveResult.IsCompleted) + { + //webSocket已断开 + return; + } + + //webSocket数据帧 + var dataFrame = receiveResult.DataFrame; + + //此处可以处理数据 + } + } + + } + } + } + + #endregion WebApi调用上下文WebSocket升级 +} diff --git a/src/TouchSocket.Shared/InternalVisible.cs b/examples/WebApi/WebApiServer/MyClass.cs similarity index 86% rename from src/TouchSocket.Shared/InternalVisible.cs rename to examples/WebApi/WebApiServer/MyClass.cs index f408e399a..c37b8bee1 100644 --- a/src/TouchSocket.Shared/InternalVisible.cs +++ b/examples/WebApi/WebApiServer/MyClass.cs @@ -10,8 +10,10 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -#if DEBUG -using System.Runtime.CompilerServices; +namespace WebApiServerApp; -[assembly: InternalsVisibleTo("XUnitTestProject")] -#endif \ No newline at end of file +public class MyClass +{ + public int A { get; set; } + public int B { get; set; } +} diff --git a/src/TouchSocket/Plugins/HeartbeatPlugin.cs b/examples/WebApi/WebApiServer/MySerializerFormatter.cs similarity index 59% rename from src/TouchSocket/Plugins/HeartbeatPlugin.cs rename to examples/WebApi/WebApiServer/MySerializerFormatter.cs index 7aada40e4..00c8d74a8 100644 --- a/src/TouchSocket/Plugins/HeartbeatPlugin.cs +++ b/examples/WebApi/WebApiServer/MySerializerFormatter.cs @@ -12,21 +12,23 @@ using System; using TouchSocket.Core; +using TouchSocket.Http; -namespace TouchSocket.Sockets; +namespace WebApiServerApp; -/// -/// 心跳插件的基类,定义了心跳插件的基本结构和功能。 -/// -public abstract class HeartbeatPlugin : PluginBase +internal class MySerializerFormatter : ISerializerFormatter { - /// - /// 最大失败次数,默认3。 - /// - public int MaxFailCount { get; set; } = 3; + public int Order { get; set; } - /// - /// 心跳间隔。默认3秒。 - /// - public TimeSpan Tick { get; set; } = TimeSpan.FromSeconds(3); -} \ No newline at end of file + public bool TryDeserialize(HttpContext state, in string source, Type targetType, out object target) + { + //反序列化 + throw new NotImplementedException(); + } + + public bool TrySerialize(HttpContext state, in TObject target, out string source) + { + //序列化 + throw new NotImplementedException(); + } +} diff --git a/examples/WebApi/WebApiServer/Program.cs b/examples/WebApi/WebApiServer/Program.cs index 80fea902d..90a9e637e 100644 --- a/examples/WebApi/WebApiServer/Program.cs +++ b/examples/WebApi/WebApiServer/Program.cs @@ -12,7 +12,6 @@ using System; using System.IO; -using System.Threading; using System.Threading.Tasks; using TouchSocket.Core; using TouchSocket.Http; @@ -37,9 +36,11 @@ internal class Program store.RegisterServer();//注册服务 #if DEBUG + #region WebApi服务端代码生成 //下列代码,会生成客户端的调用代码。 var codeString = store.GetProxyCodes("WebApiProxy", typeof(WebApiAttribute)); File.WriteAllText("../../../WebApiProxy.cs", codeString); + #endregion #endif }); @@ -69,24 +70,28 @@ internal class Program a.Add(); - a.UseWebApi() - .ConfigureConverter(converter => + a.UseWebApi(options => { - //配置转换器 + options.ConfigureConverter(converter => + { + //配置转换器 - //converter.Clear();//可以选择性的清空现有所有格式化器 + //converter.Clear();//可以选择性的清空现有所有格式化器 - //添加Json格式化器,可以自定义Json的一些设置 - converter.AddJsonSerializerFormatter(new Newtonsoft.Json.JsonSerializerSettings() { Formatting = Newtonsoft.Json.Formatting.None }); + //添加Json格式化器,可以自定义Json的一些设置 + converter.AddJsonSerializerFormatter(new Newtonsoft.Json.JsonSerializerSettings() { Formatting = Newtonsoft.Json.Formatting.None }); - //添加Xml格式化器 - //converter.AddXmlSerializerFormatter(); + //添加Xml格式化器 + //converter.AddXmlSerializerFormatter(); - //converter.Add(new MySerializerFormatter()); + //converter.Add(new MySerializerFormatter()); + }); }); - a.UseSwagger()//使用Swagger页面 - .UseLaunchBrowser();//启动浏览器 + a.UseSwagger(options => + { + options.UseLaunchBrowser(); + });//使用Swagger页面 //此插件是http的兜底插件,应该最后添加。作用是当所有路由不匹配时返回404.且内部也会处理Option请求。可以更好的处理来自浏览器的跨域探测。 a.UseDefaultHttpServicePlugin(); @@ -95,324 +100,37 @@ internal class Program Console.WriteLine("以下连接用于测试webApi"); Console.WriteLine($"使用:http://127.0.0.1:7789/ApiServer/Sum?a=10&b=20"); - - //var client = new HttpClient(); - //await client.ConnectAsync("http://127.0.0.1:7789");//先做连接 - - ////创建一个请求 - //var request = new HttpRequest(); - //request.InitHeaders() - // .AddHeader(HttpHeaders.Accept, "text/plain") - // .SetUrl("/ApiServer/GetString") - // .SetHost(client.RemoteIPHost.Host) - // .AsGet(); - - - //using (var responseResult = await client.RequestAsync(request, 1000 * 10)) - //{ - // var response = responseResult.Response; - - // string str = await response.GetBodyAsync(); - //} Console.ReadKey(); } -} -[CustomResponse] -public partial class ApiServer : SingletonRpcServer -{ - private readonly ILog m_logger; - - public ApiServer(ILog logger) + private static async Task SimpleStart() { - this.m_logger = logger; - } - - [EnableCors("cors")]//使用跨域 - [Router("[api]/[action]ab")]//此路由会以"/ApiServer/Sumab"实现 - [Router("[api]/[action]")]//此路由会以"/ApiServer/Sum"实现 - [WebApi(Method = HttpMethodType.Get)] - public int Sum(int a, int b) - { - return a + b; - } - - [WebApi(Method = HttpMethodType.Get)] - public int SumCallContext(IWebApiCallContext callContext, int a, int b) - { - if (callContext.Caller is IHttpSessionClient httpSessionClient) - { - Console.WriteLine($"IP:{httpSessionClient.IP}"); - Console.WriteLine($"Port:{httpSessionClient.Port}"); - Console.WriteLine($"Id:{httpSessionClient.Id}"); - } - - //http内容 - var httpContext = callContext.HttpContext; - - //http请求 - var request = httpContext.Request; - //http响应 - var response = httpContext.Response; - return a + b; - } - - [WebApi(Method = HttpMethodType.Get)] - public MyClass GetMyClass() - { - return new MyClass() - { - A = 1, - B = 2 - }; - } - - [WebApi(Method = HttpMethodType.Post)] - public int TestPost(MyClass myClass) - { - return myClass.A + myClass.B; - } - - /// - /// 使用调用上下文,响应文件下载。 - /// - /// - [WebApi(Method = HttpMethodType.Get)] - public async Task DownloadFile(IWebApiCallContext callContext, string id) - { - if (id == "rrqm") - { - await callContext.HttpContext.Response.FromFileAsync(new FileInfo(@"D:\System\Windows.iso"), callContext.HttpContext.Request); - return "ok"; - } - return "id不正确。"; - } - - /// - /// 使用调用上下文,获取实际请求体。 - /// - /// - [WebApi(Method = HttpMethodType.Post)] - [Router("[api]/[action]")] - public async Task PostContent(IWebApiCallContext callContext) - { - if (callContext.Caller is IHttpSessionClient socketClient) - { - this.m_logger.Info($"IP:{socketClient.IP},Port:{socketClient.Port}");//获取Ip和端口 - } - - var content = await callContext.HttpContext.Request.GetContentAsync(); - this.m_logger.Info($"共计:{content.Length}"); - - return "ok"; - } - - /// - /// 使用调用上下文,上传多个小文件。 - /// - /// - [WebApi(Method = HttpMethodType.Post)] - public async Task UploadMultiFile(IWebApiCallContext callContext, string id) - { - var formFiles = await callContext.HttpContext.Request.GetFormCollectionAsync(); - if (formFiles != null) - { - foreach (var item in formFiles.Files) + #region 启动WebApi服务器 + var service = new HttpService(); + await service.SetupAsync(new TouchSocketConfig() + .SetListenIPHosts(7789) + .ConfigureContainer(a => { - Console.WriteLine($"fileName={item.FileName},name={item.Name}"); - - //写入实际数据 - File.WriteAllBytes(item.FileName, item.Data.ToArray()); - } - } - return "ok"; - } - - /// - /// 使用调用上下文,上传大文件。 - /// - /// - [WebApi(Method = HttpMethodType.Post)] - public async Task UploadBigFile(IWebApiCallContext callContext) - { - using (var stream = File.Create("text.file")) - { - await callContext.HttpContext.Request.ReadCopyToAsync(stream); - } - Console.WriteLine("ok"); - return "ok"; - } - - [WebApi(Method = HttpMethodType.Get)] - public string GetString() - { - Console.WriteLine("GetString"); - return "hello"; - } - - [WebApi(Method = HttpMethodType.Get)] - public int SumFromForm([FromForm] int a, [FromForm] int b) - { - return a + b; - } - - [WebApi(Method = HttpMethodType.Get)] - public int SumFromQuery([FromQuery(Name = "aa")] int a, [FromQuery] int b) - { - return a + b; - } - - [WebApi(Method = HttpMethodType.Get)] - public int SumFromHeader([FromHeader] int a, [FromHeader] int b) - { - return a + b; - } -} - -public class MyApiServer : SingletonRpcServer -{ - private readonly ILog m_logger; - - public MyApiServer(ILog logger) - { - this.m_logger = logger; - } - - [Router("/[api]/[action]")] - [WebApi(Method = HttpMethodType.Get)] - public async Task ConnectWS(IWebApiCallContext callContext) - { - if (callContext.Caller is HttpSessionClient sessionClient) - { - var result = await sessionClient.SwitchProtocolToWebSocketAsync(callContext.HttpContext); - if (!result.IsSuccess) - { - Console.WriteLine(result.Message); - return; - } - - this.m_logger.Info("WS通过WebApi连接"); - var webSocket = sessionClient.WebSocket; - - webSocket.AllowAsyncRead = true; - - while (true) - { - using (var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30))) + a.AddConsoleLogger(); + a.AddRpcStore(store => { - using (var receiveResult = await webSocket.ReadAsync(tokenSource.Token)) - { - if (receiveResult.IsCompleted) - { - //webSocket已断开 - return; - } - - //webSocket数据帧 - var dataFrame = receiveResult.DataFrame; - - //此处可以处理数据 - } - } - - } - } - } -} - - -public class CustomResponseAttribute : RpcActionFilterAttribute -{ - public override async Task ExecutedAsync(ICallContext callContext, object[] parameters, InvokeResult invokeResult, Exception exception) - { - if (invokeResult.Status == InvokeStatus.Success) - { - //正常情况,直接返回数据 - return invokeResult; - } - else - { - //非正常情况,可以获取到错误信息 - var errorMsg = invokeResult.Message; - //和异常 - var errorException = exception; - - return new InvokeResult() + store.RegisterServer();//注册服务 + }); + }) + .ConfigurePlugins(a => { - Status = InvokeStatus.Success, - Result = exception?.Message ?? "自定义结果" - }; - } + a.UseTcpSessionCheckClear(); + a.UseWebApi(); - //if (callContext is IWebApiCallContext webApiCallContext) - //{ - // var response = webApiCallContext.HttpContext.Response; - // if (!response.Responsed) - // { - // response.SetStatus(500, "自定义状态码"); - // await response.AnswerAsync(); - // } + //此插件是http的兜底插件,应该最后添加。作用是当所有路由不匹配时返回404.且内部也会处理Option请求。可以更好的处理来自浏览器的跨域探测。 + a.UseDefaultHttpServicePlugin(); + })); + await service.StartAsync(); - //} + Console.WriteLine("以下连接用于测试webApi"); + Console.WriteLine($"使用:http://127.0.0.1:7789/ApiServer/Sum?a=10&b=20"); + #endregion - //return invokeResult; } } - -public class MyClass -{ - public int A { get; set; } - public int B { get; set; } -} - -/// -/// 鉴权插件 -/// -internal class AuthenticationPlugin : PluginBase, IHttpPlugin -{ - public async Task OnHttpRequest(IHttpSessionClient client, HttpContextEventArgs e) - { - //string aut = e.Context.Request.Headers["Authorization"]; - //if (aut.IsNullOrEmpty())//授权header为空 - //{ - // await e.Context.Response - // .SetStatus(401, "授权失败") - // .AnswerAsync(); - // return; - //} - - //伪代码,假设使用jwt解码成功。那就执行下一个插件。 - //if (jwt.Encode(aut)) - //{ - // 此处可以做一些授权相关的。 - //} - await e.InvokeNext(); - } -} - -internal class MySerializerFormatter : ISerializerFormatter -{ - public int Order { get; set; } - - public bool TryDeserialize(HttpContext state, in string source, Type targetType, out object target) - { - //反序列化 - throw new NotImplementedException(); - } - - public bool TrySerialize(HttpContext state, in object target, out string source) - { - //序列化 - throw new NotImplementedException(); - } -} - -internal class MyActionFilterAttribute : RpcActionFilterAttribute -{ - public override Task ExecutedAsync(ICallContext callContext, object[] parameters, InvokeResult invokeResult, Exception exception) - { - Console.WriteLine(invokeResult.Message); - return base.ExecutedAsync(callContext, parameters, invokeResult, exception); - } -} \ No newline at end of file diff --git a/examples/WebApi/WebApiServer/WebApiProxy.cs b/examples/WebApi/WebApiServer/WebApiProxy.cs index d413d2c4b..e6d5dff11 100644 --- a/examples/WebApi/WebApiServer/WebApiProxy.cs +++ b/examples/WebApi/WebApiServer/WebApiProxy.cs @@ -3,1059 +3,1071 @@ */ #pragma warning disable using System; +using TouchSocket.Core; +using TouchSocket.Sockets; +using TouchSocket.Rpc; using System.Collections.Generic; using System.Diagnostics; using System.Text; using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Http; -using TouchSocket.Rpc; -using TouchSocket.Sockets; using TouchSocket.WebApi; namespace WebApiProxy { - public interface IApiServer : TouchSocket.Rpc.IRemoteServer - { - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - System.Int32 Sum(System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default); - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - Task SumAsync(System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default); - - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - System.Int32 SumCallContext(System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default); - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - Task SumCallContextAsync(System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default); - - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - MyClass GetMyClass(IInvokeOption invokeOption = default); - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - Task GetMyClassAsync(IInvokeOption invokeOption = default); - - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - System.Int32 TestPost(MyClass myClass, IInvokeOption invokeOption = default); - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - Task TestPostAsync(MyClass myClass, IInvokeOption invokeOption = default); - - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - System.String DownloadFile(System.String id, IInvokeOption invokeOption = default); - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - Task DownloadFileAsync(System.String id, IInvokeOption invokeOption = default); - - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - System.String PostContent(IInvokeOption invokeOption = default); - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - Task PostContentAsync(IInvokeOption invokeOption = default); - - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - System.String UploadMultiFile(System.String id, IInvokeOption invokeOption = default); - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - Task UploadMultiFileAsync(System.String id, IInvokeOption invokeOption = default); - - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - System.String UploadBigFile(IInvokeOption invokeOption = default); - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - Task UploadBigFileAsync(IInvokeOption invokeOption = default); - - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - System.String GetString(IInvokeOption invokeOption = default); - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - Task GetStringAsync(IInvokeOption invokeOption = default); - - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - System.Int32 SumFromForm(System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default); - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - Task SumFromFormAsync(System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default); - - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - System.Int32 SumFromQuery(System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default); - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - Task SumFromQueryAsync(System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default); - - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - System.Int32 SumFromHeader(System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default); - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - Task SumFromHeaderAsync(System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default); - - } - public class ApiServer : IApiServer - { - public ApiServer(IRpcClient client) - { - this.Client = client; - } - public IRpcClient Client { get; private set; } - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - public System.Int32 Sum(System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default) - { - if (this.Client == null) - { - throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); - } - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Get; - _request.Headers = null; - _request.Querys = new KeyValuePair[] { new KeyValuePair("a", a.ToString()), new KeyValuePair("b", b.ToString()) }; - _request.Forms = null; - return (System.Int32)this.Client.Invoke("/apiserver/sumab", typeof(System.Int32), invokeOption, _request); - - } - /// - ///无注释信息 - /// - public async Task SumAsync(System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default) - { - if (this.Client == null) - { - throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); - } - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Get; - _request.Headers = null; - _request.Querys = new KeyValuePair[] { new KeyValuePair("a", a.ToString()), new KeyValuePair("b", b.ToString()) }; - _request.Forms = null; - return (System.Int32)await this.Client.InvokeAsync("/apiserver/sumab", typeof(System.Int32), invokeOption, _request); - - } - - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - public System.Int32 SumCallContext(System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default) - { - if (this.Client == null) - { - throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); - } - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Get; - _request.Headers = null; - _request.Querys = new KeyValuePair[] { new KeyValuePair("a", a.ToString()), new KeyValuePair("b", b.ToString()) }; - _request.Forms = null; - return (System.Int32)this.Client.Invoke("/apiserver/sumcallcontext", typeof(System.Int32), invokeOption, _request); - - } - /// - ///无注释信息 - /// - public async Task SumCallContextAsync(System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default) - { - if (this.Client == null) - { - throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); - } - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Get; - _request.Headers = null; - _request.Querys = new KeyValuePair[] { new KeyValuePair("a", a.ToString()), new KeyValuePair("b", b.ToString()) }; - _request.Forms = null; - return (System.Int32)await this.Client.InvokeAsync("/apiserver/sumcallcontext", typeof(System.Int32), invokeOption, _request); - - } - - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - public MyClass GetMyClass(IInvokeOption invokeOption = default) - { - if (this.Client == null) - { - throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); - } - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Get; - _request.Headers = null; - _request.Querys = null; - _request.Forms = null; - return (MyClass)this.Client.Invoke("/apiserver/getmyclass", typeof(MyClass), invokeOption, _request); - - } - /// - ///无注释信息 - /// - public async Task GetMyClassAsync(IInvokeOption invokeOption = default) - { - if (this.Client == null) - { - throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); - } - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Get; - _request.Headers = null; - _request.Querys = null; - _request.Forms = null; - return (MyClass)await this.Client.InvokeAsync("/apiserver/getmyclass", typeof(MyClass), invokeOption, _request); - - } - - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - public System.Int32 TestPost(MyClass myClass, IInvokeOption invokeOption = default) - { - if (this.Client == null) - { - throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); - } - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Post; - _request.Headers = null; - _request.Querys = null; - _request.Forms = null; - _request.Body = myClass; - return (System.Int32)this.Client.Invoke("/apiserver/testpost", typeof(System.Int32), invokeOption, _request); - - } - /// - ///无注释信息 - /// - public async Task TestPostAsync(MyClass myClass, IInvokeOption invokeOption = default) - { - if (this.Client == null) - { - throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); - } - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Post; - _request.Headers = null; - _request.Querys = null; - _request.Forms = null; - _request.Body = myClass; - return (System.Int32)await this.Client.InvokeAsync("/apiserver/testpost", typeof(System.Int32), invokeOption, _request); - - } - - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - public System.String DownloadFile(System.String id, IInvokeOption invokeOption = default) - { - if (this.Client == null) - { - throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); - } - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Get; - _request.Headers = new KeyValuePair[] { new KeyValuePair("Accept", "text/plain") }; - _request.Querys = new KeyValuePair[] { new KeyValuePair("id", id?.ToString()) }; - _request.Forms = null; - return (System.String)this.Client.Invoke("/apiserver/downloadfile", typeof(System.String), invokeOption, _request); - - } - /// - ///无注释信息 - /// - public async Task DownloadFileAsync(System.String id, IInvokeOption invokeOption = default) - { - if (this.Client == null) - { - throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); - } - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Get; - _request.Headers = new KeyValuePair[] { new KeyValuePair("Accept", "text/plain") }; - _request.Querys = new KeyValuePair[] { new KeyValuePair("id", id?.ToString()) }; - _request.Forms = null; - return (System.String)await this.Client.InvokeAsync("/apiserver/downloadfile", typeof(System.String), invokeOption, _request); - - } - - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - public System.String PostContent(IInvokeOption invokeOption = default) - { - if (this.Client == null) - { - throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); - } - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Post; - _request.Headers = new KeyValuePair[] { new KeyValuePair("Accept", "text/plain") }; - _request.Querys = null; - _request.Forms = null; - return (System.String)this.Client.Invoke("/apiserver/postcontent", typeof(System.String), invokeOption, _request); - - } - /// - ///无注释信息 - /// - public async Task PostContentAsync(IInvokeOption invokeOption = default) - { - if (this.Client == null) - { - throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); - } - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Post; - _request.Headers = new KeyValuePair[] { new KeyValuePair("Accept", "text/plain") }; - _request.Querys = null; - _request.Forms = null; - return (System.String)await this.Client.InvokeAsync("/apiserver/postcontent", typeof(System.String), invokeOption, _request); - - } - - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - public System.String UploadMultiFile(System.String id, IInvokeOption invokeOption = default) - { - if (this.Client == null) - { - throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); - } - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Post; - _request.Headers = new KeyValuePair[] { new KeyValuePair("Accept", "text/plain") }; - _request.Querys = new KeyValuePair[] { new KeyValuePair("id", id?.ToString()) }; - _request.Forms = null; - return (System.String)this.Client.Invoke("/apiserver/uploadmultifile", typeof(System.String), invokeOption, _request); - - } - /// - ///无注释信息 - /// - public async Task UploadMultiFileAsync(System.String id, IInvokeOption invokeOption = default) - { - if (this.Client == null) - { - throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); - } - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Post; - _request.Headers = new KeyValuePair[] { new KeyValuePair("Accept", "text/plain") }; - _request.Querys = new KeyValuePair[] { new KeyValuePair("id", id?.ToString()) }; - _request.Forms = null; - return (System.String)await this.Client.InvokeAsync("/apiserver/uploadmultifile", typeof(System.String), invokeOption, _request); - - } - - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - public System.String UploadBigFile(IInvokeOption invokeOption = default) - { - if (this.Client == null) - { - throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); - } - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Post; - _request.Headers = new KeyValuePair[] { new KeyValuePair("Accept", "text/plain") }; - _request.Querys = null; - _request.Forms = null; - return (System.String)this.Client.Invoke("/apiserver/uploadbigfile", typeof(System.String), invokeOption, _request); - - } - /// - ///无注释信息 - /// - public async Task UploadBigFileAsync(IInvokeOption invokeOption = default) - { - if (this.Client == null) - { - throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); - } - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Post; - _request.Headers = new KeyValuePair[] { new KeyValuePair("Accept", "text/plain") }; - _request.Querys = null; - _request.Forms = null; - return (System.String)await this.Client.InvokeAsync("/apiserver/uploadbigfile", typeof(System.String), invokeOption, _request); - - } - - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - public System.String GetString(IInvokeOption invokeOption = default) - { - if (this.Client == null) - { - throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); - } - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Get; - _request.Headers = new KeyValuePair[] { new KeyValuePair("Accept", "text/plain") }; - _request.Querys = null; - _request.Forms = null; - return (System.String)this.Client.Invoke("/apiserver/getstring", typeof(System.String), invokeOption, _request); - - } - /// - ///无注释信息 - /// - public async Task GetStringAsync(IInvokeOption invokeOption = default) - { - if (this.Client == null) - { - throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); - } - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Get; - _request.Headers = new KeyValuePair[] { new KeyValuePair("Accept", "text/plain") }; - _request.Querys = null; - _request.Forms = null; - return (System.String)await this.Client.InvokeAsync("/apiserver/getstring", typeof(System.String), invokeOption, _request); - - } - - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - public System.Int32 SumFromForm(System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default) - { - if (this.Client == null) - { - throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); - } - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Get; - _request.Headers = null; - _request.Querys = null; - _request.Forms = new KeyValuePair[] { new KeyValuePair("a", a.ToString()), new KeyValuePair("b", b.ToString()) }; - return (System.Int32)this.Client.Invoke("/apiserver/sumfromform", typeof(System.Int32), invokeOption, _request); - - } - /// - ///无注释信息 - /// - public async Task SumFromFormAsync(System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default) - { - if (this.Client == null) - { - throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); - } - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Get; - _request.Headers = null; - _request.Querys = null; - _request.Forms = new KeyValuePair[] { new KeyValuePair("a", a.ToString()), new KeyValuePair("b", b.ToString()) }; - return (System.Int32)await this.Client.InvokeAsync("/apiserver/sumfromform", typeof(System.Int32), invokeOption, _request); - - } - - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - public System.Int32 SumFromQuery(System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default) - { - if (this.Client == null) - { - throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); - } - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Get; - _request.Headers = null; - _request.Querys = new KeyValuePair[] { new KeyValuePair("aa", a.ToString()), new KeyValuePair("b", b.ToString()) }; - _request.Forms = null; - return (System.Int32)this.Client.Invoke("/apiserver/sumfromquery", typeof(System.Int32), invokeOption, _request); - - } - /// - ///无注释信息 - /// - public async Task SumFromQueryAsync(System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default) - { - if (this.Client == null) - { - throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); - } - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Get; - _request.Headers = null; - _request.Querys = new KeyValuePair[] { new KeyValuePair("aa", a.ToString()), new KeyValuePair("b", b.ToString()) }; - _request.Forms = null; - return (System.Int32)await this.Client.InvokeAsync("/apiserver/sumfromquery", typeof(System.Int32), invokeOption, _request); - - } - - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - public System.Int32 SumFromHeader(System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default) - { - if (this.Client == null) - { - throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); - } - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Get; - _request.Headers = new KeyValuePair[] { new KeyValuePair("a", a.ToString()), new KeyValuePair("b", b.ToString()) }; - _request.Querys = null; - _request.Forms = null; - return (System.Int32)this.Client.Invoke("/apiserver/sumfromheader", typeof(System.Int32), invokeOption, _request); - - } - /// - ///无注释信息 - /// - public async Task SumFromHeaderAsync(System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default) - { - if (this.Client == null) - { - throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); - } - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Get; - _request.Headers = new KeyValuePair[] { new KeyValuePair("a", a.ToString()), new KeyValuePair("b", b.ToString()) }; - _request.Querys = null; - _request.Forms = null; - return (System.Int32)await this.Client.InvokeAsync("/apiserver/sumfromheader", typeof(System.Int32), invokeOption, _request); - - } - - } - public static class ApiServerExtensions - { - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - public static System.Int32 Sum(this TClient client, System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default) where TClient : - TouchSocket.WebApi.IWebApiClientBase - { - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Get; - _request.Headers = null; - _request.Querys = new KeyValuePair[] { new KeyValuePair("a", a.ToString()), new KeyValuePair("b", b.ToString()) }; - _request.Forms = null; - return (System.Int32)client.Invoke("/apiserver/sumab", typeof(System.Int32), invokeOption, _request); - - } - /// - ///无注释信息 - /// - public static async Task SumAsync(this TClient client, System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default) where TClient : - TouchSocket.WebApi.IWebApiClientBase - { - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Get; - _request.Headers = null; - _request.Querys = new KeyValuePair[] { new KeyValuePair("a", a.ToString()), new KeyValuePair("b", b.ToString()) }; - _request.Forms = null; - return (System.Int32)await client.InvokeAsync("/apiserver/sumab", typeof(System.Int32), invokeOption, _request); - - } - - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - public static System.Int32 SumCallContext(this TClient client, System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default) where TClient : - TouchSocket.WebApi.IWebApiClientBase - { - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Get; - _request.Headers = null; - _request.Querys = new KeyValuePair[] { new KeyValuePair("a", a.ToString()), new KeyValuePair("b", b.ToString()) }; - _request.Forms = null; - return (System.Int32)client.Invoke("/apiserver/sumcallcontext", typeof(System.Int32), invokeOption, _request); - - } - /// - ///无注释信息 - /// - public static async Task SumCallContextAsync(this TClient client, System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default) where TClient : - TouchSocket.WebApi.IWebApiClientBase - { - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Get; - _request.Headers = null; - _request.Querys = new KeyValuePair[] { new KeyValuePair("a", a.ToString()), new KeyValuePair("b", b.ToString()) }; - _request.Forms = null; - return (System.Int32)await client.InvokeAsync("/apiserver/sumcallcontext", typeof(System.Int32), invokeOption, _request); - - } - - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - public static MyClass GetMyClass(this TClient client, IInvokeOption invokeOption = default) where TClient : - TouchSocket.WebApi.IWebApiClientBase - { - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Get; - _request.Headers = null; - _request.Querys = null; - _request.Forms = null; - return (MyClass)client.Invoke("/apiserver/getmyclass", typeof(MyClass), invokeOption, _request); - - } - /// - ///无注释信息 - /// - public static async Task GetMyClassAsync(this TClient client, IInvokeOption invokeOption = default) where TClient : - TouchSocket.WebApi.IWebApiClientBase - { - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Get; - _request.Headers = null; - _request.Querys = null; - _request.Forms = null; - return (MyClass)await client.InvokeAsync("/apiserver/getmyclass", typeof(MyClass), invokeOption, _request); - - } - - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - public static System.Int32 TestPost(this TClient client, MyClass myClass, IInvokeOption invokeOption = default) where TClient : - TouchSocket.WebApi.IWebApiClientBase - { - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Post; - _request.Headers = null; - _request.Querys = null; - _request.Forms = null; - _request.Body = myClass; - return (System.Int32)client.Invoke("/apiserver/testpost", typeof(System.Int32), invokeOption, _request); - - } - /// - ///无注释信息 - /// - public static async Task TestPostAsync(this TClient client, MyClass myClass, IInvokeOption invokeOption = default) where TClient : - TouchSocket.WebApi.IWebApiClientBase - { - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Post; - _request.Headers = null; - _request.Querys = null; - _request.Forms = null; - _request.Body = myClass; - return (System.Int32)await client.InvokeAsync("/apiserver/testpost", typeof(System.Int32), invokeOption, _request); - - } - - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - public static System.String DownloadFile(this TClient client, System.String id, IInvokeOption invokeOption = default) where TClient : - TouchSocket.WebApi.IWebApiClientBase - { - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Get; - _request.Headers = new KeyValuePair[] { new KeyValuePair("Accept", "text/plain") }; - _request.Querys = new KeyValuePair[] { new KeyValuePair("id", id?.ToString()) }; - _request.Forms = null; - return (System.String)client.Invoke("/apiserver/downloadfile", typeof(System.String), invokeOption, _request); - - } - /// - ///无注释信息 - /// - public static async Task DownloadFileAsync(this TClient client, System.String id, IInvokeOption invokeOption = default) where TClient : - TouchSocket.WebApi.IWebApiClientBase - { - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Get; - _request.Headers = new KeyValuePair[] { new KeyValuePair("Accept", "text/plain") }; - _request.Querys = new KeyValuePair[] { new KeyValuePair("id", id?.ToString()) }; - _request.Forms = null; - return (System.String)await client.InvokeAsync("/apiserver/downloadfile", typeof(System.String), invokeOption, _request); - - } - - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - public static System.String PostContent(this TClient client, IInvokeOption invokeOption = default) where TClient : - TouchSocket.WebApi.IWebApiClientBase - { - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Post; - _request.Headers = new KeyValuePair[] { new KeyValuePair("Accept", "text/plain") }; - _request.Querys = null; - _request.Forms = null; - return (System.String)client.Invoke("/apiserver/postcontent", typeof(System.String), invokeOption, _request); - - } - /// - ///无注释信息 - /// - public static async Task PostContentAsync(this TClient client, IInvokeOption invokeOption = default) where TClient : - TouchSocket.WebApi.IWebApiClientBase - { - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Post; - _request.Headers = new KeyValuePair[] { new KeyValuePair("Accept", "text/plain") }; - _request.Querys = null; - _request.Forms = null; - return (System.String)await client.InvokeAsync("/apiserver/postcontent", typeof(System.String), invokeOption, _request); - - } - - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - public static System.String UploadMultiFile(this TClient client, System.String id, IInvokeOption invokeOption = default) where TClient : - TouchSocket.WebApi.IWebApiClientBase - { - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Post; - _request.Headers = new KeyValuePair[] { new KeyValuePair("Accept", "text/plain") }; - _request.Querys = new KeyValuePair[] { new KeyValuePair("id", id?.ToString()) }; - _request.Forms = null; - return (System.String)client.Invoke("/apiserver/uploadmultifile", typeof(System.String), invokeOption, _request); - - } - /// - ///无注释信息 - /// - public static async Task UploadMultiFileAsync(this TClient client, System.String id, IInvokeOption invokeOption = default) where TClient : - TouchSocket.WebApi.IWebApiClientBase - { - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Post; - _request.Headers = new KeyValuePair[] { new KeyValuePair("Accept", "text/plain") }; - _request.Querys = new KeyValuePair[] { new KeyValuePair("id", id?.ToString()) }; - _request.Forms = null; - return (System.String)await client.InvokeAsync("/apiserver/uploadmultifile", typeof(System.String), invokeOption, _request); - - } - - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - public static System.String UploadBigFile(this TClient client, IInvokeOption invokeOption = default) where TClient : - TouchSocket.WebApi.IWebApiClientBase - { - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Post; - _request.Headers = new KeyValuePair[] { new KeyValuePair("Accept", "text/plain") }; - _request.Querys = null; - _request.Forms = null; - return (System.String)client.Invoke("/apiserver/uploadbigfile", typeof(System.String), invokeOption, _request); - - } - /// - ///无注释信息 - /// - public static async Task UploadBigFileAsync(this TClient client, IInvokeOption invokeOption = default) where TClient : - TouchSocket.WebApi.IWebApiClientBase - { - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Post; - _request.Headers = new KeyValuePair[] { new KeyValuePair("Accept", "text/plain") }; - _request.Querys = null; - _request.Forms = null; - return (System.String)await client.InvokeAsync("/apiserver/uploadbigfile", typeof(System.String), invokeOption, _request); - - } - - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - public static System.String GetString(this TClient client, IInvokeOption invokeOption = default) where TClient : - TouchSocket.WebApi.IWebApiClientBase - { - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Get; - _request.Headers = new KeyValuePair[] { new KeyValuePair("Accept", "text/plain") }; - _request.Querys = null; - _request.Forms = null; - return (System.String)client.Invoke("/apiserver/getstring", typeof(System.String), invokeOption, _request); - - } - /// - ///无注释信息 - /// - public static async Task GetStringAsync(this TClient client, IInvokeOption invokeOption = default) where TClient : - TouchSocket.WebApi.IWebApiClientBase - { - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Get; - _request.Headers = new KeyValuePair[] { new KeyValuePair("Accept", "text/plain") }; - _request.Querys = null; - _request.Forms = null; - return (System.String)await client.InvokeAsync("/apiserver/getstring", typeof(System.String), invokeOption, _request); - - } - - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - public static System.Int32 SumFromForm(this TClient client, System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default) where TClient : - TouchSocket.WebApi.IWebApiClientBase - { - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Get; - _request.Headers = null; - _request.Querys = null; - _request.Forms = new KeyValuePair[] { new KeyValuePair("a", a.ToString()), new KeyValuePair("b", b.ToString()) }; - return (System.Int32)client.Invoke("/apiserver/sumfromform", typeof(System.Int32), invokeOption, _request); - - } - /// - ///无注释信息 - /// - public static async Task SumFromFormAsync(this TClient client, System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default) where TClient : - TouchSocket.WebApi.IWebApiClientBase - { - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Get; - _request.Headers = null; - _request.Querys = null; - _request.Forms = new KeyValuePair[] { new KeyValuePair("a", a.ToString()), new KeyValuePair("b", b.ToString()) }; - return (System.Int32)await client.InvokeAsync("/apiserver/sumfromform", typeof(System.Int32), invokeOption, _request); - - } - - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - public static System.Int32 SumFromQuery(this TClient client, System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default) where TClient : - TouchSocket.WebApi.IWebApiClientBase - { - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Get; - _request.Headers = null; - _request.Querys = new KeyValuePair[] { new KeyValuePair("aa", a.ToString()), new KeyValuePair("b", b.ToString()) }; - _request.Forms = null; - return (System.Int32)client.Invoke("/apiserver/sumfromquery", typeof(System.Int32), invokeOption, _request); - - } - /// - ///无注释信息 - /// - public static async Task SumFromQueryAsync(this TClient client, System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default) where TClient : - TouchSocket.WebApi.IWebApiClientBase - { - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Get; - _request.Headers = null; - _request.Querys = new KeyValuePair[] { new KeyValuePair("aa", a.ToString()), new KeyValuePair("b", b.ToString()) }; - _request.Forms = null; - return (System.Int32)await client.InvokeAsync("/apiserver/sumfromquery", typeof(System.Int32), invokeOption, _request); - - } - - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - public static System.Int32 SumFromHeader(this TClient client, System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default) where TClient : - TouchSocket.WebApi.IWebApiClientBase - { - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Get; - _request.Headers = new KeyValuePair[] { new KeyValuePair("a", a.ToString()), new KeyValuePair("b", b.ToString()) }; - _request.Querys = null; - _request.Forms = null; - return (System.Int32)client.Invoke("/apiserver/sumfromheader", typeof(System.Int32), invokeOption, _request); - - } - /// - ///无注释信息 - /// - public static async Task SumFromHeaderAsync(this TClient client, System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default) where TClient : - TouchSocket.WebApi.IWebApiClientBase - { - var _request = new WebApiRequest(); - _request.Method = HttpMethodType.Get; - _request.Headers = new KeyValuePair[] { new KeyValuePair("a", a.ToString()), new KeyValuePair("b", b.ToString()) }; - _request.Querys = null; - _request.Forms = null; - return (System.Int32)await client.InvokeAsync("/apiserver/sumfromheader", typeof(System.Int32), invokeOption, _request); - - } - - } - public class MyClass - { - public System.Int32 A { get; set; } - public System.Int32 B { get; set; } - } +public interface IApiServer:TouchSocket.Rpc.IRemoteServer +{ +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +System.Int32 Sum(System.Int32 a,System.Int32 b,InvokeOption invokeOption = default); +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +Task SumAsync(System.Int32 a,System.Int32 b,InvokeOption invokeOption = default); + +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +System.Int32 SumCallContext(System.Int32 a,System.Int32 b,InvokeOption invokeOption = default); +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +Task SumCallContextAsync(System.Int32 a,System.Int32 b,InvokeOption invokeOption = default); + +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +MyClass GetMyClass(InvokeOption invokeOption = default); +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +Task GetMyClassAsync(InvokeOption invokeOption = default); + +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +System.Int32 TestPost(MyClass myClass,InvokeOption invokeOption = default); +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +Task TestPostAsync(MyClass myClass,InvokeOption invokeOption = default); + +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +System.String DownloadFile(System.String id,InvokeOption invokeOption = default); +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +Task DownloadFileAsync(System.String id,InvokeOption invokeOption = default); + +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +System.String PostContent(InvokeOption invokeOption = default); +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +Task PostContentAsync(InvokeOption invokeOption = default); + +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +System.String UploadMultiFile(System.String id,InvokeOption invokeOption = default); +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +Task UploadMultiFileAsync(System.String id,InvokeOption invokeOption = default); + +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +System.String UploadBigFile(InvokeOption invokeOption = default); +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +Task UploadBigFileAsync(InvokeOption invokeOption = default); + +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +System.String GetString(InvokeOption invokeOption = default); +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +Task GetStringAsync(InvokeOption invokeOption = default); + +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +System.Int32 SumFromForm(System.Int32 a,System.Int32 b,InvokeOption invokeOption = default); +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +Task SumFromFormAsync(System.Int32 a,System.Int32 b,InvokeOption invokeOption = default); + +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +System.Int32 SumFromQuery(System.Int32 a,System.Int32 b,InvokeOption invokeOption = default); +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +Task SumFromQueryAsync(System.Int32 a,System.Int32 b,InvokeOption invokeOption = default); + +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +System.Int32 SumFromHeader(System.Int32 a,System.Int32 b,InvokeOption invokeOption = default); +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +Task SumFromHeaderAsync(System.Int32 a,System.Int32 b,InvokeOption invokeOption = default); + +} +public class ApiServer :IApiServer +{ +public ApiServer(IRpcClient client) +{ +this.Client=client; +} +public IRpcClient Client{get;private set; } +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +public System.Int32 Sum(System.Int32 a,System.Int32 b,InvokeOption invokeOption = default) +{ +if(this.Client==null) +{ +throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); +} +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Get; +_request.Headers = null; +_request.Querys = new KeyValuePair[] {new KeyValuePair("a",a.ToString()),new KeyValuePair("b",b.ToString())}; +_request.Forms = null; +return (System.Int32) this.Client.Invoke("/apiserver/sumab", typeof(System.Int32), invokeOption, _request); + +} +/// +///无注释信息 +/// +public async Task SumAsync(System.Int32 a,System.Int32 b,InvokeOption invokeOption = default) +{ +if(this.Client==null) +{ +throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); +} +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Get; +_request.Headers = null; +_request.Querys = new KeyValuePair[] {new KeyValuePair("a",a.ToString()),new KeyValuePair("b",b.ToString())}; +_request.Forms = null; +return (System.Int32) await this.Client.InvokeAsync("/apiserver/sumab", typeof(System.Int32), invokeOption, _request); + +} + +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +public System.Int32 SumCallContext(System.Int32 a,System.Int32 b,InvokeOption invokeOption = default) +{ +if(this.Client==null) +{ +throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); +} +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Get; +_request.Headers = null; +_request.Querys = new KeyValuePair[] {new KeyValuePair("a",a.ToString()),new KeyValuePair("b",b.ToString())}; +_request.Forms = null; +return (System.Int32) this.Client.Invoke("/apiserver/sumcallcontext", typeof(System.Int32), invokeOption, _request); + +} +/// +///无注释信息 +/// +public async Task SumCallContextAsync(System.Int32 a,System.Int32 b,InvokeOption invokeOption = default) +{ +if(this.Client==null) +{ +throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); +} +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Get; +_request.Headers = null; +_request.Querys = new KeyValuePair[] {new KeyValuePair("a",a.ToString()),new KeyValuePair("b",b.ToString())}; +_request.Forms = null; +return (System.Int32) await this.Client.InvokeAsync("/apiserver/sumcallcontext", typeof(System.Int32), invokeOption, _request); + +} + +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +public MyClass GetMyClass(InvokeOption invokeOption = default) +{ +if(this.Client==null) +{ +throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); +} +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Get; +_request.Headers = null; +_request.Querys = null; +_request.Forms = null; +return (MyClass) this.Client.Invoke("/apiserver/getmyclass", typeof(MyClass), invokeOption, _request); + +} +/// +///无注释信息 +/// +public async Task GetMyClassAsync(InvokeOption invokeOption = default) +{ +if(this.Client==null) +{ +throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); +} +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Get; +_request.Headers = null; +_request.Querys = null; +_request.Forms = null; +return (MyClass) await this.Client.InvokeAsync("/apiserver/getmyclass", typeof(MyClass), invokeOption, _request); + +} + +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +public System.Int32 TestPost(MyClass myClass,InvokeOption invokeOption = default) +{ +if(this.Client==null) +{ +throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); +} +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Post; +_request.Headers = null; +_request.Querys = null; +_request.Forms = null; +_request.Body = myClass; +return (System.Int32) this.Client.Invoke("/apiserver/testpost", typeof(System.Int32), invokeOption, _request); + +} +/// +///无注释信息 +/// +public async Task TestPostAsync(MyClass myClass,InvokeOption invokeOption = default) +{ +if(this.Client==null) +{ +throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); +} +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Post; +_request.Headers = null; +_request.Querys = null; +_request.Forms = null; +_request.Body = myClass; +return (System.Int32) await this.Client.InvokeAsync("/apiserver/testpost", typeof(System.Int32), invokeOption, _request); + +} + +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +public System.String DownloadFile(System.String id,InvokeOption invokeOption = default) +{ +if(this.Client==null) +{ +throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); +} +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Get; +_request.Headers = new KeyValuePair[] {new KeyValuePair("Accept","text/plain")}; +_request.Querys = new KeyValuePair[] {new KeyValuePair("id",id?.ToString())}; +_request.Forms = null; +return (System.String) this.Client.Invoke("/apiserver/downloadfile", typeof(System.String), invokeOption, _request); + +} +/// +///无注释信息 +/// +public async Task DownloadFileAsync(System.String id,InvokeOption invokeOption = default) +{ +if(this.Client==null) +{ +throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); +} +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Get; +_request.Headers = new KeyValuePair[] {new KeyValuePair("Accept","text/plain")}; +_request.Querys = new KeyValuePair[] {new KeyValuePair("id",id?.ToString())}; +_request.Forms = null; +return (System.String) await this.Client.InvokeAsync("/apiserver/downloadfile", typeof(System.String), invokeOption, _request); + +} + +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +public System.String PostContent(InvokeOption invokeOption = default) +{ +if(this.Client==null) +{ +throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); +} +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Post; +_request.Headers = new KeyValuePair[] {new KeyValuePair("Accept","text/plain")}; +_request.Querys = null; +_request.Forms = null; +return (System.String) this.Client.Invoke("/apiserver/postcontent", typeof(System.String), invokeOption, _request); + +} +/// +///无注释信息 +/// +public async Task PostContentAsync(InvokeOption invokeOption = default) +{ +if(this.Client==null) +{ +throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); +} +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Post; +_request.Headers = new KeyValuePair[] {new KeyValuePair("Accept","text/plain")}; +_request.Querys = null; +_request.Forms = null; +return (System.String) await this.Client.InvokeAsync("/apiserver/postcontent", typeof(System.String), invokeOption, _request); + +} + +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +public System.String UploadMultiFile(System.String id,InvokeOption invokeOption = default) +{ +if(this.Client==null) +{ +throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); +} +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Post; +_request.Headers = new KeyValuePair[] {new KeyValuePair("Accept","text/plain")}; +_request.Querys = new KeyValuePair[] {new KeyValuePair("id",id?.ToString())}; +_request.Forms = null; +return (System.String) this.Client.Invoke("/apiserver/uploadmultifile", typeof(System.String), invokeOption, _request); + +} +/// +///无注释信息 +/// +public async Task UploadMultiFileAsync(System.String id,InvokeOption invokeOption = default) +{ +if(this.Client==null) +{ +throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); +} +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Post; +_request.Headers = new KeyValuePair[] {new KeyValuePair("Accept","text/plain")}; +_request.Querys = new KeyValuePair[] {new KeyValuePair("id",id?.ToString())}; +_request.Forms = null; +return (System.String) await this.Client.InvokeAsync("/apiserver/uploadmultifile", typeof(System.String), invokeOption, _request); + +} + +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +public System.String UploadBigFile(InvokeOption invokeOption = default) +{ +if(this.Client==null) +{ +throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); +} +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Post; +_request.Headers = new KeyValuePair[] {new KeyValuePair("Accept","text/plain")}; +_request.Querys = null; +_request.Forms = null; +return (System.String) this.Client.Invoke("/apiserver/uploadbigfile", typeof(System.String), invokeOption, _request); + +} +/// +///无注释信息 +/// +public async Task UploadBigFileAsync(InvokeOption invokeOption = default) +{ +if(this.Client==null) +{ +throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); +} +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Post; +_request.Headers = new KeyValuePair[] {new KeyValuePair("Accept","text/plain")}; +_request.Querys = null; +_request.Forms = null; +return (System.String) await this.Client.InvokeAsync("/apiserver/uploadbigfile", typeof(System.String), invokeOption, _request); + +} + +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +public System.String GetString(InvokeOption invokeOption = default) +{ +if(this.Client==null) +{ +throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); +} +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Get; +_request.Headers = new KeyValuePair[] {new KeyValuePair("Accept","text/plain")}; +_request.Querys = null; +_request.Forms = null; +return (System.String) this.Client.Invoke("/apiserver/getstring", typeof(System.String), invokeOption, _request); + +} +/// +///无注释信息 +/// +public async Task GetStringAsync(InvokeOption invokeOption = default) +{ +if(this.Client==null) +{ +throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); +} +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Get; +_request.Headers = new KeyValuePair[] {new KeyValuePair("Accept","text/plain")}; +_request.Querys = null; +_request.Forms = null; +return (System.String) await this.Client.InvokeAsync("/apiserver/getstring", typeof(System.String), invokeOption, _request); + +} + +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +public System.Int32 SumFromForm(System.Int32 a,System.Int32 b,InvokeOption invokeOption = default) +{ +if(this.Client==null) +{ +throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); +} +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Get; +_request.Headers = null; +_request.Querys = null; +_request.Forms = new KeyValuePair[] {new KeyValuePair("a",a.ToString()),new KeyValuePair("b",b.ToString())}; +return (System.Int32) this.Client.Invoke("/apiserver/sumfromform", typeof(System.Int32), invokeOption, _request); + +} +/// +///无注释信息 +/// +public async Task SumFromFormAsync(System.Int32 a,System.Int32 b,InvokeOption invokeOption = default) +{ +if(this.Client==null) +{ +throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); +} +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Get; +_request.Headers = null; +_request.Querys = null; +_request.Forms = new KeyValuePair[] {new KeyValuePair("a",a.ToString()),new KeyValuePair("b",b.ToString())}; +return (System.Int32) await this.Client.InvokeAsync("/apiserver/sumfromform", typeof(System.Int32), invokeOption, _request); + +} + +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +public System.Int32 SumFromQuery(System.Int32 a,System.Int32 b,InvokeOption invokeOption = default) +{ +if(this.Client==null) +{ +throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); +} +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Get; +_request.Headers = null; +_request.Querys = new KeyValuePair[] {new KeyValuePair("aa",a.ToString()),new KeyValuePair("b",b.ToString())}; +_request.Forms = null; +return (System.Int32) this.Client.Invoke("/apiserver/sumfromquery", typeof(System.Int32), invokeOption, _request); + +} +/// +///无注释信息 +/// +public async Task SumFromQueryAsync(System.Int32 a,System.Int32 b,InvokeOption invokeOption = default) +{ +if(this.Client==null) +{ +throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); +} +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Get; +_request.Headers = null; +_request.Querys = new KeyValuePair[] {new KeyValuePair("aa",a.ToString()),new KeyValuePair("b",b.ToString())}; +_request.Forms = null; +return (System.Int32) await this.Client.InvokeAsync("/apiserver/sumfromquery", typeof(System.Int32), invokeOption, _request); + +} + +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +public System.Int32 SumFromHeader(System.Int32 a,System.Int32 b,InvokeOption invokeOption = default) +{ +if(this.Client==null) +{ +throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); +} +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Get; +_request.Headers = new KeyValuePair[] {new KeyValuePair("a",a.ToString()),new KeyValuePair("b",b.ToString())}; +_request.Querys = null; +_request.Forms = null; +return (System.Int32) this.Client.Invoke("/apiserver/sumfromheader", typeof(System.Int32), invokeOption, _request); + +} +/// +///无注释信息 +/// +public async Task SumFromHeaderAsync(System.Int32 a,System.Int32 b,InvokeOption invokeOption = default) +{ +if(this.Client==null) +{ +throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); +} +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Get; +_request.Headers = new KeyValuePair[] {new KeyValuePair("a",a.ToString()),new KeyValuePair("b",b.ToString())}; +_request.Querys = null; +_request.Forms = null; +return (System.Int32) await this.Client.InvokeAsync("/apiserver/sumfromheader", typeof(System.Int32), invokeOption, _request); + +} + +} +public static class ApiServerExtensions +{ +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +public static System.Int32 Sum(this TClient client,System.Int32 a,System.Int32 b,InvokeOption invokeOption = default) where TClient: +TouchSocket.WebApi.IWebApiClientBase{ +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Get; +_request.Headers = null; +_request.Querys = new KeyValuePair[] {new KeyValuePair("a",a.ToString()),new KeyValuePair("b",b.ToString())}; +_request.Forms = null; +return (System.Int32) client.Invoke("/apiserver/sumab", typeof(System.Int32), invokeOption, _request); + +} +/// +///无注释信息 +/// +public static async Task SumAsync(this TClient client,System.Int32 a,System.Int32 b,InvokeOption invokeOption = default) where TClient: +TouchSocket.WebApi.IWebApiClientBase{ +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Get; +_request.Headers = null; +_request.Querys = new KeyValuePair[] {new KeyValuePair("a",a.ToString()),new KeyValuePair("b",b.ToString())}; +_request.Forms = null; +return (System.Int32) await client.InvokeAsync("/apiserver/sumab", typeof(System.Int32), invokeOption, _request); + +} + +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +public static System.Int32 SumCallContext(this TClient client,System.Int32 a,System.Int32 b,InvokeOption invokeOption = default) where TClient: +TouchSocket.WebApi.IWebApiClientBase{ +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Get; +_request.Headers = null; +_request.Querys = new KeyValuePair[] {new KeyValuePair("a",a.ToString()),new KeyValuePair("b",b.ToString())}; +_request.Forms = null; +return (System.Int32) client.Invoke("/apiserver/sumcallcontext", typeof(System.Int32), invokeOption, _request); + +} +/// +///无注释信息 +/// +public static async Task SumCallContextAsync(this TClient client,System.Int32 a,System.Int32 b,InvokeOption invokeOption = default) where TClient: +TouchSocket.WebApi.IWebApiClientBase{ +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Get; +_request.Headers = null; +_request.Querys = new KeyValuePair[] {new KeyValuePair("a",a.ToString()),new KeyValuePair("b",b.ToString())}; +_request.Forms = null; +return (System.Int32) await client.InvokeAsync("/apiserver/sumcallcontext", typeof(System.Int32), invokeOption, _request); + +} + +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +public static MyClass GetMyClass(this TClient client,InvokeOption invokeOption = default) where TClient: +TouchSocket.WebApi.IWebApiClientBase{ +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Get; +_request.Headers = null; +_request.Querys = null; +_request.Forms = null; +return (MyClass) client.Invoke("/apiserver/getmyclass", typeof(MyClass), invokeOption, _request); + +} +/// +///无注释信息 +/// +public static async Task GetMyClassAsync(this TClient client,InvokeOption invokeOption = default) where TClient: +TouchSocket.WebApi.IWebApiClientBase{ +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Get; +_request.Headers = null; +_request.Querys = null; +_request.Forms = null; +return (MyClass) await client.InvokeAsync("/apiserver/getmyclass", typeof(MyClass), invokeOption, _request); + +} + +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +public static System.Int32 TestPost(this TClient client,MyClass myClass,InvokeOption invokeOption = default) where TClient: +TouchSocket.WebApi.IWebApiClientBase{ +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Post; +_request.Headers = null; +_request.Querys = null; +_request.Forms = null; +_request.Body = myClass; +return (System.Int32) client.Invoke("/apiserver/testpost", typeof(System.Int32), invokeOption, _request); + +} +/// +///无注释信息 +/// +public static async Task TestPostAsync(this TClient client,MyClass myClass,InvokeOption invokeOption = default) where TClient: +TouchSocket.WebApi.IWebApiClientBase{ +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Post; +_request.Headers = null; +_request.Querys = null; +_request.Forms = null; +_request.Body = myClass; +return (System.Int32) await client.InvokeAsync("/apiserver/testpost", typeof(System.Int32), invokeOption, _request); + +} + +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +public static System.String DownloadFile(this TClient client,System.String id,InvokeOption invokeOption = default) where TClient: +TouchSocket.WebApi.IWebApiClientBase{ +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Get; +_request.Headers = new KeyValuePair[] {new KeyValuePair("Accept","text/plain")}; +_request.Querys = new KeyValuePair[] {new KeyValuePair("id",id?.ToString())}; +_request.Forms = null; +return (System.String) client.Invoke("/apiserver/downloadfile", typeof(System.String), invokeOption, _request); + +} +/// +///无注释信息 +/// +public static async Task DownloadFileAsync(this TClient client,System.String id,InvokeOption invokeOption = default) where TClient: +TouchSocket.WebApi.IWebApiClientBase{ +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Get; +_request.Headers = new KeyValuePair[] {new KeyValuePair("Accept","text/plain")}; +_request.Querys = new KeyValuePair[] {new KeyValuePair("id",id?.ToString())}; +_request.Forms = null; +return (System.String) await client.InvokeAsync("/apiserver/downloadfile", typeof(System.String), invokeOption, _request); + +} + +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +public static System.String PostContent(this TClient client,InvokeOption invokeOption = default) where TClient: +TouchSocket.WebApi.IWebApiClientBase{ +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Post; +_request.Headers = new KeyValuePair[] {new KeyValuePair("Accept","text/plain")}; +_request.Querys = null; +_request.Forms = null; +return (System.String) client.Invoke("/apiserver/postcontent", typeof(System.String), invokeOption, _request); + +} +/// +///无注释信息 +/// +public static async Task PostContentAsync(this TClient client,InvokeOption invokeOption = default) where TClient: +TouchSocket.WebApi.IWebApiClientBase{ +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Post; +_request.Headers = new KeyValuePair[] {new KeyValuePair("Accept","text/plain")}; +_request.Querys = null; +_request.Forms = null; +return (System.String) await client.InvokeAsync("/apiserver/postcontent", typeof(System.String), invokeOption, _request); + +} + +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +public static System.String UploadMultiFile(this TClient client,System.String id,InvokeOption invokeOption = default) where TClient: +TouchSocket.WebApi.IWebApiClientBase{ +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Post; +_request.Headers = new KeyValuePair[] {new KeyValuePair("Accept","text/plain")}; +_request.Querys = new KeyValuePair[] {new KeyValuePair("id",id?.ToString())}; +_request.Forms = null; +return (System.String) client.Invoke("/apiserver/uploadmultifile", typeof(System.String), invokeOption, _request); + +} +/// +///无注释信息 +/// +public static async Task UploadMultiFileAsync(this TClient client,System.String id,InvokeOption invokeOption = default) where TClient: +TouchSocket.WebApi.IWebApiClientBase{ +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Post; +_request.Headers = new KeyValuePair[] {new KeyValuePair("Accept","text/plain")}; +_request.Querys = new KeyValuePair[] {new KeyValuePair("id",id?.ToString())}; +_request.Forms = null; +return (System.String) await client.InvokeAsync("/apiserver/uploadmultifile", typeof(System.String), invokeOption, _request); + +} + +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +public static System.String UploadBigFile(this TClient client,InvokeOption invokeOption = default) where TClient: +TouchSocket.WebApi.IWebApiClientBase{ +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Post; +_request.Headers = new KeyValuePair[] {new KeyValuePair("Accept","text/plain")}; +_request.Querys = null; +_request.Forms = null; +return (System.String) client.Invoke("/apiserver/uploadbigfile", typeof(System.String), invokeOption, _request); + +} +/// +///无注释信息 +/// +public static async Task UploadBigFileAsync(this TClient client,InvokeOption invokeOption = default) where TClient: +TouchSocket.WebApi.IWebApiClientBase{ +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Post; +_request.Headers = new KeyValuePair[] {new KeyValuePair("Accept","text/plain")}; +_request.Querys = null; +_request.Forms = null; +return (System.String) await client.InvokeAsync("/apiserver/uploadbigfile", typeof(System.String), invokeOption, _request); + +} + +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +public static System.String GetString(this TClient client,InvokeOption invokeOption = default) where TClient: +TouchSocket.WebApi.IWebApiClientBase{ +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Get; +_request.Headers = new KeyValuePair[] {new KeyValuePair("Accept","text/plain")}; +_request.Querys = null; +_request.Forms = null; +return (System.String) client.Invoke("/apiserver/getstring", typeof(System.String), invokeOption, _request); + +} +/// +///无注释信息 +/// +public static async Task GetStringAsync(this TClient client,InvokeOption invokeOption = default) where TClient: +TouchSocket.WebApi.IWebApiClientBase{ +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Get; +_request.Headers = new KeyValuePair[] {new KeyValuePair("Accept","text/plain")}; +_request.Querys = null; +_request.Forms = null; +return (System.String) await client.InvokeAsync("/apiserver/getstring", typeof(System.String), invokeOption, _request); + +} + +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +public static System.Int32 SumFromForm(this TClient client,System.Int32 a,System.Int32 b,InvokeOption invokeOption = default) where TClient: +TouchSocket.WebApi.IWebApiClientBase{ +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Get; +_request.Headers = null; +_request.Querys = null; +_request.Forms = new KeyValuePair[] {new KeyValuePair("a",a.ToString()),new KeyValuePair("b",b.ToString())}; +return (System.Int32) client.Invoke("/apiserver/sumfromform", typeof(System.Int32), invokeOption, _request); + +} +/// +///无注释信息 +/// +public static async Task SumFromFormAsync(this TClient client,System.Int32 a,System.Int32 b,InvokeOption invokeOption = default) where TClient: +TouchSocket.WebApi.IWebApiClientBase{ +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Get; +_request.Headers = null; +_request.Querys = null; +_request.Forms = new KeyValuePair[] {new KeyValuePair("a",a.ToString()),new KeyValuePair("b",b.ToString())}; +return (System.Int32) await client.InvokeAsync("/apiserver/sumfromform", typeof(System.Int32), invokeOption, _request); + +} + +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +public static System.Int32 SumFromQuery(this TClient client,System.Int32 a,System.Int32 b,InvokeOption invokeOption = default) where TClient: +TouchSocket.WebApi.IWebApiClientBase{ +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Get; +_request.Headers = null; +_request.Querys = new KeyValuePair[] {new KeyValuePair("aa",a.ToString()),new KeyValuePair("b",b.ToString())}; +_request.Forms = null; +return (System.Int32) client.Invoke("/apiserver/sumfromquery", typeof(System.Int32), invokeOption, _request); + +} +/// +///无注释信息 +/// +public static async Task SumFromQueryAsync(this TClient client,System.Int32 a,System.Int32 b,InvokeOption invokeOption = default) where TClient: +TouchSocket.WebApi.IWebApiClientBase{ +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Get; +_request.Headers = null; +_request.Querys = new KeyValuePair[] {new KeyValuePair("aa",a.ToString()),new KeyValuePair("b",b.ToString())}; +_request.Forms = null; +return (System.Int32) await client.InvokeAsync("/apiserver/sumfromquery", typeof(System.Int32), invokeOption, _request); + +} + +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +public static System.Int32 SumFromHeader(this TClient client,System.Int32 a,System.Int32 b,InvokeOption invokeOption = default) where TClient: +TouchSocket.WebApi.IWebApiClientBase{ +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Get; +_request.Headers = new KeyValuePair[] {new KeyValuePair("a",a.ToString()),new KeyValuePair("b",b.ToString())}; +_request.Querys = null; +_request.Forms = null; +return (System.Int32) client.Invoke("/apiserver/sumfromheader", typeof(System.Int32), invokeOption, _request); + +} +/// +///无注释信息 +/// +public static async Task SumFromHeaderAsync(this TClient client,System.Int32 a,System.Int32 b,InvokeOption invokeOption = default) where TClient: +TouchSocket.WebApi.IWebApiClientBase{ +var _request=new WebApiRequest(); +_request.Method = HttpMethodType.Get; +_request.Headers = new KeyValuePair[] {new KeyValuePair("a",a.ToString()),new KeyValuePair("b",b.ToString())}; +_request.Querys = null; +_request.Forms = null; +return (System.Int32) await client.InvokeAsync("/apiserver/sumfromheader", typeof(System.Int32), invokeOption, _request); + +} + +} +public class MyClass +{ +public System.Int32 A { get; set; } +public System.Int32 B { get; set; } +} } diff --git a/examples/WebApi/WebApiServer/WebApiServerApp.csproj b/examples/WebApi/WebApiServer/WebApiServerApp.csproj index e28bed431..d8f8c233e 100644 --- a/examples/WebApi/WebApiServer/WebApiServerApp.csproj +++ b/examples/WebApi/WebApiServer/WebApiServerApp.csproj @@ -1,4 +1,4 @@ - + Exe @@ -6,29 +6,7 @@ - - + + - - - diff --git a/examples/WebSocket/AsyncWebSocketConsoleApp/AsyncWebSocketConsoleApp.csproj b/examples/WebSocket/AsyncWebSocketConsoleApp/AsyncWebSocketConsoleApp.csproj index ae1d14113..eacdf4bed 100644 --- a/examples/WebSocket/AsyncWebSocketConsoleApp/AsyncWebSocketConsoleApp.csproj +++ b/examples/WebSocket/AsyncWebSocketConsoleApp/AsyncWebSocketConsoleApp.csproj @@ -7,6 +7,6 @@ enable - + diff --git a/examples/WebSocket/AsyncWebSocketConsoleApp/Program.cs b/examples/WebSocket/AsyncWebSocketConsoleApp/Program.cs index a8192b63f..0a9cafa6f 100644 --- a/examples/WebSocket/AsyncWebSocketConsoleApp/Program.cs +++ b/examples/WebSocket/AsyncWebSocketConsoleApp/Program.cs @@ -22,13 +22,36 @@ internal class Program private static async Task Main(string[] args) { var service = await CreateHttpService(); + + #region WebSocket客户端使用ReadAsync读取数据 using (var client = await GetClient()) { + //当WebSocket想要使用ReadAsync时,需要设置此值为true + client.AllowAsyncRead = true; while (true) { + //发送数据 await client.SendAsync(Console.ReadLine()); + + //设置1分钟的接收超时 + using var cts = new CancellationTokenSource(1000 * 60); + using (var receiveResult = await client.ReadAsync(cts.Token)) + { + if (receiveResult.IsCompleted) + { + //但对于客户端来说,可以直接使用client.Online来判断连接状态。 + Console.WriteLine($"WebSocket连接已关闭,关闭代码:{client.CloseStatus},信息:{receiveResult.Message}"); + break; + } + + var dataFrame = receiveResult.DataFrame; + + client.Logger.Info($"客户端收到数据:{dataFrame.ToText()}"); + } } } + #endregion + } /// @@ -42,6 +65,19 @@ internal class Program { a.AddConsoleLogger(); }) + #region WebSocket断线重连 + .ConfigurePlugins(a => + { + a.UseReconnection(); + + //使用健康插件进行绝对存活检测,默认10秒检测一次。 + a.UseCheckClear(options => + { + options.Tick = TimeSpan.FromSeconds(10); + }); + }) + #endregion + .SetRemoteIPHost("ws://127.0.0.1:7789/ws")); await client.ConnectAsync(); @@ -60,9 +96,12 @@ internal class Program }) .ConfigurePlugins(a => { - a.UseWebSocket()//添加WebSocket功能 - .SetWSUrl("/ws")//设置url直接可以连接。 - .UseAutoPong();//当收到ping报文时自动回应pong + //添加WebSocket功能 + a.UseWebSocket(options => + { + options.SetUrl("/ws");//设置url直接可以连接。 + options.SetAutoPong(true);//当收到ping报文时自动回应pong + }); a.Add(); })); @@ -74,7 +113,8 @@ internal class Program } } -internal class MyReadWebSocketPlugin : PluginBase, IWebSocketHandshakedPlugin +#region WebSocket服务器使用ReadAsync读取数据 +internal class MyReadWebSocketPlugin : PluginBase, IWebSocketConnectedPlugin { private readonly ILog m_logger; @@ -83,7 +123,7 @@ internal class MyReadWebSocketPlugin : PluginBase, IWebSocketHandshakedPlugin this.m_logger = logger; } - public async Task OnWebSocketHandshaked(IWebSocket client, HttpContextEventArgs e) + public async Task OnWebSocketConnected(IWebSocket client, HttpContextEventArgs e) { //当WebSocket想要使用ReadAsync时,需要设置此值为true client.AllowAsyncRead = true; @@ -92,22 +132,22 @@ internal class MyReadWebSocketPlugin : PluginBase, IWebSocketHandshakedPlugin while (true) { - using (var receiveResult = await client.ReadAsync(CancellationToken.None)) + //设置1分钟的接收超时 + using var cts = new CancellationTokenSource(1000 * 60); + using (var receiveResult = await client.ReadAsync(cts.Token)) { - if (receiveResult.DataFrame == null) + if (receiveResult.IsCompleted) { + Console.WriteLine($"WebSocket连接已关闭,关闭代码:{client.CloseStatus},信息:{receiveResult.Message}"); break; } - //判断是否为最后数据 - //例如发送方发送了一个10Mb的数据,接收时可能会多次接收,所以需要此属性判断。 - if (receiveResult.DataFrame.FIN) - { - if (receiveResult.DataFrame.IsText) - { - this.m_logger.Info($"WebSocket文本:{receiveResult.DataFrame.ToText()}"); - } - } + var dataFrame = receiveResult.DataFrame; + + this.m_logger.Info($"服务器收到数据:{dataFrame.ToText()}"); + + //回应客户端 + await client.SendAsync(dataFrame.ToText()); } } @@ -115,4 +155,5 @@ internal class MyReadWebSocketPlugin : PluginBase, IWebSocketHandshakedPlugin this.m_logger.Info("WebSocket断开连接"); await e.InvokeNext(); } -} \ No newline at end of file +} +#endregion diff --git a/examples/WebSocket/WebSocketConsoleApp/Program.cs b/examples/WebSocket/WebSocketConsoleApp/Program.cs index 7b21afb7e..10d72624b 100644 --- a/examples/WebSocket/WebSocketConsoleApp/Program.cs +++ b/examples/WebSocket/WebSocketConsoleApp/Program.cs @@ -12,6 +12,7 @@ using System; using System.IO; +using System.Net.WebSockets; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -38,6 +39,11 @@ internal class Program consoleAction.Add("5", "发送字符串", SendText); consoleAction.Add("6", "发送部分字符串", SendSubstringText); consoleAction.Add("7", "调用Add", SendAdd); + consoleAction.Add("8", "发送二进制", SendBinary); + consoleAction.Add("9", "发送自定义消息", SendCustomMessage); + consoleAction.Add("10", "发送Ping消息", SendPingMessage); + consoleAction.Add("11", "发送分包消息", SendContMessage); + consoleAction.ShowAll(); @@ -46,8 +52,109 @@ internal class Program await consoleAction.RunCommandLineAsync(); } + private static async Task SendContMessage() + { + #region WebSocket直接连接服务器 + using var webSocket = new WebSocketClient(); + await webSocket.SetupAsync(new TouchSocketConfig() + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .SetRemoteIPHost("ws://127.0.0.1:7789/ws")); + await webSocket.ConnectAsync(); + #endregion + + + #region WebSocket发送分包数据 + for (int i = 0; i < 10; i++) + { + //发送文本分包消息 + //最后一个参数表示是否为最后一个包 + await webSocket.SendAsync($"hello{i}", i == 9); + } + + for (int i = 0; i < 10; i++) + { + //发送二进制分包消息 + //最后一个参数表示是否为最后一个包 + await webSocket.SendAsync(new byte[] { 0, 1, 2, 3, 4 }, i == 9); + } + #endregion + + #region WebSocket关闭连接 + await webSocket.CloseAsync("正常关闭"); + + //或者使用关闭状态码 + await webSocket.CloseAsync( WebSocketCloseStatus.NormalClosure,"正常关闭"); + #endregion + } + + private static async Task SendPingMessage() + { + using var webSocket = new WebSocketClient(); + await webSocket.SetupAsync(new TouchSocketConfig() + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .SetRemoteIPHost("ws://127.0.0.1:7789/ws")); + await webSocket.ConnectAsync(); + + for (int i = 0; i < 10; i++) + { + #region WebSocket发送Ping或者Pong消息 + await webSocket.PingAsync(); + + //如果收到了Ping消息,则需要回应Pong。 + //await webSocket.PongAsync(); + #endregion + } + } + + private static async Task SendCustomMessage() + { + using var webSocket = new WebSocketClient(); + await webSocket.SetupAsync(new TouchSocketConfig() + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .SetRemoteIPHost("ws://127.0.0.1:7789/ws")); + await webSocket.ConnectAsync(); + + #region WebSocket发送自定义消息 + var frame = new WSDataFrame(Encoding.UTF8.GetBytes("Hello")); + frame.Opcode = WSDataType.Text; + frame.FIN = true; + + //设置RSV位,一般情况下,RSV位不允许随意设置,除非你非常清楚你在做什么。 + frame.RSV1 = true; + frame.RSV2 = true; + frame.RSV3 = true; + await webSocket.SendAsync(frame); + #endregion + } + + private static async Task SendBinary() + { + using var webSocket = new WebSocketClient(); + await webSocket.SetupAsync(new TouchSocketConfig() + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .SetRemoteIPHost("ws://127.0.0.1:7789/ws")); + await webSocket.ConnectAsync(); + + #region WebSocket发送二进制 + await webSocket.SendAsync(new byte[] { 1, 2, 3, 4, 5 }); + #endregion + } + private static async Task SendAdd() { + #region WebSocket命令行插件客户端调用 var client = new WebSocketClient(); await client.SetupAsync(new TouchSocketConfig() .ConfigureContainer(a => @@ -56,7 +163,7 @@ internal class Program }) .ConfigurePlugins(a => { - a.UseWebSocketReconnection(); + a.UseReconnection(); a.Add(typeof(IWebSocketReceivedPlugin), async (IHttpSession c, WSDataFrameEventArgs e) => { client.Logger.Info($"收到Add的计算结果:{e.DataFrame.ToText()}"); @@ -67,6 +174,7 @@ internal class Program await client.ConnectAsync(); await client.SendAsync("Add 10 20"); + #endregion await Task.Delay(1000); await client.CloseAsync("我想关就关"); @@ -95,15 +203,18 @@ internal class Program private static async Task SendText() { - var client = new WebSocketClient(); - await client.SetupAsync(new TouchSocketConfig() + using var webSocket = new WebSocketClient(); + await webSocket.SetupAsync(new TouchSocketConfig() .ConfigureContainer(a => { a.AddConsoleLogger(); }) .SetRemoteIPHost("ws://127.0.0.1:7789/ws")); - await client.ConnectAsync(); - await client.SendAsync("hello"); + await webSocket.ConnectAsync(); + + #region WebSocket发送文本 + await webSocket.SendAsync("hello"); + #endregion } private static void ConsoleAction_OnException(Exception obj) @@ -116,38 +227,33 @@ internal class Program /// private static async Task ConnectWith_ws() { + #region 简单创建WebSocket客户端 using var client = new WebSocketClient(); - await client.SetupAsync(new TouchSocketConfig() - .ConfigureContainer(a => - { - a.AddConsoleLogger(); - }) - .ConfigurePlugins(a => - { - a.Add(typeof(IWebSocketHandshakedPlugin), () => - { - Console.WriteLine("WebSocketHandshaked"); - }); + client.Connected=(c,e)=> + { + Console.WriteLine("Connected"); + return EasyTask.CompletedTask; + }; - a.Add(typeof(IWebSocketClosingPlugin), () => - { - Console.WriteLine("WebSocketClosing"); - }); + #region WebSocket客户端使用Received委托接收数据 + client.Received = (c, e) => + { + Console.WriteLine(e.DataFrame.ToText()); + return EasyTask.CompletedTask; + }; + #endregion - a.Add(typeof(IWebSocketClosedPlugin), () => - { - Console.WriteLine("WebSocketClosed"); - }); + client.Closed=(c,e)=> + { + Console.WriteLine("Closed"); + return EasyTask.CompletedTask; + }; - a.UseWebSocketHeartbeat() - .SetTick(TimeSpan.FromSeconds(1)); - - a.UseWebSocketReconnection(); - }) - .SetRemoteIPHost("ws://127.0.0.1:7789/ws")); - await client.ConnectAsync(); + await client.ConnectAsync("ws://127.0.0.1:7789/ws"); client.Logger.Info("通过ws://127.0.0.1:7789/ws连接成功"); + #endregion + await Task.Delay(1000000); } @@ -155,73 +261,184 @@ internal class Program /// /// 通过/wsquery,传入参数连接 /// - private static void ConnectWith_wsquery() + private static async Task ConnectWith_wsquery() { + #region WebSocket带Query参数连接 using var client = new WebSocketClient(); - client.SetupAsync(new TouchSocketConfig() + await client.SetupAsync(new TouchSocketConfig() .ConfigureContainer(a => { a.AddConsoleLogger(); }) .SetRemoteIPHost("ws://127.0.0.1:7789/wsquery?token=123456")); - client.ConnectAsync(); + await client.ConnectAsync(); client.Logger.Info("通过ws://127.0.0.1:7789/wsquery?token=123456连接成功"); + #endregion + } /// /// 通过/wsheader,传入header连接 /// - private static void ConnectWith_wsheader() + private static async Task ConnectWith_wsheader() { + #region WebSocket使用特定Header连接 using var client = new WebSocketClient(); - client.SetupAsync(new TouchSocketConfig() + await client.SetupAsync(new TouchSocketConfig() .ConfigureContainer(a => { a.AddConsoleLogger(); }) .ConfigurePlugins(a => { - a.Add(typeof(IWebSocketHandshakingPlugin), async (IWebSocket webSocket, HttpContextEventArgs e) => + a.AddWebSocketConnectedPlugin(async (IWebSocket webSocket, HttpContextEventArgs e) => { e.Context.Request.Headers.Add("token", "123456"); await e.InvokeNext(); }); }) .SetRemoteIPHost("ws://127.0.0.1:7789/wsheader")); - client.ConnectAsync(); + await client.ConnectAsync(); client.Logger.Info("通过ws://127.0.0.1:7789/wsheader连接成功"); + #endregion + } /// /// 使用Post方式连接 /// - private static void ConnectWith_Post_ws() + private static async Task ConnectWith_Post_ws() { + #region WebSocket使用Post方式连接 using var client = new WebSocketClient(); - client.SetupAsync(new TouchSocketConfig() + await client.SetupAsync(new TouchSocketConfig() .ConfigureContainer(a => { a.AddConsoleLogger(); }) .ConfigurePlugins(a => { - a.Add(typeof(IWebSocketHandshakingPlugin), async (IWebSocket webSocket, HttpContextEventArgs e) => + a.AddWebSocketConnectedPlugin(async (IWebSocket webSocket, HttpContextEventArgs e) => { e.Context.Request.Method = HttpMethod.Post;//将请求方法改为Post await e.InvokeNext(); }); }) .SetRemoteIPHost("ws://127.0.0.1:7789/postws")); - client.ConnectAsync(); + await client.ConnectAsync(); client.Logger.Info("通过ws://127.0.0.1:7789/postws连接成功"); + #endregion + + } + private static async Task CreateHttpService2() + { + #region 创建WebSocket服务器 {11-16} + var service = new HttpService(); + + var config = new TouchSocketConfig(); + config.SetListenIPHosts(7789) + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .ConfigurePlugins(a => + { + //添加WebSocket功能 + a.UseWebSocket(options => + { + options.SetUrl("/ws");//设置Url,当客户端使用ws://127.0.0.1:7789/ws直接可以连接。 + options.SetAutoPong(true);//当收到Ping报文时自动回应Pong + }); + }); + + //加载配置 + await service.SetupAsync(config); + + //启动服务 + await service.StartAsync(); + + service.Logger.Info("WebSocket服务器已启动,地址: ws://127.0.0.1:7789/ws"); + #endregion + + #region WebSocket服务器遍历所有连接并发送数据 + var clients = service.Clients; + foreach (var client in clients) + { + if (client.Protocol == Protocol.WebSocket)//先判断是不是websocket协议 + { + if (client.Id == "id")//再按指定id发送,或者直接广播发送 + { + var webSocket = client.WebSocket; + await webSocket.SendAsync("hello"); + } + } + } + #endregion + + return service; } + private static async Task CreateHttpService3() + { + var config = new TouchSocketConfig(); + config.SetListenIPHosts(7789) + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .ConfigurePlugins(a => + { + //添加WebSocket功能 + a.UseWebSocket(options => + { + options.SetAutoPong(true);//当收到Ping报文时自动回应Pong + + #region WebSocket连接验证 + //添加自定义验证逻辑后,必须自己验证Url + options.SetVerifyConnection(async (client, context) => + { + if (!context.Request.IsUpgrade()) + { + //如果不包含升级协议的header,就直接返回false。 + return false; + } + // 在此处添加验证逻辑,例如:匹配特定的URL + if (context.Request.UrlEquals("/ws")) + { + return true; // 允许连接 + } + + //如果url不匹配,则可以直接拒绝。 + //await context.Response + //.SetStatus(403, "url不正确") + //.AnswerAsync(); + return false; + }); + #endregion + }); + }); + + #region 使用WebSocket接收插件 + config.ConfigurePlugins(a => + { + //添加WebSocket功能 + a.UseWebSocket("/ws"); + + a.Add(); + }); + #endregion + + + } private static async Task CreateHttpService() { + #region WebSocket命令行插件注册使用 var service = new HttpService(); + + var config = new TouchSocketConfig(); await service.SetupAsync(new TouchSocketConfig()//加载配置 .SetListenIPHosts(7789) .ConfigureContainer(a => @@ -234,16 +451,17 @@ internal class Program }) .ConfigurePlugins(a => { - a.UseWebSocket()//添加WebSocket功能 - //.SetWSUrl("/ws")//设置url直接可以连接。 - .SetVerifyConnection(VerifyConnection) - //.UseAutoPong()//当收到ping报文时自动回应pong - ; + a.UseWebSocket(options => + { + options.SetUrl("/ws");//设置url直接可以连接。 + options.SetVerifyConnection(VerifyConnection); + options.SetAutoPong(true);//当收到ping报文时自动回应pong + }); //a.Add(); - //a.Add(); - a.Add(); + a.Add(); + //a.Add(); a.UseWebApi(); })); @@ -256,6 +474,7 @@ internal class Program service.Logger.Info("WebApi支持的连接地址=>ws://127.0.0.1:7789/MyServer/ConnectWS"); service.Logger.Info("WebApi支持的连接地址=>ws://127.0.0.1:7789/MyServer/ws"); return service; + #endregion } /// @@ -313,143 +532,9 @@ internal class Program return false; } - - internal class MyReadTextWebSocketPlugin : PluginBase, IWebSocketHandshakedPlugin - { - private readonly ILog m_logger; - - public MyReadTextWebSocketPlugin(ILog logger) - { - this.m_logger = logger; - } - - public async Task OnWebSocketHandshaked(IWebSocket client, HttpContextEventArgs e) - { - //当WebSocket想要使用ReadAsync时,需要设置此值为true - client.AllowAsyncRead = true; - - //此处即表明websocket已连接 - - MemoryStream stream = default;//中继包缓存 - var isText = false;//标识是否为文本 - while (true) - { - using (var receiveResult = await client.ReadAsync(CancellationToken.None)) - { - if (receiveResult.IsCompleted) - { - break; - } - - var dataFrame = receiveResult.DataFrame; - var data = receiveResult.DataFrame.PayloadData; - - switch (dataFrame.Opcode) - { - case WSDataType.Cont: - { - //如果是非net9.0即以上,即:NetFramework平台使用。原因是stream不支持span写入 - //var segment = data.AsSegment(); - //stream.Write(segment.Array, segment.Offset, segment.Count); - - //如果是net9.0以上,直接写入span即可 - stream.Write(data.Span); - - //收到的是中继包 - if (dataFrame.FIN)//判断是否为最终包 - { - //是 - - if (isText)//判断是否为文本 - { - this.m_logger.Info($"WebSocket文本:{Encoding.UTF8.GetString(stream.ToArray())}"); - } - else - { - this.m_logger.Info($"WebSocket二进制:{stream.Length}长度"); - } - } - else - { - //否,继续缓存 - } - } - break; - case WSDataType.Text: - { - if (dataFrame.FIN)//判断是不是最后的包 - { - //是,则直接输出 - //说明上次并没有中继数据缓存,直接输出本次内容即可 - this.m_logger.Info($"WebSocket文本:{dataFrame.ToText()}"); - } - else - { - isText = true; - - //否,则说明数据太大了,分中继包了。 - //则,初始化缓存容器 - stream ??= new MemoryStream(); - - //下面则是缓存逻辑 - - //如果是非net9.0即以上,即:NetFramework平台使用。原因是stream不支持span写入 - //var segment = data.AsSegment(); - //stream.Write(segment.Array, segment.Offset, segment.Count); - - //如果是net9.0以上,直接写入span即可 - stream.Write(data.Span); - } - } - break; - case WSDataType.Binary: - { - if (dataFrame.FIN)//判断是不是最后的包 - { - //是,则直接输出 - //说明上次并没有中继数据缓存,直接输出本次内容即可 - this.m_logger.Info($"WebSocket二进制:{data.Length}长度"); - } - else - { - isText = false; - - //否,则说明数据太大了,分中继包了。 - //则,初始化缓存容器 - stream ??= new MemoryStream(); - - //下面则是缓存逻辑 - - //如果是非net9.0即以上,即:NetFramework平台使用。原因是stream不支持span写入 - //var segment = data.AsSegment(); - //stream.Write(segment.Array, segment.Offset, segment.Count); - - //如果是net9.0以上,直接写入span即可 - stream.Write(data.Span); - } - } - break; - case WSDataType.Close: - break; - case WSDataType.Ping: - break; - case WSDataType.Pong: - break; - default: - break; - } - } - } - - //此处即表明websocket已断开连接 - this.m_logger.Info("WebSocket断开连接"); - await e.InvokeNext(); - } - } - public class MyWebSocketPlugin : PluginBase, - IWebSocketHandshakingPlugin, - IWebSocketHandshakedPlugin, + IWebSocketConnectingPlugin, + IWebSocketConnectedPlugin, IWebSocketReceivedPlugin, IWebSocketClosingPlugin, IWebSocketClosedPlugin @@ -459,7 +544,7 @@ internal class Program this.m_logger = logger; } - public async Task OnWebSocketHandshaking(IWebSocket client, HttpContextEventArgs e) + public async Task OnWebSocketConnecting(IWebSocket client, HttpContextEventArgs e) { if (client.Client is IHttpSessionClient socketClient) { @@ -475,7 +560,7 @@ internal class Program await e.InvokeNext(); } - public async Task OnWebSocketHandshaked(IWebSocket client, HttpContextEventArgs e) + public async Task OnWebSocketConnected(IWebSocket client, HttpContextEventArgs e) { this.m_logger.Info("WebSocket成功连接"); await e.InvokeNext(); @@ -483,29 +568,16 @@ internal class Program private readonly ILog m_logger; + public async Task OnWebSocketReceived(IWebSocket client, WSDataFrameEventArgs e) { - switch (e.DataFrame.Opcode) + #region WebSocket处理中继数据 + var dataFrame = e.DataFrame; + switch (dataFrame.Opcode) { case WSDataType.Close: { this.m_logger.Info("远程请求断开"); - - //var byteBlock = e.DataFrame.PayloadData; - //byteBlock.SeekToStart(); - //var ss = byteBlock.ReadUInt16(EndianType.Big); - //using (var frame = new WSDataFrame()) - //{ - // frame.Opcode = WSDataType.Close; - // frame.FIN = true; - // frame.PayloadData=new ByteBlock(1024*64); - // frame.PayloadData.WriteUInt16(1000, EndianType.Big); - // frame.PayloadData.Write(Encoding.UTF8.GetBytes("hello")); - // await client.SendAsync(frame); - //} - - //client.Client.TryShutdown(); - await client.CloseAsync("断开"); } return; @@ -567,6 +639,8 @@ internal class Program } break; } + #endregion + await e.InvokeNext(); } @@ -584,23 +658,47 @@ internal class Program } } - private class MyHttpPlugin : PluginBase, IHttpPlugin + #region 创建WebSocket接收插件 + class MyWebSocketReceivePlugin : PluginBase, IWebSocketReceivedPlugin { - public async Task OnHttpRequest(IHttpSessionClient client, HttpContextEventArgs e) + public async Task OnWebSocketReceived(IWebSocket webSocket, WSDataFrameEventArgs e) { - if (e.Context.Request.IsGet()) + //处理接收的数据 + var dataFrame = e.DataFrame; + switch (dataFrame.Opcode) { - if (e.Context.Request.UrlEquals("/GetSwitchToWebSocket")) - { - var result = await client.SwitchProtocolToWebSocketAsync(e.Context); - return; - } + case WSDataType.Cont: + //处理中继包 + break; + case WSDataType.Text: + //处理文本包 + Console.WriteLine(dataFrame.ToText()); + break; + case WSDataType.Binary: + //处理二进制包 + var data = dataFrame.PayloadData; + Console.WriteLine($"收到二进制数据,长度:{data.Length}"); + break; + case WSDataType.Close: + //处理关闭包 + break; + case WSDataType.Ping: + //处理Ping包 + break; + case WSDataType.Pong: + //处理Pong包 + break; + default: + //处理其他包 + break; } - await e.InvokeNext(); } } + #endregion + + #region WebSocket命令行插件声明 /// /// 命令行插件。 /// 声明的方法必须为公开实例方法、以"Command"结尾,且支持json字符串,参数之间空格隔开。 @@ -623,6 +721,7 @@ internal class Program return sumClass; } } + #endregion public class SumClass { @@ -658,4 +757,41 @@ internal class Program } } } -} \ No newline at end of file + + #region 自定义转换WebSocket + class CustomWebSocketPlugin : PluginBase, IHttpPlugin + { + public async Task OnHttpRequest(IHttpSessionClient client, HttpContextEventArgs e) + { + if (e.Context.Request.IsUpgrade()) + { + if (e.Context.Request.UrlEquals("/customws")) + { + var result = await client.SwitchProtocolToWebSocketAsync(e.Context); + if (!result.IsSuccess) + { + //切换协议失败 + return; + } + //切换协议成功,下面可以使用wsClient进行收发数据了。 + var webSocket = client.WebSocket; + + } + } + await e.InvokeNext(); + } + } + #endregion + + + #region 从继承创建WebSocket客户端 + class MyWebSocketClient:WebSocketClient + { + protected override Task OnWebSocketReceived(WSDataFrameEventArgs e) + { + return base.OnWebSocketReceived(e); + } + } + #endregion +} + diff --git a/examples/WebSocket/WebSocketConsoleApp/WebSocketConsoleApp.csproj b/examples/WebSocket/WebSocketConsoleApp/WebSocketConsoleApp.csproj index 299d8615c..3d485a9b4 100644 --- a/examples/WebSocket/WebSocketConsoleApp/WebSocketConsoleApp.csproj +++ b/examples/WebSocket/WebSocketConsoleApp/WebSocketConsoleApp.csproj @@ -1,4 +1,4 @@ - + Exe @@ -27,8 +27,8 @@ - - + + diff --git a/examples/XmlRpc/DispatchProxyXmlRpcClientConsoleApp/DispatchProxyXmlRpcClientConsoleApp.csproj b/examples/XmlRpc/DispatchProxyXmlRpcClientConsoleApp/DispatchProxyXmlRpcClientConsoleApp.csproj index 1e31549b0..c54158ef3 100644 --- a/examples/XmlRpc/DispatchProxyXmlRpcClientConsoleApp/DispatchProxyXmlRpcClientConsoleApp.csproj +++ b/examples/XmlRpc/DispatchProxyXmlRpcClientConsoleApp/DispatchProxyXmlRpcClientConsoleApp.csproj @@ -8,6 +8,6 @@ - + diff --git a/examples/XmlRpc/XmlRpcClientApp/Program.cs b/examples/XmlRpc/XmlRpcClientApp/Program.cs index 0f99123ea..131128825 100644 --- a/examples/XmlRpc/XmlRpcClientApp/Program.cs +++ b/examples/XmlRpc/XmlRpcClientApp/Program.cs @@ -25,11 +25,16 @@ internal class Program { var client = await GetXmlRpcClientAsync(); - //直接调用 - var result1 =await client.InvokeTAsync("Sum", InvokeOption.WaitInvoke, 10, 20); + #region XmlRpc直接调用 + var result1 = await client.InvokeTAsync("Sum", InvokeOption.WaitInvoke, 10, 20); + #endregion + Console.WriteLine($"直接调用,返回结果:{result1}"); - var result2 = client.Sum(10, 20);//此Sum方法是服务端生成的代理。 + #region XmlRpc代理调用 + var result2 = await client.SumAsync(10, 20);//此Sum方法是服务端生成的代理。 + #endregion + Console.WriteLine($"代理调用,返回结果:{result2}"); Console.ReadKey(); @@ -37,9 +42,11 @@ internal class Program private static async Task GetXmlRpcClientAsync() { - var jsonRpcClient = new XmlRpcClient(); - await jsonRpcClient.ConnectAsync("http://127.0.0.1:7789/xmlRpc"); + #region 创建XmlRpc客户端 + var client = new XmlRpcClient(); + await client.ConnectAsync("http://127.0.0.1:7789/xmlRpc"); + #endregion Console.WriteLine("连接成功"); - return jsonRpcClient; + return client; } } \ No newline at end of file diff --git a/examples/XmlRpc/XmlRpcClientApp/XmlRpcClientApp.csproj b/examples/XmlRpc/XmlRpcClientApp/XmlRpcClientApp.csproj index a0aa46155..319458a63 100644 --- a/examples/XmlRpc/XmlRpcClientApp/XmlRpcClientApp.csproj +++ b/examples/XmlRpc/XmlRpcClientApp/XmlRpcClientApp.csproj @@ -1,4 +1,4 @@ - + Exe net9.0 @@ -7,6 +7,6 @@ - + \ No newline at end of file diff --git a/examples/XmlRpc/XmlRpcServerApp/Program.cs b/examples/XmlRpc/XmlRpcServerApp/Program.cs index 55088758b..97c320b5b 100644 --- a/examples/XmlRpc/XmlRpcServerApp/Program.cs +++ b/examples/XmlRpc/XmlRpcServerApp/Program.cs @@ -24,6 +24,7 @@ internal class Program { private static void Main(string[] args) { + #region 创建XmlRpc服务器 var service = new HttpService(); service.SetupAsync(new TouchSocketConfig() @@ -42,11 +43,15 @@ internal class Program }) .ConfigurePlugins(a => { - a.UseXmlRpc() - .SetXmlRpcUrl("/xmlRpc"); + a.UseXmlRpc(options => + { + options.SetAllowXmlRpc("/xmlRpc"); + }); }) .SetListenIPHosts(7789)); service.StartAsync(); + #endregion + service.Logger.Info("服务器已启动"); Console.ReadKey(); @@ -57,6 +62,7 @@ internal class Program } } +#region 声明XmlRpc服务 public partial class XmlServer : SingletonRpcServer { [XmlRpc(MethodInvoke = true)] @@ -76,4 +82,7 @@ public class MyClass { public int A { get; set; } public int B { get; set; } -} \ No newline at end of file +} +#endregion + + diff --git a/examples/XmlRpc/XmlRpcServerApp/RpcProxy.cs b/examples/XmlRpc/XmlRpcServerApp/RpcProxy.cs index aadd96a5a..b4f43d773 100644 --- a/examples/XmlRpc/XmlRpcServerApp/RpcProxy.cs +++ b/examples/XmlRpc/XmlRpcServerApp/RpcProxy.cs @@ -1,181 +1,179 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - /* 此代码由Rpc工具直接生成,非必要请不要修改此处代码 */ #pragma warning disable using System; +using TouchSocket.Core; +using TouchSocket.Sockets; +using TouchSocket.Rpc; using System.Collections.Generic; using System.Diagnostics; using System.Text; using System.Threading.Tasks; -using TouchSocket.Core; -using TouchSocket.Rpc; -using TouchSocket.Sockets; namespace RpcProxy { - public interface IXmlServer : TouchSocket.Rpc.IRemoteServer - { - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - System.Int32 Sum(System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default); - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - Task SumAsync(System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default); +public interface IXmlServer:TouchSocket.Rpc.IRemoteServer +{ +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +System.Int32 Sum(System.Int32 a,System.Int32 b,InvokeOption invokeOption = default); +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +Task SumAsync(System.Int32 a,System.Int32 b,InvokeOption invokeOption = default); - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - System.Int32 TestClass(MyClass myClass, IInvokeOption invokeOption = default); - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - Task TestClassAsync(MyClass myClass, IInvokeOption invokeOption = default); - - } - public class XmlServer : IXmlServer - { - public XmlServer(IRpcClient client) - { - this.Client = client; - } - public IRpcClient Client { get; private set; } - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - public System.Int32 Sum(System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default) - { - if (this.Client == null) - { - throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); - } - object[] @_parameters = new object[] { a, b }; - System.Int32 returnData = (System.Int32)this.Client.Invoke("Sum", typeof(System.Int32), invokeOption, @_parameters); - return returnData; - } - /// - ///无注释信息 - /// - public async Task SumAsync(System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default) - { - if (this.Client == null) - { - throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); - } - object[] parameters = new object[] { a, b }; - return (System.Int32)await this.Client.InvokeAsync("Sum", typeof(System.Int32), invokeOption, parameters); - } - - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - public System.Int32 TestClass(MyClass myClass, IInvokeOption invokeOption = default) - { - if (this.Client == null) - { - throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); - } - object[] @_parameters = new object[] { myClass }; - System.Int32 returnData = (System.Int32)this.Client.Invoke("TestClass", typeof(System.Int32), invokeOption, @_parameters); - return returnData; - } - /// - ///无注释信息 - /// - public async Task TestClassAsync(MyClass myClass, IInvokeOption invokeOption = default) - { - if (this.Client == null) - { - throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); - } - object[] parameters = new object[] { myClass }; - return (System.Int32)await this.Client.InvokeAsync("TestClass", typeof(System.Int32), invokeOption, parameters); - } - - } - public static class XmlServerExtensions - { - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - public static System.Int32 Sum(this TClient client, System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default) where TClient : - TouchSocket.XmlRpc.IXmlRpcClient - { - object[] @_parameters = new object[] { a, b }; - System.Int32 returnData = (System.Int32)client.Invoke("Sum", typeof(System.Int32), invokeOption, @_parameters); - return returnData; - } - /// - ///无注释信息 - /// - public static async Task SumAsync(this TClient client, System.Int32 a, System.Int32 b, IInvokeOption invokeOption = default) where TClient : - TouchSocket.XmlRpc.IXmlRpcClient - { - object[] parameters = new object[] { a, b }; - return (System.Int32)await client.InvokeAsync("Sum", typeof(System.Int32), invokeOption, parameters); - } - - /// - ///无注释信息 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - public static System.Int32 TestClass(this TClient client, MyClass myClass, IInvokeOption invokeOption = default) where TClient : - TouchSocket.XmlRpc.IXmlRpcClient - { - object[] @_parameters = new object[] { myClass }; - System.Int32 returnData = (System.Int32)client.Invoke("TestClass", typeof(System.Int32), invokeOption, @_parameters); - return returnData; - } - /// - ///无注释信息 - /// - public static async Task TestClassAsync(this TClient client, MyClass myClass, IInvokeOption invokeOption = default) where TClient : - TouchSocket.XmlRpc.IXmlRpcClient - { - object[] parameters = new object[] { myClass }; - return (System.Int32)await client.InvokeAsync("TestClass", typeof(System.Int32), invokeOption, parameters); - } - - } - public class MyClass - { - public System.Int32 A { get; set; } - public System.Int32 B { get; set; } - } +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +System.Int32 TestClass(MyClass myClass,InvokeOption invokeOption = default); +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +Task TestClassAsync(MyClass myClass,InvokeOption invokeOption = default); + +} +public class XmlServer :IXmlServer +{ +public XmlServer(IRpcClient client) +{ +this.Client=client; +} +public IRpcClient Client{get;private set; } +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +public System.Int32 Sum(System.Int32 a,System.Int32 b,InvokeOption invokeOption = default) +{ +if(this.Client==null) +{ +throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); +} +object[] @_parameters = new object[]{a,b}; +System.Int32 returnData=(System.Int32)this.Client.Invoke("Sum",typeof(System.Int32),invokeOption, @_parameters); +return returnData; + +} +/// +///无注释信息 +/// +public async Task SumAsync(System.Int32 a,System.Int32 b,InvokeOption invokeOption = default) +{ +if(this.Client==null) +{ +throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); +} +object[] parameters = new object[]{a,b}; +return (System.Int32) await this.Client.InvokeAsync("Sum",typeof(System.Int32),invokeOption, parameters); + +} + +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +public System.Int32 TestClass(MyClass myClass,InvokeOption invokeOption = default) +{ +if(this.Client==null) +{ +throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); +} +object[] @_parameters = new object[]{myClass}; +System.Int32 returnData=(System.Int32)this.Client.Invoke("TestClass",typeof(System.Int32),invokeOption, @_parameters); +return returnData; + +} +/// +///无注释信息 +/// +public async Task TestClassAsync(MyClass myClass,InvokeOption invokeOption = default) +{ +if(this.Client==null) +{ +throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); +} +object[] parameters = new object[]{myClass}; +return (System.Int32) await this.Client.InvokeAsync("TestClass",typeof(System.Int32),invokeOption, parameters); + +} + +} +public static class XmlServerExtensions +{ +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +public static System.Int32 Sum(this TClient client,System.Int32 a,System.Int32 b,InvokeOption invokeOption = default) where TClient: +TouchSocket.XmlRpc.IXmlRpcClient{ +object[] @_parameters = new object[]{a,b}; +System.Int32 returnData=(System.Int32)client.Invoke("Sum",typeof(System.Int32),invokeOption, @_parameters); +return returnData; + +} +/// +///无注释信息 +/// +public static async Task SumAsync(this TClient client,System.Int32 a,System.Int32 b,InvokeOption invokeOption = default) where TClient: +TouchSocket.XmlRpc.IXmlRpcClient{ +object[] parameters = new object[]{a,b}; +return (System.Int32) await client.InvokeAsync("Sum",typeof(System.Int32),invokeOption, parameters); + +} + +/// +///无注释信息 +/// +/// 调用超时 +/// Rpc调用异常 +/// 其他异常 +[AsyncToSyncWarning] +public static System.Int32 TestClass(this TClient client,MyClass myClass,InvokeOption invokeOption = default) where TClient: +TouchSocket.XmlRpc.IXmlRpcClient{ +object[] @_parameters = new object[]{myClass}; +System.Int32 returnData=(System.Int32)client.Invoke("TestClass",typeof(System.Int32),invokeOption, @_parameters); +return returnData; + +} +/// +///无注释信息 +/// +public static async Task TestClassAsync(this TClient client,MyClass myClass,InvokeOption invokeOption = default) where TClient: +TouchSocket.XmlRpc.IXmlRpcClient{ +object[] parameters = new object[]{myClass}; +return (System.Int32) await client.InvokeAsync("TestClass",typeof(System.Int32),invokeOption, parameters); + +} + +} +public class MyClass +{ +public System.Int32 A { get; set; } +public System.Int32 B { get; set; } +} } diff --git a/examples/XmlRpc/XmlRpcServerApp/XmlRpcServerApp.csproj b/examples/XmlRpc/XmlRpcServerApp/XmlRpcServerApp.csproj index 302641e13..ddf5dc943 100644 --- a/examples/XmlRpc/XmlRpcServerApp/XmlRpcServerApp.csproj +++ b/examples/XmlRpc/XmlRpcServerApp/XmlRpcServerApp.csproj @@ -1,4 +1,4 @@ - + Exe @@ -6,7 +6,7 @@ - + diff --git a/examples/替换脚本.exe b/examples/替换脚本.exe new file mode 100644 index 000000000..810dd907e Binary files /dev/null and b/examples/替换脚本.exe differ diff --git a/gitmessage-template.txt b/gitmessage-template.txt new file mode 100644 index 000000000..91bcd6bb5 --- /dev/null +++ b/gitmessage-template.txt @@ -0,0 +1,49 @@ +# TouchSocket Git 提交模板 +# +# 提交格式:<类型>(<范围>): <简短描述> +# +# <详细描述> +# +# <相关Issue> +# +# 类型说明: +# 新增: 新功能 (feat) +# 修复: Bug修复 (fix) +# 文档: 文档更新 (docs) +# 样式: 代码格式调整,不影响功能 (style) +# 重构: 代码重构 (refactor) +# 优化: 性能优化 (perf) +# 测试: 测试相关 (test) +# 构建: 构建系统或外部依赖 (build) +# 集成: CI配置文件和脚本 (ci) +# 杂务: 其他杂务,如更新依赖 (chore) +# 回滚: 回滚提交 (revert) +# +# 范围说明(可选): +# Core: TouchSocket.Core +# Sockets: TouchSocket.Sockets +# Http: TouchSocket.Http +# Mqtt: TouchSocket.Mqtt +# Rpc: TouchSocket.Rpc +# Dmtp: TouchSocket.Dmtp +# WebApi: TouchSocket.WebApi +# Modbus: TouchSocket.Modbus +# SerialPorts: TouchSocket.SerialPorts +# docs: 文档相关 +# examples: 示例代码 +# tests: 测试代码 +# +# 示例: +# 新增(Core): 新增BytePool内存池管理功能 +# +# - 实现高效的内存池分配机制 +# - 支持自动扩容和回收 +# - 提升大量数据传输场景的性能 +# +# Closes #123 +# +# 注意事项: +# - 使用现在时态描述("添加"而不是"添加了") +# - 首行不超过50字符 +# - 详细描述每行不超过72字符 +# - 如果修复了Issue,请用"Closes #issue_number" diff --git a/handbook/.github/chatmodes/TouchSocket技术文档写作助手.chatmode.md b/handbook/.github/chatmodes/TouchSocket技术文档写作助手.chatmode.md new file mode 100644 index 000000000..9c99b29dc --- /dev/null +++ b/handbook/.github/chatmodes/TouchSocket技术文档写作助手.chatmode.md @@ -0,0 +1,52 @@ +--- +description: 'TouchSocket技术文档写作助手 - 按照TouchSocket框架的专业写作风格撰写技术文档' +tools: [] +--- +你是一个专业的技术文档撰写者,请按照以下风格要求撰写TouchSocket框架的技术文档: + +## 1. 文档结构规范 +- 使用层级标题结构:一级标题(##)用于大章节,二级标题(###)用于子功能点 +- 按照"定义-说明-特点-应用场景-架构-配置-插件-使用方法-示例"等的逻辑顺序组织内容 +- 每个功能点都要有清晰的编号(如"7.1 简单创建") + +## 2. 语言风格特点 +- **简洁明了**:开门见山,直接说明功能作用,避免冗余描述 +- **专业准确**:使用准确的技术术语,如"SessionClient"、"IOCP多线程"等 +- **用户友好**:多使用"可以"、"支持"等肯定表述,传达框架的能力 +- **重点标记**:如果是英文术语,出现时用反引号(``)包裹 + +## 3. 内容组织原则 +- **功能导向**:每个章节都围绕一个具体功能展开 +- **渐进式讲解**:从简单使用到高级定制,循序渐进 +- **实用性强**:重点突出实际应用场景和配置方法 + +## 4. 代码部分 +- **代码示例**:使用``示例代码,region名称要具体,且唯一 +- **架构图**:使用mermaid流程图清晰展示系统架构 +- **标签提示**:使用`推荐`突出最佳实践,但不要使用Tag标签没定义的标签 + +## 5. 注意事项格式 +- 使用`:::caution 注意:::`强调重要限制和注意事项 +- 使用`:::tip 建议:::`提供最佳实践建议 +- 使用`:::info 信息:::`补充说明相关信息 +- 用✅❌符号对比正确和错误的用法 + +## 6. 技术特色体现 +- **突出高性能**:明确标注性能数据(如"每秒200w条8字节") +- **强调易用性**:多使用"简单"、"一键式"等词汇 +- **体现灵活性**:说明多种实现方式的可选性 +- **插件化设计**:详细说明插件机制和扩展能力 + +## 7. 代码展示规范 +- 提供多种实现方式的对比(简单创建vs泛型创建) +- 代码示例要完整可运行 +- 用序号清晰标注实现步骤 +- 重要代码行用注释说明关键点 + +## 8. 用户体验考虑 +- 提供常见问题的解决方案 +- 给出具体的配置建议和默认值说明 +- 强调向下兼容性和最佳实践 +- 提供相关文档的交叉引用链接 + +请严格按照这个风格要求撰写技术文档,确保内容专业、易懂、实用。 \ No newline at end of file diff --git a/handbook/.github/prompts/优化文档代码.prompt.md b/handbook/.github/prompts/优化文档代码.prompt.md new file mode 100644 index 000000000..11cd736ef --- /dev/null +++ b/handbook/.github/prompts/优化文档代码.prompt.md @@ -0,0 +1,41 @@ +--- +mode: agent +--- + +# 前提 + +你是一个CSharp编程文档生成专家,精通各种类型的文档写作,包括技术文档、用户手册、项目报告等。你能够根据用户提供的信息和要求,生成结构清晰、内容详实、语言流畅的文档。 + +# 定义 + +- src文件夹:指以当前工作区根路径为参考下的 "../src/" 文件夹,包含所有csharp源代码文件。 +- examples文件夹:指以当前文件路径为参考下的 "../examples/" 文件夹,包含所有示例代码文件。 + +# 文档生成前置工作 + +1. 阅读用户提供的需求,理解文档的主题、目标读者和预期用途。 +2. 你需要先阅读源代码内容,他们都在src文件夹下,src文件夹下有多个子文件夹,每个子文件夹代表一个独立的模块。每个模块中包含若干CSharp源代码文件(.cs)和一个README.md文件。README.md文件中包含该模块的简要介绍和使用说明。 +3. 在生成文档时,你还需要参考一些示例代码,这些示例代码都在examples文件夹下,examples文件夹下有多个子文件夹,每个子文件夹代表一个独立的示例。每个示例中包含若干CSharp源代码文件(.cs),文件中包含该示例的简要介绍和使用说明。 + +# 在文档中的代码要求(绝对必须) + +- 在生成的文档中,所有csharp代码绝对不要直接包含代码块。 +- 把需要包含的代码示例,全部在**示例工程中**编写,并使用region包裹,在文档中只需要使用`CustomCodeBlock`引用即可。 +- 如果需要引用的代码示例在示例工程中不方便使用region,或者不能简洁地表达,可以在**示例工程中**新建一个文件,或方法,专门用于放置这些代码示例。 + +# 示例代码 + +示例代码工程(整个解决方案)在文档编写完成后,必须保证能够编译通过。 + +# region 标签生成规则 + +- region 标签的命名必须简洁明了,能够准确表达代码示例的内容。 +- region 标签的命名主体使用中文。 +- region 标签的命名格式为:`<当前文档个主题>`+`<功能描述>`。例如:`Http启用跨域`、`WebApi使用FromQuery传参`、`Rpc使用调用上下文`等。 +- region 标签中(即`CustomCodeBlock`)只填充region即可,不用写filePath等其他东西。 +- region 标签格式:。 + +# 任务 + +- 你的任务是把文档中的代码示例提取到示例工程中,或者直接使用示例工程中的代码,并使用region包裹,然后在文档中使用`CustomCodeBlock`引用这些代码示例。你需要确保生成的文档符合用户的要求,内容准确无误,语言表达清晰流畅。 +- 如果文档中已经有`CustomCodeBlock`,则不再修改这些`CustomCodeBlock`,只需要处理没有`CustomCodeBlock`的代码示例即可。 diff --git a/handbook/.github/prompts/文档生成.prompt.md b/handbook/.github/prompts/文档生成.prompt.md new file mode 100644 index 000000000..2dda42afd --- /dev/null +++ b/handbook/.github/prompts/文档生成.prompt.md @@ -0,0 +1,47 @@ +--- +mode: agent +--- + +# 前提 + +你是一个CSharp编程文档生成专家,精通各种类型的文档写作,包括技术文档、用户手册、项目报告等。你能够根据用户提供的信息和要求,生成结构清晰、内容详实、语言流畅的文档。 + +# 定义 + +- src文件夹:指以当前工作区根路径为参考下的 "../src/" 文件夹,包含所有csharp源代码文件。 +- examples文件夹:指以当前文件路径为参考下的 "../examples/" 文件夹,包含所有示例代码文件。 + +# 文档生成前置工作 + +1. 阅读用户提供的需求,理解文档的主题、目标读者和预期用途。 +2. 你需要先阅读源代码内容,他们都在src文件夹下,src文件夹下有多个子文件夹,每个子文件夹代表一个独立的模块。每个模块中包含若干CSharp源代码文件(.cs)和一个README.md文件。README.md文件中包含该模块的简要介绍和使用说明。 +3. 在生成文档时,你还需要参考一些示例代码,这些示例代码都在examples文件夹下,examples文件夹下有多个子文件夹,每个子文件夹代表一个独立的示例。每个示例中包含若干CSharp源代码文件(.cs),文件中包含该示例的简要介绍和使用说明。 + +# 在文档中的代码要求(绝对必须) + +- 在生成的文档中,所有csharp代码绝对不要直接包含代码块。 +- 把需要包含的代码示例,全部在**示例工程中**编写,并使用region包裹,在文档中只需要使用`CustomCodeBlock`引用即可。 +- 如果需要引用的代码示例在示例工程中不方便使用region,或者不能简洁地表达,可以在**示例工程中**新建一个文件,或方法,专门用于放置这些代码示例。 + +# 示例代码 + +示例代码工程(整个解决方案)在文档编写完成后,必须保证能够编译通过。 + +# region 标签生成规则 + +- region 标签的命名必须简洁明了,能够准确表达代码示例的内容。 +- region 标签的命名主体使用中文。 +- region 标签的命名格式为:`<当前文档个主题>`+`<功能描述>`。例如:`Http启用跨域`、`WebApi使用FromQuery传参`、`Rpc使用调用上下文`等。 +- region 标签中(即`CustomCodeBlock`)只填充region即可,不用写filePath等其他东西。 +- region 标签格式:。 + +# 视频链接规则 + +- 如果文档中包含视频链接,在重写文档时,链接必须保持。 +- 不要生成不存在的视频链接。 + + + +# 任务 + +你的任务是根据用户提供的需求,生成相应的CSharp编程文档。你需要根据源代码和示例代码,提取关键信息,组织内容结构,撰写文档内容。你需要确保生成的文档符合用户的要求,内容准确无误,语言表达清晰流畅。 diff --git a/handbook/.vscode/doc.code-snippets b/handbook/.vscode/doc.code-snippets index c1e139018..b5c667e35 100644 --- a/handbook/.vscode/doc.code-snippets +++ b/handbook/.vscode/doc.code-snippets @@ -122,6 +122,13 @@ ], "description": "importHighlight" }, + "importCustomCodeBlock": { + "prefix": "importCustomCodeBlock", + "body": [ + "import CustomCodeBlock from './CodeBlocks/CustomCodeBlock';" + ], + "description": "importCustomCodeBlock" + }, "importBilibiliCard": { "prefix": "importBilibiliCard", "body": [ @@ -157,6 +164,13 @@ ], "description": "CardLink" }, + "CustomCodeBlock": { + "prefix": "CustomCodeBlock", + "body": [ + "" + ], + "description": "CustomCodeBlock" + }, "BilibiliCard": { "prefix": "BilibiliCard", "body": [ diff --git a/handbook/.vscode/settings.json b/handbook/.vscode/settings.json index ea6a1b4e4..b85160e31 100644 --- a/handbook/.vscode/settings.json +++ b/handbook/.vscode/settings.json @@ -28,5 +28,38 @@ "winform" ], "editor.wordWrap": "on", - // "editor.wordWrapColumn": 80 + // "editor.wordWrapColumn": 80, + // 文件编码设置 + "files.encoding": "utf8", + "files.autoGuessEncoding": false, + // Git 提交配置 + "git.inputValidationLength": 50, + "git.inputValidationSubjectLength": 50, + "git.verboseCommit": true, + "scm.inputFontFamily": "Consolas, 'Courier New', monospace", + "scm.inputFontSize": 14, + // TouchSocket 项目提交规范提示 + "git.commitTemplate": "../../gitmessage-template.txt", + // Copilot 配置 + "github.copilot.enable": { + "*": true, + "plaintext": true, + "markdown": true, + "scminput": true + }, + // Git 提交相关的 Copilot 提示 + "github.copilot.editor.enableAutoCompletions": true, + // Paste Image 插件配置 + "pasteImage.path": "${projectRoot}/static/img/docs/", + "pasteImage.basePath": "${projectRoot}", + "pasteImage.forceUnixStyleSeparator": true, + "pasteImage.prefix": "", + "pasteImage.suffix": "", + "pasteImage.insertPattern": "", + "pasteImage.namePrefix": "", + "pasteImage.nameSuffix": "", + "pasteImage.encodePath": "urlEncodeSpace", + "pasteImage.showFilePathConfirmInputBox": false, + "pasteImage.filePathConfirmInputBoxMode": "onlyName", + "pasteImage.defaultName": "${currentFileNameWithoutExt}-YYYYMMDDHHmmss" } \ No newline at end of file diff --git a/handbook/docs/CodeBlocks/CustomCodeBlock.js b/handbook/docs/CodeBlocks/CustomCodeBlock.js new file mode 100644 index 000000000..2a73717c1 --- /dev/null +++ b/handbook/docs/CodeBlocks/CustomCodeBlock.js @@ -0,0 +1,353 @@ +import React, { useState, useEffect } from 'react'; +import CodeBlock from '@theme/CodeBlock'; +import { extractCodeRegion, getAvailableRegions } from './codesData'; + +/** + * 检测是否为构建环境 + * @returns {boolean} - 是否为构建环境 + */ +const isBuildEnvironment = () => +{ + // 安全地访问 process 对象 + if (typeof process === 'undefined') + { + return typeof window === 'undefined'; // SSR环境,视为构建环境 + } + + // 更激进的构建环境检测 - 在所有可能的构建场景下都返回 true + const isProduction = process.env.NODE_ENV === 'production'; + const isDocusaurusBuild = !!(process.env.DOCUSAURUS_CURRENT_LOCALE || + process.env.BUILD_PHASE === 'build' || + process.env.npm_lifecycle_event === 'build'); + const isSSR = typeof window === 'undefined'; + + // 强制在任何看起来像构建的环境中启用错误检测 + const isBuildMode = isProduction || isDocusaurusBuild || isSSR; + + return isBuildMode; +}; + +/** + * 解析高亮规则,例如 "1,2-3" 或 "{1,2-3}" 转换为数组 [1, 2, 3] + * @param {string} highlightText - 高亮规则字符串 + * @returns {number[]} - 行号数组 + */ +const parseHighlightRules = (highlightText) => +{ + if (!highlightText || typeof highlightText !== 'string') + { + return []; + } + + // 移除可能的花括号(兼容旧格式) + const rulesText = highlightText.replace(/[{}]/g, '').trim(); + if (!rulesText) + { + return []; + } + + const lines = []; + const parts = rulesText.split(','); + + for (const part of parts) + { + const trimmedPart = part.trim(); + + // 检查是否是范围(如 "2-5") + if (trimmedPart.includes('-')) + { + const [start, end] = trimmedPart.split('-').map(s => parseInt(s.trim(), 10)); + if (!isNaN(start) && !isNaN(end) && start <= end) + { + for (let i = start; i <= end; i++) + { + lines.push(i); + } + } + } else + { + // 单个行号 + const lineNumber = parseInt(trimmedPart, 10); + if (!isNaN(lineNumber)) + { + lines.push(lineNumber); + } + } + } + + // 去重并排序 + return [...new Set(lines)].sort((a, b) => a - b); +}; + +/** + * 将行号数组转换为Docusaurus支持的高亮字符串格式 + * @param {number[]} lines - 行号数组 + * @returns {string} - 高亮字符串,如 "1,3-5,8" + */ +const formatHighlightString = (lines) => +{ + if (!lines || lines.length === 0) return ''; + + // 确保行号已排序并去重 + const sortedLines = [...new Set(lines)].sort((a, b) => a - b); + + const ranges = []; + let i = 0; + + while (i < sortedLines.length) + { + const start = sortedLines[i]; + let end = start; + + // 寻找连续的数字 + while (i + 1 < sortedLines.length && sortedLines[i + 1] === sortedLines[i] + 1) + { + i++; + end = sortedLines[i]; + } + + // 如果是连续范围,用 start-end 格式,否则单独列出 + if (start === end) + { + ranges.push(start.toString()); + } else + { + ranges.push(`${start}-${end}`); + } + i++; + } + + return ranges.join(','); +}; + +/** + * CodeBlock组件 - 根据region名称显示对应的代码块 + * @param {Object} props + * @param {string} props.region - region的名称 + * @param {string} props.highlight - 高亮规则,例如 "1,2-3,5" 或 "{1,2-3,5}" + * @param {string} props.language - 代码语言,默认为'csharp' + * @param {string} props.title - 代码块标题,默认使用region名称 + * @param {boolean} props.showLineNumbers - 是否显示行号,默认为true + * @param {boolean} props.showAvailableRegions - 是否在错误时显示可用的regions,默认为false + * @param {boolean} props.showSourceFile - 是否在标题中显示源文件,默认为false + */ +const CustomCodeBlock = ({ + region, + highlight, + language = 'csharp', + title, + showLineNumbers = true, + showAvailableRegions = false, + showSourceFile = false, + ...props +}) => +{ + // 🚨 在组件渲染时立即进行验证 - 这会发生在服务端渲染阶段 + if (isBuildEnvironment()) + { + if (!region) + { + const error = new Error(`[BUILD VALIDATION FAILED] CustomCodeBlock 缺少 region 参数`); + throw error; // 直接抛出,不等待useEffect + } + + // 检查region是否存在(支持旧格式) + let regionName = region; + const legacyMatch = region.match(/^(.+?)\{([^}]+)\}$/); + if (legacyMatch) + { + regionName = legacyMatch[1].trim(); + } + + const extractedInfo = extractCodeRegion(regionName); + if (extractedInfo === null) + { + const error = new Error(`[BUILD VALIDATION FAILED] 找不到名为 "${regionName}" 的代码区域`); + throw error; // 直接抛出,不等待useEffect + } + } + + const [codeInfo, setCodeInfo] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [highlightLines, setHighlightLines] = useState([]); + + useEffect(() => + { + const loadCode = () => + { + try + { + setLoading(true); + setError(null); + setHighlightLines([]); + + if (!region) + { + const errorMessage = '请提供region参数'; + + // 在构建环境中抛出异常,阻止网站发布 + if (isBuildEnvironment()) + { + throw new Error(`[CodeBlock Build Error] ${errorMessage}`); + } + + setError(errorMessage); + setCodeInfo(null); + return; + } + + // 检查region是否包含旧格式的高亮规则(向下兼容) + let regionName = region; + let legacyHighlightRules = ''; + + const legacyMatch = region.match(/^(.+?)\{([^}]+)\}$/); + if (legacyMatch) + { + regionName = legacyMatch[1].trim(); + legacyHighlightRules = legacyMatch[2]; + } + + const extractedInfo = extractCodeRegion(regionName); + + if (extractedInfo === null) + { + const errorMessage = `找不到名为 "${regionName}" 的代码区域`; + + // 在构建环境中抛出异常,阻止网站发布 + if (isBuildEnvironment()) + { + throw new Error(`[CodeBlock Build Error] ${errorMessage} (region: "${regionName}")`); + } + + setError(errorMessage); + setCodeInfo(null); + } else + { + setCodeInfo(extractedInfo); + + // 确定要使用的高亮规则(优先级:highlight参数 > 旧格式 > region定义中的高亮) + let highlightRulesToUse = highlight || legacyHighlightRules; + + // 如果没有外部提供的高亮规则,使用从region定义中解析出的高亮信息 + if (!highlightRulesToUse && extractedInfo.highlightLines && extractedInfo.highlightLines.length > 0) + { + highlightRulesToUse = extractedInfo.highlightLines.join(','); + } + + if (highlightRulesToUse) + { + const parsedHighlightLines = parseHighlightRules(highlightRulesToUse); + setHighlightLines(parsedHighlightLines); + } + } + } catch (err) + { + const errorMessage = `处理代码失败: ${err.message}`; + + // 在构建环境中抛出异常,阻止网站发布 + if (isBuildEnvironment()) + { + throw new Error(`[CodeBlock Build Error] ${errorMessage} (region: "${region}")`); + } + + setError(errorMessage); + setCodeInfo(null); + } finally + { + setLoading(false); + } + }; + + loadCode(); + }, [region, highlight, showAvailableRegions]); + + if (loading) + { + return ( +
+
🔄 正在加载代码...
+
+ ); + } + + if (error) + { + return ( +
+
❌ {error}
+
+ ); + } + + if (!codeInfo || !codeInfo.code.trim()) + { + return ( +
+
⚠️ 代码区域 "{region.replace(/\{[^}]+\}$/, '')}" 为空
+
+ ); + } + + // 生成标题 + let codeBlockTitle = title; + if (!codeBlockTitle) + { + // 移除可能存在的旧格式高亮规则部分,只显示region名称 + const cleanRegionName = region.replace(/\{[^}]+\}$/, ''); + codeBlockTitle = showSourceFile + ? `${cleanRegionName} (来自: ${codeInfo.sourceFile})` + : `代码区域: ${cleanRegionName}`; + } + + // 构建最终的代码内容和高亮信息 + // 去除尾随的所有空白字符(包括 \r\n、\n、空格、制表符等),防止显示时多一行 + let finalCode = codeInfo.code.replace(/\s+$/, ''); + let finalMetastring = ''; + + if (highlightLines.length > 0) + { + const highlightString = formatHighlightString(highlightLines); + // 使用 metastring 格式 + finalMetastring = `{${highlightString}}`; + } + + // 安全地检查是否为开发环境 + const isDevelopment = typeof process !== 'undefined' && process.env.NODE_ENV === 'development'; + + return ( + + {finalCode} + + ); +}; + +export default CustomCodeBlock; \ No newline at end of file diff --git a/handbook/docs/CodeBlocks/generateCodesModule.js b/handbook/docs/CodeBlocks/generateCodesModule.js new file mode 100644 index 000000000..297789921 --- /dev/null +++ b/handbook/docs/CodeBlocks/generateCodesModule.js @@ -0,0 +1,446 @@ +const fs = require('fs'); +const path = require('path'); + +// 配置要搜索的目录列表(相对于项目根目录) +const SEARCH_DIRECTORIES = [ + 'examples', // 示例代码目录 +]; + +// 排除的目录模式 +const EXCLUDE_PATTERNS = ['obj', 'bin', '.vs', 'packages', 'node_modules']; + +/** + * 递归搜索目录下的所有.cs文件 + * @param {string} dirPath - 目录路径 + * @param {string[]} excludePatterns - 排除的路径模式 + * @returns {string[]} - .cs文件路径数组 + */ +function findCsFiles(dirPath, excludePatterns = EXCLUDE_PATTERNS) +{ + const files = []; + + if (!fs.existsSync(dirPath)) + { + console.warn(`⚠️ 目录不存在: ${dirPath}`); + return files; + } + + const items = fs.readdirSync(dirPath); + + for (const item of items) + { + const itemPath = path.join(dirPath, item); + const stat = fs.statSync(itemPath); + + // 检查是否需要排除 - 修复:检查路径分段而非整个路径字符串 + const pathParts = itemPath.split(path.sep); + const shouldExclude = excludePatterns.some(pattern => + pathParts.some(part => part.toLowerCase() === pattern.toLowerCase()) + ); + + if (shouldExclude) + { + continue; + } + + if (stat.isDirectory()) + { + // 递归搜索子目录 + files.push(...findCsFiles(itemPath, excludePatterns)); + } else if (item.toLowerCase().endsWith('.cs')) + { + files.push(itemPath); + } + } + + return files; +} + +/** + * 读取多个目录的cs文件并合并内容 + * @returns {Object} - 合并后的代码内容和文件信息 + */ +function readAndMergeCsFiles() +{ + const projectRoot = path.join(__dirname, '..', '..', '..'); + + console.log('🚀 开始搜索配置的目录...'); + console.log('📂 配置的搜索目录:'); + SEARCH_DIRECTORIES.forEach(dir => console.log(` - ${dir}`)); + + let allCsFiles = []; + let validDirectories = []; + + // 遍历所有配置的目录 + for (const searchDir of SEARCH_DIRECTORIES) + { + const targetDir = path.join(projectRoot, searchDir); + + if (!fs.existsSync(targetDir)) + { + console.warn(`⚠️ 目录不存在,跳过: ${searchDir}`); + continue; + } + + console.log(`🔍 搜索目录: ${targetDir}`); + const csFiles = findCsFiles(targetDir); + + if (csFiles.length > 0) + { + console.log(` 找到 ${csFiles.length} 个 .cs 文件`); + allCsFiles.push(...csFiles); + validDirectories.push(searchDir); + } else + { + console.log(` 该目录下没有 .cs 文件`); + } + } + + if (allCsFiles.length === 0) + { + console.warn(`⚠️ 在配置的目录中没有找到任何 .cs 文件`); + return { content: '', files: [], directories: [] }; + } + + console.log(`\n📁 总计找到 ${allCsFiles.length} 个 .cs 文件:`); + + let mergedContent = ''; + const fileInfos = []; + + for (const filePath of allCsFiles) + { + try + { + const content = fs.readFileSync(filePath, 'utf8'); + const relativePath = path.relative(projectRoot, filePath); + + console.log(` - ${relativePath} (${content.length} 字符)`); + + // 添加文件标识注释 + const fileHeader = `\n// ===== FILE: ${relativePath} =====\n`; + mergedContent += fileHeader + content + '\n'; + + fileInfos.push({ + path: filePath, + relativePath: relativePath, + size: content.length + }); + } catch (error) + { + console.error(`❌ 读取文件失败 ${filePath}: ${error.message}`); + } + } + + return { + content: mergedContent, + files: fileInfos, + directories: validDirectories + }; +} + +/** + * 生成JavaScript模块,搜索所有配置的目录 + */ +function generateCodesModule() +{ + try + { + console.log('🚀 开始生成代码模块...'); + + const { content: codesContent, files: fileInfos, directories: validDirectories } = readAndMergeCsFiles(); + + if (!codesContent.trim()) + { + console.error('❌ 没有找到任何代码内容'); + process.exit(1); + } + + const moduleContent = `// 自动生成的文件 - 请勿手动编辑 +// 由 generateCodesModule.js 生成 +// 搜索目录: ${SEARCH_DIRECTORIES.join(', ')} +// 有效目录: ${validDirectories.join(', ')} +// 包含文件: ${fileInfos.map(f => f.relativePath).join(', ')} + +export const codesContent = ${JSON.stringify(codesContent)}; + +// 文件信息 +export const fileInfos = ${JSON.stringify(fileInfos, null, 2)}; + +// 搜索目录配置 +export const searchDirectories = ${JSON.stringify(SEARCH_DIRECTORIES)}; +export const validDirectories = ${JSON.stringify(validDirectories)}; + +/** + * 解析高亮语法 + * @param {string} highlightStr - 高亮字符串,如 "{1,2-3,5}" + * @returns {number[]} - 高亮行数组 + */ +function parseHighlightSyntax(highlightStr) { + if (!highlightStr) return []; + + // 移除大括号 + const content = highlightStr.replace(/[{}]/g, ''); + if (!content) return []; + + const lines = []; + const parts = content.split(','); + + for (const part of parts) { + const trimmed = part.trim(); + if (trimmed.includes('-')) { + // 范围语法,如 "2-3" + const [start, end] = trimmed.split('-').map(num => parseInt(num.trim())); + if (!isNaN(start) && !isNaN(end) && start <= end) { + for (let i = start; i <= end; i++) { + lines.push(i); + } + } + } else { + // 单行语法,如 "1" + const num = parseInt(trimmed); + if (!isNaN(num)) { + lines.push(num); + } + } + } + + // 去重并排序 + return [...new Set(lines)].sort((a, b) => a - b); +} + +/** + * 从代码内容中提取指定region的代码 + * @param {string} regionTitle - region的名称 + * @returns {Object|null} - 提取的代码块信息或null + */ +export function extractCodeRegion(regionTitle) { + const content = codesContent; + const lines = content.split('\\n'); + + // 转义特殊字符以用于正则表达式 + const escapeRegex = (str) => { + return str.replace(/[.*+?^\\$\\{\\}()|]/g, '\\\\\\\\$&') + .replace(/\\[/g, '\\\\\\\\[') + .replace(/\\]/g, '\\\\\\\\]') + .replace(/\\\\\\\\/g, '\\\\\\\\\\\\\\\\'); + }; + const escapedTitle = escapeRegex(regionTitle); + + // 修改正则表达式以支持高亮语法和尾部空白(\\r等) + // 匹配 #region RegionName {1,2-3} 或 #region RegionName + const regionStartPattern = new RegExp(\`^\\\\s*#region\\\\s+\${escapedTitle}(?:\\\\s*\\\\{([^}]+)\\\\})?\\\\s*$\`); + const anyRegionStartPattern = /^\\s*#region\\s+(.+?)(?:\\s*\\{[^}]+\\})?\\s*$/; + const regionEndPattern = /^\\s*#endregion(?:\\s+.*?)?\\s*$/; + + let startIndex = -1; + let endIndex = -1; + let sourceFile = null; + let highlightLines = []; + + // 找到目标region开始位置 + for (let i = 0; i < lines.length; i++) { + const match = regionStartPattern.exec(lines[i]); + if (match) { + startIndex = i + 1; // 跳过#region行 + + // 解析高亮语法 + if (match[1]) { + highlightLines = parseHighlightSyntax('{' + match[1] + '}'); + } + + // 查找region所在的源文件 + for (let j = i; j >= 0; j--) { + const line = lines[j]; + const fileMatch = line.match(/^\\/\\/ ===== FILE: (.+) =====$/); + if (fileMatch) { + sourceFile = fileMatch[1]; + break; + } + } + break; + } + } + + if (startIndex === -1) { + return null; // 没找到对应的region + } + + // 找到对应的#endregion,需要处理嵌套情况 + let regionDepth = 1; // 当前region深度,从1开始(因为已经进入了目标region) + + for (let i = startIndex; i < lines.length; i++) { + const line = lines[i]; + + // 检查是否遇到了新的#region(嵌套) + if (anyRegionStartPattern.test(line)) { + regionDepth++; // 进入更深层的region + } + // 检查是否遇到了#endregion + else if (regionEndPattern.test(line)) { + regionDepth--; // 退出一层region + + // 如果深度回到0,说明找到了目标region的结束位置 + if (regionDepth === 0) { + endIndex = i; + break; + } + } + } + + if (endIndex === -1) { + return null; // 没找到对应的#endregion + } + + // 提取代码块并移除多余的空白行 + const codeLines = lines.slice(startIndex, endIndex); + + // 移除开头和结尾的空行 + while (codeLines.length > 0 && codeLines[0].trim() === '') { + codeLines.shift(); + } + while (codeLines.length > 0 && codeLines[codeLines.length - 1].trim() === '') { + codeLines.pop(); + } + + // 统一缩进处理 + let code = ''; + if (codeLines.length > 0) { + // 找到最小缩进 + const minIndent = codeLines + .filter(line => line.trim() !== '') + .reduce((min, line) => { + const indent = line.match(/^\\s*/)[0].length; + return Math.min(min, indent); + }, Infinity); + + // 移除统一的缩进 + if (minIndent > 0 && minIndent !== Infinity) { + code = codeLines.map(line => line.slice(minIndent)).join('\\n'); + } else { + code = codeLines.join('\\n'); + } + } + + return { + code: code, + sourceFile: sourceFile || 'unknown', + startLine: startIndex, + endLine: endIndex, + highlightLines: highlightLines // 添加高亮行信息 + }; +} + +/** + * 获取所有可用的region列表 + * @returns {Array} - region信息数组,包含名称和来源文件 + */ +export function getAvailableRegions() { + const content = codesContent; + const lines = content.split('\\n'); + const regionPattern = /^\\s*#region\\s+(.+)\\s*$/; + const regions = []; + let currentFile = null; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + + // 检查文件标识 + const fileMatch = line.match(/^\\/\\/ ===== FILE: (.+) =====$/); + if (fileMatch) { + currentFile = fileMatch[1]; + continue; + } + + // 检查region + const regionMatch = line.match(regionPattern); + if (regionMatch) { + regions.push({ + name: regionMatch[1].trim(), + file: currentFile || 'unknown', + line: i + 1 + }); + } + } + + return regions; +} +`; + + const outputPath = path.join(__dirname, 'codesData.js'); + fs.writeFileSync(outputPath, moduleContent, 'utf8'); + + console.log('✅ 代码内容已成功生成到 codesData.js'); + console.log(`📁 输出文件: ${outputPath}`); + console.log(`� 搜索的目录: ${SEARCH_DIRECTORIES.join(', ')}`); + console.log(`✅ 有效目录: ${validDirectories.join(', ')}`); + console.log(`�📊 合并了 ${fileInfos.length} 个文件,总计 ${codesContent.length} 个字符`); + + // 显示找到的regions + const availableRegions = getAvailableRegions(codesContent); + if (availableRegions.length > 0) + { + // 仅在 start 脚本时输出详细的区域列表,build 时不输出 + // 检查是否在 build 环境中(通过 NODE_ENV 或命令行参数) + const isBuild = process.env.NODE_ENV === 'production' || process.argv.includes('--build'); + if (!isBuild) + { + console.log('🔍 找到以下代码区域:'); + availableRegions.forEach(region => + { + console.log(` - ${region.name} (来自: ${region.file})`); + }); + } + } + + } catch (error) + { + console.error('❌ 生成失败:', error.message); + process.exit(1); + } +} + +/** + * 辅助函数:从代码内容中获取regions + */ +function getAvailableRegions(content) +{ + const lines = content.split('\n'); + const regionPattern = /^\s*#region\s+(.+)\s*$/; + const regions = []; + let currentFile = null; + + for (let i = 0; i < lines.length; i++) + { + const line = lines[i]; + + // 检查文件标识 + const fileMatch = line.match(/^\/\/ ===== FILE: (.+) =====$/); + if (fileMatch) + { + currentFile = fileMatch[1]; + continue; + } + + // 检查region + const regionMatch = line.match(regionPattern); + if (regionMatch) + { + regions.push({ + name: regionMatch[1].trim(), + file: currentFile || 'unknown', + line: i + 1 + }); + } + } + + return regions; +} + +// 如果直接运行此脚本 +if (require.main === module) +{ + console.log('📂 使用配置的搜索目录:', SEARCH_DIRECTORIES); + generateCodesModule(); +} + +module.exports = { generateCodesModule, findCsFiles, readAndMergeCsFiles, SEARCH_DIRECTORIES }; diff --git a/handbook/docs/adapterbuilder.mdx b/handbook/docs/adapterbuilder.mdx index 021bd56b2..7d9b8edea 100644 --- a/handbook/docs/adapterbuilder.mdx +++ b/handbook/docs/adapterbuilder.mdx @@ -6,7 +6,7 @@ title: 适配器消息构建器 import BilibiliCard from '@site/src/components/BilibiliCard.js'; import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; -### 定义 + diff --git a/handbook/docs/adapterdescription.mdx b/handbook/docs/adapterdescription.mdx index 87e3e477d..f7d8972ec 100644 --- a/handbook/docs/adapterdescription.mdx +++ b/handbook/docs/adapterdescription.mdx @@ -6,7 +6,7 @@ title: 介绍及使用 import BilibiliCard from '@site/src/components/BilibiliCard.js'; import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; -### 定义 + diff --git a/handbook/docs/adaptererrorcorrection.mdx b/handbook/docs/adaptererrorcorrection.mdx index b17b447bc..437099506 100644 --- a/handbook/docs/adaptererrorcorrection.mdx +++ b/handbook/docs/adaptererrorcorrection.mdx @@ -6,7 +6,7 @@ title: 适配器纠错 import BilibiliCard from '@site/src/components/BilibiliCard.js'; import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; -### 定义 + @@ -43,7 +43,7 @@ import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; 其中,Tag为4字节,Length为4字节,Value为可变长度的数据。 -要解析该数据,我们非常容易的会想到使用[模版解析固定包头适配器](./customfixedheaderdatahandlingadapter.mdx)来解决问题。 +要解析该数据,我们非常容易的会想到使用[模版解析固定包头适配器](./singlethreadstreamadapter.mdx)来解决问题。 所以代码大概如下: @@ -126,7 +126,7 @@ public class MyFixedHeaderRequestInfo : IFixedHeaderRequestInfo 即时重置缓存,是指当数据不完整时,立即重置缓存。这也就意味着,当数据不完整时,我们会立即放弃本次数据的解析。直接进行下一次数据的解析。 -该操作对于[用户自定义解析](./customdatahandlingadapter.mdx)是容易的。您只需要调用`this.Reset()`即可。 +该操作对于[用户自定义解析](./singlethreadstreamadapter.mdx)是容易的。您只需要调用`this.Reset()`即可。 但是对于模版解析,可能需要传递一些委托来完成重置工作。 @@ -233,7 +233,7 @@ public class MyFixedHeaderCustomDataHandlingAdapter : CustomFixedHeaderDataHandl ## 2.5 断开连接 -当数据有异常时,直接断开连接,也不失为一种处理方式。同样,该操作对于[用户自定义解析](./customdatahandlingadapter.mdx)是容易的。您只需要调用`Owner`的相关操作即可。例如: +当数据有异常时,直接断开连接,也不失为一种处理方式。同样,该操作对于[用户自定义解析](./singlethreadstreamadapter.mdx)是容易的。您只需要调用`Owner`的相关操作即可。例如: 可以声明一个方法。用于关闭连接。 diff --git a/handbook/docs/appmessenger.mdx b/handbook/docs/appmessenger.mdx index db0104711..d22c230fa 100644 --- a/handbook/docs/appmessenger.mdx +++ b/handbook/docs/appmessenger.mdx @@ -4,8 +4,9 @@ title: 应用信使 --- import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; +import CardLink from "@site/src/components/CardLink.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; -### 定义 @@ -23,66 +24,101 @@ import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; 然后一般情况下,建议在构造函数中,注册消息。 -```csharp {5} showLineNumbers -public class MessageObject : IMessageObject -{ - public MessageObject() - { - AppMessenger.Default.Register(this); - } - - [AppMessage] - public Task Add(int a, int b) - { - return Task.FromResult(a + b); - } - - [AppMessage] - public Task Sub(int a, int b) - { - return Task.FromResult(a - b); - } -} -``` + :::info 信息 对于实例类,如果构造函数中,没有注册消息,那么在构造函数之后,也可以使用其**实例**注册消息。 -```csharp {2} showLineNumbers -var messageObject = new MessageObject(); -AppMessenger.Default.Register(messageObject); -``` + ::: -### 2.1 注册静态方法 +### 2.2 注册静态方法 -注册静态方法,只需在类中直接声明**异步公共实例**方法,并使用`AppMessage`特性标记即可。 +注册静态方法,只需在类中直接声明**异步公共静态**方法,并使用`AppMessage`特性标记即可。 -```csharp showLineNumbers -public static class MessageObject : IMessageObject -{ - [AppMessage] - public static Task StaticAdd(int a, int b) - { - return Task.FromResult(a + b); - } -} -``` + 使用`RegisterStatic`进行注册 -```csharp showLineNumbers -AppMessenger.Default.RegisterStatic(); -``` + ## 三、触发 触发时,泛型类型,即时返回值类型。 -```csharp showLineNumbers -int add = await appMessenger.SendAsync("Add", 20, 10); + -int sub =await appMessenger.SendAsync("Sub", 20, 10); +## 四、注销 + +当不再需要某个消息订阅时,可以通过`Unregister`方法进行注销。支持按对象注销和按Token注销两种方式。 + +### 4.1 按对象注销 + +注销指定对象的所有消息订阅。 + + + +### 4.2 按Token注销 + +也可以直接通过Token来注销特定的消息订阅。 + +```csharp showLineNumbers +AppMessenger.Default.Unregister("Add"); ``` + +## 五、高级功能 + +### 5.1 允许多个订阅 + +默认情况下,同一个Token只能注册一次。如果需要让多个对象订阅同一个消息,需要设置`AllowMultiple`属性为`true`。 + + + +### 5.2 检查消息是否可发送 + +可以使用`CanSendMessage`方法检查某个Token是否已经被注册。 + +```csharp showLineNumbers +if (AppMessenger.Default.CanSendMessage("Add")) +{ + var result = await AppMessenger.Default.SendAsync("Add", 10, 20); +} +``` + +### 5.3 获取所有已注册的消息 + +可以通过`GetAllMessage`方法获取所有已注册的消息Token。 + +```csharp showLineNumbers +var allMessages = AppMessenger.Default.GetAllMessage(); +foreach (var token in allMessages) +{ + Console.WriteLine($"已注册的消息: {token}"); +} +``` + +### 5.4 清除所有订阅 + +使用`Clear`方法可以清除所有已注册的消息订阅。 + +```csharp showLineNumbers +AppMessenger.Default.Clear(); +``` + +## 六、注意事项 + +1. **弱引用机制**: `AppMessenger`内部使用弱引用保存订阅者,避免强引用导致的内存泄漏。如果订阅对象被回收,相关的订阅也会自动失效。 + +2. **异步方法**: 所有标记为`AppMessage`的方法都应该是异步方法,返回`Task`或`Task`。 + +3. **Token命名**: 如果不显式指定Token,将使用方法名作为Token。也可以通过`AppMessage`特性的参数自定义Token: + ```csharp + [AppMessage("CustomToken")] + public Task MyMethod(int a, int b) { ... } + ``` + +4. **实例生命周期**: 建议在对象构造函数中注册消息,并确保对象在需要接收消息期间保持存活。 + +5. **线程安全**: `AppMessenger`是线程安全的,可以在多线程环境中安全使用。 diff --git a/handbook/docs/blog.mdx b/handbook/docs/blog.mdx index 9eb9f2829..cd4ac86b0 100644 --- a/handbook/docs/blog.mdx +++ b/handbook/docs/blog.mdx @@ -9,4 +9,4 @@ title: 博客 - [C# Tcp服务器如何限制同一个IP的连接数量?](https://blog.csdn.net/qq_40374647/article/details/125390655) - [C# 实现为Tcp服务器设计访问黑名单、白名单](https://blog.csdn.net/qq_40374647/article/details/128640132) - [C# Tcp服务器实现多端口、多协议解析](https://blog.csdn.net/qq_40374647/article/details/128641766) -- [C# 优雅的为Tcp客户端设置心跳数据包](https://blog.csdn.net/qq_40374647/article/details/125598921) \ No newline at end of file +- [C# 优雅的为Tcp客户端设置心跳数据包](https://blog.csdn.net/qq_40374647/article/details/125598921) diff --git a/handbook/docs/bytepool.mdx b/handbook/docs/bytepool.mdx index 25f8fbbc4..ff963ff49 100644 --- a/handbook/docs/bytepool.mdx +++ b/handbook/docs/bytepool.mdx @@ -5,8 +5,8 @@ title: 内存池 import CardLink from "@site/src/components/CardLink.js"; import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; -### 定义 @@ -29,10 +29,7 @@ import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; 当然您可以创建只属于自己的`ArrayPool`。 -```csharp showLineNumbers -ArrayPool bytePool = new ArrayPool(maxArrayLength: 1024 * 1024, maxArraysPerBucket: 50); -``` - + 其中: @@ -45,34 +42,19 @@ ArrayPool bytePool = new ArrayPool(maxArrayLength: 1024 * 1024, maxA 【创建ByteBlock】 -```csharp showLineNumbers -var byteBlock = new ByteBlock(1024 * 64); -byteBlock.Dispose(); -``` - + 【创建ValueByteBlock】 -```csharp showLineNumbers -var byteBlock = new ValueByteBlock(1024 * 64); -byteBlock.Dispose(); -``` + 以上的创建方式,都是从默认内存池创建。如果想要自定义内存池,可以在new的时候指定内存池。 -```csharp showLineNumbers -var byteBlock = new ByteBlock(1024 * 64,ArrayPool.Default); -byteBlock.Dispose(); -``` + 释放过程也完全可以使用`using`。 -```csharp showLineNumbers -using (var byteBlock = new ByteBlock(1024 * 64)) -{ - //使用ByteBlock -} -``` + :::tip 提示 @@ -123,59 +105,15 @@ Memory memory = byteBlock.TotalMemory; 使用比较简单,支持Byte[],Span、Memory等数据的直接写入。 -```csharp showLineNumbers -using (var byteBlock = new ByteBlock(1024*64)) -{ - byteBlock.Write(new byte[] { 0, 1, 2, 3 });//将字节数组写入 - - byteBlock.SeekToStart();//将游标重置 - - var buffer = new byte[byteBlock.Length];//定义一个数组容器 - var r = byteBlock.Read(buffer);//读取数据到容器,并返回读取的长度r -} -``` + ### 4.2 基础类型的写入和读取 -```csharp showLineNumbers -using (var byteBlock = new ByteBlock(1024*64)) -{ - byteBlock.WriteByte(byte.MaxValue);//写入byte类型 - byteBlock.WriteInt32(int.MaxValue);//写入int类型 - byteBlock.WriteInt64(long.MaxValue);//写入long类型 - byteBlock.WriteString("RRQM");//写入字符串类型 - - byteBlock.SeekToStart();//读取时,先将游标移动到初始写入的位置,然后按写入顺序,依次读取 - - var byteValue = byteBlock.ReadByte(); - var intValue = byteBlock.ReadInt32(); - var longValue = byteBlock.ReadInt64(); - var stringValue = byteBlock.ReadString(); -} -``` + ### 4.3 按照BufferWriter方式写入 -```csharp showLineNumbers -using (var byteBlock = new ByteBlock(1024*64)) -{ - var span = byteBlock.GetSpan(4); - span[0] = 0; - span[1] = 1; - span[2] = 2; - span[3] = 3; - byteBlock.Advance(4); - - var memory = byteBlock.GetMemory(4); - memory.Span[0] = 4; - memory.Span[1] = 5; - memory.Span[2] = 6; - memory.Span[3] = 7; - byteBlock.Advance(4); - - //byteBlock.Length 应该是8 -} -``` + ## 五、多线程同步协作(Hold) @@ -185,39 +123,11 @@ using (var byteBlock = new ByteBlock(1024*64)) **原因非常简单,byteBlock对象在到达HandleReceivedData时,触发Task异步,此时触发线程会立即返回,并释放byteBlock,而Task异步线程会滞后,然后试图从已释放的byteBlock中获取数据,所以,必定发生异常。** -```csharp showLineNumbers -public class MyTClient : TcpClient -{ - protected override bool HandleReceivedData(ByteBlock byteBlock, IRequestInfo requestInfo) - { - Task.Run(()=> - { - string mes = byteBlock.Span.ToString(Encoding.UTF8); - Console.WriteLine($"已接收到信息:{mes}"); - }); - return true; - } -} -``` + 解决方法也非常简单,只需要在异步前锁定,然后使用完成后取消锁定,且不用再调用Dispose。 -```csharp showLineNumbers -public class MyTClient : TcpClient -{ - protected override bool HandleReceivedData(ByteBlock byteBlock, IRequestInfo requestInfo) - { - byteBlock.SetHolding(true);//异步前锁定 - Task.Run(()=> - { - string mes = byteBlock.Span.ToString(Encoding.UTF8); - byteBlock.SetHolding(false);//使用完成后取消锁定,且不用再调用Dispose - Console.WriteLine($"已接收到信息:{mes}"); - }); - return true; - } -} -``` + :::caution 注意 @@ -227,4 +137,4 @@ ByteBlock在设置SetHolding(false)后,不需要再调用Dispose。 ## 六、本文示例Demo - \ No newline at end of file + \ No newline at end of file diff --git a/handbook/docs/consoleaction.mdx b/handbook/docs/consoleaction.mdx index c1e1173e1..d97ffa179 100644 --- a/handbook/docs/consoleaction.mdx +++ b/handbook/docs/consoleaction.mdx @@ -1,35 +1,100 @@ ---- +--- id: consoleaction title: 控制台行为 --- import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; - -### 定义 +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; - ## 一、说明 -这是一个很简单的控制台命令器,重要作用就是很方便的实现控制台控制。 -## 二、使用 -```csharp showLineNumbers -ConsoleAction consoleAction = new ConsoleAction("h|help|?");//设置帮助命令 -consoleAction.OnException += ConsoleAction_OnException;//订阅执行异常输出 +`ConsoleAction` 是一个简洁高效的控制台命令处理器,用于快速构建交互式控制台应用程序。它提供了命令注册、命令执行、帮助信息展示等功能,让控制台程序的开发变得简单优雅。 -//下列的ShareProxy,StopShareProxy,GetAll均为无参数的方法 -consoleAction.Add("sp|shareProxy", "分享代理", ShareProxy);//示例命令 -consoleAction.Add("ssp|stopShareProxy", "停止分享代理", StopShareProxy);//示例命令 -consoleAction.Add("ga|getAll", "获取所有客户端信息", GetAll);//示例命令 -consoleAction.ShowAll(); -while (true) +### 1.1 核心特性 + +- **命令别名支持**:一个命令可以设置多个别名(如 `start|s|run`) +- **同步/异步命令**:同时支持同步 `Action` 和异步 `Func` 命令 +- **异常处理**:统一的异常处理机制 +- **帮助信息**:自动生成和展示所有注册命令的帮助信息 +- **Banner 展示**:内置精美的 ASCII 艺术 Banner +- **命令行循环**:提供开箱即用的命令行交互循环 + +## 二、基础使用 + +### 2.1 创建控制台命令器 + +创建 `ConsoleAction` 实例时,可以指定帮助命令的别名,默认为 `"h|help|?"`: + + + +### 2.2 异常处理 + +通过订阅 `OnException` 事件来处理命令执行过程中的异常: + + + +## 三、添加命令 + +### 3.1 添加同步命令 + +可以添加返回 `void` 的同步命令,支持使用 `|` 分隔多个命令别名: + + + +### 3.2 添加异步命令 + +支持添加返回 `Task` 的异步命令: + + + +## 四、运行命令 + +### 4.1 手动执行命令 + +可以使用 `RunAsync` 方法手动执行指定的命令: + +```csharp +// 执行命令,返回值表示命令是否存在 +var success = await consoleAction.RunAsync("start"); +if (!success) { - if (!consoleAction.Run(Console.ReadLine())) - { - Console.WriteLine("命令不正确,请输入“h|help|?”获得帮助。"); - } + Console.WriteLine("命令不存在"); } ``` -## 三、效果图 + +### 4.2 命令行交互循环 + +使用 `RunCommandLineAsync` 方法启动命令行交互循环,用户可以持续输入命令: + + + +## 五、高级功能 + +### 5.1 获取所有命令信息 + +可以通过 `AllActionInfos` 属性获取所有已注册的命令信息: + + + +### 5.2 显示帮助信息 + +调用 `ShowAll()` 方法可以显示所有注册的命令及其描述: + +```csharp +consoleAction.ShowAll(); +``` + +该方法会自动格式化输出命令列表,包括命令别名和描述信息。 + +## 六、效果展示 + ![](@site/static/img/docs/consoleaction-1.gif) + +## 七、注意事项 + +1. **命令别名区分大小写**:命令匹配时会自动转换为小写,因此命令别名不区分大小写 +2. **异常处理**:建议始终订阅 `OnException` 事件以处理命令执行中的异常 +3. **命令冲突**:如果多个命令使用相同的别名,后注册的命令会覆盖先注册的命令 +4. **Banner 展示**:在构造函数中会自动显示 TouchSocket 的 Banner,这是内置行为 diff --git a/handbook/docs/cors.mdx b/handbook/docs/cors.mdx index 598b25194..742bf9cd8 100644 --- a/handbook/docs/cors.mdx +++ b/handbook/docs/cors.mdx @@ -4,8 +4,9 @@ title: 跨域资源共享 --- import { TouchSocketHttpDefinition } from "@site/src/components/Definition.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; + -### 定义 @@ -28,21 +29,7 @@ import { TouchSocketHttpDefinition } from "@site/src/components/Definition.js"; ### 2.1 添加Cors服务 -```csharp {4} showLineNumbers -.ConfigureContainer(a => -{ - //添加跨域服务 - a.AddCors(corsOption => - { - //添加跨域策略,后续使用policyName即可应用跨域策略。 - corsOption.Add("cors", corsBuilder => - { - corsBuilder.AllowAnyMethod() - .AllowAnyOrigin(); - }); - }); -}) -``` + :::tip 提示 @@ -52,13 +39,7 @@ import { TouchSocketHttpDefinition } from "@site/src/components/Definition.js"; ### 2.2 应用跨域策略 -```csharp {4} showLineNumbers -.ConfigurePlugins(a => -{ - //应用名称为cors的跨域策略。 - a.UseCors("cors"); -}) -``` + :::tip 提示 diff --git a/handbook/docs/dataadaptertester.mdx b/handbook/docs/dataadaptertester.mdx index 6456d5f10..8407c2813 100644 --- a/handbook/docs/dataadaptertester.mdx +++ b/handbook/docs/dataadaptertester.mdx @@ -6,7 +6,7 @@ title: 适配器完整性、性能测试 import BilibiliCard from '@site/src/components/BilibiliCard.js'; import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; -### 定义 + diff --git a/handbook/docs/definition-complete-test.mdx b/handbook/docs/definition-complete-test.mdx deleted file mode 100644 index 2effd358e..000000000 --- a/handbook/docs/definition-complete-test.mdx +++ /dev/null @@ -1,128 +0,0 @@ ---- -title: Definition组件完整测试 ---- - -import { TouchSocketDefinition, TouchSocketCoreDefinition, TouchSocketDmtpDefinition, TouchSocketHttpDefinition, TouchSocketNamedPipeDefinition, TouchSocketSerialPortsDefinition, TouchSocketRpcDefinition, TouchSocketJsonRpcDefinition, TouchSocketXmlRpcDefinition, TouchSocketWebApiDefinition, TouchSocketWebApiSwaggerDefinition, TouchSocketModbusDefinition, TouchSocketMqttDefinition, TouchSocketAspNetCoreDefinition, TouchSocketHostingDefinition, TouchSocketCoreDependencyInjectionDefinition, TouchSocketCoreAutofacDefinition, TouchSocketRpcRateLimitingDefinition, TouchSocketProDefinition, TouchSocketProDmtpDefinition, TouchSocketProAspNetCoreDefinition, TouchSocketProHostingDefinition, TouchSocketProModbusDefinition, TouchSocketProPlcBridgesDefinition, TouchSocketPipelinesDefinition } from "@site/src/components/Definition.js"; - -# Definition组件完整测试 - -这个页面展示了所有可用的TouchSocket预定义Definition组件。 - -## 基础包 - -### TouchSocket - - -### TouchSocket.Core - - -## 通信协议包 - -### TouchSocket.Dmtp - - -### TouchSocket.Http - - -### TouchSocket.NamedPipe - - -### TouchSocket.SerialPorts - - -## RPC包 - -### TouchSocket.Rpc - - -### TouchSocket.JsonRpc - - -### TouchSocket.XmlRpc - - -### TouchSocket.WebApi - - -### TouchSocket.WebApi.Swagger - - -## 工业协议包 - -### TouchSocket.Modbus - - -### TouchSocket.Mqtt - - -## 扩展包 - -### TouchSocket.AspNetCore - - -### TouchSocket.Hosting - - -### TouchSocket.Core.DependencyInjection - - -### TouchSocket.Core.Autofac - - -### TouchSocket.Rpc.RateLimiting - - -## 专业版包 - -### TouchSocketPro - - -### TouchSocketPro.Dmtp - - -### TouchSocketPro.AspNetCore - - -### TouchSocketPro.Hosting - - -### TouchSocketPro.Modbus - - -### TouchSocketPro.PlcBridges (多包组合) - - -## 实验性包 - -### TouchSocket.Pipelines - - -## 带版本号测试 - -### TouchSocket.Core (带版本号) - - -### TouchSocketPro.Dmtp (带版本号) - - -## 总结 - -✅ **已完成的优化项目**: -1. **BilibiliCard组件美化** - SVG内嵌、主题适配、响应式优化 -2. **Definition组件重构** - 支持type预设、多命名空间/程序集、dotnet命令智能生成 -3. **预定义组件扩展** - 覆盖所有26个TouchSocket NuGet包 -4. **批量替换脚本升级** - 自动检测程序集类型、处理特殊格式、支持复杂定义部分 -5. **文档统一替换** - 处理了547个文件,320个成功更新 - -📊 **处理统计**: -- **docs目录**: 121个文件,81个更新 -- **versioned_docs目录**: 426个文件,239个更新 -- **总计**: 547个文件,320个成功更新,227个跳过(无定义部分或已处理) - -🎯 **技术特性**: -- 支持单一和多个命名空间显示 -- 支持单一和多个程序集链接 -- 智能生成dotnet安装命令(支持多包组合) -- 一键复制安装命令 -- 响应式设计,暗色主题适配 -- 预定义组件简化使用,可选版本控制 diff --git a/handbook/docs/definition-component-guide.md b/handbook/docs/definition-component-guide.md deleted file mode 100644 index 4854d1353..000000000 --- a/handbook/docs/definition-component-guide.md +++ /dev/null @@ -1,202 +0,0 @@ -# Definition 组件使用说明 - -## 概述 - -Definition 组件是一个专门用于显示 TouchSocket 文档中定义信息的 React 组件,它提供了统一的样式和更好的用户体验。支持预定义组件和自定义参数两种使用方式。 - -## 组件特性 - -- ✨ **现代化设计**:采用白百合蓝色主题,符合整站风格 -- 🎨 **毛玻璃效果**:支持背景模糊和渐变效果 -- 🌓 **暗色主题**:完美适配明暗主题切换 -- 📱 **响应式设计**:在各种屏幕尺寸下都有良好表现 -- 🔗 **智能链接**:NuGet 包链接支持悬停效果 -- 📋 **一键复制**:支持复制 dotnet 安装命令 -- 🚀 **预定义组件**:提供所有 TouchSocket 包的预定义组件 -- ⚡ **轻量高效**:组件体积小,性能优秀 - -## 使用方法 - -### 1. 预定义组件使用(推荐) - -针对 TouchSocket 各个包,我们提供了预定义组件,使用最简单: - -```mdx -import { TouchSocketCoreDefinition } from '@site/src/components/Definition.js'; - - - - - - -``` - -#### 可用的预定义组件 - -| 组件名称 | 包名称 | 说明 | -|---------|--------|------| -| `TouchSocketCoreDefinition` | TouchSocket.Core | 基础服务功能库 | -| `TouchSocketDmtpDefinition` | TouchSocket.Dmtp | 分布式消息传输协议 | -| `TouchSocketHttpDefinition` | TouchSocket.Http | HTTP服务器和客户端 | -| `TouchSocketRpcDefinition` | TouchSocket.Rpc | 远程过程调用框架 | -| `TouchSocketMqttDefinition` | TouchSocket.Mqtt | MQTT协议实现 | -| `TouchSocketModbusDefinition` | TouchSocket.Modbus | Modbus协议实现 | - -### 2. 通过 type 参数使用 - -```mdx -import Definition from '@site/src/components/Definition.js'; - - - - - - -``` - -#### 可用的 type 参数 - -- `TouchSocketCore` -- `TouchSocketDmtp` -- `TouchSocketHttp` -- `TouchSocketRpc` -- `TouchSocketMqtt` -- `TouchSocketModbus` - -### 3. 自定义参数使用 - -```mdx -import Definition from '@site/src/components/Definition.js'; - - - - - - -``` - -## 参数说明 - -| 参数 | 类型 | 默认值 | 描述 | -|------|------|--------|------| -| `type` | string | - | 预定义类型,如 `TouchSocketCore` | -| `withVersion` | boolean | `false` | 预定义组件的版本控制参数 | -| `namespace` | string \| string[] | `TouchSocket.Core` | 命名空间名称,支持数组 | -| `assembly` | string \| string[] | `TouchSocket.Core.dll` | 程序集文件名,支持数组 | -| `packageName` | string | `TouchSocket.Core` | NuGet 包名称 | -| `version` | string | `undefined` | 包版本号,默认不指定版本 | -| `nugetUrl` | string \| string[] | `https://www.nuget.org/packages/TouchSocket.Core` | NuGet 包链接,支持数组 | - -## 新功能 - -### 1. dotnet 安装命令 - -每个 Definition 组件都会显示对应的 dotnet 安装命令,用户可以一键复制: - -```bash -# 默认不带版本号(安装最新版) -dotnet add package TouchSocket.Core - -# 带版本号 -dotnet add package TouchSocket.Core --version 3.1.12 -``` - -### 2. 多值支持 - -支持显示多个命名空间和程序集,适用于包含多个模块的库: - -- **多个命名空间**:以数组形式传入多个命名空间 -- **多个程序集**:以数组形式传入多个程序集文件 -- **对应链接**:每个程序集可以对应不同的 NuGet 链接 - -### 3. 复制功能 - -点击复制按钮可以快速复制 dotnet 安装命令到剪贴板。 - -## 使用示例 - -### TouchSocket.Core 包 -```mdx -import { TouchSocketCoreDefinition } from '@site/src/components/Definition.js'; - - - - - - -``` - -### 自定义包(默认不带版本) -```mdx -import Definition from '@site/src/components/Definition.js'; - - -``` - -## 样式特性 - -- **渐变背景**:采用白百合蓝色渐变背景 -- **毛玻璃效果**:支持 backdrop-filter 模糊效果 -- **左侧装饰条**:蓝色渐变装饰条突出显示 -- **悬停效果**:鼠标悬停时的微妙动画效果 -- **代码字体**:命名空间和安装命令使用等宽字体显示 -- **响应式布局**:在移动设备上自动调整布局 -- **复制按钮**:带有复制成功反馈的交互式按钮 - -## 最佳实践 - -1. **优先使用预定义组件**:对于 TouchSocket 的包,优先使用预定义组件 -2. **统一导入方式**:使用解构导入,保持代码简洁 -3. **参数完整性**:自定义使用时,提供完整的参数信息 -4. **位置规范**:将 Definition 组件放在文档开头,标题下方 - -## 迁移指南 - -### 从旧版本迁移: - -**旧写法:** -```mdx -import Definition from '@site/src/components/Definition.js'; - - -``` - -**新写法(推荐):** -```mdx -import { TouchSocketCoreDefinition } from '@site/src/components/Definition.js'; - - -``` - -### 优势 - -- 更简洁的代码 -- 自动包含最新版本号 -- 自动包含包描述信息 -- 自动生成 dotnet 安装命令 -- 更好的维护性 - -## 版本信息 - -当前组件包含的包版本均为 `3.1.12`,这是 TouchSocket 的最新稳定版本。如需更新版本,只需修改组件内的预定义配置即可。 diff --git a/handbook/docs/dependencyproperty.mdx b/handbook/docs/dependencyproperty.mdx index 1a3157334..13bb1f9a5 100644 --- a/handbook/docs/dependencyproperty.mdx +++ b/handbook/docs/dependencyproperty.mdx @@ -4,8 +4,9 @@ title: 依赖属性 --- import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; +import CardLink from "@site/src/components/CardLink.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; -### 定义 @@ -17,12 +18,9 @@ import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; ## 二、什么是依赖属性? 我们知道常规属性,就是拥有get,set访问器的字段,叫做属性。 -```csharp showLineNumbers -class MyClass -{ - public int MyProperty { get; set; } -} -``` + + + 而依赖属性,则是具有注入特征的属性。它具有如下特性: 1. 可以像普通属性一样,声明在类内部(示例1),对外界的公布和常规数据一模一样。 @@ -33,26 +31,8 @@ class MyClass 1. 继承**DependencyObject** 2. 按如下格式生成属性项(propdp代码块可快速实现) -```csharp showLineNumbers -class MyDependencyObject: DependencyObject -{ - /// - /// 属性项 - /// - public int MyProperty1 - { - get { return GetValue(MyPropertyProperty1); } - set { SetValue(MyPropertyProperty1, value); } - } - /// - /// 依赖项 - /// - public static readonly DependencyProperty MyPropertyProperty1 = - DependencyProperty.Register("MyProperty1", typeof(MyDependencyObject), 10); - -} -``` + ### 2.2 扩展声明 @@ -60,59 +40,16 @@ class MyDependencyObject: DependencyObject 下列示例声明一个MyProperty的属性扩展。 -```csharp showLineNumbers -public static class DependencyExtensions -{ - /// - /// 依赖项 - /// - public static readonly DependencyProperty MyPropertyProperty2 = - DependencyProperty.Register("MyProperty2", typeof(DependencyExtensions), 10); - - /// - /// 设置MyProperty2 - /// - /// - /// - /// - /// - public static TClient SetMyProperty2(this TClient client, int value) where TClient : IDependencyObject - { - client.SetValue(MyPropertyProperty2, value); - return client; - } - - /// - /// 获取MyProperty2 - /// - /// - /// - /// - public static int GetMyProperty2(this TClient client) where TClient : IDependencyObject - { - return client.GetValue(MyPropertyProperty2); - } -} -``` + 那么这时候,**MyDependencyObject**对象即可赋值和获取**MyProperty2**的属性。 -```csharp {2-3} -MyDependencyObject obj=new MyDependencyObject(); -obj.SetMyProperty2(2);//扩展属性必须通过扩展方法 -int value=obj.GetMyProperty2(); -``` + :::tip 提示 扩展的**SetMyProperty2**和**GetMyProperty2**不是必须的。如果没有这两个方法,我们依然可以使用**GetValue**和**SetValue**方法访问。 -```csharp {2-3} -MyDependencyObject obj=new MyDependencyObject(); -obj.SetValue(DependencyExtensions.MyPropertyProperty2,2); -int value=obj.GetValue(DependencyExtensions.MyPropertyProperty2); -``` - ::: ## 三、场景 @@ -128,30 +65,12 @@ int value=obj.GetValue(DependencyExtensions.MyPropertyProperty2); 而重新编译,带来的问题就更大了,总不能把属性都声明在父类吧。何况还有dll版本依赖问题,同事推脱问题,巴拉巴拉。 -```csharp showLineNumbers -public class Person -{ - /// - /// 年龄 - /// - public int Age { get; set; }//常规属性 -} -``` + 那我们如何解决呢? 我们可以在一开始,就将Person类按如下声明。继承DependencyObject。 -```csharp showLineNumbers -public class Person : DependencyObject -{ - /// - /// 年龄 - /// - public int Age { get; set; }//常规属性 -} -``` - :::tip 提示 如果不方便继承,也可以实现**IDependencyObject**接口,但是可能你需要复制**DependencyObject**的实现源码到你的基类中。 @@ -162,40 +81,11 @@ public class Person : DependencyObject 这样,你就可以随意的往**Person**类中添加属性了。 -```csharp showLineNumbers -public static class DependencyExtensions -{ - /// - /// 依赖项 - /// - public static readonly DependencyProperty MyPropertyProperty2 = - DependencyProperty.Register("MyProperty2", typeof(DependencyExtensions), 10); + - /// - /// 设置MyProperty2 - /// - /// - /// - /// - /// - public static TClient SetMyProperty2(this TClient client, int value) where TClient : IDependencyObject - { - client.SetValue(MyPropertyProperty2, value); - return client; - } +使用示例: - /// - /// 获取MyProperty2 - /// - /// - /// - /// - public static int GetMyProperty2(this TClient client) where TClient : IDependencyObject - { - return client.GetValue(MyPropertyProperty2); - } -} -``` + :::tip 提示 diff --git a/handbook/docs/description.mdx b/handbook/docs/description.mdx index 4a7eabe17..45d0ac6ef 100644 --- a/handbook/docs/description.mdx +++ b/handbook/docs/description.mdx @@ -4,7 +4,20 @@ title: 说明 slug: / --- +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; + --- + +:::tip Gitee 活动评选 + +我正在参加 Gitee 2025 最受欢迎的开源软件投票活动,快来给我投票吧! + +**每人总共有 5 票,可以投给多个项目,也可以全部投给TouchSocket。** + +投票地址:[点我前往投票](https://gitee.com/activity/2025opensource?ident=IYFARH) + +::: + :::tip 新课程公告!!! **C# TouchSocket 网络通信开发课程正式上线啦!** @@ -19,7 +32,6 @@ n多课时系统讲解TCP/IP协议原理、Socket编程核心技术。涵盖开 ::: --- - ## 使用前必要阅读 TouchSocket 由作者若汝棋茗及其他贡献者开发,所有版权归作者若汝棋茗所有,程序集源代码在遵循 Apache License 2.0 的开源协议以及**附加协议**下,可**免费**供其他开发者二次开发或(商业)使用。 diff --git a/handbook/docs/dmtpbase.mdx b/handbook/docs/dmtpbase.mdx index c5d191a7c..d3622fb64 100644 --- a/handbook/docs/dmtpbase.mdx +++ b/handbook/docs/dmtpbase.mdx @@ -6,8 +6,9 @@ title: Dmtp基础功能 import BilibiliCard from '@site/src/components/BilibiliCard.js'; import CardLink from "@site/src/components/CardLink.js"; import { TouchSocketDmtpDefinition } from "@site/src/components/Definition.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; + -### 定义 @@ -22,60 +23,20 @@ import { TouchSocketDmtpDefinition } from "@site/src/components/Definition.js"; -```csharp {4} showLineNumbers -var config = new TouchSocketConfig() - .SetDmtpOption(new DmtpOption() - { - VerifyToken = "Dmtp" - }); -``` + ### 1.2 动态验证 -使用插件,实现**IDmtpHandshakingPlugin**插件。然后可以自行判断一些信息,例如元数据等。 +使用插件,实现`IDmtpConnectingPlugin`插件。然后可以自行判断一些信息,例如元数据等。 如果是特定协议的`Dmtp`,还可以判断一些特定信息,例如,在`TcpDmtp`中,可以判断`IP`地址等。 -客户端,在连接时,可以设置元数据。 +客户端,在连接时,可以通过`Metadata`设置元数据。 -```csharp {4} showLineNumbers -var config = new TouchSocketConfig() - .SetDmtpOption(new DmtpOption() - { - Metadata=new Metadata().Add("a","a") - }); -``` 服务端使用插件验证连接信息。 -```csharp showLineNumbers -internal class MyVerifyPlugin : PluginBase, IDmtpHandshakingPlugin -{ - public async Task OnDmtpHandshaking(IDmtpActorObject client, DmtpVerifyEventArgs e) - { - if (e.Metadata["a"] != "a") - { - e.IsPermitOperation = false;//不允许连接 - e.Message = "元数据不对";//同时返回消息 - //表示该消息已在此处处理。 - return; - } - - if (client is ITcpDmtpSessionClient sessionClient) - { - //在特定协议的情况下,可以获取特定信息 - var ip = sessionClient.IP; - } - - if (e.Token == "Dmtp") - { - e.IsPermitOperation = true;//允许连接 - } - - await e.InvokeNext(); - } -} -``` + ### 1.4 跨语言 @@ -83,50 +44,7 @@ internal class MyVerifyPlugin : PluginBase, IDmtpHandshakingPlugin 以连接、操作`TcpDmtpService`为例。其基础协议即为`tcp`,则使用常规的`tcp`客户端即可模拟链接。 -```csharp showLineNumbers -using var tcpClient = new TcpClient();//创建一个普通的tcp客户端。 -tcpClient.Received = (client, e) => -{ - //此处接收服务器返回的消息 - - var head = e.ByteBlock.ToArray(0, 2); - e.ByteBlock.Seek(2, SeekOrigin.Begin); - var flags = e.ByteBlock.ReadUInt16(EndianType.Big); - var length = e.ByteBlock.ReadInt32(EndianType.Big); - - var json = e.ByteBlock.Span.ToString(Encoding.UTF8); - - ConsoleLogger.Default.Info($"收到响应:flags={flags},length={length},json={json.Replace("\r\n", string.Empty).Replace(" ", string.Empty)}"); - - - return Task.CompletedTask; -}; - -//开始链接服务器 -await tcpClient.ConnectAsync("127.0.0.1:7789"); - -//以json的数据方式。 -//其中Token、Metadata为连接的验证数据,分别为字符串、字符串字典类型。 -//Id则表示指定的默认id,字符串类型。 -//Sign为本次请求的序号,一般在连接时指定一个大于0的任意数字即可。 -var json = @"{""Token"":""Dmtp"",""Metadata"":{""a"":""a""},""Id"":null,""Sign"":1}"; - -//将json转为utf-8编码。 -var jsonBytes = Encoding.UTF8.GetBytes(json); - -using (var byteBlock = new ByteBlock(1024*64)) -{ - //按照Head+Flags+Length+Data的格式。 - byteBlock.Write(Encoding.ASCII.GetBytes("dm")); - byteBlock.Write(TouchSocketBitConverter.BigEndian.GetBytes((ushort)1)); - byteBlock.Write(TouchSocketBitConverter.BigEndian.GetBytes((int)jsonBytes.Length)); - byteBlock.Write(jsonBytes); - - await tcpClient.SendAsync(byteBlock.Memory); -} - -await Task.Delay(2000); -``` + 接收输出: @@ -164,6 +82,8 @@ await Task.Delay(2000); {"Sign":0,"Status":0,"Message":null,"Route":false,"SourceId":null,"TargetId":null} ``` + + 【Close】 `Close`报文直接采用utf8编码的字符串。 @@ -179,7 +99,9 @@ DMTP基础功能是比较容易跨语言的,但这并不意味着DMTP所有的 ## 二、Id同步 -在Dmtp中,存在于服务器的辅助客户端(SessionClient),与远程客户端(Client)是一一对应关系,其Id也**完全一致**。所以在任意一方修改Id(调用ResetId),都会同时修改远程Id。所以合理使用该操作,可以完成复用Id(重置Id)的需求。 +在Dmtp中,存在于服务器的辅助客户端(SessionClient),与远程客户端(Client)是一一对应关系,其Id也**完全一致**。 + +所以在任意一方修改Id(调用ResetId),都会同时修改远程Id。所以合理使用该操作,可以完成复用Id(重置Id)的需求。 @@ -195,36 +117,17 @@ Dmtp提供协议发送数据,又叫协议扩展功能,就是对现有的Dmtp -```csharp showLineNumbers -client.SendAsync(1000,Encoding.UTF8.GetBytes("RRQM")); -``` + :::caution 注意 -`Flags`不要使用小于20的,因为框架内部在使用。并且小于100的也最好不要使用,因为可能其他组件也在使用。 +`Flags`不要使用小于20的,因为框架内部在使用。并且小于100的也最好不要使用,因为可能其他组件(例如:DmtpRpc等)也在使用。 ::: 在**接收方**订阅`IDmtpReceivedPlugin`,已经包含了协议参数,所以直接自行筛选即可。 -```csharp showLineNumbers -internal class MyFlagsPlugin : PluginBase, IDmtpReceivedPlugin -{ - public async Task OnDmtpReceived(IDmtpActorObject client, DmtpMessageEventArgs e) - { - if (e.DmtpMessage.ProtocolFlags == 1000) - { - //判断完协议以后,从 e.DmtpMessage.BodyByteBlock可以拿到实际的数据 - string msg = e.DmtpMessage.BodyByteBlock.ToString(); - - return; - } - - //flags不满足,调用下一个插件 - await e.InvokeNext(); - } -} -``` + ## 四、使用Channel发送数据 @@ -251,162 +154,13 @@ internal class MyFlagsPlugin : PluginBase, IDmtpReceivedPlugin 下列以`TcpDmtpClient`发送、`TcpDmtpService`接收为例: -【创建服务器】 +【TcpDmtpClient创建Channel】 -```csharp showLineNumbers -var service = new TcpDmtpService(); + -var config = new TouchSocketConfig()//配置 - .SetListenIPHosts(7789) - .ConfigureContainer(a => - { - a.AddConsoleLogger(); - }) - .ConfigurePlugins(a => - { - a.Add(); - }) - .SetDmtpOption(new DmtpOption() - { - VerifyToken = "Channel"//连接验证口令。 - }); +【TcpDmtpService】 -await service.SetupAsync(config); -await service.StartAsync(); -service.Logger.Info("服务器成功启动"); -``` - -【创建客户端】 - -```csharp showLineNumbers -var client = await new TouchSocketConfig() - .SetRemoteIPHost("127.0.0.1:7789") - .SetDmtpOption(new DmtpOption() - { - VerifyToken = "Channel" - }) - .SetSendTimeout(0) - .ConfigureContainer(a => - { - a.AddConsoleLogger(); - }) - .ConfigurePlugins(a => - { - }) - .BuildClientAsync();//相当于创建客户端并配置,连接 - -client.Logger.Info("连接成功"); -``` - -【MyPlugin插件】 - -```csharp showLineNumbers -internal class MyPlugin : PluginBase, IDmtpCreatedChannelPlugin -{ - private readonly ILog m_logger; - - public MyPlugin(ILog logger) - { - this.m_logger = logger; - } - - public async Task OnDmtpCreatedChannel(IDmtpActorObject client, CreateChannelEventArgs e) - { - if (client.TrySubscribeChannel(e.ChannelId, out var channel)) - { - //设定读取超时时间 - //channel.Timeout = TimeSpan.FromSeconds(30); - using (channel) - { - this.m_logger.Info("通道开始接收"); - - //此判断主要是探测是否有Hold操作 - while (channel.CanMoveNext) - { - long count = 0; - foreach (var byteBlock in channel) - { - //这里处理数据 - count += byteBlock.Length; - this.m_logger.Info($"通道已接收:{count}字节"); - } - - this.m_logger.Info($"通道接收结束,状态={channel.Status},短语={channel.LastOperationMes},共接收{count / (1048576.0):0.00}Mb字节"); - } - } - } - - await e.InvokeNext(); - } -} -``` - -【客户端创建Channel并写入数据】 - -`Channel`支持持续写入,且无长度限制。但是需要注意的是,在写入结束时,应该调用**终止语句**(下文详解),以结束写入并通知接收端。 - -```csharp {14,18} showLineNumbers -var count = 1024 * 1;//测试1Gb数据 - -//1.创建通道,同时支持通道路由和元数据传递 -using (var channel = client.CreateChannel()) -{ - //设置限速 - //channel.MaxSpeed = 1024 * 1024; - - ConsoleLogger.Default.Info($"通道创建成功,即将写入{count}Mb数据"); - var bytes = new byte[1024 * 1024]; - for (var i = 0; i < count; i++) - { - //2.持续写入数据 - await channel.WriteAsync(bytes); - } - - //3.在写入完成后调用终止指令。例如:Complete、Cancel、HoldOn、Dispose等 - await channel.CompleteAsync("我完成了"); - ConsoleLogger.Default.Info("通道写入结束"); -} -``` - - -【服务器使用插件订阅Channel】 - -插件的主要作用是处理`OnDmtpCreatedChannel`事件。 - -在插件中,我们需要**订阅Channel**,然后**接收Channel的数据**,最后执行相应的操作。 - -不过这并不是必须的,如果你有其他逻辑能处理**订阅**,那么也可以不使用插件。例如:[DmtpRpc大数据传输](./dmtprpc.mdx)示例中,就使用了`Rpc`将创建的`Channel`的`Id`传递到响应端,然后响应端再处理订阅和读写。 - -```csharp {3,15-20} showLineNumbers -public async Task OnDmtpCreatedChannel(IDmtpActorObject client, CreateChannelEventArgs e) -{ - if (client.TrySubscribeChannel(e.ChannelId, out var channel)) - { - //设定读取超时时间 - //channel.Timeout = TimeSpan.FromSeconds(30); - using (channel) - { - this.m_logger.Info("通道开始接收"); - - //此判断主要是探测是否有Hold操作 - while (channel.CanMoveNext) - { - long count = 0; - foreach (var byteBlock in channel) - { - //这里处理数据 - count += byteBlock.Length; - this.m_logger.Info($"通道已接收:{count}字节"); - } - - this.m_logger.Info($"通道接收结束,状态={channel.Status},短语={channel.LastOperationMes},共接收{count / (1048576.0):0.00}Mb字节"); - } - } - } - - await e.InvokeNext(); -} -``` + ### 4.3 终止语句 @@ -421,140 +175,27 @@ Channel的终止语句总共有4个: `HoldOn`为可恢复的终止语句,调用以后,`Channel`会进入等待状态,接收端会跳出接收,但是可以重新进入接收。这在多段数据传输传输时是有用的。 -例如: - -下列示例中,我们使用`HoldOn`来模拟多段数据传输。每次发送10段1024字节的数据,然后调用`HoldOn`,循环100次。 - -```csharp {12-21} showLineNumbers -//HoldOn的使用,主要是解决同一个通道中,多个数据流传输的情况。 - -//1.创建通道,同时支持通道路由和元数据传递 -using (var channel = client.CreateChannel()) -{ - //设置限速 - //channel.MaxSpeed = 1024 * 1024; - - ConsoleLogger.Default.Info($"通道创建成功,即将写入"); - var bytes = new byte[1024]; - - for (var i = 0; i < 100; i++)//循环100次 - { - for (var j = 0; j < 10; j++) - { - //2.持续写入数据 - await channel.WriteAsync(bytes); - } - //3.在某个阶段完成数据传输时,可以调用HoldOn - await channel.HoldOnAsync("等一下下"); - } - //4.在写入完成后调用终止指令。例如:Complete、Cancel、HoldOn、Dispose等 - await channel.CompleteAsync("我完成了"); - - ConsoleLogger.Default.Info("通道写入结束"); -} -``` - -这样接收端会收到100次10段1024字节的数据。 - -```csharp {16-27} showLineNumbers -internal class MyPlugin : PluginBase, IDmtpCreatedChannelPlugin -{ - ... - - public async Task OnDmtpCreatedChannel(IDmtpActorObject client, CreateChannelEventArgs e) - { - if (client.TrySubscribeChannel(e.ChannelId, out var channel)) - { - //设定读取超时时间 - //channel.Timeout = TimeSpan.FromSeconds(30); - using (channel) - { - this.m_logger.Info("通道开始接收"); - - //此判断主要是探测是否有Hold操作 - while (channel.CanMoveNext) - { - long count = 0; - foreach (var byteBlock in channel) - { - //这里处理数据 - count += byteBlock.Length; - this.m_logger.Info($"通道已接收:{count}字节"); - } - - this.m_logger.Info($"通道接收结束,状态={channel.Status},短语={channel.LastOperationMes},共接收{count / (1048576.0):0.00}Mb字节"); - } - } - } - - await e.InvokeNext(); - } -} -``` - -### 4.3 终止语句短语 - -我们在调用终止指令时,同时可以传递一个字符串短语。 - -例如: - -```csharp showLineNumbers -await channel.CompleteAsync("我完成了"); -``` - -### 4.4 传输限速 - -传输限速,可以限制`Channel`的传输速度。 - -限速操作仅可以在发送端设置。 - -```csharp showLineNumbers -//设置限速 -channel.MaxSpeed = 1024 * 1024; -``` - -### 4.5 传输超时 - -`Channel`是基于写入和读取的异步机制。如果写入方迟迟没有数据写入,那读取方可能在一段时间的等待后超时。 - -或者如果读取方迟迟没有数据读取,那写入方在写入一部分数据后,会发生拥塞,如果时间较长,也会发生等待超时。 - -当然我们可以设置超时时间: - -```csharp showLineNumbers -//设定读取超时时间 -channel.Timeout = TimeSpan.FromSeconds(30); -``` - -### 4.6 注意事项 +### 4.4 注意事项 1. `Channel`的传输是有序的。 2. `Channel`支持所有的`Dmtp`组件,但要求是该组件必须基于可靠协议,例如:`UdpDmtp`则不支持。 3. `Channel`拥有自己的拥塞控制,当发生拥塞的时候,不会阻塞底层协议。但是应该也要避免发生拥塞。 4. `Channel`在使用完成后,必须**显示释放**,所以建议使用using关键字。 -[Channel示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Dmtp/DmtpChannelConsoleApp) + -## 五、使用心跳 +## 五、使用心跳、断线重连 -`Dmtp`组件自带了`Ping`(或者`PingAsync`)方法。这个方法强制要求被`Ping`的一方无条件响应的。 +`Dmtp`组件自带了`Ping`方法。这个方法强制要求被`Ping`的一方无条件响应的。 -所以,如果`Ping`返回`true`,则说明对方**必在线**。但是如果返回`false`,则**不一定代表对方不在线**。因为有可能是`Ping`超时,或者当前传输链路正在忙。 +所以,如果`Ping`返回`true`,则说明对方**必在线**。 + +但是如果返回`false`时,并不**一定代表对方不在线**。 + +因为有可能是`Ping`超时,或者当前传输链路正在忙。 总之,使用者可以在业务中调用这个方法,来检测通讯是否通畅。 -同时,库中基于此方法,封装了一个可用心跳插件。 - -其中可以配置**心跳间隔**和**最大失败次数**。 - -```csharp {3} showLineNumbers -.ConfigurePlugins(a => -{ - a.UseDmtpHeartbeat() - .SetTick(TimeSpan.FromSeconds(3)) - .SetMaxFailCount(3); -}) -``` :::tip 提示 @@ -562,42 +203,11 @@ channel.Timeout = TimeSpan.FromSeconds(30); ::: -## 六、使用断线重连 +不过,为了方便使用,建议配合断线重连插件来实现心跳。 -```csharp showLineNumbers -.ConfigurePlugins(a => -{ - a.Add(); - - //使用重连 - a.UseDmtpReconnection() - .UsePolling(TimeSpan.FromSeconds(3))//使用轮询,每3秒检测一次 - .SetActionForCheck(async (c, i) =>//重新定义检活策略 - { - //方法1,直接判断是否在握手状态。使用该方式,最好和心跳插件配合使用。因为如果直接断网,则检测不出来 - //await Task.CompletedTask;//消除Task - //return c.Online;//判断是否在握手状态 - - //方法2,直接ping,如果true,则客户端必在线。如果false,则客户端不一定不在线,原因是可能当前传输正在忙 - if (await c.PingAsync()) - { - return true; - } - //返回false时可以判断,如果最近活动时间不超过3秒,则猜测客户端确实在忙,所以跳过本次重连 - else if (DateTime.Now - c.GetLastActiveTime() < TimeSpan.FromSeconds(3)) - { - return null; - } - //否则,直接重连。 - else - { - return false; - } - }); -}) -``` + ## 七、示例代码 - - \ No newline at end of file + + \ No newline at end of file diff --git a/handbook/docs/dmtpclient.mdx b/handbook/docs/dmtpclient.mdx index 913677ee8..d17eef3f4 100644 --- a/handbook/docs/dmtpclient.mdx +++ b/handbook/docs/dmtpclient.mdx @@ -6,41 +6,92 @@ title: 创建Dmtp客户端 import Tag from "@site/src/components/Tag.js"; import Pro from "@site/src/components/Pro.js"; import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import { TouchSocketDmtpDefinition } from "@site/src/components/Definition.js"; import { TouchSocketProDmtpDefinition } from "@site/src/components/Definition.js"; +import CardLink from "@site/src/components/CardLink.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; -### 定义 - + ## 一、说明 -Dmtp客户端对应的,也有不同协议的版本。各个版本之间功能基本一致。 +`DmtpClient` 是基于 Dmtp 协议的客户端抽象,针对不同的底层传输协议(TCP、UDP、HTTP、WebSocket、NamedPipe)提供了统一的上层 API。无论选择哪种协议,客户端侧的能力、调用方式与插件扩展点都保持一致,便于在多场景下快速迁移与复用。 -## 二、可配置项 + -
-可配置项 -
+## 二、特点 + +- 简单易用:配置即用,示例开箱即跑。 +- 多协议统一:支持 TCP、UDP、HTTP、WebSocket、NamedPipe 多种传输,API 统一。 +- 高性能:内存池与多线程设计,传输、RPC、文件等能力性能稳定。 +- 可靠通信:内置握手认证、心跳与异常关闭处理,结合重连插件可构建更稳健的连接。 +- 插件驱动:基于委托与插件体系,支持全链路 AOP 能力。 +- 生态完备:无论使用哪种底层协议,均可使用 Dmtp 的文件传输、RPC 调用、远程访问等高级功能。 + +### 2.1 与服务器配合 + +当 `DmtpClient` 连接到服务器后,服务器会为其创建对应的 `SessionClient` 实例,后续的数据交互在两端的 Dmtp 协议栈中完成,上层仅关注统一 API。 + +## 三、产品应用场景 + +- 需要跨平台与跨协议的统一通信栈。 +- 需要在客户端侧使用 RPC、文件传输、远程访问等高级能力。 +- 工业现场设备、IoT 网关、桌面/服务进程等需要稳定长连接通信的场景。 +- 微服务/边缘节点作为调用方,向中心服务发起高并发、低时延通信。 + +## 四、客户端架构 + +客户端与服务器的关系示意如下: + +```mermaid +flowchart TD; + DmtpClient-->DmtpService; + DmtpService-->DmtpSessionClient; +``` + +- 客户端本地维护 Dmtp 协议栈,完成握手、认证、心跳、通道管理等。 +- 服务器侧为每个客户端维护一个对应的 `SessionClient`,两端通过 Dmtp 协议进行统一交互。 + +## 五、可配置项 + +### 5.1 Dmtp特有配置 #### SetDmtpOption 设置Dmtp相关配置。其中包含: -- VerifyToken:设置验证口令。 -- Id:连接时指定Id。 -- Metadata:连接时指定元数据。可以在连接时,指定一些字符串键值对。 + -
-
+- **VerifyToken**:设置验证口令,作用类似账号密码。客户端连接时必须提供正确的Token才能建立连接。 +- **VerifyTimeout**:验证连接超时时间。仅用于服务器。意为:当服务器收到基础链接,在指定的时间内如果没有收到握手信息,则直接视为无效链接,直接断开。 -## 三、支持插件接口 +### 5.2 底层协议配置 + +根据不同的底层协议,DmtpService还可以配置相应协议的特有选项: + +- **基于TCP时**:支持[TcpService可配置项](./tcpservice.mdx)的所有配置,如SSL、NoDelay、端口复用等。 +- **基于UDP时**:支持[UdpSession可配置项](./udpsession.mdx)的所有配置。 +- **基于HTTP时**:支持[HttpService可配置项](./httpservice.mdx)的所有配置。 +- **基于NamedPipe时**:支持[NamedPipeService可配置项](./namedpipeservice.mdx)的所有配置。 +### 5.2 底层协议配置 + +根据不同的底层协议,客户端还可叠加相应协议的特有配置: + +- 基于 TCP 时:支持 [TcpClient 可配置项](./tcpclient.mdx) 的所有配置。 +- 基于 UDP 时:支持 [UdpSession 可配置项](./udpsession.mdx) 的所有配置。 +- 基于 HTTP 时:支持 [HttpClient 可配置项](./httpclient.mdx) 的所有配置。 +- 基于 NamedPipe 时:支持 [NamedPipeClient 可配置项](./namedpipeclient.mdx) 的所有配置。 +- 基于 WebSocket 时:参照 WebSocket 客户端的常规配置,保持 Dmtp 上层一致。 + +## 六、支持插件接口 声明自定义插件类,实现`IPlugin`接口,或者继承`PluginBase`,然后实现所需插件接口,即可实现事务的触发。 | 插件方法 | 功能 | | --- | --- | -| IDmtpHandshakingPlugin | 客户端在验证连接。默认情况下,框架会首先验证连接Token是否正确,如果不正确则直接拒绝。不会有任何投递。用户也可以使用Metadata进行动态验证。 | -| IDmtpHandshakedPlugin | 客户端完成握手连接验证 | +| IDmtpConnectingPlugin | 客户端在验证连接。默认情况下,框架会首先验证连接Token是否正确,如果不正确则直接拒绝。不会有任何投递。用户也可以使用Metadata进行动态验证。 | +| IDmtpConnectedPlugin | 客户端完成握手连接验证 | | IDmtpReceivedPlugin | 在收到Dmtp格式的数据包时触发 | | IDmtpRoutingPlugin | 当需要路由数据时触发,并且必须返回e.IsPermitOperation=true时,才允许路由 | | IDmtpCreatedChannelPlugin | 在收到创建通道的请求时候触发。 | @@ -48,49 +99,23 @@ Dmtp客户端对应的,也有不同协议的版本。各个版本之间功能 | IDmtpClosedPlugin | 在Dmtp连接断开时触发。 | -## 四、创建 +## 七、创建客户端 -### 4.1 TcpDmtpClient +### 7.1 TcpDmtpClient `TcpDmtpClient`对应`TcpDmtpService`服务器。基本创建如下,支持[创建TcpClient](./tcpclient.mdx)的所有配置。 -```csharp showLineNumbers -var client = new TcpDmtpClient(); -await client.SetupAsync(new TouchSocketConfig() - .SetRemoteIPHost("127.0.0.1:7789") - .SetDmtpOption(new DmtpOption() - { - VerifyToken = "Dmtp" - })); -await client.ConnectAsync(); -``` + -### 4.2 UdpDmtp +### 7.2 UdpDmtp `UdpDmtp`对应`UdpDmtp`服务器,即`UdpDmtp`即是服务器,又是客户端。基本创建如下,支持[创建UdpSession](./udpsession.mdx)的所有配置。 -```csharp {1,4-6,11} showLineNumbers -var client = new UdpDmtp(); - - await client.SetupAsync(new TouchSocketConfig() - .SetRemoteIPHost("127.0.0.1:7797") - .UseUdpReceive() - .SetDmtpOption(new DmtpOption() - { - VerifyToken = "Dmtp" - }) - .ConfigureContainer(a => - { - }) - .ConfigurePlugins(a => - { - })); - client.Start(); -``` + :::info 备注 @@ -98,90 +123,40 @@ var client = new UdpDmtp(); ::: -### 4.3 HttpDmtpClient +### 7.3 HttpDmtpClient `HttpDmtpClient`对应`HttpDmtpService`,或者`HttpMiddlewareDmtpService`服务器。基本创建如下,支持[创建HttpClient](./httpclient.mdx)的所有配置。 -```csharp showLineNumbers -var client = new HttpDmtpClient(); -await client.SetupAsync(new TouchSocketConfig() - .SetRemoteIPHost("127.0.0.1:7789") - .SetDmtpOption(new DmtpOption() - { - VerifyToken = "Dmtp" - })); -await client.ConnectAsync(); -``` + -### 4.4 WebSocketDmtpClient +### 7.4 WebSocketDmtpClient -```csharp showLineNumbers -var websocketDmtpClient = new WebSocketDmtpClient(); -websocketDmtpClient.Setup(new TouchSocketConfig() - .SetDmtpOption(new DmtpOption() - { - VerifyToken = "Dmtp" - }) - .SetRemoteIPHost("ws://localhost:5174/WebSocketDmtp")); -await websocketDmtpClient.ConnectAsync(); -``` + -[基于Aspnetcore的Dmtp示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Dmtp/DmtpWebApplication) - - -### 4.5 DmtpClient工厂 - -DmtpClient工厂工作模式是一种,由多个传输通讯连接组成的连接池。默认提供了两种类型的客户端工厂,分别为`TcpDmtpClientFactory`,`HttpDmtpClientFactory`。两者工作流程及配置基本相同,下面以`TcpDmtpClientFactory`为例。 - -【创建】 -```csharp showLineNumbers -var clientFactory = new TcpDmtpClientFactory() -{ - MinCount=5,//最小数量,在主连接器成功建立以后,会检测可用连接是否大于该值,否的话会自动建立。 - MaxCount = 10,//最大数量,当超过该数量的连接后,会等待指定时间。 - ConnectTimeout = TimeSpan.FromSeconds(10),//连接超时时间 - GetConfig = () => - { - return new TouchSocketConfig() - .SetRemoteIPHost("tcp://127.0.0.1:7789"); - } -}; -``` - -【使用】 - -```csharp {3} showLineNumbers -using (var clientFactoryResult = await clientFactory.GetClient()) -{ - var client = clientFactoryResult.Client; -} -``` - -:::tip 提示 - -默认情况下,`clientFactory.GetClient()`会最大等待1秒,如果超过1秒还没有可用的`client`,会再新建一个客户端,并返回。然后在使用结束后,会判断是否需要释放该`client`,如果池中的`client`数量`大于MaxCount`,则会释放该`client`。 - -::: - -### 4.6 NamedPipeDmtpClient +### 7.5 NamedPipeDmtpClient `NamedPipeDmtpClient`对应`NamedPipeDmtpService`服务器。基本创建如下,支持[创建NamedPipeClient](./namedpipeclient.mdx)的所有配置。 -```csharp showLineNumbers -var client = new NamedPipeDmtpClient(); -await client.SetupAsync(new TouchSocketConfig() - .SetPipeName("TouchSocketPipe")//设置管道名称 - .SetDmtpOption(new DmtpOption() - { - VerifyToken = "Dmtp" - })); -await client.ConnectAsync(); -``` + -[基于NamedPipe的Dmtp示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Dmtp/NamedPipeDmtpConsoleApp) \ No newline at end of file +### 7.6 TcpDmtpClientFactory + +DmtpClient工厂工作模式是一种,由多个传输通讯连接组成的连接池。 + + + +### 7.7 HttpDmtpClientFactory + +DmtpClient工厂工作模式是一种,由多个传输通讯连接组成的连接池。 + + + +## 八、示例Demo + + \ No newline at end of file diff --git a/handbook/docs/dmtpcustomactor.mdx b/handbook/docs/dmtpcustomactor.mdx index 3c31338b5..874eef66a 100644 --- a/handbook/docs/dmtpcustomactor.mdx +++ b/handbook/docs/dmtpcustomactor.mdx @@ -5,7 +5,7 @@ title: 自定义DmtpActor import { TouchSocketDmtpDefinition } from "@site/src/components/Definition.js"; -### 定义 + @@ -387,10 +387,10 @@ static class SimpleDmtpRpcExtension 功能插件是为Actor提供创建时机,和必要的配置信息。如果有需要,还可以抛出插件信息(例如[文件传输](./dmtptransferfile.mdx) )。 ```csharp showLineNumbers -class SimpleDmtpRpcFeature : PluginBase, IDmtpHandshakingPlugin, IDmtpReceivedPlugin +class SimpleDmtpRpcFeature : PluginBase, IDmtpConnectingPlugin, IDmtpReceivedPlugin { readonly Dictionary m_pairs = new Dictionary(); - public async Task OnDmtpHandshaking(IDmtpActorObject client, DmtpVerifyEventArgs e) + public async Task OnDmtpConnecting(IDmtpActorObject client, DmtpVerifyEventArgs e) { var actor = new SimpleDmtpRpcActor(client.DmtpActor) { diff --git a/handbook/docs/dmtpdescription.mdx b/handbook/docs/dmtpdescription.mdx index d83dbea2a..ff8f152ed 100644 --- a/handbook/docs/dmtpdescription.mdx +++ b/handbook/docs/dmtpdescription.mdx @@ -6,7 +6,7 @@ import Pro from "@site/src/components/Pro.js"; import BilibiliCard from '@site/src/components/BilibiliCard.js'; import { TouchSocketDmtpDefinition } from "@site/src/components/Definition.js"; -### 定义 + diff --git a/handbook/docs/dmtpredis.mdx b/handbook/docs/dmtpredis.mdx index 275b02758..129fe94a2 100644 --- a/handbook/docs/dmtpredis.mdx +++ b/handbook/docs/dmtpredis.mdx @@ -4,66 +4,87 @@ title: Redis缓存 --- import { TouchSocketDmtpDefinition } from "@site/src/components/Definition.js"; - -### 定义 +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; +import CardLink from "@site/src/components/CardLink.js"; - ## 一、说明 -REmote DIctionary Server(Redis) 是一个key-value存储系统,也是一个简单的非关系型数据库。 +REmote DIctionary Server(Redis) 是一个 key-value 存储系统,也是一个简单的非关系型数据库。 :::caution 警告 -此组件是基于Dmtp协议的Redis。所以无法连接到常规Redis中。但是此组件无论是扩展性还是性能,都是远胜常规Redis的。 +此组件是基于 Dmtp 协议的 Redis。所以无法连接到常规 Redis 中。但是此组件无论是扩展性还是性能,都是远胜常规 Redis 的。 ::: -## 二、使用 +## 二、特性 -Redis是由RedisFeature功能插件提供的,所以需要添加`UseDmtpRedis`。 +基于 Dmtp 协议的 Redis 实现具有以下特性: -```csharp showLineNumbers -.ConfigurePlugins(a => -{ - a.UseDmtpRedis();//添加Redis功能插件 -}) -``` +- **高性能**:基于 Dmtp 协议的高效通信机制 +- **强类型支持**:支持泛型,可以直接存储和获取强类型数据 +- **异步操作**:完全支持异步操作,提高并发性能 +- **可扩展性**:支持自定义缓存实现,可以轻松实现持久化 +- **简单易用**:API 设计简洁,易于集成和使用 -【请求端】 +## 三、服务器配置 -```csharp showLineNumbers -var client = GetTcpDmtpClient(); +在服务器端,需要通过插件的方式启用 Redis 功能。 -//获取Redis -var redis = client.GetDmtpRedisActor(); + -//执行Set -var result = redis.Set("1", "1"); -client.Logger.Info($"Set result={result}"); -client.Logger.Info($"ContainsCache result={redis.ContainsCache("1")}"); +## 四、客户端配置 -//执行Get -var result1 = redis.Get("1"); -client.Logger.Info($"Get result={result}"); +在客户端,同样需要添加 Redis 插件以启用相关功能。 -//执行Remove -result = redis.RemoveCache("1"); -client.Logger.Info($"Get result={result}"); -redis.ClearCache(); -``` + -## 三、缓存持久化 +## 五、基本使用 -默认情况下,Redis组件会使用`MemoryCache`作为实际储存。所以要实现缓存持久化,只需要替换该缓存、或者保存该缓存即可。 +配置完成后,可以通过 `GetDmtpRedisActor()` 方法获取 Redis 操作对象,然后进行各种缓存操作。 -```csharp showLineNumbers -.ConfigurePlugins(a => -{ - a.UseDmtpRedis()//必须添加Redis访问插件 - .SetCache(new MemoryCache());//这里可以设置缓存持久化,此处仍然是使用内存缓存。 -}) -``` + -[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Dmtp/DmtpRedisConsoleApp) \ No newline at end of file +### 5.1 主要方法说明 + +- **SetAsync**:设置缓存值,支持指定过期时间(毫秒) +- **GetAsync**:获取缓存值,支持泛型 +- **AddAsync**:添加缓存项,如果键已存在则不进行任何操作 +- **ContainsCacheAsync**:检查指定键是否存在 +- **RemoveCacheAsync**:移除指定的缓存项 +- **ClearCacheAsync**:清空所有缓存 + +## 六、缓存持久化 + +默认情况下,Redis 组件会使用 `MemoryCache>` 作为实际存储。要实现缓存持久化,只需要替换该缓存实现即可。 + + + +### 6.1 自定义缓存实现 + +您可以实现 `ICache>` 接口来创建自己的缓存持久化方案,例如: + +- 基于文件的持久化 +- 基于数据库的持久化 +- 基于其他存储系统的持久化 + +只需要在配置时通过 `options.Cache` 属性指定自定义的缓存实现即可。 + +## 七、数据序列化 + +Redis 组件默认使用 `BytesSerializerConverter` 进行数据序列化和反序列化。该转换器支持各种常见的数据类型,包括基本类型、字符串、字节数组等。 + +如需使用自定义的序列化方式,可以通过 `IDmtpRedisActor.Converter` 属性进行设置。 + +## 八、注意事项 + +1. **协议兼容性**:此 Redis 实现基于 Dmtp 协议,与标准 Redis 协议不兼容 +2. **过期时间**:所有缓存操作都支持设置过期时间,单位为毫秒 +3. **异步操作**:建议使用异步方法以获得最佳性能 +4. **类型安全**:使用泛型方法时,确保存储和读取的类型一致 + +## 九、示例代码 + + \ No newline at end of file diff --git a/handbook/docs/dmtpremoteaccess.mdx b/handbook/docs/dmtpremoteaccess.mdx index 54b376a7a..cea11ed3a 100644 --- a/handbook/docs/dmtpremoteaccess.mdx +++ b/handbook/docs/dmtpremoteaccess.mdx @@ -4,6 +4,8 @@ title: 远程文件系统 --- import Tag from "@site/src/components/Tag.js"; import Pro from "@site/src/components/Pro.js"; +import CardLink from "@site/src/components/CardLink.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; ## 一、说明 @@ -13,12 +15,7 @@ import Pro from "@site/src/components/Pro.js"; 该功能由`RemoteAccessFeature`功能插件提供。所以需要`客户端`和`服务器`都需要`UseRemoteAccess`。 -```csharp showLineNumbers -.ConfigurePlugins(a => -{ - a.UseRemoteAccess(); -}); -``` + 以获取文件夹信息为例: @@ -28,11 +25,7 @@ import Pro from "@site/src/components/Pro.js"; 同时可以传递一个元数据组。用于载入自定义消息。 -```csharp showLineNumbers -var result = await this.m_client.GetRemoteAccessActor().GetDirectoryInfoAsync("c:/新建文件夹", millisecondsTimeout: 30 * 1000); - -//然后通过DirectoryInfo属性,可以获取到请求文件夹的信息,包括创建时间、修改时间、子文件夹、子文件等。 -``` + :::tip 提示 @@ -45,30 +38,7 @@ var result = await this.m_client.GetRemoteAccessActor().GetDirectoryInfoAsync("c 响应端定义一个插件,实现`IDmtpRemoteAccessingPlugin`,然后实现接口。 -```csharp showLineNumbers -public class MyRemoteAccessPlugin : PluginBase, IDmtpRemoteAccessingPlugin -{ - public async Task OnRemoteAccessing(IDmtpActorObject client, RemoteAccessingEventArgs e) - { - //Console.WriteLine($"有客户端正在请求远程操作"); - //Console.WriteLine($"类型:{e.AccessType},模式:{e.AccessMode}"); - //Console.WriteLine($"请求路径:{e.Path}"); - //Console.WriteLine($"目标路径:{e.TargetPath}"); - - //Console.WriteLine("请输入y/n决定是否允许其操作?"); - - //var input = Console.ReadLine(); - //if (input == "y") - //{ - // e.IsPermitOperation = true; - // return; - //} - - //如果当前插件无法处理,转至下一个插件 - await e.InvokeNext(); - } -} -``` + :::caution 警告 @@ -76,4 +46,4 @@ public class MyRemoteAccessPlugin : PluginBase, IDmtpRemoteAccessingPlugin ::: -[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Dmtp/RemoteAccessApp) \ No newline at end of file + \ No newline at end of file diff --git a/handbook/docs/dmtpremotestream.mdx b/handbook/docs/dmtpremotestream.mdx index e00cbdc75..0bd2a6b57 100644 --- a/handbook/docs/dmtpremotestream.mdx +++ b/handbook/docs/dmtpremotestream.mdx @@ -1,13 +1,14 @@ --- id: dmtpremotestream -title: b.远程流映射 +title: 远程流映射 --- import Tag from "@site/src/components/Tag.js"; import Pro from "@site/src/components/Pro.js"; import { TouchSocketProDmtpDefinition } from "@site/src/components/Definition.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; +import CardLink from "@site/src/components/CardLink.js"; -### 定义 @@ -27,72 +28,25 @@ import { TouchSocketProDmtpDefinition } from "@site/src/components/Definition.js 该功能由`DmtpRemoteStreamFeature`功能插件提供。所以需要`客户端`和`服务器`都需要`UseDmtpRemoteStream`。 -```csharp showLineNumbers -.ConfigurePlugins(a => -{ - a.UseDmtpRemoteStream(); -}); -``` + 【请求端】 任意Dmtp终端均可以调用LoadRemoteStream创建一个流数据映射。 同时可以传递一个元数据组。用于载入自定义消息。 -```csharp showLineNumbers -var metadata = new Metadata(); -metadata.Add("tag", "tag1"); - -var remoteStream = client.GetDmtpRemoteStreamActor().LoadRemoteStream(metadata); -``` + 【响应端】 响应端定义一个插件,实现`IDmtpRemoteStreamPlugin`,然后实现需要载入的具体流信息。示例中是以`MemoryStream`作为流主体。 -```csharp showLineNumbers -internal class MyRemoteStreamPlugin : PluginBase, IDmtpRemoteStreamPlugin -{ - public async Task OnLoadingStream(ITcpDmtpSessionClient client, LoadingStreamEventArgs e) - { - if (e.Metadata["tag"] == "tag1") - { - e.IsPermitOperation = true;//需要允许操作 - - client.Logger.Info("开始载入流"); - //当请求方请求映射流的时候,会触发此方法。 - using (var stream = new MemoryStream()) - { - await e.WaitingLoadStreamAsync(stream, TimeSpan.FromSeconds(60)); - - client.Logger.Info($"载入的流已被释放,流中信息:{Encoding.UTF8.GetString(stream.ToArray())}"); - } - - return; - } - - //如果不满足,调用下一个插件 - await e.InvokeNext(); - } -} -``` + ## 四、读写 当RemoteStream被成功创建以后,即可直接Read、Write。因为RemoteStream继承自Stream。 -```csharp showLineNumbers -var client = GetClient(); -var remoteStream = client.GetDmtpRemoteStreamActor().LoadRemoteStream(new Metadata().AddOrUpdate("1", "1")); - -byte[] data = new byte[] { 0, 1, 2, 3, 4 }; -remoteStream.Write(data); - -remoteStream.Position = 0; -byte[] buffer=new byte[5]; -remoteStream.Read(buffer); - -remoteStream.SafeDispose(); -``` + ## 五、释放 @@ -105,4 +59,6 @@ remoteStream.SafeDispose(); -[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Dmtp/RemoteStreamConsoleApp) +## 七、示例Demo + + diff --git a/handbook/docs/dmtprouterpackage.mdx b/handbook/docs/dmtprouterpackage.mdx index f51c5bf64..154fcab80 100644 --- a/handbook/docs/dmtprouterpackage.mdx +++ b/handbook/docs/dmtprouterpackage.mdx @@ -6,8 +6,10 @@ title: 路由包传输 import Tag from "@site/src/components/Tag.js"; import Pro from "@site/src/components/Pro.js"; import { TouchSocketProDmtpDefinition } from "@site/src/components/Definition.js"; +import CardLink from "@site/src/components/CardLink.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; + -### 定义 @@ -30,100 +32,22 @@ import { TouchSocketProDmtpDefinition } from "@site/src/components/Definition.js 路由包传输模式是由DmtpRouterPackageFeature功能插件提供的,所以需要添加`UseDmtpRouterPackage`。 -```csharp showLineNumbers -.ConfigurePlugins(a => -{ - a.UseDmtpRouterPackage();//添加路由包功能插件 -}) -``` + 其次,要完成路由包传输,就得自己定义请求包和响应包。此处简单定义两个包用于测试。具体关于包的打包、解包详细操作可以看[包序列化](./ipackage.mdx) -```csharp showLineNumbers -/// -/// 定义请求包 -/// -class MyRequestPackage : DmtpRouterPackage -{ - /// - /// 包尺寸大小。此值并非需要精准数值,只需要估计数据即可。其作用为申请内存池。所以数据应当大小合适。 - /// - public override int PackageSize => 1024 * 1024; + - /// - /// 自定义一个内存属性。 - /// - public ByteBlock ByteBlock { get; set; } - - public override void PackageBody(ByteBlock byteBlock) - { - base.PackageBody(byteBlock); - byteBlock.WriteByteBlock(this.ByteBlock); - } - - public override void UnpackageBody(ByteBlock byteBlock) - { - base.UnpackageBody(byteBlock); - this.ByteBlock = byteBlock.ReadByteBlock(1024*64); - } -} - -/// -/// 定义响应包 -/// -class MyResponsePackage : DmtpRouterPackage -{ - /// - /// 包尺寸大小。此值并非需要精准数值,只需要估计数据即可。其作用为申请内存池。所以数据应当大小合适。 - /// - public override int PackageSize => 1024; -} -``` + 【请求端】 -```csharp showLineNumbers -using (ByteBlock byteBlock = new ByteBlock(1024*512)) -{ - byteBlock.SetLength(byteBlock.Capacity);//此处模拟一个大数据块 - MyRequestPackage requestPackage = new MyRequestPackage() - { - ByteBlock = byteBlock, - Metadata = new Metadata() { { "a", "a" } } - }; - var response = client.GetDmtpRouterPackageActor().Request(requestPackage); - - //client.Logger.Info(response.Message); -} -``` + 【响应端】 响应端除了`UseDmtpRouterPackage`之外,还需要添加一个插件,实现`IDmtpRouterPackagePlugin`功能,主要在里面实现响应的动作。 -```csharp showLineNumbers -class MyPlugin : PluginBase, IDmtpRouterPackagePlugin -{ - private readonly ILog m_logger; + - public MyPlugin(ILog logger) - { - this.m_logger = logger; - } - public async Task OnReceivedRouterPackage(IDmtpActorObject client, RouterPackageEventArgs e) - { - //m_logger.Info($"收到包请求"); - await e.ResponseAsync(new MyResponsePackage() - { - Message = "Success" - }); - //m_logger.Info($"已响应包请求"); - - //一般在当前插件无法处理时调用下一插件。此处则不应该调用 - //await e.InvokeNext(); - } -} -``` - - -[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Dmtp/RouterPackageConsoleApp) \ No newline at end of file + \ No newline at end of file diff --git a/handbook/docs/dmtprpc.mdx b/handbook/docs/dmtprpc.mdx index 56cf98225..e18558f5c 100644 --- a/handbook/docs/dmtprpc.mdx +++ b/handbook/docs/dmtprpc.mdx @@ -5,8 +5,8 @@ title: Rpc功能 import BilibiliCard from '@site/src/components/BilibiliCard.js'; import { TouchSocketDmtpDefinition } from "@site/src/components/Definition.js"; - -### 定义 +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; +import CardLink from "@site/src/components/CardLink.js"; @@ -35,49 +35,9 @@ RPC(Remote Procedure Call)远程过程调用协议,一种通过网络从 ### 2.1 定义服务 -1. 在**服务器**端中新建一个类名为`MyRpcServer`。 -2. 继承于`SingletonRpcServer`类、或实现`ISingletonRpcServer`。亦或者将服务器声明为**瞬时生命**的服务,继承`TransientRpcServer`、或`ITransientRpcServer`。 -3. 在该类中写**公共方法**,并用`DmtpRpc`属性标签标记。 +在**服务器**端中新建一个类,继承于`SingletonRpcServer`类、或实现`ISingletonRpcServer`。亦或者将服务器声明为**瞬时生命**的服务,继承`TransientRpcServer`、或`ITransientRpcServer`。详情请看:[Rpc服务注册](./rpcregister.mdx)。然后在该类中写**公共方法**,并用`DmtpRpc`属性标签标记。 -```csharp showLineNumbers -public partial class MyRpcServer : SingletonRpcServer -{ - [Description("登录")]//服务描述,在生成代理时,会变成注释。 - [DmtpRpc(InvokeKey ="Login")]//服务注册的函数键,此处为显式指定。默认不传参的时候,为该函数类全名+方法名的全小写。 - public bool Login(string account, string password) - { - if (account == "123" && password == "abc") - { - return true; - } - - return false; - } -} -``` - -```csharp showLineNumbers -public partial class MyRpcServer : TransientRpcServer -{ - [Description("登录")]//服务描述,在生成代理时,会变成注释。 - [DmtpRpc(InvokeKey ="Login")]//服务注册的函数键,此处为显式指定。默认不传参的时候,为该函数类全名+方法名的全小写。 - public bool Login(string account,string password) - { - if (account=="123"&&password=="abc") - { - return true; - } - - return false; - } -} -``` - -:::info 信息 - -`ITransientRpcServer`和`ISingletonRpcServer`相比,意为瞬时生命服务,即实现`ITransientRpcServer`的服务,在每次被调用时,都会创建一个新的服务实例。其优点为可以直接通过`this.CallContext`属性获得调用上下文。其缺点则是每次调用时会多消耗一些性能。 - -::: + ### 2.2 启动Dmtp并注册Rpc服务 @@ -85,218 +45,59 @@ public partial class MyRpcServer : TransientRpcServer 更多注册Rpc的方法请看[注册Rpc服务](./rpcregister.mdx) -```csharp showLineNumbers -var service = new TcpDmtpService(); -var config = new TouchSocketConfig()//配置 - .SetListenIPHosts(7789) - .ConfigureContainer(a=> - { - a.AddRpcStore(store => - { - store.RegisterServer();//注册服务 - }); - }) - .ConfigurePlugins(a => - { - a.UseDmtpRpc(); - }) - .SetDmtpOption(new DmtpOption() - { - VerifyToken = "Rpc"//连接验证口令。 - }); - -await service.SetupAsync(config); - -await service.StartAsync(); - -service.Logger.Info($"{service.GetType().Name}已启动"); -``` + ### 2.3 调用Rpc +#### 2.3.1 创建DmtpRpc客户端 + +适用于DmtpRpc的客户端其实就是常规的Dmtp客户端,只需要在插件中添加DmtpRpc插件即可。下列以TcpDmtpClient为例,其他客户端请看[创建Dmtp客户端](./dmtpclient.mdx)。 + + + #### 2.3.1 直接调用 直接调用,则是不使用**任何代理**,使用**字符串**和**参数**直接Call Rpc,使用比较简单。 -下列以TcpDmtpClient为例,其他客户端请看[创建Dmtp客户端](./dmtpclient.mdx)。 - -```csharp showLineNumbers -var client = new TcpDmtpClient(); -await client.SetupAsync(new TouchSocketConfig() - .SetRemoteIPHost("127.0.0.1:7789") - .ConfigurePlugins(a => - { - a.UseDmtpRpc(); - }) - .SetDmtpOption(new DmtpOption() - { - VerifyToken = "Rpc"//连接验证口令。 - })); -await client.ConnectAsync(); - -bool result =(bool) client.GetDmtpRpcActor().Invoke(typeof(bool),"Login", InvokeOption.WaitInvoke, "123", "abc"); -``` + :::info 信息 -直接调用时,第一个参数为`返回值`类型,当没有返回值时则可以不用。第二个参数为`调用键`,调用键默认情况下为服务类的“`命名空间+类名+方法名`”的`全小写`。但在本案例中直接指定了以“Login”为调用键。第三个参数为`调用配置`参数,可设置调用超时时间,取消调用等功能。示例中使用的预设,实际上可以自行new InvokeOption()。后续参数为`调用参数`。 +直接调用时,泛型参数为`返回值`类型,当没有返回值时则可以不用。 + +第1个参数为`调用键`,调用键默认情况下为服务类的“`命名空间+类名+方法名`”的`全小写`。但在本案例中直接指定了以“Add”为调用键。 + +第2个参数为`调用配置`参数,可设置调用超时时间,取消调用等功能。 + +后续参数为`调用参数`。 ::: -或者使用`InvokeT`的扩展方法调用。在有返回值时,可以直接泛型传参。 - -```csharp showLineNumbers -bool result =client.GetDmtpRpcActor().InvokeT("Login", InvokeOption.WaitInvoke, "123", "abc"); -``` #### 2.3.2 代理调用 -代理调用的便捷在于,客户端不用再知道哪些服务可调,也不用再纠结调用的参数类型正不正确,因为这些,代理工具都会替你做好。 +代理调用的便捷在于,客户端不用再知道哪些服务可调,也不用再纠结调用的参数类型正不正确,因为这些,代理工具都会替你做好。代理的详细使用请看:[Rpc代理生成](./rpcgenerateproxy.mdx) -详细步骤: - -1. [生成代理文件](./rpcgenerateproxy.mdx) -2. 将生成的cs文件添加到调用端一起编译。 - -:::info 备注 - -以上示例,会生成下列代理代码。 - -::: - - -
-生成的代理 -
- - -```csharp showLineNumbers -using System; -using TouchSocket.Core; -using TouchSocket.Sockets; -using TouchSocket.Rpc; -using TouchSocket.Dmtp.Rpc; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; -using System.Threading.Tasks; -namespace RpcProxy -{ - public interface IMyRpcServer : TouchSocket.Rpc.IRemoteServer - { - /// - ///登录 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - System.Boolean Login(System.String account, System.String password, IInvokeOption invokeOption = default); - /// - ///登录 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - Task LoginAsync(System.String account, System.String password, IInvokeOption invokeOption = default); - } - public class MyRpcServer : IMyRpcServer - { - public MyRpcServer(IRpcClient client) - { - this.Client = client; - } - public IRpcClient Client { get; private set; } - /// - ///登录 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - public System.Boolean Login(System.String account, System.String password, IInvokeOption invokeOption = default) - { - if (Client == null) - { - throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); - } - object[] parameters = new object[] { account, password }; - System.Boolean returnData = (System.Boolean)Client.Invoke(typeof(System.Boolean), "Login", invokeOption, parameters); - return returnData; - } - /// - ///登录 - /// - public async Task LoginAsync(System.String account, System.String password, IInvokeOption invokeOption = default) - { - if (Client == null) - { - throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); - } - object[] parameters = new object[] { account, password }; - return (System.Boolean)await Client.InvokeAsync(typeof(System.Boolean), "Login", invokeOption, parameters); - } - } - public static class MyRpcServerExtensions - { - /// - ///登录 - /// - /// 调用超时 - /// Rpc调用异常 - /// 其他异常 - public static System.Boolean Login(this TClient client, System.String account, System.String password, IInvokeOption invokeOption = default) where TClient : - TouchSocket.Rpc.IRpcClient - { - object[] parameters = new object[] { account, password }; - System.Boolean returnData = (System.Boolean)client.Invoke(typeof(System.Boolean), "Login", invokeOption, parameters); - return returnData; - } - /// - ///登录 - /// - public static async Task LoginAsync(this TClient client, System.String account, System.String password, IInvokeOption invokeOption = default) where TClient : - TouchSocket.Rpc.IRpcClient - { - object[] parameters = new object[] { account, password }; - return (System.Boolean)await client.InvokeAsync(typeof(System.Boolean), "Login", invokeOption, parameters); - } - } -} -``` - - -
-
- 使用代理扩展直接调用。 -```csharp showLineNumbers -var client = new TcpDmtpClient(); -await client.SetupAsync(new TouchSocketConfig() - .SetRemoteIPHost("127.0.0.1:7789") - .ConfigurePlugins(a => - { - a.UseDmtpRpc(); - }) - .SetDmtpOption(new DmtpOption() - { - VerifyToken = "Rpc"//连接验证口令。 - })); -await client.ConnectAsync(); - -bool result = client.GetDmtpRpcActor().Login("123", "abc", InvokeOption.WaitInvoke);//Login是生成的代理扩展方法。可能需要额外添加命名空间。 -``` - + :::tip 提示 client.GetDmtpRpcActor()的操作,内部还需要执行字典的查询操作。所以,如果为效率考虑的话,在连接稳定的前提下,可以保存好client.GetDmtpRpcActor()的返回值对象,直接执行Rpc操作。但是需要的注意的是,一旦重新连接,则该对象也需要重新获取。 ::: +:::tip 代理生成的代码示例 + +代理生成的代码示例可以参考示例工程中的 `RpcProxy.cs` 文件,或者参考 [Rpc代理生成](./rpcgenerateproxy.mdx) 文档。 + +::: + ## 三、反向Rpc 一般的rpc服务都是客户端发起,服务器响应。但是有时候也需要服务器发起,客户端响应,所以需要反向rpc。 @@ -306,106 +107,53 @@ client.GetDmtpRpcActor()的操作,内部还需要执行字典的查询操作 ### 3.1 定义、发布反向Rpc服务 -实际上,Dmtp的全称(Duplex Message Transport Protocol双工消息传输协议),Duplex意为双工,则表明,当Dmtp客户端连接到服务以后,拥有与服务器同等的通讯权限与功能。所以客户端发布Rpc服务的步骤和服务器完全一致。即:当客户端和服务器建立连接以后,就不再区分谁是客户端,谁是服务器了。只关心,**谁能提供服务,谁在调用服务**。 +实际上,Dmtp的全称(Duplex Message Transport Protocol双工消息传输协议),Duplex意为双工,则表明,当Dmtp客户端连接到服务以后,拥有与服务器同等的通讯权限与功能。 + +所以客户端发布Rpc服务的步骤和服务器完全一致。 + +即:当客户端和服务器建立连接以后,就不再区分谁是客户端,谁是服务器了。只关心,**谁能提供服务,谁在调用服务**。 下列就以简单的示例下,由客户端声明服务,服务器调用服务。 -具体步骤: +#### 3.1.1 声明反向Rpc服务 -1. 在**客户端项目**中定义Rpc服务,名为`ReverseCallbackServer`。 -2. 用**DmtpRpc**标记需要公开的公共方法。 +具体步骤和服务器端一致。 -```csharp showLineNumbers -public partial class ReverseCallbackServer : SingletonRpcServer -{ - [DmtpRpc(MethodInvoke = true)]//使用方法名作为调用键 - public string SayHello(string name) - { - return $"{name},hi"; - } -} -``` + -**【客户端注册发布服务】** +#### 3.1.2 客户端注册发布服务 -```csharp showLineNumbers -var client = new TcpDmtpClient(); -await client.SetupAsync(new TouchSocketConfig() - .ConfigureContainer(a => - { - a.AddConsoleLogger(); - }) - .ConfigureContainer(a => - { - a.AddRpcStore(store => - { - store.RegisterServer(); - }); - }) - .ConfigurePlugins(a => - { - a.UseDmtpRpc(); - }) - .SetRemoteIPHost("127.0.0.1:7789") - .SetDmtpOption(new DmtpOption() - { - VerifyToken = "Rpc"//连接验证口令。 - })); -await client.ConnectAsync(); -client.Logger.Info($"连接成功,Id={client.Id}"); -``` +客户端发布服务的步骤和服务器端一致。 + + ### 3.2 调用反向Rpc -服务器回调客户端,最终必须通过**服务器辅助类客户端**(`ITcpSessionClient`的派生类),以`TcpDmtpService`为例,其辅助客户端为`TcpDmtpSessionClient`(或其接口:`ITcpDmtpSessionClient`)。 - -下列示例以TcpDmtpSessionClient为例,其余一致。 +服务器回调客户端,最终必须通过**服务器辅助类客户端**(`ISessionClient`的派生类)来执行。 +以`TcpDmtpService`为例,其辅助客户端为`TcpDmtpSessionClient`(或其接口:`ITcpDmtpSessionClient`)。 #### 3.2.1 通过服务器直接获取 可以获取所有`TcpDmtpSessionClient`,进行广播式调用。 -```csharp showLineNumbers -foreach (var item in service.GetClients()) -{ - item.GetDmtpRpcActor().InvokeT("SayHello", InvokeOption.WaitInvoke, "张三"); -} -``` + 也可以先筛选Id,然后再调用。 -```csharp showLineNumbers -var id = service.GetIds().FirstOrDefault(a => a.Equals("特定id")); -if (service.TryGetClient(id, out var SessionClient)) -{ - SessionClient.GetDmtpRpcActor().InvokeT("SayHello", InvokeOption.WaitInvoke, "张三"); -} -``` +#### 3.2.2 通过生命周期获取 -#### 3.2.2 通过调用上下文获取 +一般的,Dmtp会有完整的生命周期管理。例如:`IDmtpConnectedPlugin`、`IDmtpClosedPlugin`等,详情请看:[Dmtp服务器](./dmtpservice.mdx)。 -例如:下列声明在服务器端的Rpc服务MyRpcServer,使其使用瞬时服务(也可以通过函数注入服务)。 +所以可以通过生命周期获取到对应的客户端。此处不再赘述。 -上下文的Caller,即为服务器辅助类终端,进行强转即可。 +#### 3.2.3 通过调用上下文获取 -使用该方式可以实现,当客户端调用服务器的Add接口的时候,服务器又回调客户端的SayHello接口。 +当服务端的的Rpc服务被调用时可以通过调用上下文,获取Caller,即可以获取到对应的客户端。 -```csharp showLineNumbers -partial class MyRpcServer : TransientRpcServer -{ - [DmtpRpc(MethodInvoke = true)]//使用函数名直接调用 - public int Add(int a, int b) - { - if (this.CallContext.Caller is ITcpDmtpSessionClient SessionClient) - { - SessionClient.GetDmtpRpcActor().InvokeT("SayHello",InvokeOption.WaitInvoke,"张三"); - } - int sum = a + b; - return sum; - } -} -``` +使用该方式可以实现,当客户端调用服务器的Rpc方法的时候,服务器又回调客户端的Rpc方法。 + + :::tip 提示 @@ -416,7 +164,9 @@ partial class MyRpcServer : TransientRpcServer ## 四、客户端互Call Rpc -除了Rpc,反向Rpc,DmtpRpc还支持**客户端**之间互Call Rpc。服务的定义与Rpc一样。 +除了常规Rpc,反向Rpc,DmtpRpc还支持**客户端**之间互Call Rpc。 + +互Call Rpc的服务的定义与常规Rpc一样。 @@ -425,19 +175,11 @@ partial class MyRpcServer : TransientRpcServer 客户端1调用客户端2的方法,需要知道对方的**Id**。然后和调用Rpc方法一致。然后使用下列函数调用即可。 -```csharp showLineNumbers -var client1 = GetTcpDmtpClient(); -var client2 = GetTcpDmtpClient(); - -client1.GetDmtpRpcActor().InvokeT(client2.Id,"Notice",InvokeOption.WaitInvoke,"Hello"); -``` + 亦或者 -```csharp showLineNumbers -var targetRpcClient = client1.CreateTargetDmtpRpcActor(client2.Id); -targetRpcClient.InvokeT("Notice", InvokeOption.WaitInvoke, "Hello"); -``` + :::tip 提示 @@ -459,31 +201,11 @@ targetRpcClient.InvokeT("Notice", InvokeOption.WaitInvoke, "Hello"); 配置路由。 -```csharp {3} -.ConfigureContainer(a => -{ - a.AddDmtpRouteService(); - a.AddConsoleLogger(); -}) -``` + 同意转发路由数据。 -```csharp showLineNumbers -internal class MyPlugin : PluginBase,IDmtpRoutingPlugin -{ - public async Task OnDmtpRouting(IDmtpActorObject client, PackageRouterEventArgs e) - { - if (e.RouterType == RouteType.Rpc) - { - e.IsPermitOperation = true; - return; - } - - await e.InvokeNext(); - } -} -``` + ## 五、调用配置 @@ -521,15 +243,7 @@ FeedbackType是调用反馈类型,其枚举值分别有OnlySend、WaitSend、W 例如: - ```csharp showLineNumbers - a.UseDmtpRpc() -.ConfigureDefaultSerializationSelector(selector => -{ - selector.JsonSerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented; - selector.FastSerializerContext=default; - ... -}); - ``` + #### 5.2.2 支持AOT的序列化选择器 @@ -555,41 +269,11 @@ FeedbackType是调用反馈类型,其枚举值分别有OnlySend、WaitSend、W 下列将使用[MemoryPack](https://www.nuget.org/packages/MemoryPack) 序列化为例。 -```csharp {3,10} showLineNumbers -public class MemoryPackSerializationSelector : ISerializationSelector -{ - public object DeserializeParameter(ref TByteBlock byteBlock, SerializationType serializationType, Type parameterType) where TByteBlock : IByteBlock - { - var len = byteBlock.ReadInt32(); - var span = byteBlock.ReadToSpan(len); - return MemoryPackSerializer.Deserialize(parameterType, span); - } - - public void SerializeParameter(ref TByteBlock byteBlock, SerializationType serializationType, in object parameter) where TByteBlock : IByteBlock - { - var pos = byteBlock.Position; - byteBlock.Seek(4, SeekOrigin.Current); - var memoryPackWriter = new MemoryPackWriter(ref byteBlock, null); - - MemoryPackSerializer.Serialize(parameter.GetType(), ref memoryPackWriter, parameter); - - var newPos = byteBlock.Position; - byteBlock.Position = pos; - byteBlock.WriteInt32(memoryPackWriter.WrittenCount); - byteBlock.Position = newPos; - } -} -``` + 然后配置序列化器 -```csharp {4} -.ConfigurePlugins(a => -{ - a.UseDmtpRpc() - .SetSerializationSelector(new MemoryPackSerializationSelector()); -}) -``` + :::caution 注意 @@ -764,15 +448,7 @@ internal sealed class DefaultSerializationSelector : ISerializationSelector 最后在使用时,因为序列化类型是枚举值,所以使用时需要强制转换一下。 -```csharp {4} -var invokeOption = new DmtpInvokeOption()//调用配置 -{ - FeedbackType = FeedbackType.WaitInvoke,//调用反馈类型 - SerializationType = (SerializationType)4,//序列化类型 - Timeout = 5000,//调用超时设置 - Token = tokenSource.Token//配置可取消令箭 -}; -``` + [序列化选择器Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Dmtp/SerializationSelectorConsoleApp) @@ -795,22 +471,7 @@ CancellationToken是取消令箭源,可用于取消Rpc的调用。 ::: -```csharp showLineNumbers -var client = GetTcpDmtpClient(); - -//设置调用配置 -var tokenSource = new CancellationTokenSource();//可取消令箭源,可用于取消Rpc的调用 -var invokeOption = new DmtpInvokeOption()//调用配置 -{ - FeedbackType = FeedbackType.WaitInvoke,//调用反馈类型 - SerializationType = SerializationType.FastBinary,//序列化类型 - Timeout = 5000,//调用超时设置 - Token = tokenSource.Token//配置可取消令箭 -}; - -var sum = client.GetDmtpRpcActor().InvokeT("Add", invokeOption, 10, 20); -client.Logger.Info($"调用Add方法成功,结果:{sum}"); -``` + ### 5.5 Metadata 元数据 @@ -820,27 +481,11 @@ Metadata是字符串键值对,其作用类似http的headers,用于传递一 在请求时可以通过DmtpInvokeOption进行传参。 -```csharp {6} showLineNumbers -var invokeOption = new DmtpInvokeOption()//调用配置 -{ - FeedbackType = FeedbackType.WaitInvoke,//调用反馈类型 - SerializationType = SerializationType.FastBinary,//序列化类型 - Timeout = 5000,//调用超时设置 - Metadata=new Metadata(){{"a","a"}} -}; - -var metadata = client.GetDmtpRpcActor().InvokeT("CallContextMetadata", invokeOption); -``` + 在接收可以通过IDmtpCallContext(调用上下文)获取到Metadata。 -```csharp {4} showLineNumbers -[DmtpRpc(MethodInvoke = true)] -public Metadata CallContextMetadata(IDmtpRpcCallContext callContext) -{ - return callContext.Metadata; -} -``` + ## 六、Rpc大数据传输 @@ -857,11 +502,7 @@ public Metadata CallContextMetadata(IDmtpRpcCallContext callContext) 该方法简单粗暴,能够解决一定程度的大数据问题,但并不建议这么做。 - -```csharp showLineNumbers -TouchSocketConfig config = new TouchSocketConfig()//配置 - .SetMaxPackageSize(1024 * 1024 * 10) -``` + :::caution 注意 @@ -880,57 +521,11 @@ TouchSocketConfig config = new TouchSocketConfig()//配置 【Service端】 -```csharp showLineNumbers -/// -/// 测试客户端请求,服务器响应大量流数据 -/// -/// -/// -[Description("测试客户端请求,服务器响应大量流数据")] -[DmtpRpc] -public async Task RpcPullChannel(ICallContext callContext, int channelID) -{ - var size = 0; - var package = 1024 * 64; - if (callContext.Caller is TcpDmtpSessionClient SessionClient) - { - if (SessionClient.TrySubscribeChannel(channelID, out var channel)) - { - for (var i = 0; i < 10; i++) - { - size += package; - await channel.WriteAsync(new byte[package]); - } - await channel.CompleteAsync();//必须调用指令函数,如Complete,Cancel,Dispose - } - } - return size; -} -``` + 【Client端】 -```csharp showLineNumbers -using var client = GetTcpDmtpClient(); -ChannelStatus status = ChannelStatus.Default; -int size = 0; -var channel = await client.CreateChannelAsync(new Metadata() { {"rpc","rpc" } });//创建通道 -Task task = Task.Run(() =>//这里必须用异步 -{ - using (channel) - { - foreach (var currentByteBlock in channel) - { - var data = currentByteBlock.Span; - size += currentByteBlock.Length;//此处可以处理传递来的流数据 - } - status = channel.Status;//最后状态 - } -}); -int result =await client.GetDmtpRpcActor().RpcPullChannelAsync(channel.Id);//RpcPullChannel是代理方法,此处会阻塞至服务器全部发送完成。 -await task;//等待异步接收完成 -Console.WriteLine($"状态:{status},size={size}"); -``` + @@ -938,56 +533,11 @@ Console.WriteLine($"状态:{status},size={size}"); 【Service端】 -```csharp showLineNumbers -/// -/// "测试推送" -/// -/// -/// -[Description("测试客户端推送流数据")] -[DmtpRpc] -public async Task RpcPushChannel(ICallContext callContext, int channelID) -{ - int size = 0; - - if (callContext.Caller is TcpDmtpSessionClient SessionClient) - { - if (SessionClient.TrySubscribeChannel(channelID, out var channel)) - { - await foreach (var currentByteBlock in channel) - { - var data = currentByteBlock.Span; - size += currentByteBlock.Length;//此处处理流数据 - } - } - } - return size; -} -``` + 【Client端】 -```csharp showLineNumbers -using var client = GetTcpDmtpClient(); -ChannelStatus status = ChannelStatus.Default; -int size = 0; -int package = 1024; -var channel = await client.CreateChannelAsync();//创建通道 -Task task = Task.Run(async () =>//这里必须用异步 -{ - for (int i = 0; i < 1024; i++) - { - size += package; - await channel.WriteAsync(new byte[package]); - } - await channel.CompleteAsync();//必须调用指令函数,如Complete,Cancel,Dispose - - status = channel.Status; -}); -int result = await client.GetDmtpRpcActor().RpcPushChannelAsync(channel.Id);//RpcPushChannel是代理方法,此处会阻塞至服务器全部完成。 -await task;//等待异步接收完成 -Console.WriteLine($"状态:{status},result={result}"); -``` + ## 七、限制代理接口 @@ -1003,48 +553,16 @@ Console.WriteLine($"状态:{status},result={result}"); 然后新建类,命名为`MyDmtpRpcActor`,继承`DmtpRpcActor`,然后分别实现`IRpcClient1`与`IRpcClient2`两个接口。 -```csharp showLineNumbers -interface IRpcClient1:IDmtpRpcActor -{ - -} - -interface IRpcClient2 : IDmtpRpcActor -{ - -} - -class MyDmtpRpcActor : DmtpRpcActor, IRpcClient1, IRpcClient2 -{ - public MyDmtpRpcActor(IDmtpActor smtpActor) : base(smtpActor) - { - } -} -``` + 然后在`UseDmtpRpc`时,设置`SetCreateDmtpRpcActor`,这样获得的实际实例则会是`MyDmtpRpcActor`类型。 -```csharp {6} -var client = new TcpDmtpClient(); -await client.SetupAsync(new TouchSocketConfig() - .ConfigurePlugins(a => - { - a.UseDmtpRpc() - .SetCreateDmtpRpcActor((actor)=>new MyDmtpRpcActor(actor)); - }) - .SetRemoteIPHost("127.0.0.1:7789") - .SetDmtpOption(new DmtpOption() - { - VerifyToken = "Rpc"//连接验证口令。 - })); -await client.ConnectAsync(); -``` + 最后在获得RpcActor时,就可以按接口获取。然后配合服务器代码接口约束,就可以实现我们所期望的功能。 -```csharp showLineNumbers -IRpcClient1 rpcClient1= client.GetDmtpRpcActor(); -IRpcClient2 rpcClient2= client.GetDmtpRpcActor(); -``` + -[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Dmtp/DmtpRpcServerConsoleApp) +## 八、示例Demo + + \ No newline at end of file diff --git a/handbook/docs/dmtpservice.mdx b/handbook/docs/dmtpservice.mdx index 10cbf337a..513204c34 100644 --- a/handbook/docs/dmtpservice.mdx +++ b/handbook/docs/dmtpservice.mdx @@ -1,52 +1,114 @@ --- id: dmtpservice -title: 创建Dmtp服务器 +title: 创建DmtpService --- import Tag from "@site/src/components/Tag.js"; import Pro from "@site/src/components/Pro.js"; import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import { TouchSocketDmtpDefinition } from "@site/src/components/Definition.js"; import { TouchSocketProDmtpDefinition } from "@site/src/components/Definition.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; +import CardLink from "@site/src/components/CardLink.js"; -### 定义 - + ## 一、说明 -Dmtp的服务器有多种形式的host,每种服务器的创建都大同小异,且功能基本一致。 +`DmtpService`是基于`Dmtp`协议的服务器基类,它不参与实际的数据交互,只是配置、激活、管理、注销、重建`SessionClient`类实例。而`SessionClient`是当`DmtpClient`(客户端)成功连接服务器以后,由服务器新建的一个实例类,后续的所有通信,也都是通过该实例完成的。 + +`Dmtp`的服务器有多种形式的host,每种服务器的创建都大同小异,且功能基本一致。基于不同的底层协议(TCP、UDP、HTTP、WebSocket、NamedPipe),提供了统一的`Dmtp`协议接口。 -## 二、服务器架构 +## 二、特点 -Dmtp服务器的架构与其所属的基础协议架构一致,例如,在基于tcp协议时,其架构就和tcp服务器一致。在收到**新客户端连接**时,会创建一个**TcpDmtpSessionClient**的类实例,与**客户端TcpDmtpClient**一一对应,后续的数据通信均由此实例负责。 +- 简单易用。 +- 支持多种底层协议(TCP、UDP、HTTP、WebSocket、NamedPipe)。 +- 统一的Dmtp协议接口,无论底层协议如何,上层API保持一致。 +- 多线程。 +- 内存池支持 +- 高性能数据传输和RPC调用。 +- **多地址监听**(TCP协议时可以一次性监听多个IP及端口) +- 适配器预处理,一键式解决**分包**、**粘包**等问题。 +- 超简单的同步发送、异步发送、接收等操作。 +- 基于委托、[插件](./pluginsmanager.mdx)驱动,让每一步都能执行AOP。 +- 支持文件传输、RPC调用、远程访问等高级功能。 + +### 2.1 性能测试 + +Dmtp基于底层协议的性能表现,当使用TCP作为底层协议时,性能与`TcpService`基本一致。同时,Dmtp还提供了更多高级功能如RPC、文件传输等,在保证高性能的同时提供了更丰富的功能。 + +## 三、产品应用场景 + +- 需要高级通信功能的场景:RPC调用、文件传输、远程访问等。 +- 跨协议统一接口需求:可以轻松在不同底层协议间切换而不改变上层代码。 +- 企业级应用:支持认证、路由、通道管理等企业级功能。 +- 微服务架构:提供高性能的服务间通信解决方案。 + +## 四、服务器架构 + +### 4.1 连接架构 + +Dmtp服务器的架构与其所属的基础协议架构一致,例如,在基于TCP协议时,其架构就和TCP服务器一致。服务器在收到**新客户端连接**时,会创建一个对应的`SessionClient`派生类实例(如`TcpDmtpSessionClient`),与客户端DmtpClient一一对应,后续的数据通信均由此实例负责。 + +`SessionClient`在`Service`里面以字典映射。`ID`为键,`SessionClient`本身为值。 + +```mermaid +flowchart TD; + DmtpService-->DmtpSessionClient-1; + DmtpService-->DmtpSessionClient-2; + DmtpService-->DmtpSessionClient-3; + DmtpSessionClient-1-->DmtpClient-1; + DmtpSessionClient-2-->DmtpClient-2; + DmtpSessionClient-3-->DmtpClient-3; + +``` + +### 4.2 Scoped 生命周期 + +`DmtpService`在[支持Scoped](ioc.mdx)的`IOC`容器中工作时,也是支持`Scoped`区域划分的。 + +一般情况下,`DmtpService`在`Setup`时,首先会创建一个`Scoped`区域,用于整个`DmtpService`的生命周期。在`DmtpService`释放(`Dispose`)时释放。 + +然后,当有新客户端连接后,会为每个`SessionClient`的派生类实例也创建一个`Scoped`区域,用于`SessionClient`的生命周期。当连接断开时,会释放此区域。 -## 三、可配置项 +## 五、可配置项 -
-可配置项 -
+### 5.1 Dmtp特有配置 #### SetDmtpOption 设置Dmtp相关配置。其中包含: -- VerifyToken:设置验证口令。 -- VerifyTimeout:验证连接超时时间。仅用于服务器。意为:当服务器收到基础链接,在指定的时间内如果没有收到握手信息,则直接视为无效链接,直接断开。 + -
-
+ -## 四、支持插件接口 +- **VerifyToken**:设置验证口令,作用类似账号密码。客户端连接时必须提供正确的Token才能建立连接。 +- **VerifyTimeout**:验证连接超时时间。仅用于服务器。意为:当服务器收到基础链接,在指定的时间内如果没有收到握手信息,则直接视为无效链接,直接断开。 + +### 5.2 底层协议配置 + +根据不同的底层协议,DmtpService还可以配置相应协议的特有选项: + +- **基于TCP时**:支持[TcpService可配置项](./tcpservice.mdx)的所有配置,如SSL、NoDelay、端口复用等。 +- **基于UDP时**:支持[UdpSession可配置项](./udpsession.mdx)的所有配置。 +- **基于HTTP时**:支持[HttpService可配置项](./httpservice.mdx)的所有配置。 +- **基于NamedPipe时**:支持[NamedPipeService可配置项](./namedpipeservice.mdx)的所有配置。 + +## 六、支持插件接口 声明自定义插件类,实现`IPlugin`接口,或者继承`PluginBase`,然后实现所需插件接口,即可实现事务的触发。 | 插件方法 | 功能 | | --- | --- | -| IDmtpHandshakingPlugin | 客户端在验证连接。默认情况下,框架会首先验证连接Token是否正确,如果不正确则直接拒绝。不会有任何投递。用户也可以使用Metadata进行动态验证。 | -| IDmtpHandshakedPlugin | 客户端完成握手连接验证 | +| IDmtpConnectingPlugin | 客户端在验证连接。默认情况下,框架会首先验证连接Token是否正确,如果不正确则直接拒绝。不会有任何投递。用户也可以使用Metadata进行动态验证。 | +| IDmtpConnectedPlugin | 客户端完成握手连接验证 | | IDmtpReceivedPlugin | 在收到Dmtp格式的数据包时触发 | | IDmtpRoutingPlugin | 当需要路由数据时触发,并且必须返回e.IsPermitOperation=true时,才允许路由 | | IDmtpCreatedChannelPlugin | 在收到创建通道的请求时候触发。 | @@ -54,55 +116,25 @@ Dmtp服务器的架构与其所属的基础协议架构一致,例如,在基 | IDmtpClosedPlugin | 在Dmtp连接断开时触发。 | -## 五、创建服务器 +## 七、创建服务器 +DmtpService的创建,主要是根据不同的底层协议,创建不同的DmtpService派生类实例。 -### 5.1 TcpDmtpService +### 7.1 TcpDmtpService `TcpDmtpService`是基于`Tcp`协议的Dmtp。在可配置的基础之上,还可以配置与[TcpService可配置项](./tcpservice.mdx)相关的配置。 -```csharp showLineNumbers -var service = new TcpDmtpService(); -var config = new TouchSocketConfig()//配置 - .SetListenIPHosts(7789) - .ConfigureContainer(a => - { - a.AddConsoleLogger(); - }) - .SetDmtpOption(new DmtpOption() - { - VerifyToken = "Dmtp"//设定连接口令,作用类似账号密码 - }); + -await service.SetupAsync(config); - -await service.StartAsync(); - -service.Logger.Info($"{service.GetType().Name}已启动"); -``` - -### 5.2 UdpDmtp +### 7.2 UdpDmtp `UdpDmtp`是基于`Udp`协议Dmtp。在可配置的基础之上,还可以配置与[UdpSession可配置项](./udpsession.mdx)相关的配置。 -```csharp showLineNumbers -var udpDmtp = new UdpDmtp(); - -var config = new TouchSocketConfig(); -config.SetBindIPHost(new IPHost(7789)) - .ConfigureContainer(a => - { - a.AddConsoleLogger(); - }); - -await udpDmtp.SetupAsync(config); - -await udpDmtp.StartAsync(); -``` + :::info 备注 @@ -110,33 +142,15 @@ await udpDmtp.StartAsync(); ::: -### 5.3 HttpDmtpService +### 7.3 HttpDmtpService `HttpDmtpService`是基于`Http`升级协议。在该解析器中,配置设置[HttpService](./httpservice.mdx)一致。 -```csharp showLineNumbers -var service = new HttpDmtpService(); -var config = new TouchSocketConfig()//配置 - .SetListenIPHosts(new IPHost[] { new IPHost(7789) }) - .ConfigureContainer(a => - { - a.AddConsoleLogger(); - }) - .SetDmtpOption(new DmtpOption() - { - VerifyToken = "Dmtp" - }); + -await service.SetupAsync(config); - -await service.StartAsync(); - -service.Logger.Info($"{service.GetType().Name}已启动"); -``` - -### 5.4 基于AspNetCore的Websocket协议 +### 7.4 基于AspNetCore的Websocket协议 具体步骤 @@ -147,37 +161,7 @@ service.Logger.Info($"{service.GetType().Name}已启动"); -在Services时,添加`AddWebSocketDmtpService`,并且配置相关项。 - -```csharp {3} showLineNumbers -//添加WebSocket协议的Dmtp -//客户端使用WebSocketDmtpClient -builder.Services.AddWebSocketDmtpService(config => -{ - config - .SetDmtpOption(new DmtpOption() - { - VerifyToken = "Dmtp" - }) - .ConfigurePlugins(a => - { - //添加插件 - a.Add(); - }); -}); -``` - -启用中间件 - -首先必须先启用`WebSocket`。其次使用`UseWebSocketDmtp`即可。 - -```csharp showLineNumbers -var app = builder.Build(); -app.UseWebSockets(); -app.UseWebSocketDmtp("/WebSocketDmtp");//WebSocketDmtp必须在UseWebSockets之后使用。 -``` - -### 5.5 基于AspNetCore的Http协议 +### 7.5 基于AspNetCore的Http协议 具体步骤 @@ -187,75 +171,27 @@ app.UseWebSocketDmtp("/WebSocketDmtp");//WebSocketDmtp必须在UseWebSockets之 -在Services时,添加`AddWebSocketDmtpService`,并且配置相关项。 - -```csharp showLineNumbers -//Pro功能 -//添加基于Http升级协议的Dmtp。 -//客户端使用HttpDmtpClient -builder.Services.AddHttpMiddlewareDmtpService(config => -{ - config.SetDmtpOption(new DmtpOption() - { - VerifyToken = "Dmtp" - }) - .ConfigurePlugins(a => - { - //添加插件 - a.Add(); - }); -}); -``` - -启用中间件 - -使用`UseHttpDmtp`即可。 - -```csharp showLineNumbers -var app = builder.Build(); -app.UseHttpDmtp(); //HttpDmtp可以单独直接使用。不需要其他。 -``` - :::tip 提示 在整个Apsnetcore的Host中,所有组件会共用一个容器。所以建议使用`ConfigureContainer`统一设置。 -```csharp showLineNumbers -builder.Services.ConfigureContainer(container => -{ - container.AddConsoleLogger(); - container.AddDmtpRouteService(); -}); -``` + ::: [基于Aspnetcore的Dmtp示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Dmtp/DmtpWebApplication) -### 5.6 基于NamedPipe协议 +### 7.6 基于NamedPipe协议 这是基于`NamedPipe`的Dmtp。在可配置的基础之上,还可以配置与[NamedPipeService可配置项](./namedpipeservice.mdx)相关的配置。 -```csharp showLineNumbers -var service = new NamedPipeDmtpService(); -var config = new TouchSocketConfig()//配置 - .SetPipeName("TouchSocketPipe")//设置管道名称 - .ConfigureContainer(a => - { - a.AddConsoleLogger(); - }) - .SetDmtpOption(new DmtpOption() - { - VerifyToken = "Dmtp"//设定连接口令,作用类似账号密码 - }); + -await service.SetupAsync(config); +[基于NamedPipe的Dmtp示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Dmtp/NamedPipeDmtpConsoleApp) -await service.StartAsync(); -service.Logger.Info($"{service.GetType().Name}已启动"); -``` +## 八、示例Demo -[基于NamedPipe的Dmtp示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Dmtp/NamedPipeDmtpConsoleApp) \ No newline at end of file + \ No newline at end of file diff --git a/handbook/docs/dmtptransferfile.mdx b/handbook/docs/dmtptransferfile.mdx index 733645059..ac023f634 100644 --- a/handbook/docs/dmtptransferfile.mdx +++ b/handbook/docs/dmtptransferfile.mdx @@ -8,8 +8,8 @@ import Pro from "@site/src/components/Pro.js"; import CardLink from "@site/src/components/CardLink.js"; import BilibiliCard from '@site/src/components/BilibiliCard.js'; import Definition from "@site/src/components/Definition.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; -### 定义 @@ -154,66 +154,11 @@ string类型的键值对,用于和接收方交互数据。 传输文件功能,是`DmtpFileTransferFeature`功能插件基于Dmtp协议提供的功能。所以,`Dmtp服务器`和`客户端`都必须配置添加该功能插件。即调用`UseDmtpFileTransfer`。 -```csharp {3} -config.ConfigurePlugins(a => -{ - a.UseDmtpFileTransfer()//必须添加文件传输插件 - //.SetRootPath("C:\\新建文件夹")//设置RootPath - .SetMaxSmallFileLength(1024 * 1024);//设置小文件的最大限制长度 - a.Add(); -}) -``` + 声明`MyPlugin`插件,是为了方便处理`OnDmtpFileTransferring`和`OnDmtpFileTransferred`函数。 -```csharp showLineNumbers -internal class MyPlugin : PluginBase, IDmtpFileTransferringPlugin, IDmtpFileTransferredPlugin -{ - private readonly ILog m_logger; - - public MyPlugin(ILog logger) - { - this.m_logger = logger; - } - - /// - /// 该方法,会在每个文件被请求(推送)结束时触发。传输不一定成功,具体信息需要从e.Result判断状态。 - /// 其次,该方法也不一定会被执行,例如:在传输过程中,直接断网,则该方法将不会执行。 - /// - /// - /// - /// - public async Task OnDmtpFileTransferred(IDmtpActorObject client, FileTransferredEventArgs e) - { - //传输结束,但是不一定成功,甚至该方法都不一定会被触发,具体信息需要从e.Result判断状态。 - this.m_logger.Info($"传输文件结束,请求类型={e.TransferType},文件名={e.ResourcePath},请求状态={e.Result}"); - await e.InvokeNext(); - } - - - /// - /// 该方法,会在每个文件被请求(推送)时第一时间触发。 - /// 当请求文件时,可以重新指定请求的文件路径,即对e.ResourcePath直接赋值。 - /// 当推送文件时,可以重新指定保存文件路径,即对e.SavePath直接赋值。 - /// - /// 注意:当文件夹不存在时,需要手动创建。 - /// - /// - /// - /// - public async Task OnDmtpFileTransferring(IDmtpActorObject client, FileTransferringEventArgs e) - { - foreach (var item in e.Metadata.Keys) - { - Console.WriteLine($"Key={item},Value={e.Metadata[item]}"); - } - e.IsPermitOperation = true;//每次传输都需要设置true,表示允许传输 - //有可能是上传,也有可能是下载 - this.m_logger.Info($"请求传输文件,请求类型={e.TransferType},请求文件名={e.ResourcePath}"); - await e.InvokeNext(); - } -} -``` + ### 6.1 客户端向服务器请求文件 @@ -221,81 +166,13 @@ internal class MyPlugin : PluginBase, IDmtpFileTransferringPlugin, IDmtpFileTran 【客户端代码】 -```csharp showLineNumbers -var filePath = "ClientPullFileFromService.Test"; -var saveFilePath = "SaveClientPullFileFromService.Test"; - -var metadata = new Metadata();//传递到服务器的元数据 -metadata.Add("1", "1"); -metadata.Add("2", "2"); - -var fileOperator = new FileOperator()//实例化本次传输的控制器,用于获取传输进度、速度、状态等。 -{ - SavePath = saveFilePath,//客户端本地保存路径 - ResourcePath = filePath,//请求文件的资源路径 - Metadata = metadata,//传递到服务器的元数据 - Timeout = TimeSpan.FromSeconds(60),//传输超时时长 - TryCount = 10,//当遇到失败时,尝试次数 - FileSectionSize = 1024 * 512//分包大小,当网络较差时,应该适当减小该值 -}; - -fileOperator.MaxSpeed=1024 * 1024;//设置最大限速为1Mb。 - -//此处的作用相当于Timer,定时每秒输出当前的传输进度和速度。 -var loopAction = LoopAction.CreateLoopAction(-1, 1000, (loop) => -{ - if (fileOperator.IsEnd) - { - loop.Dispose(); - } - client.Logger.Info($"进度:{fileOperator.Progress},速度:{fileOperator.Speed()}"); -}); - -loopAction.RunAsync(); - -//此方法会阻塞,直到传输结束,也可以使用PullFileAsync -IResult result =await client.GetDmtpFileTransferActor().PullFileAsync(fileOperator); -``` + ### 6.2 客户端向服务器推送文件 -```csharp showLineNumbers -var filePath = "ClientPushFileFromService.Test"; -var saveFilePath = "SaveClientPushFileFromService.Test"; - -var metadata = new Metadata();//传递到服务器的元数据 -metadata.Add("1", "1"); -metadata.Add("2", "2"); - -var fileOperator = new FileOperator()//实例化本次传输的控制器,用于获取传输进度、速度、状态等。 -{ - SavePath = saveFilePath,//服务器本地保存路径 - ResourcePath = filePath,//客户端本地即将上传文件的资源路径 - Metadata = metadata,//传递到服务器的元数据 - Timeout = TimeSpan.FromSeconds(60),//传输超时时长 - TryCount = 10,//当遇到失败时,尝试次数 - FileSectionSize = 1024 * 512//分包大小,当网络较差时,应该适当减小该值 -}; - -fileOperator.MaxSpeed=1024 * 1024;//设置最大限速为1Mb。 - -//此处的作用相当于Timer,定时每秒输出当前的传输进度和速度。 -var loopAction = LoopAction.CreateLoopAction(-1, 1000, (loop) => -{ - if (fileOperator.IsEnd) - { - loop.Dispose(); - } - client.Logger.Info($"进度:{fileOperator.Progress},速度:{fileOperator.Speed()}"); -}); - -loopAction.RunAsync(); - -//此方法会阻塞,直到传输结束,也可以使用PushFileAsync -IResult result =await client.GetDmtpFileTransferActor().PushFileAsync(fileOperator); -``` + ### 6.3 服务器向客户端请求文件 @@ -303,92 +180,13 @@ IResult result =await client.GetDmtpFileTransferActor().PushFileAsync(fileOperat 服务器主动向客户端请求文件,必须通过Id,找到其`SessionClient`的派生类。 -```csharp showLineNumbers -string targetId="targetId"; -if (!service.TryGetClient(targetId, out var SessionClient)) -{ - throw new Exception($"没有找到Id={targetId}的客户端"); -} - -var filePath = "ServicePullFileFromClient.Test"; -var saveFilePath = "SaveServicePullFileFromClient.Test"; - -var metadata = new Metadata();//传递到客户端的元数据 -metadata.Add("1", "1"); -metadata.Add("2", "2"); - -var fileOperator = new FileOperator()//实例化本次传输的控制器,用于获取传输进度、速度、状态等。 -{ - SavePath = saveFilePath,//服务器本地保存路径 - ResourcePath = filePath,//请求客户端文件的资源路径 - Metadata = metadata,//传递到客户端的元数据 - Timeout = TimeSpan.FromSeconds(60),//传输超时时长 - TryCount = 10,//当遇到失败时,尝试次数 - FileSectionSize = 1024 * 512//分包大小,当网络较差时,应该适当减小该值 -}; - -fileOperator.MaxSpeed=1024 * 1024;//设置最大限速为1Mb。 - -//此处的作用相当于Timer,定时每秒输出当前的传输进度和速度。 -var loopAction = LoopAction.CreateLoopAction(-1, 1000, (loop) => -{ - if (fileOperator.IsEnd) - { - loop.Dispose(); - } - SessionClient.Logger.Info($"进度:{fileOperator.Progress},速度:{fileOperator.Speed()}"); -}); - -loopAction.RunAsync(); - -//此方法会阻塞,直到传输结束,也可以使用PullFileAsync -IResult result =await SessionClient.GetDmtpFileTransferActor().PullFileAsync(fileOperator); -``` + ### 6.4 服务器向客户端推送文件 服务器主动向客户端推送文件,必须通过Id,找到其`SessionClient`的派生类。 -```csharp showLineNumbers -if (!service.TryGetClient(targetId, out var SessionClient)) -{ - throw new Exception($"没有找到Id={targetId}的客户端"); -} - -var filePath = "ServicePushFileFromClient.Test"; -var saveFilePath = "SaveServicePushFileFromClient.Test"; - -var metadata = new Metadata();//传递到客户端的元数据 -metadata.Add("1", "1"); -metadata.Add("2", "2"); - -var fileOperator = new FileOperator()//实例化本次传输的控制器,用于获取传输进度、速度、状态等。 -{ - SavePath = saveFilePath,//客户端本地保存路径 - ResourcePath = filePath,//服务器文件的资源路径 - Metadata = metadata,//传递到客户端的元数据 - Timeout = TimeSpan.FromSeconds(60),//传输超时时长 - TryCount = 10,//当遇到失败时,尝试次数 - FileSectionSize = 1024 * 512//分包大小,当网络较差时,应该适当减小该值 -}; - -fileOperator.MaxSpeed=1024 * 1024;//设置最大限速为1Mb。 - -//此处的作用相当于Timer,定时每秒输出当前的传输进度和速度。 -var loopAction = LoopAction.CreateLoopAction(-1, 1000, (loop) => -{ - if (fileOperator.IsEnd) - { - loop.Dispose(); - } - SessionClient.Logger.Info($"进度:{fileOperator.Progress},速度:{fileOperator.Speed()}"); -}); - -loopAction.RunAsync(); - -//此方法会阻塞,直到传输结束,也可以使用PushFileAsync -IResult result =await SessionClient.GetDmtpFileTransferActor().PushFileAsync(fileOperator); -``` + ### 6.5 客户端之间传输文件 @@ -400,25 +198,11 @@ IResult result =await SessionClient.GetDmtpFileTransferActor().PushFileAsync(fil 【添加路由策略】 -```csharp {3} -.ConfigureContainer(a => -{ - a.AddDmtpRouteService();//添加路由策略 -}) -``` + 【同意路由】 -```csharp {5} -internal class MyPlugin : PluginBase, IDmtpRoutingPlugin -{ - public async Task OnDmtpRouting(IDmtpActorObject client, PackageRouterEventArgs e) - { - e.IsPermitOperation = true;//允许路由 - await e.InvokeNext(); - } -} -``` + :::caution 注意 @@ -439,31 +223,7 @@ internal class MyPlugin : PluginBase, IDmtpRoutingPlugin -```csharp showLineNumbers -//关于断点续传 -//在执行完PullFile(fileOperator)或PushFile(fileOperator)时。只要返回的结果不是Success。 -//那么就意味着传输没有完成。 -//而续传的机制就是,在执行传输之前,如果fileOperator.ResourceInfo为空,则开始新的传输。如果不为空,则尝试续传。 -//而对于失败的传输,未完成的信息都在fileOperator.ResourceInfo中。 -//所以我们可以使用一个变量(或字典)来存fileOperator.ResourceInfo的值。 -//亦或者可以把ResourceInfo的值持久化。 -//然后在重新发起请求传输值前,先对fileOperator.ResourceInfo做有效赋值。即可尝试断点传输。 - -byte[] cacheBytes;//这就是持久化后的数据。你可以将此数据写入到文件或数据库。 -using (var byteBlock=new ByteBlock(1024*64)) -{ - fileOperator.ResourceInfo.Save(byteBlock); - - cacheBytes = byteBlock.ToArray(); -} - -//然后想要续传的时候。先把缓存数据转为FileResourceInfo。 -using (var byteBlock=new ByteBlock(cacheBytes)) -{ - var resourceInfo = new FileResourceInfo(byteBlock); - //然后把resourceInfo赋值给新建的FileOperator的ResourceInfo属性。 -} -``` + :::caution 注意 @@ -492,40 +252,13 @@ using (var byteBlock=new ByteBlock(cacheBytes)) 使用方法非常简单,因为`FileOperator`中允许传入一个可取消令箭。所以只需要通过`CancellationTokenSource`,将`Token`传入,然后直接使用`CancellationTokenSource`取消即可。 -```csharp {1,7,13} showLineNumbers -var tokenSource = new CancellationTokenSource(); - -_=Task.Run(async () => -{ - //此处模拟五秒后自动取消传输 - await Task.Delay(5000); - tokenSource.Cancel(); -}); - -var fileOperator = new FileOperator()//实例化本次传输的控制器,用于获取传输进度、速度、状态等。 -{ - ... - Token = tokenSource.Token -}; - -IResult result =await SessionClient.GetDmtpFileTransferActor().PullFileAsync(fileOperator); -``` + ### 8.2 使用CancellationFileOperator `CancellationFileOperator`是继承`FileOperator`并且已经实现取消传输的操作器。原理和`Token`一致,这里只是做了一次封装。 -```csharp {1,7} showLineNumbers -var fileOperator = new CancellationFileOperator()//实例化本次传输的控制器,用于获取传输进度、速度、状态等。 -{ - ... -}; - -//模拟五秒后自动取消传输,或者直接Cancel -fileOperator.CancelAfter(TimeSpan.FromSeconds(5)); - -IResult result =await SessionClient.GetDmtpFileTransferActor().PullFileAsync(fileOperator); -``` + :::tip 提示 @@ -549,19 +282,7 @@ IResult result =await SessionClient.GetDmtpFileTransferActor().PullFileAsync(fil 1. 直接调用PullSmallFile或者PullSmallFileAsync,获取到实际的文件数据。 2. 通过Save方法,将数据写入文件。也可以自行保存。 -```csharp showLineNumbers -var filePath = "PullSmallFileFromService.Test"; -var saveFilePath = "SavePullSmallFileFromService.Test"; - -var metadata = new Metadata();//传递到服务器的元数据 -metadata.Add("1", "1"); -metadata.Add("2", "2"); - -//此方法会阻塞,直到传输结束,也可以使用PullSmallFileAsync -PullSmallFileResult result =await client.GetDmtpFileTransferActor().PullSmallFileAsync(filePath, metadata); -byte[] data = result.Value;//此处即是下载的小文件的实际数据 -result.Save(saveFilePath,overwrite:true);//将数据保存到指定路径。 -``` + ### 9.2 推送小文件 @@ -570,28 +291,7 @@ result.Save(saveFilePath,overwrite:true);//将数据保存到指定路径。 1. 直接调用PushSmallFile或者PushSmallFileAsync。 2. 返回值即表示是否成功。 -```csharp showLineNumbers -var filePath = "PushSmallFileFromService.Test"; -var saveFilePath = "SavePushSmallFileFromService.Test"; -if (!File.Exists(filePath))//创建测试文件 -{ - using (var stream = File.OpenWrite(filePath)) - { - stream.SetLength(1024); - } -} - -var metadata = new Metadata();//传递到服务器的元数据 -metadata.Add("1", "1"); -metadata.Add("2", "2"); - -//此方法会阻塞,直到传输结束,也可以使用PullSmallFileAsync -var result =await client.GetDmtpFileTransferActor().PushSmallFileAsync(saveFilePath,new FileInfo(filePath), metadata); -if (result.IsSuccess()) -{ - //成功 -} -``` + :::tip 提示 @@ -619,118 +319,15 @@ ClientFactory的通信模型使用的是一个主通信端+多个传输客户端 对于客户端的配置,请详细参考[创建DmtpClient工厂](./dmtpclient.mdx) -```csharp showLineNumbers -var clientFactory = new TcpDmtpClientFactory() -{ - MinCount=5,//最小数量,在主连接器成功建立以后,会检测可用连接是否大于该值,否的话会自动建立。 - MaxCount = 10,//最大数量,当超过该数量的连接后,会等待指定时间,或者永久等待。 - ConnectTimeout = TimeSpan.FromSeconds(10),//连接超时时间 - GetConfig = () => - { - return new TouchSocketConfig() - .SetRemoteIPHost("127.0.0.1:7789") - .ConfigurePlugins(a => - { - a.UseDmtpFileTransfer() - .SetMaxSmallFileLength(1024 * 1024); - - a.AddDmtpFileTransferringPlugin(async (c, e) => - { - e.IsPermitOperation = true; //允许传输操作 - await e.InvokeNext(); - }); - }) - .SetDmtpOption(new DmtpOption() - { - VerifyToken = "Dmtp", - }); - } -}; -``` + ### 10.2 多线程请求文件 -```csharp showLineNumbers -using var clientFactory = CreateClientFactory(); -ConsoleLogger.Default.Info("开始从服务器下载文件"); -var filePath = "MultithreadingClientPullFileFromService.Test"; -var saveFilePath = "SaveMultithreadingClientPullFileFromService.Test"; - -var metadata = new Metadata();//传递到服务器的元数据 -metadata.Add("1", "1"); -metadata.Add("2", "2"); - -var fileOperator = new MultithreadingFileOperator//实例化本次传输的控制器,用于获取传输进度、速度、 -{ - SavePath = saveFilePath,//客户端本地保存路径 - ResourcePath = filePath,//请求文件的资源路径 - Metadata = metadata,//传递到服务器的元数据 - Timeout = TimeSpan.FromSeconds(60),//传输超时时长 - TryCount = 10,//当遇到失败时,尝试次数 - FileSectionSize = 1024 * 512,//分包大小,当网络较差时,应该适当减小该值 - MultithreadingCount = 10//多线程数量 -}; - -fileOperator.SetMaxSpeed(1024*1024);//设置最大限速为1Mb。 - -//此处的作用相当于Timer,定时每秒输出当前的传输进度和速度。 -var loopAction = LoopAction.CreateLoopAction(-1, 1000, (loop) => -{ - if (fileOperator.IsEnd) - { - loop.Dispose(); - } - ConsoleLogger.Default.Info($"进度:{fileOperator.Progress},速度:{fileOperator.Speed()}"); -}); - -loopAction.RunAsync(); - -//此方法会阻塞,直到传输结束 -IResult result =await clientFactory.PullFileAsync(fileOperator); - -ConsoleLogger.Default.Info($"从服务器下载文件结束,{result}"); -``` + ### 10.3 多线程推送文件 -```csharp showLineNumbers -using var clientFactory = CreateClientFactory(); -ConsoleLogger.Default.Info("开始向服务器推送文件"); -var filePath = "MultithreadingClientPushFileFromService.Test"; -var saveFilePath = "SaveMultithreadingClientPushFileFromService.Test"; - -var metadata = new Metadata();//传递到服务器的元数据 -metadata.Add("1", "1"); -metadata.Add("2", "2"); - -var fileOperator = new MultithreadingFileOperator//实例化本次传输的控制器,用于获取传输进度、速度、状态等 -{ - SavePath = saveFilePath,//客户端本地保存路径 - ResourcePath = filePath,//请求文件的资源路径 - Metadata = metadata,//传递到服务器的元数据 - Timeout = TimeSpan.FromSeconds(60),//传输超时时长 - TryCount = 10,//当遇到失败时,尝试次数 - FileSectionSize = 1024 * 512,//分包大小,当网络较差时,应该适当减小该值 - MultithreadingCount = 10//多线程数量 -}; - -fileOperator.SetMaxSpeed(1024*1024);//设置最大限速为1Mb。 - -//此处的作用相当于Timer,定时每秒输出当前的传输进度和速度。 -var loopAction = LoopAction.CreateLoopAction(-1, 1000, (loop) => -{ - if (fileOperator.IsEnd) - { - loop.Dispose(); - } - ConsoleLogger.Default.Info($"进度:{fileOperator.Progress},速度:{fileOperator.Speed()}"); -}); - -loopAction.RunAsync(); - -//此方法会阻塞,直到传输结束 -IResult result =await clientFactory.PushFileAsync(fileOperator); -``` + ### 10.4 客户端之间传输文件 @@ -745,16 +342,7 @@ IResult result =await clientFactory.PushFileAsync(fileOperator); -```csharp showLineNumbers -clientFactory.SetFindTransferIds((client, targetId) => -{ - //此处的操作不唯一,可能需要rpc实现。 - //其目的比较简单,就是获取到targetId对应的主客户端的所有传输客户端的Id集合。 - //这样就实现了多个客户端向多个客户端传输文件的目的。 - - return new string[] { targetId };//此处为模拟结果。 -}); -``` + :::info 信息 @@ -764,4 +352,4 @@ clientFactory.SetFindTransferIds((client, targetId) => ## 十一、示例代码 - \ No newline at end of file + \ No newline at end of file diff --git a/handbook/docs/donate.mdx b/handbook/docs/donate.mdx index 70a9fcb16..50b23b137 100644 --- a/handbook/docs/donate.mdx +++ b/handbook/docs/donate.mdx @@ -165,3 +165,10 @@ title: 支持作者 |142|阳**|50¥|2025年3月21日| |143|**伟|200¥|2025年4月18日| |144|A***|88¥|2025年6月2日| +|145|匿名|66¥|2025年8月12日| +|146|*楠|10¥|2025年8月15日| +|147|i**|888¥|2025年9月9日| +|148|C**|66¥|2025年10月21日| +|149|那**|8.8¥|2025年10月24日| +|150|偷**|50¥|2025年11月20日| + diff --git a/handbook/docs/dynamicmethod.mdx b/handbook/docs/dynamicmethod.mdx index 6dbfde64e..0baed0ea2 100644 --- a/handbook/docs/dynamicmethod.mdx +++ b/handbook/docs/dynamicmethod.mdx @@ -5,481 +5,176 @@ title: 动态方法调用(DynamicMethod) import CardLink from "@site/src/components/CardLink.js"; import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; + -### 定义 -## 一、核心概念 +## 一、说明 -动态方法调用模块提供高效、灵活的方法反射调用方案,支持多种底层实现方式,显著提升反射调用性能。特别针对AOT(Ahead-of-Time)编译环境优化,同时保持传统反射场景的高性能表现。 +动态方法调用模块提供了一种高效、灵活的方法调用方案,可以在运行时动态调用被`[DynamicMethod]`标记的方法。该模块会优先使用源生成器生成高性能调用代码,在源生成不可用时自动降级为表达式树或反射方式。 -## 二、核心特性 +## 二、特性 -- **多引擎支持**: - - IL代码生成(DynamicBuilderType.IL) - - 表达式树(DynamicBuilderType.Expression) - - 传统反射(DynamicBuilderType.Reflect) - - 源生成(DynamicBuilderType.SourceGenerator) -- **性能卓越**:相比原生反射调用,性能提升10倍性能 -- **AOT友好**:源生成模式实现零反射,完美支持iOS/Android等AOT环境 -- **智能异步支持**:自动识别Task/ValueTask返回值类型 -- **全参数支持**:支持ref/out参数、泛型参数、参数默认值 -- **灵活扩展**:支持自定义动态方法特性标记 +- **源生成优先**:编译时自动生成方法调用代码,零反射开销 +- **AOT友好**:完美支持AOT环境(如iOS/Android),无运行时代码生成 +- **自动降级**:源生成不可用时自动使用表达式树,最后降级为反射 +- **异步支持**:自动识别Task/Task\返回值,支持异步调用 +- **完整参数支持**:支持out参数、ref参数 +- **扩展性强**:支持自定义特性继承`DynamicMethodAttribute` ## 三、快速开始 -### 3.1 基本声明 +### 3.1 标记方法 + +使用`[DynamicMethod]`特性标记需要动态调用的方法: + + + +### 3.2 调用方法 + +创建`Method`实例并调用: + + + +## 四、核心API + +### 4.1 Method类 + +`Method`类是动态方法调用的核心类,提供以下构造函数: ```csharp showLineNumbers -public class MyClass -{ - [DynamicMethod] - public void SimpleMethod() - { - Console.WriteLine("Method executed"); - } -} +// 从MethodInfo创建 +public Method(MethodInfo method) + +// 从类型和方法名创建 +public Method(Type targetType, string methodName) + +// 使用自定义IDynamicMethodInfo创建 +public Method(MethodInfo method, IDynamicMethodInfo dynamicMethodInfo) ``` -### 3.2 基础调用 - -```csharp showLineNumbers -// 创建方法包装器 -var method = new Method(typeof(MyClass), nameof(MyClass.SimpleMethod)); - -// 实例化对象 -var instance = new MyClass(); - -// 执行方法调用 -method.Invoke(instance); -``` - -## 四、核心功能详解 - -### 4.1 构建器类型选择 - -```csharp showLineNumbers -// 使用IL生成 -var ilMethod = new Method( - typeof(MyClass), - nameof(MyClass.SimpleMethod), - DynamicBuilderType.IL -); - -// 使用表达式树 -var exprMethod = new Method( - typeof(MyClass), - nameof(MyClass.SimpleMethod), - DynamicBuilderType.Expression -); - -// 使用源生成(性能最强,AOT环境推荐) -var sourceGenMethod = new Method( - typeof(MyClass), - nameof(MyClass.SimpleMethod), - DynamicBuilderType.SourceGenerator -); -``` - -:::tip 构建器选择建议 - -- **任何时候**:使用SourceGenerator,无论性能还是AOT环境,都是首选。 - +:::info 自动选择策略 +使用第一、二种构造函数时,`Method`会按以下优先级自动选择实现方式: +1. 源生成器(SourceGenerator) - 优先使用 +2. 表达式树(Expression) - 源生成不可用时 +3. 反射(Reflect) - 表达式树失败时的保底方案 ::: -### 4.2 异步方法支持 +### 4.2 Method属性 ```csharp showLineNumbers -public class MyClass -{ - [DynamicMethod] - public async Task GetDataAsync() - { - await Task.Delay(100); - return 42; - } -} +// 方法元数据 +public MethodInfo Info { get; } -// 异步调用 -var method = new Method(typeof(MyClass), nameof(MyClass.GetDataAsync)); -var result = await method.InvokeAsync(instance); -Console.WriteLine($"Result: {result}"); // 输出 42 +// 方法名称 +public string Name { get; } + +// 返回值类型枚举 +public MethodReturnKind ReturnKind { get; } + +// 是否有返回值(void和Task视为无返回值) +public bool HasReturn { get; } + +// 真实返回类型(Task时为T,void/Task时为null) +public Type RealReturnType { get; } + +// 是否为可等待方法(返回Task或Task) +public bool IsAwaitable { get; } ``` -### 4.3 复杂参数处理 +### 4.3 MethodReturnKind枚举 ```csharp showLineNumbers -public class MyClass +public enum MethodReturnKind { - [DynamicMethod] - public void ProcessData( - string input, - ref int counter, - out string result) - { - counter++; - result = $"{input}_{counter}"; - } -} - -// 调用示例 -var parameters = new object[] { "data", 0, null }; -method.Invoke(instance, parameters); - -Console.WriteLine($"Result: {parameters[2]}"); // 输出 "data_1" -``` - -## 五、性能优化 - -### 5.1 性能对比测试 - -:::tip 10000次调用性能分析 - -**核心结论** - -1. **同步方法(Add)性能** - - **直接调用** 最快(~2.1µs),无内存分配。 - - **源生成器(SourceGeneratorRun)** 在 .NET 8+ 接近直接调用性能(~3.1µs),较 IL/表达式树快 6-7 倍,较反射快 28-30 倍。 - - **反射(MethodInfo)** 性能最差(.NET 6: 400µs → .NET 8: 88µs),.NET 8+ 反射性能显著优化。 - -2. **异步方法(AddAsync)性能** - - **直接调用** 仍最优(.NET 8: 38µs,.NET 9: 39µs)。 - - **源生成器** 表现接近 IL/表达式树(.NET 8: 91µs vs 103µs),较反射快 2-3 倍(.NET 8: 91µs vs 184µs)。 - - **.NET Framework 4.8.1** 异步性能最差(反射达 1,305µs),内存分配显著高于其他版本。 - -3. **内存分配** - - 同步方法:源生成器与 IL/表达式树分配 80B,反射额外多 1B(.NET 6)。 - - 异步方法:所有动态方法分配 ~720KB(.NET Core)或 ~802KB(Framework),反射在 Framework 分配超 1MB。 - -**附:关键数据对比(.NET 8)** - -| 方法 | 同步耗时 | 异步耗时 | 内存分配 | -|----------------------|---------|---------|----------| -| DirectRun | 2.1µs | 38µs | 56B | -| SourceGeneratorRun | 3.1µs | 91µs | 80B | -| MethodInfoRun | 88µs | 184µs | 80B | - -::: - -```csharp {7,12,18,23,29,34,40,45} showLineNumbers -| Method | Job | Runtime | Mean | Error | StdDev | Gen0 | Gen1 | Allocated | -|----------------------------- |--------------------- |--------------------- |-------------:|----------:|----------:|---------:|-------:|----------:| -| DirectRun_Add | .NET 6.0 | .NET 6.0 | 2.132 us | 0.0031 us | 0.0027 us | - | - | 56 B | -| MethodILRun_Add | .NET 6.0 | .NET 6.0 | 23.887 us | 0.0795 us | 0.0744 us | - | - | 80 B | -| MethodExpressionRun_Add | .NET 6.0 | .NET 6.0 | 20.108 us | 0.1542 us | 0.1443 us | - | - | 80 B | -| MethodInfoRun_Add | .NET 6.0 | .NET 6.0 | 399.999 us | 0.3781 us | 0.3537 us | - | - | 81 B | -| SourceGeneratorRun_Add | .NET 6.0 | .NET 6.0 | 17.648 us | 0.0945 us | 0.0884 us | - | - | 80 B | -| DirectRun_AddAsync | .NET 6.0 | .NET 6.0 | 46.567 us | 0.4623 us | 0.4098 us | 45.8984 | - | 720080 B | -| MethodILRun_AddAsync | .NET 6.0 | .NET 6.0 | 141.010 us | 0.8145 us | 0.7619 us | 45.8984 | - | 720080 B | -| MethodExpressionRun_AddAsync | .NET 6.0 | .NET 6.0 | 142.758 us | 0.8197 us | 0.7266 us | 45.8984 | - | 720080 B | -| MethodInfoRun_AddAsync | .NET 6.0 | .NET 6.0 | 563.645 us | 1.8681 us | 1.6560 us | 45.8984 | - | 720081 B | -| SourceGeneratorRun_AddAsync | .NET 6.0 | .NET 6.0 | 141.399 us | 0.8781 us | 0.8214 us | 45.8984 | - | 720080 B | -| StaticMethodRun | .NET 6.0 | .NET 6.0 | 2.130 us | 0.0018 us | 0.0017 us | - | - | 56 B | -| DirectRun_Add | .NET 8.0 | .NET 8.0 | 2.140 us | 0.0116 us | 0.0109 us | - | - | 56 B | -| MethodILRun_Add | .NET 8.0 | .NET 8.0 | 19.332 us | 0.0491 us | 0.0459 us | - | - | 80 B | -| MethodExpressionRun_Add | .NET 8.0 | .NET 8.0 | 12.889 us | 0.1189 us | 0.1054 us | - | - | 80 B | -| MethodInfoRun_Add | .NET 8.0 | .NET 8.0 | 88.879 us | 0.1784 us | 0.1581 us | - | - | 80 B | -| SourceGeneratorRun_Add | .NET 8.0 | .NET 8.0 | 3.160 us | 0.0073 us | 0.0068 us | 0.0038 | - | 80 B | -| DirectRun_AddAsync | .NET 8.0 | .NET 8.0 | 38.401 us | 0.1199 us | 0.1063 us | 45.8984 | - | 720080 B | -| MethodILRun_AddAsync | .NET 8.0 | .NET 8.0 | 103.627 us | 1.2387 us | 1.1587 us | 45.8984 | - | 720080 B | -| MethodExpressionRun_AddAsync | .NET 8.0 | .NET 8.0 | 101.368 us | 0.7190 us | 0.6725 us | 45.8984 | - | 720080 B | -| MethodInfoRun_AddAsync | .NET 8.0 | .NET 8.0 | 183.882 us | 0.7238 us | 0.6044 us | 45.8984 | - | 720080 B | -| SourceGeneratorRun_AddAsync | .NET 8.0 | .NET 8.0 | 91.024 us | 0.6482 us | 0.6063 us | 45.8984 | - | 720080 B | -| StaticMethodRun | .NET 8.0 | .NET 8.0 | 2.150 us | 0.0073 us | 0.0068 us | - | - | 56 B | -| DirectRun_Add | .NET 9.0 | .NET 9.0 | 2.119 us | 0.0024 us | 0.0022 us | - | - | 56 B | -| MethodILRun_Add | .NET 9.0 | .NET 9.0 | 19.348 us | 0.0133 us | 0.0124 us | - | - | 80 B | -| MethodExpressionRun_Add | .NET 9.0 | .NET 9.0 | 11.753 us | 0.0625 us | 0.0585 us | - | - | 80 B | -| MethodInfoRun_Add | .NET 9.0 | .NET 9.0 | 91.756 us | 0.2344 us | 0.2193 us | - | - | 80 B | -| SourceGeneratorRun_Add | .NET 9.0 | .NET 9.0 | 3.145 us | 0.0056 us | 0.0046 us | 0.0038 | - | 80 B | -| DirectRun_AddAsync | .NET 9.0 | .NET 9.0 | 39.219 us | 0.1669 us | 0.1561 us | 45.8984 | - | 720080 B | -| MethodILRun_AddAsync | .NET 9.0 | .NET 9.0 | 107.920 us | 0.4728 us | 0.4191 us | 45.8984 | - | 720080 B | -| MethodExpressionRun_AddAsync | .NET 9.0 | .NET 9.0 | 106.539 us | 0.3894 us | 0.3642 us | 45.8984 | - | 720080 B | -| MethodInfoRun_AddAsync | .NET 9.0 | .NET 9.0 | 189.294 us | 0.7231 us | 0.6764 us | 45.8984 | - | 720080 B | -| SourceGeneratorRun_AddAsync | .NET 9.0 | .NET 9.0 | 99.195 us | 0.3677 us | 0.3440 us | 45.8984 | - | 720080 B | -| StaticMethodRun | .NET 9.0 | .NET 9.0 | 2.125 us | 0.0036 us | 0.0034 us | - | - | 56 B | -| DirectRun_Add | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 2.134 us | 0.0047 us | 0.0042 us | 0.0076 | - | 56 B | -| MethodILRun_Add | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 30.162 us | 0.0629 us | 0.0588 us | - | - | 80 B | -| MethodExpressionRun_Add | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 56.063 us | 0.0559 us | 0.0467 us | - | - | 80 B | -| MethodInfoRun_Add | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 770.931 us | 5.9446 us | 5.5606 us | 50.7813 | - | 321027 B | -| SourceGeneratorRun_Add | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 17.521 us | 0.0350 us | 0.0310 us | - | - | 80 B | -| DirectRun_AddAsync | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 41.866 us | 0.1300 us | 0.1216 us | 127.5024 | 0.0610 | 802442 B | -| MethodILRun_AddAsync | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 523.507 us | 2.1568 us | 1.9120 us | 126.9531 | - | 802445 B | -| MethodExpressionRun_AddAsync | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 554.333 us | 2.3657 us | 2.2129 us | 126.9531 | - | 802445 B | -| MethodInfoRun_AddAsync | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 1,304.826 us | 1.4392 us | 1.2758 us | 177.7344 | - | 1123389 B | -| SourceGeneratorRun_AddAsync | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 508.533 us | 0.8991 us | 0.8410 us | 126.9531 | - | 802445 B | -| StaticMethodRun | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 3.267 us | 0.0070 us | 0.0062 us | 0.0114 | - | 80 B | -``` - -
-基准测试代码 -
- -```csharp showLineNumbers -[SimpleJob(RuntimeMoniker.Net481)] -[SimpleJob(RuntimeMoniker.Net60)] -[SimpleJob(RuntimeMoniker.Net80)] -[SimpleJob(RuntimeMoniker.Net90)] -[MemoryDiagnoser] -public class BenchmarkInvokeMethodClass -{ - public BenchmarkInvokeMethodClass() - { - var methodInfo_Add = typeof(MyMethodClass1).GetMethod(nameof(MyMethodClass1.Add)); - var methodInfo_AddAsync = typeof(MyMethodClass1).GetMethod(nameof(MyMethodClass1.AddAsync)); - - this.m_method_Add_IL = new Method(methodInfo_Add, DynamicBuilderType.IL); - this.m_method_Add_Expression = new Method(methodInfo_Add, DynamicBuilderType.Expression); - this.m_method_Add_SourceGenerator = new Method(methodInfo_Add, DynamicBuilderType.SourceGenerator); - this.m_method_Add_Reflect = new Method(methodInfo_Add, DynamicBuilderType.Reflect); - - this.m_method_AddAsync_IL = new Method(methodInfo_AddAsync, DynamicBuilderType.IL); - this.m_method_AddAsync_Expression = new Method(methodInfo_AddAsync, DynamicBuilderType.Expression); - this.m_method_AddAsync_SourceGenerator = new Method(methodInfo_AddAsync, DynamicBuilderType.SourceGenerator); - this.m_method_AddAsync_Reflect = new Method(methodInfo_AddAsync, DynamicBuilderType.Reflect); - - var AddStatic = typeof(MyMethodClass1).GetProperty("AddStaticAction"); - } - - private readonly Method m_method_Add_IL; - private readonly Method m_method_Add_Expression; - private readonly Method m_method_Add_SourceGenerator; - private readonly Method m_method_Add_Reflect; - - private readonly Method m_method_AddAsync_IL; - private readonly Method m_method_AddAsync_Expression; - private readonly Method m_method_AddAsync_SourceGenerator; - private readonly Method m_method_AddAsync_Reflect; - - public int Count=10000; - - [Benchmark] - public void DirectRun_Add() - { - var myClass1 = new MyMethodClass1(); - - var a = new object(); - - var objects = new object[] { a }; - - for (var i = 0; i < this.Count; i++) - { - myClass1.Add(a); - } - } - - [Benchmark] - public void MethodILRun_Add() - { - var myClass2 = new MyMethodClass1(); - - var a = new object(); - - var objects = new object[] { a }; - for (var i = 0; i < this.Count; i++) - { - this.m_method_Add_IL.Invoke(myClass2, objects); - } - } - - [Benchmark] - public void MethodExpressionRun_Add() - { - var myClass2 = new MyMethodClass1(); - - var a = new object(); - - var objects = new object[] { a }; - for (var i = 0; i < this.Count; i++) - { - this.m_method_Add_Expression.Invoke(myClass2, objects); - } - } - - [Benchmark] - public void MethodInfoRun_Add() - { - var myClass2 = new MyMethodClass1(); - - var a = new object(); - - var objects = new object[] { a }; - for (var i = 0; i < this.Count; i++) - { - this.m_method_Add_Reflect.Invoke(myClass2, objects); - } - } - - [Benchmark] - public void SourceGeneratorRun_Add() - { - var myClass2 = new MyMethodClass1(); - - var a = new object(); - - var objects = new object[] { a }; - for (var i = 0; i < this.Count; i++) - { - this.m_method_Add_SourceGenerator.Invoke(myClass2, objects); - } - } - - [Benchmark] - public async Task DirectRun_AddAsync() - { - var myClass1 = new MyMethodClass1(); - - var a = new object(); - - var objects = new object[] { a }; - - for (var i = 0; i < this.Count; i++) - { - await myClass1.AddAsync(a); - } - } - - [Benchmark] - public async Task MethodILRun_AddAsync() - { - var myClass2 = new MyMethodClass1(); - - var a = new object(); - - var objects = new object[] { a }; - for (var i = 0; i < this.Count; i++) - { - await this.m_method_Add_IL.InvokeAsync(myClass2, objects); - } - } - - [Benchmark] - public async Task MethodExpressionRun_AddAsync() - { - var myClass2 = new MyMethodClass1(); - - var a = new object(); - - var objects = new object[] { a }; - for (var i = 0; i < this.Count; i++) - { - await this.m_method_Add_Expression.InvokeAsync(myClass2, objects); - } - } - - [Benchmark] - public async Task MethodInfoRun_AddAsync() - { - var myClass2 = new MyMethodClass1(); - - var a = new object(); - - var objects = new object[] { a }; - for (var i = 0; i < this.Count; i++) - { - await this.m_method_Add_Reflect.InvokeAsync(myClass2, objects); - } - } - - [Benchmark] - public async Task SourceGeneratorRun_AddAsync() - { - var myClass2 = new MyMethodClass1(); - - var a = new object(); - - var objects = new object[] { a }; - for (var i = 0; i < this.Count; i++) - { - await this.m_method_Add_SourceGenerator.InvokeAsync(myClass2, objects); - } - } - - [Benchmark] - public void StaticMethodRun() - { - var myClass1 = new MyMethodClass1(); - - var a = new object(); - - var objects = new object[] { a }; - - for (var i = 0; i < this.Count; i++) - { - MyMethodClass1.AddStatic(myClass1, objects); - } - } -} - - -public class MyMethodClass1 -{ - public static object AddStatic(object myClass, object[] ps) - { - var a = ps[0]; - var result = ((MyMethodClass1)myClass).Add(a); - return result; - } - - //这个是被调用函数。 - [DynamicMethod] - public object Add(object obj) - { - return obj; - } - [DynamicMethod] - public Task AddAsync(object obj) - { - return Task.FromResult(obj); - } + Void, // void方法 + Object, // 返回对象 + Awaitable, // 返回Task + AwaitableObject // 返回Task } ``` - - +## 五、使用示例 +### 5.1 同步方法调用 -### 5.2 缓存策略 + -```csharp showLineNumbers -// 推荐在应用初始化时预加载方法 -public static class MethodCache -{ - public static readonly Method ProcessMethod = new Method( - typeof(MyClass), - nameof(MyClass.ProcessData), - DynamicBuilderType.IL - ); -} + -// 后续重复使用缓存实例 -MethodCache.ProcessMethod.Invoke(instance); -``` +### 5.2 异步Task调用 -## 六、高级用法 + -### 6.1 自定义特性标记 + -```csharp showLineNumbers -// 定义自定义特性 -[AttributeUsage(AttributeTargets.Method)] -public class MyDynamicAttribute : DynamicMethodAttribute {} +### 5.3 异步Task\调用 -// 标记方法 -public class CustomService -{ - [MyDynamic] - public void CustomOperation() { } -} + -// 获取自定义标记方法 -var methods = typeof(CustomService) - .GetMethods() - .Where(m => m.IsDefined(typeof(MyDynamicAttribute))); -``` + -## 七、示例项目 +### 5.4 out和ref参数 - + + + + +### 5.5 自定义动态方法特性 + +可以创建继承自`DynamicMethodAttribute`的自定义特性,源生成器会自动识别: + + + + + +## 六、性能测试 + +### 6.1 性能对比 + +以下是10000次调用的性能测试结果(基于示例项目AotDynamicMethodConsoleApp): + + + + + +:::tip 性能建议 +- **推荐使用源生成器**:源生成器方式性能最佳,接近直接调用性能 +- **缓存Method实例**:避免重复创建`Method`对象,应在初始化时创建并缓存 +- **AOT环境必用源生成**:在AOT环境下,源生成器是唯一高性能选择 +::: + +### 6.2 实现方式对比 + +| 实现方式 | 性能 | AOT支持 | 说明 | +|---------|------|---------|------| +| 源生成器 | ⭐⭐⭐⭐⭐ | ✅ | 编译时生成,零反射,性能最佳 | +| 表达式树 | ⭐⭐⭐⭐ | ⚠️ | 运行时编译,性能优秀,部分AOT不支持 | +| 反射 | ⭐⭐ | ✅ | 保底方案,性能较低 | + +## 七、注意事项 + +### 7.1 标记要求 + +- 方法必须使用`[DynamicMethod]`特性或其派生特性标记 +- 源生成器在编译时扫描特性,运行时添加特性无效 + +### 7.2 源生成器限制 + +- 源生成器只处理当前程序集中标记的方法 +- 跨程序集的方法需要在定义程序集中标记 +- 私有方法也可以标记,但需要注意访问权限 + +### 7.3 使用建议 + +**✅ 推荐:缓存Method实例** + + + +## 八、示例项目 + + \ No newline at end of file diff --git a/handbook/docs/enterprise.mdx b/handbook/docs/enterprise.mdx index 7fdc24ff6..94b373ae9 100644 --- a/handbook/docs/enterprise.mdx +++ b/handbook/docs/enterprise.mdx @@ -33,13 +33,11 @@ import Pro from "@site/src/components/Pro.js"; ### 2.1 Tcp组件 -- [轮询式断线重连](./reconnection.mdx#三使用pollingkeepalive插件-Pro) -- [TLV适配器](./tlvdatahandlingadapter.mdx) - 其余功能 ### 2.2 NAT组件 -- [转发客户端重连](./natservice.mdx#四转发断线重连-Pro) +- [转发客户端重连](./natservice.mdx) - 其余功能 ### 2.3 UDP组件 @@ -67,7 +65,6 @@ import Pro from "@site/src/components/Pro.js"; - [多线程文件传输](./dmtptransferfile.mdx) - [小文件传输](./dmtptransferfile.mdx) - 文件传输限速 -- [EventBus功能](https://www.yuque.com/rrqm/touchsocket/ipt4zr) - Redis ### 2.8 Http组件 @@ -118,7 +115,7 @@ import Pro from "@site/src/components/Pro.js"; ### 4.1 个人独立授权 -授权归属于购买者个人所有,规定购买者可将所购产品只能应用于所属个人的任何软件(产品)上,可以以此盈利,但必须遵守[个人使用协议](./description.mdx#qLp3q)。 +授权归属于购买者个人所有,规定购买者可将所购产品只能应用于所属个人的任何软件(产品)上,可以以此盈利,但必须遵守[个人使用协议](./description.mdx)。 ### 4.2 个人企业授权 diff --git a/handbook/docs/fastbinaryformatter.mdx b/handbook/docs/fastbinaryformatter.mdx index 1071c494a..c2241c1a5 100644 --- a/handbook/docs/fastbinaryformatter.mdx +++ b/handbook/docs/fastbinaryformatter.mdx @@ -4,8 +4,9 @@ title: 高性能二进制序列化 --- import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; +import CardLink from "@site/src/components/CardLink.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; -### 定义 @@ -32,57 +33,19 @@ import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; 一般的,可以非常简单的对支持类型进行序列化和反序列化。 -```csharp showLineNumbers -var bytes = FastBinaryFormatter.SerializeToBytes(10); -var newObj = FastBinaryFormatter.Deserialize(bytes); -``` + ### 2.2 使用内存池块 在使用过程中,如果使用到频繁的序列化、反序列化,可以使用内存块,可以减少内存的申请和释放。 -```csharp {6,12} showLineNumbers -//申请内存块,并指定此次序列化可能使用到的最大尺寸。 -//合理的尺寸设置可以避免内存块扩张。 -using (var block = new ByteBlock(1024*64)) -{ - //将数据序列化到内存块 - FastBinaryFormatter.Serialize(block, 10); - - //在反序列化前,将内存块数据游标移动至正确位。 - block.SeekToStart(); - - //反序列化 - var newObj = FastBinaryFormatter.Deserialize(block); -} -``` + ### 2.3 使用值类型内存池块 -常规内存块是“类”,所以在使用时,自身对象会产生GC垃圾,如果追求极致序列化,则可以使用值类型内存池块,可以做到**零GC**分配。 +常规内存块是"类",所以在使用时,自身对象会产生GC垃圾,如果追求极致序列化,则可以使用值类型内存池块,可以做到**零GC**分配。 -```csharp {8,14} showLineNumbers -//申请内存块,并指定此次序列化可能使用到的最大尺寸。 -//合理的尺寸设置可以避免内存块扩张。 -var block = new ValueByteBlock(1024 * 64); - -try -{ - //将数据序列化到内存块 - FastBinaryFormatter.Serialize(ref block, 10); - - //在反序列化前,将内存块数据游标移动至正确位。 - block.SeekToStart(); - - //反序列化 - var newObj = FastBinaryFormatter.Deserialize(ref block); -} -finally -{ - //因为使用了ref block,所以无法使用using,只能使用try-finally - block.Dispose(); -} -``` + ## 三、常规配置 @@ -90,76 +53,19 @@ finally 例如:下列类型中,只有`P1`和`P3`成员将有效。 -```csharp showLineNumbers -public class MyClass1 -{ - private int m_p5; - - //公共属性,有效 - public int P1 { get; set; } - - //自动公共属性,即使包含set访问器,但private,无效 - public int P2 { get; private set; } - - //公共字段,有效 - public int P3; - - //私有字段,无效 - private int P4; - - //公共属性,不包含set访问器,无效 - public int P5 => m_p5; - - public void SetP4(int value) - { - this.P4 = value; - } - - public void SetP2(int value) - { - this.P2 = value; - } - public void SetP5(int value) - { - this.m_p5 = value; - } -} -``` + ### 3.1 忽略成员 忽略成员,可以通过特性`[FastNonSerialized]`来忽略。 -例如: - -```csharp {6} showLineNumbers -public class MyClass1 -{ - ... - - //公共属性,但忽略,无效 - [FastNonSerialized] - public int P6 { get; set; } - -} -``` +在上面的`MyClass1`类中,`P6`属性使用了`[FastNonSerialized]`特性标记,因此不会被序列化。 ### 3.2 强制成员 对于只读成员,有时候也需要序列化时,可以通过特性`[FastSerialized]`来强制。 -例如: - -```csharp {6} showLineNumbers -public class MyClass1 -{ - ... - - //自动公共属性,包含set访问器,即使private,但因为FastSerialized后,有效 - [FastSerialized] - public int P7 { get;private set; } -} -``` +在上面的`MyClass1`类中,`P7`属性虽然set访问器是private的,但使用了`[FastSerialized]`特性后,仍然会被序列化。 :::caution 注意 @@ -173,53 +79,16 @@ public class MyClass1 例如:下列`MyClass2`与`MyClass3`是两个不同类型 -```csharp showLineNumbers -public class MyClass2 -{ - public int P1 { get; set; } -} - -public class MyClass3 -{ - public int P1 { get; set; } - public string P2 { get; set; } -} -``` + 也可以互相序列化和反序列化。 -```csharp showLineNumbers -var myClass2 = new MyClass2() -{ - P1 = 10 -}; -var bytes = FastBinaryFormatter.SerializeToBytes(myClass2); + -var newObj = FastBinaryFormatter.Deserialize(bytes); -``` - -反序列化后的`MyClass3` - -```csharp showLineNumbers -{"P1":10,"P2":null} -``` +反序列化后的`MyClass3`的P1值为10,P2值为null。 但如果是成员名称一致,但基础类型不一致的,则不会成功,且可能会抛出异常。 -例如: - -```csharp {3,8} showLineNumbers -public class MyClass2 -{ - public int P1 { get; set; } -} - -public class MyClass3 -{ - public string P1 { get; set; } -} -``` - :::tip 提示 兼容类型的使用,可以一定程度的解决一些兼容性问题,尤其是增加、或移除成员时都可以兼容。但是当修改成员类型时,可能会导致序列化数据丢失。 @@ -234,29 +103,9 @@ public class MyClass3 例如: -对于下列类,如果不使用特性,序列化体积可达**40字节**。 +对于下列类,如果不使用特性,序列化体积可达**40字节**。使用特性后,体积可以减少到**18字节**。 -```csharp showLineNumbers -public class MyClass4 -{ - public int MyProperty1 { get; set; } - public int MyProperty2 { get; set; } -} -``` - -使用特性后,体积可以减少到**18字节**。 - -```csharp {1,4,7} showLineNumbers -[FastSerialized(EnableIndex =true)] -public class MyClass4 -{ - [FastMember(1)] - public int MyProperty1 { get; set; } - - [FastMember(2)] - public int MyProperty2 { get; set; } -} -``` + :::info 信息 @@ -273,13 +122,7 @@ public class MyClass4 对于下列类,只有两个int类属性是有效值。 -```csharp showLineNumbers -public class MyClass5 -{ - public int P1 { get; set; } - public int P2 { get; set; } -} -``` + 所以,我们需要自定义一个转换器。来将这2个`int`值,转换成有效数据。 @@ -287,34 +130,7 @@ public class MyClass5 然后实现`Read`和`Write`方法。实现逻辑如下: -```csharp {3,15} showLineNumbers -public sealed class MyClass5FastBinaryConverter : FastBinaryConverter -{ - protected override MyClass5 Read(ref TByteBlock byteBlock, Type type) - { - //此处不用考虑为null的情况 - //我们只需要把有效信息按写入的顺序,读取即可。 - - var myClass5 = new MyClass5(); - myClass5.P1 = byteBlock.ReadInt32(); - myClass5.P2 = byteBlock.ReadInt32(); - - return myClass5; - } - - protected override void Write(ref TByteBlock byteBlock, in MyClass5 obj) - { - //此处不用考虑为null的情况 - //我们只需要把有效信息写入即可。 - //对于MyClass5类,只有两个属性是有效的。 - - //所以,依次写入属性值即可 - byteBlock.WriteInt32(obj.P1); - byteBlock.WriteInt32(obj.P2); - - } -} -``` + :::info 信息 @@ -322,22 +138,13 @@ public sealed class MyClass5FastBinaryConverter : FastBinaryConverter ::: -最后附加转换器即可 +最后附加转换器即可,可以通过在类上添加`[FastConverter]`特性: -```csharp {1} -[FastConverter(typeof(MyClass5FastBinaryConverter))] -public class MyClass5 -{ - public int P1 { get; set; } - public int P2 { get; set; } -} -``` + 或者直接往`FastBinaryFormatter`中添加转换器。 -```csharp showLineNumbers -FastBinaryFormatter.AddFastBinaryConverter(typeof(MyClass5),new MyClass5FastBinaryConverter()); -``` + :::caution 注意 @@ -353,40 +160,9 @@ FastBinaryFormatter.AddFastBinaryConverter(typeof(MyClass5),new MyClass5FastBina 只需要对需要转换的对象实现`IPackage`接口(或继承`PackageBase`)即可。 -例如: - -下列类,实现了`IPackage`接口。在序列化时,会调用`Package`方法,反序列化时,会调用`Unpackage`方法。 - -```csharp {6,12} showLineNumbers -public class MyClass6:PackageBase -{ - public int P1 { get; set; } - public int P2 { get; set; } - - public override void Package(ref TByteBlock byteBlock) - { - byteBlock.WriteInt32(this.P1); - byteBlock.WriteInt32(this.P2); - } - - public override void Unpackage(ref TByteBlock byteBlock) - { - this.P1 = byteBlock.ReadInt32(); - this.P2 = byteBlock.ReadInt32(); - } -} -``` - 当然,在包模式的**源生成**可用时,也可以直接用源生成的方式实现更多细节。 -```csharp {1} showLineNumbers -[GeneratorPackage] -public partial class MyClass6:PackageBase -{ - public int P1 { get; set; } - public int P2 { get; set; } -} -``` +
由源生成的代码 @@ -430,132 +206,3 @@ namespace FastBinaryFormatterConsoleApp 当使用包模式序列化时,类型兼容性将跟随包类型一致。一般来说,新增属性是允许的,但是修改或移除属性是不允许的。 ::: - -## 八、性能测试 - -### 8.1 简单测试 - -**待测试类型** - -```csharp showLineNumbers -[Serializable] -public class MyPackPerson -{ - public int Age { get; set; } - public string Name { get; set; } -} -``` - -**结果** - -以下测试是执行10000次序列化和反序列的结果。 - -![](@site/static/img/docs/fastbinaryformatter-1.png) - -### 8.2 复杂类型测试 - -**待测试类** - -```csharp showLineNumbers - [Serializable] -public class Student -{ - public int P1 { get; set; } - public string P2 { get; set; } - public long P3 { get; set; } - public byte P4 { get; set; } - public DateTime P5 { get; set; } - public double P6 { get; set; } - public byte[] P7 { get; set; } - - public List List1 { get; set; } - public List List2 { get; set; } - public List List3 { get; set; } - - public Dictionary Dic1 { get; set; } - public Dictionary Dic2 { get; set; } - public Dictionary Dic3 { get; set; } - public Dictionary Dic4 { get; set; } -} - -[Serializable] -public class Arg -{ - public Arg(int myProperty) - { - this.MyProperty = myProperty; - } - - public Arg() - { - Person person = new Person(); - person.Name = "张三"; - person.Age = 18; - } - - public int MyProperty { get; set; } -} -[Serializable] -public class Person -{ - public string Name { get; set; } - public int Age { get; set; } -} -``` - -**赋值** - -```csharp showLineNumbers -Student student = new Student(); -student.P1 = 10; -student.P2 = "若汝棋茗"; -student.P3 = 100; -student.P4 = 0; -student.P5 = DateTime.Now; -student.P6 = 10; -student.P7 = new byte[1024 * 64]; - -Random random = new Random(); -random.NextBytes(student.P7); - -student.List1 = new List(); -student.List1.Add(1); -student.List1.Add(2); -student.List1.Add(3); - -student.List2 = new List(); -student.List2.Add("1"); -student.List2.Add("2"); -student.List2.Add("3"); - -student.List3 = new List(); -student.List3.Add(new byte[1024]); -student.List3.Add(new byte[1024]); -student.List3.Add(new byte[1024]); - -student.Dic1 = new Dictionary(); -student.Dic1.Add(1, 1); -student.Dic1.Add(2, 2); -student.Dic1.Add(3, 3); - -student.Dic2 = new Dictionary(); -student.Dic2.Add(1, "1"); -student.Dic2.Add(2, "2"); -student.Dic2.Add(3, "3"); - -student.Dic3 = new Dictionary(); -student.Dic3.Add("1", "1"); -student.Dic3.Add("2", "2"); -student.Dic3.Add("3", "3"); - -student.Dic4 = new Dictionary(); -student.Dic4.Add(1, new Arg(1)); -student.Dic4.Add(2, new Arg(2)); -student.Dic4.Add(3, new Arg(3)); -``` - -**结果** - -Fast的效率比System自带的,快了近7倍,比System.Text.Json快了4倍多,比NewtonsoftJson快了近30倍。 - -![](@site/static/img/docs/fastbinaryformatter-2.png) diff --git a/handbook/docs/filepool.mdx b/handbook/docs/filepool.mdx index 734bc37d2..39fa23098 100644 --- a/handbook/docs/filepool.mdx +++ b/handbook/docs/filepool.mdx @@ -5,7 +5,7 @@ title: 文件流池 import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; -### 定义 + diff --git a/handbook/docs/generateproxysourcegeneratordemo.mdx b/handbook/docs/generateproxysourcegeneratordemo.mdx index 4a0ad47e9..58c1e0b50 100644 --- a/handbook/docs/generateproxysourcegeneratordemo.mdx +++ b/handbook/docs/generateproxysourcegeneratordemo.mdx @@ -5,7 +5,7 @@ title: 源生成代理推荐写法 import { TouchSocketRpcDefinition } from "@site/src/components/Definition.js"; -### 定义 + diff --git a/handbook/docs/generichost.mdx b/handbook/docs/generichost.mdx index 43457c069..ea89d07e2 100644 --- a/handbook/docs/generichost.mdx +++ b/handbook/docs/generichost.mdx @@ -8,7 +8,7 @@ import TabItem from "@theme/TabItem"; import BilibiliCard from '@site/src/components/BilibiliCard.js'; import Definition from "@site/src/components/Definition.js"; -### 定义 + diff --git a/handbook/docs/httpclient.mdx b/handbook/docs/httpclient.mdx index 98b6aa3dd..918991ef4 100644 --- a/handbook/docs/httpclient.mdx +++ b/handbook/docs/httpclient.mdx @@ -3,205 +3,187 @@ id: httpclient title: 创建HttpClient --- +import Tag from "@site/src/components/Tag.js"; import CardLink from "@site/src/components/CardLink.js"; import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; import { TouchSocketHttpDefinition } from "@site/src/components/Definition.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; + -### 定义 ## 一、说明 -HttpClient是Http客户端类。主要用于请求Http报文。与.net的HttpClient不同的是,此处的HttpClient,是基于单个连接的客户端,且有明显的连接、断开连接等动作。 +`HttpClient`是Http系客户端类,他是基于单个TCP连接的Http客户端。与.NET框架中的HttpClient不同,TouchSocket的HttpClient具有明显的连接、断开连接等动作,且能够复用TCP连接来处理多个Http请求。 +## 二、特点 -## 二、可配置项 +- 简单易用。 +- 基于单个TCP连接,支持连接复用。 +- 支持Http/Https协议。 +- 支持自定义证书验证。 +- 内存池支持,高性能处理。 +- 支持流式上传和下载。 +- 支持SSE(Server-Sent Events)。 +- 基于委托、插件驱动,支持AOP扩展。 -继承TcpClient +## 三、产品应用场景 - +- HTTP API调用:RESTful API客户端实现。 +- 文件上传下载:大文件流式传输。 +- 实时通信:SSE推送消息接收。 +- 微服务通信:服务间HTTP通信。 +- 爬虫应用:网页数据抓取。 +- HTTPS安全通信:SSL/TLS加密传输。 -## 三、支持插件接口 +## 四、可配置项 -支持**ITcpPlugin**接口。 +`HttpClient`继承自`TcpClient`,所以支持所有[TcpClient](./tcpclient.mdx)的所有配置项。 - +Http基本没有专用配置,但是下面依然会介绍一些在使用Http时的常用相关配置。 -## 四、创建HttpClient +### 4.1 目标服务器地址 -### 4.1 创建常规HttpClient -```csharp showLineNumbers -var client = new HttpClient(); -await client.ConnectAsync("http://localhost:7219");//先做连接 -``` +`HttpClient`必须指定远程服务器地址,支持HTTP和HTTPS协议。支持类型: + +1. 使用HTTP协议,传入形如:`http://127.0.0.1:7219`的字符串即可。 +2. 使用HTTPS协议,传入形如:`https://127.0.0.1:7219`的字符串即可。 +3. 使用域名,必须包含协议类型,形如:`https://api.example.com`或者`http://api.example.com:8080` + + + +### 4.2 SSL/TLS配置 + +当使用HTTPS协议时,`HttpClient`支持SSL/TLS加密连接。可以通过`SetClientSslOption`方法进行配置。 + +不过一般来说,如果服务器使用受信任的证书,可以直接使用`https://`协议头连接服务器即可。 + +例如: + + + +如果是自定义证书,则需要手动加载证书,详情见[创建Ssl的TcpClient配置](./tcpclient.mdx)。 + +### 4.3 代理配置 +`HttpClient`支持HTTP代理,可以通过`SetProxy`方法进行配置。 + + +或者直接使用系统代理: + + +## 五、支持插件接口 + +支持**ITcpPlugin**所有接口,无特殊接口。 + +## 六、创建HttpClient - -### 4.2 创建Ssl的HttpClient(Https) - -```csharp showLineNumbers -var client = new HttpClient(); -await client.ConnectAsync("https://localhost:7219");//先做连接 -``` - -如果是自定义证书,则需要手动加载证书 - -```csharp showLineNumbers -var client = new HttpClient(); - -var config = new TouchSocketConfig(); -config.SetRemoteIPHost("https://localhost:7219") - .SetClientSslOption(new ClientSslOption() - { - ClientCertificates = new X509CertificateCollection() { new X509Certificate2("Socket.pfx", "Socket") }, - SslProtocols = SslProtocols.Tls12, - TargetHost = "localhost", - CertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => { return true; } - }); ; - -//配置config -await client.SetupAsync(config); -await client.ConnectAsync();//先做连接 -``` - +### 6.1 简单创建HttpClient + + + + +### 6.2 常规配置创建Http客户端 + + + :::info 备注 实际上直接使用`ConnectAsync("https://localhost:7219")`的方式是合并了`SetupAsync`与`ConnectAsync`。所以当需要额外配置时,应当遵循所有配置都在`TouchSocketConfig`的约定。这样代码也比较简单明了。 ::: -## 五、发送请求 +## 七、发送HTTP请求 -因为我的HttpClient是面向连接的,所以,在请求时,请求url只需要填写host之后的部分即可(即路由部分)。 +因为`HttpClient`是面向连接的,所以在发送请求时,请求URL只需要填写主机地址之后的部分(即路由部分)。 -### 5.1 发起Get请求到字符串 +### 7.1 获取字符串响应 -```csharp showLineNumbers -//直接发起一个Get请求,然后返回Body字符串。 -var body = await client.GetStringAsync("/WeatherForecast"); -``` + -### 5.2 发起Get请求到字节数组 +### 7.2 获取字节数组响应 -```csharp showLineNumbers -//直接发起一个Get请求,然后返回Body数组。 -var bodyBytes = await client.GetByteArrayAsync("/WeatherForecast"); -``` + -### 5.3 发起Get请求到流数据 +### 7.3 获取流数据响应(下载文件) -```csharp showLineNumbers -//直接发起一个Get请求文件,然后写入到流中。 -using (var stream=File.Create("1.txt")) -{ - await client.GetFileAsync("/WeatherForecast",stream); -} -``` + + +### 7.4 自定义请求构建 + +#### 7.4.1 构建基础自定义请求 -### 5.4 构建自定义请求 + -```csharp showLineNumbers -//创建一个请求 -var request = new HttpRequest(); -request.InitHeaders() - .SetUrl("/WeatherForecast") - .SetHost(client.RemoteIPHost.Host) - .AsGet(); - - -using (var responseResult = await client.RequestAsync(request, 1000 * 10)) -{ - var response = responseResult.Response; - Console.WriteLine(await response.GetBodyAsync());//将接收的数据,一次性转为utf8编码的字符串 -} -``` - -### 5.5 构建自定义请求,持续读取大数据 - -```csharp showLineNumbers -//创建一个请求 -var request = new HttpRequest(); -request.InitHeaders() - .SetUrl("/WeatherForecast") - .SetHost(client.RemoteIPHost.Host) - .AsGet(); - - -using (var responseResult = await client.RequestAsync(request, 1000 * 10)) -{ - var response = responseResult.Response; - - while (true) - { - using (var blockResult = await response.ReadAsync()) - { - //每次读到的数据 - var memory = blockResult.Memory; - Console.WriteLine(memory.Length); - - if (blockResult.IsCompleted) - { - //数据读完成 - break; - } - } - } -} -``` - -### 5.6 构建自定义Post请求,持续写入流 - -```csharp {5} showLineNumbers -using (var stream=File.OpenRead("TouchSocket.dll")) -{ - //创建一个请求 - var request = new HttpRequest(); - request.SetContent(new StreamHttpContent(stream));//设置流内容 - request.InitHeaders() - .SetUrl("/bigwrite") - .SetHost(client.RemoteIPHost.Host) - .AsPost(); - - using (var responseResult = await client.RequestAsync(request, 1000 * 10)) - { - var response = responseResult.Response; - } - Console.WriteLine("完成"); -} -``` +#### 7.4.2 构建自定义请求,持续读取大数据 + + +### 7.5 Post发送JSON数据 + + + +### 7.6 流式上传数据(文件上传) + + + :::tip 提示 -在构建自定义请求时,如果使用`AsGet`、`AsPost`等方法可以直接设置当前请求为`Get`、`Post`等。如果没有扩展方法可以使用时,可以使用`AsMethod`来实现,例如:`AsMethod("GET")`。 +在构建自定义请求时,如果使用`AsGet`、`AsPost`等方法可以直接设置当前请求为`Get`、`Post`等。如果没有扩展方法可以使用时,可以使用`AsMethod`来实现,例如:`AsMethod("PUT")`、`AsMethod("DELETE")`等。 ::: -## 六、传输文件 +## 八、断线重连 -### 6.1 下载文件 +因为我们的`HttpClient`是面向连接的,所以在网络较差时,可能会断线连接,此时你可以通过[Tcp客户端](./tcpclient.mdx)的机制,获取断开信息。 -```csharp {4} showLineNumbers -//直接发起一个Get请求,然后写入到流中。 -using (var stream=File.Create("1.txt")) -{ - await client.GetFileAsync("/WeatherForecast",stream); -} -``` +例如: -### 6.2 上传文件 + -```csharp showLineNumbers -await client.UploadFileAsync("/upfile", new FileInfo("filePath")); -``` +除此之外,当连接断开时,我们希望客户端可以自动进行重连,即客户端在断开服务器后,主动发起的再次连接请求。 -## 七、本文示例Demo +### 9.1 启用断线重连 - \ No newline at end of file +断线重连,依靠的是客户端断开后,或者初始化后,`Online`属性为`false`时,会尝试连接。 + +所以,重连机制在`Setup`完成后,即会生效。 + + + + + +:::caution 注意 + +**断线重连,必须满足以下几个要求:** + +1. 必须有显式的断开信息,也就是说,直接拔网线的话,**不会**立即生效,会等tcp保活到期后再生效。此处可以结合[健康活性检验插件](./tcpcommonplugins.mdx)来绝对保活。 + +::: + +:::tip 提示 + +`UseReconnection`插件,可以通过设置`SetActionForCheck`,自己规定检查活性的方法。默认情况下,只会检验`Online`属性,所以无法检验出断网等情况。如果自己控制,则可以发送心跳包,以保证在线状态。 + +::: + +### 9.2 暂停重连 + +适用于重连的客户端,都提供了`SetPauseReconnection`方法,可以暂停或者恢复重连。 + + + + +## 九、示例Demo + + \ No newline at end of file diff --git a/handbook/docs/httpservice.mdx b/handbook/docs/httpservice.mdx index 0c29838f6..a66e5b9b6 100644 --- a/handbook/docs/httpservice.mdx +++ b/handbook/docs/httpservice.mdx @@ -8,285 +8,225 @@ import TabItem from "@theme/TabItem"; import CardLink from "@site/src/components/CardLink.js"; import BilibiliCard from '@site/src/components/BilibiliCard.js'; import { TouchSocketHttpDefinition } from "@site/src/components/Definition.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; + -### 定义 ## 一、说明 -**HttpService**是能够提供Http相关服务的基础类型。 +`HttpService`是`TouchSocket`框架中用于构建高性能HTTP服务的核心组件。它提供了一个轻量级、高效的Web服务解决方案,支持现代Web开发的各种需求。无论是构建`REST API`、`Web服务`还是`文件服务器`,`HttpService`都能提供卓越的性能和简洁的开发体验。 + +**特别值得注意的是**,`HttpService`拥有业界领先的框架兼容性,从经典的`.NET Framework 4.6.2`到最新的`.NET 9.0`全系列支持,让您的项目无论是升级改造还是全新开发都能享受到统一的开发体验。 -## 二、产品特点 +## 二、核心特性 -- 支持HTTPS。 -- **多种数据接收模式** -- **多地址监听**(可以一次性监听多个IP及端口) -- **高性能** -- **支持跨域**(CORS) -- **支持AOT,且体积比AspNetCore小50%** +`HttpService`具备以下突出特性: + +- **🎯 广泛框架支持** - 从`.NET Framework 4.6.2`到`.NET 9.0`全版本支持,兼容性卓越 +- **🔐 完整`HTTPS`支持** - 支持`SSL/TLS`加密连接,确保数据传输安全 +- **📊 多种数据处理模式** - 灵活处理各种类型的`HTTP`请求数据 +- **🌐 多地址监听** - 可同时监听多个`IP`地址和端口,实现灵活部署 +- **⚡ 卓越性能表现** - 经过优化的高性能`HTTP`处理引擎 +- **🔄 跨域支持(CORS)** - 内置跨域资源共享支持,简化前后端分离开发 +- **📦 AOT原生支持** - 支持提前编译,部署体积比AspNetCore小50%,启动更快 -## 三、产品应用场景 +## 三、应用场景 -- HTTP基础使用场景:可跨平台、跨语言使用。 +`HttpService` 适用于以下多种场景: + +- **🔗 RESTful API服务** - 构建标准的REST风格的`Web API`接口 +- **🌍 跨平台Web服务** - 支持`Windows`、`Linux`、`macOS`等多平台部署 +- **🏗️ 遗留系统集成** - 完美支持`.NET Framework 4.6.2+`,可无缝集成到现有老项目 +- **🚀 现代应用开发** - 全面支持`.NET Core/.NET 5-9`,享受最新框架特性 +- **📁 文件服务器** - 高效的文件上传、下载和管理服务 +- **🔌 微服务架构** - 轻量级的微服务HTTP通信组件 +- **🖥️ 混合应用后端** - 为桌面应用、移动应用提供HTTP服务支持 +- **📡 IoT设备通信** - 物联网设备的HTTP接口服务 -## 四、服务器架构 +## 四、架构设计 -服务器在收到新客户端连接时,会创建一个`HttpSessionClient`的派生类实例,与远程`HttpClient`对应,后续的数据通信均由此实例负责。 +`HttpService`采用高效的会话管理架构设计: + +### 连接管理模型 + +当HTTP客户端与服务器建立连接时,`HttpService`会为每个连接创建一个专门的`HttpSessionClient`实例。这种设计模式确保了: + +- **会话隔离** - 每个客户端连接拥有独立的处理实例 +- **并发安全** - 多个客户端可以同时访问服务器而不相互干扰 +- **资源优化** - 合理的连接管理和资源分配 ```mermaid flowchart TD; - Service-->HttpSessionClient-1; - Service-->HttpSessionClient-2; - Service-->HttpSessionClient-3; - HttpSessionClient-1-->HttpClient-1; - HttpSessionClient-2-->HttpClient-2; - HttpSessionClient-3-->HttpClient-3; + Service[HttpService]-->HttpSessionClient-1[HttpSessionClient-1]; + Service-->HttpSessionClient-2[HttpSessionClient-2]; + Service-->HttpSessionClient-3[HttpSessionClient-3]; + HttpSessionClient-1-->HttpClient-1[HttpClient-1]; + HttpSessionClient-2-->HttpClient-2[HttpClient-2]; + HttpSessionClient-3-->HttpClient-3[HttpClient-3]; ``` -## 五、支持插件接口 +## 五、插件扩展机制 -声明自定义实例类,然后实现`IHttpPlugin`接口,即可实现下列事务的触发。或者继承自`PluginBase`类,重写相应方法即可。 +`HttpService`提供了强大的插件系统,允许开发者通过实现`IHttpPlugin`接口或继承`PluginBase`基类来扩展功能。 -| 插件方法| 功能 | -| --- | --- | -| IHttpPlugin | 当收到所有Http请求时。| +### 支持的插件接口 + +| 插件接口 | 触发时机 | 功能说明 | +| --- | --- | --- | +| `IHttpPlugin` | 接收到HTTP请求时 | 处理所有HTTP请求,支持链式处理 | + +### 插件实现方式 + +1. **接口实现** - 直接实现`IHttpPlugin`接口 +2. **基类继承** - 继承`PluginBase`类并重写相应方法 +3. **委托方式** - 通过委托函数快速添加处理逻辑 -## 六、创建HttpService +## 六、快速开始 -`HttpService`的创建,基本和`TcpService`一致,也可以通过继承实现,下列仅演示最简单实现。 +创建`HttpService`的过程简单直观,类似于`TcpService`的配置方式。并且适应[TcpService的所有配置项目](./tcpservice.mdx) -`HttpService`的相关事务,会通过**插件**触发。 - -```csharp showLineNumbers -var service = new HttpService(); -await service.SetupAsync(new TouchSocketConfig()//加载配置 - .SetListenIPHosts(7789) - .ConfigureContainer(a => - { - a.AddConsoleLogger(); - }) - .ConfigurePlugins(a => - { - //此处添加插件逻辑,插件可以使用PluginBase类实现IHttpPlugin接口完成。 - //这里使用委托直接完成 - a.Add(typeof(IHttpPlugin), async (HttpContextEventArgs e) => - { - var request = e.Context.Request;//http请求体 - var response = e.Context.Response;//http响应体 - - //判断url - if (request.UrlEquals("/say")) - { - await response - .SetStatus(200, "success") - .SetContent("hello") - .AnswerAsync(); - return; - } - - //如果上述url没有处理,则转到下一插件处理 - await e.InvokeNext(); - }); - - //default插件应该最后添加,其作用是 - //1、为找不到的路由返回404 - //2、处理header为Option的探视跨域请求。 - a.UseDefaultHttpServicePlugin(); - })); - -await service.StartAsync(); -``` - -:::tip 提示 - -`DefaultHttpServicePlugin`插件最好添加在插件中,如果没有添加的话,最好自己做好缺省路由配置。 - -::: +以下是一个完整的创建示例: -## 七、获取请求 +### 6.1 创建Http服务器 -每个`HttpClient`连接建立时,系统除了会创建一个`HttpSessionClient`与之对应之外,还会创建一个`HttpContext`实例与之对应。 -所以,对于一个连接而言,后续的所有`Http`交互,都会反复投递同一个`HttpContext`实例。 + -所以我们可以通过其`Request`与`Response`属性获取到本次Http的请求和即将响应的响应体。 +:::tip 提示 -```csharp showLineNumbers -var request = e.Context.Request;//http请求体 -var response = e.Context.Response;//http响应体 -``` +`DefaultHttpServicePlugin`插件建议添加到插件链的末尾,它负责: +- 为找不到匹配路由的请求返回404状态 +- 自动处理OPTIONS方法的CORS预检请求 + +如果不使用此插件,需要自行处理这些默认场景。 + +::: + +### 6.2 接收并处理请求 + +在http服务器中,所有的请求处理都通过插件来完成。以下是一个简单的插件示例: + + + +## 七、请求数据获取 + +### 7.1 HttpContext生命周期 + +每当HTTP客户端与服务器建立连接时,系统会创建: + +- 一个`HttpSessionClient`实例 - 负责管理客户端连接 +- 一个`HttpContext`实例 - 负责处理HTTP请求/响应上下文 + +对于同一个连接的所有HTTP请求,都会复用同一个`HttpContext`实例,通过其`Request`和`Response`属性可以访问请求数据和构建响应内容。 + + -### 7.1 获取Query参数 +### 7.2 获取Query参数 -```csharp showLineNumbers -string value = e.Context.Request.Query["key"]; -``` + -### 7.2 获取Header参数 +### 7.3 获取Header参数 -```csharp showLineNumbers -string value = e.Context.Request.Headers["key"]; -``` + 亦或者 -```csharp showLineNumbers -string value = e.Context.Request.Headers[HttpHeaders.Cookie]; -``` + -### 7.3 获取Form参数 +### 7.4 获取Form参数 -```csharp showLineNumbers -var multifileCollection =await e.Context.Request.GetFormCollectionAsync(); -foreach (var item in multifileCollection) -{ - Console.WriteLine($"key={item.Key},value={item.Value}"); -} -``` + -### 7.4 获取字符串Body内容 +### 7.5 获取字符串Body内容 -```csharp showLineNumbers -string bodyString = await e.Context.Request.GetBodyAsync(); -``` + -### 7.5 获取小体量字节Body内容 +### 7.6 获取小体量字节Body内容 -```csharp showLineNumbers -ReadOnlyMemory content = await e.Context.Request.GetContentAsync(); -``` + -### 7.6 持续读取Body内容 +### 7.7 持续读取Body内容 当数据太大时,可持续读取 -```csharp showLineNumbers -while (true) -{ - var buffer = new byte[1024 * 64]; - - using (var blockResult = await e.Context.Request.ReadAsync()) - { - //这里可以一直处理读到的数据。 - blockResult.Memory.CopyTo(buffer); - - if (blockResult.IsCompleted) - { - //结束 - break; - } - } -} -``` + -### 7.7 获取Body持续写入Stream中 +### 7.8 获取Body持续写入Stream中 当数据太大时,可持续读取数据直接到流容器中。 -```csharp showLineNumbers -using (var stream = new MemoryStream()) -{ - // - await e.Context.Request.ReadCopyToAsync(stream); -} -``` + -### 7.8 获取Body小文件 +### 7.9 获取Body小文件集合 当Body内容为小文件集合时,可以使用该功能。 -```csharp {11,13-19} showLineNumbers -if (e.Context.Request.ContentLength > 1024 * 1024 * 100)//全部数据体超过100Mb则直接拒绝接收。 -{ - await e.Context.Response - .SetStatus(403, "数据过大") - .AnswerAsync(); - return; -} + -//此操作会先接收全部数据,然后再分割数据。 -//所以上传文件不宜过大,不然会内存溢出。 -var multifileCollection =await e.Context.Request.GetFormCollectionAsync(); - -foreach (var file in multifileCollection.Files) -{ - var stringBuilder = new StringBuilder(); - stringBuilder.Append($"文件名={file.FileName}\t"); - stringBuilder.Append($"数据长度={file.Length}"); - client.Logger.Info(stringBuilder.ToString()); -} - -await e.Context.Response - .SetStatusWithSuccess() - .FromText("Ok") - .AnswerAsync(); -``` -## 八、响应请求 +## 八、响应构建与发送 -当收到`Http`请求,处理完成业务后,即可使用`e.Context.Response`直接参与本次响应。 +当接收并处理完HTTP请求后,可以通过`e.Context.Response`对象构建并发送响应数据。 + +### 响应构建流程 + +HTTP响应的构建遵循以下基本流程: + +1. **设置状态码** - 指定HTTP状态码和状态描述 +2. **添加响应头** - 设置必要的HTTP头部信息 +3. **设置响应体** - 添加响应内容数据 +4. **发送响应** - 调用`AnswerAsync()`完成响应发送 ### 8.1 设置响应状态 -```csharp showLineNumbers -e.Context.Response.SetStatus(200,"success"); -``` + ### 8.2 设置响应Header -```csharp showLineNumbers -e.Context.Response.AddHeader("key","value"); -``` - -或者 - -```csharp showLineNumbers -e.Context.Response.AddHeader(HttpHeaders.Origin, "*"); -``` + ### 8.3 设置响应内容 -```csharp showLineNumbers -e.Context.Response.SetContent("hello"); -``` + 或者直接返回`Json`、`Xml`、`Text`等内容。使用此快捷方式,会同时添加对应的`ContentType`Header。 -```csharp showLineNumbers -e.Context.Response.FromJson("{}"); -``` + + + @@ -294,86 +234,27 @@ e.Context.Response.FromJson("{}"); 当通过上述步骤,完成了响应体的构建后,即可使用`AnswerAsync`直接进行响应。 -例如:响应一个`hello`文本内容,代码大致如下 - -```csharp showLineNumbers -await e.Context.Response - .SetStatus(200, "success") - .AddHeader("key", "value")//如需要 - .FromText("hello") - .AnswerAsync(); -``` +例如:响应一个`hello`文本内容,代码大致如下: + + +当然可以链式调用API: + + + ### 8.5 插件响应Get请求 -```csharp showLineNumbers -public class MyHttpPlug1 : PluginBase, IHttpPlugin -{ - public async Task OnHttpRequest(IHttpSessionClient client, HttpContextEventArgs e) - { - var request = e.Context.Request;//http请求体 - var response = e.Context.Response;//http响应体 - - if (request.IsGet()&&request.UrlEquals("/success")) - { - //直接响应文字 - await response - .SetStatus(200, "success") - .FromText("Success") - .AnswerAsync();//直接回应 - Console.WriteLine("处理/success"); - return; - } - - //无法处理,调用下一个插件 - await e.InvokeNext(); - } -} -``` + ### 8.6 响应文件请求 -```csharp showLineNumbers -public class MyHttpPlug2 : PluginBase, IHttpPlugin -{ - public async Task OnHttpRequest(IHttpSessionClient client, HttpContextEventArgs e) - { - var request = e.Context.Request;//http请求体 - var response = e.Context.Response;//http响应体 - if (request.IsGet() && request.UrlEquals("/file")) - { - try - { - //直接回应文件。 + + + - var fileInfo = new FileInfo(@"D:\System\Windows.iso"); - var fileName = fileInfo.Name;//可以重新制定文件名称,届时,header中会添加Content-Disposition内容 - var maxSpeed = 1024 * 1024;//最大传输速度 - var bufferLength = 1024 * 64;//一般该值越大,效率越高,但同时内存占用也更大 - var autoGzip = true;//自动判断是否应用gzip压缩。 - - await response - .SetStatusWithSuccess()//必须要有状态 - .FromFileAsync(fileInfo, e.Context.Request, fileName, maxSpeed, bufferLength, autoGzip); - - //或者直接使用HttpContext - //await e.Context.FromFileAsync(fileInfo, fileName, maxSpeed, bufferLength, autoGzip); - } - catch (Exception ex) - { - await response.SetStatus(403, "error") - .FromText(ex.Message) - .AnswerAsync(); - } - - return; - } - await e.InvokeNext(); - } -} -``` + :::caution 注意 @@ -393,133 +274,51 @@ public class MyHttpPlug2 : PluginBase, IHttpPlugin ::: - - - - ### 8.7 响应页面请求 -```csharp showLineNumbers -public class MyHttpPlug3 : PluginBase, IHttpPlugin -{ - public async Task OnHttpRequest(IHttpSessionClient client, HttpContextEventArgs e) - { - var request = e.Context.Request;//http请求体 - var response = e.Context.Response;//http响应体 - if (request.IsGet() && request.UrlEquals("/html")) - { - //构建html - var sb = new StringBuilder(); - sb.Append(""); - sb.Append(""); - sb.Append(""); - sb.Append(" "); - sb.Append(" TouchSocket绚丽展示"); - sb.Append(" "); - sb.Append(""); - sb.Append(""); - sb.Append("

TouchSocket

"); - sb.Append(""); - sb.Append(""); - - //回应html - await response - .SetStatusWithSuccess()//必须要有状态 - .SetContentTypeByExtension(".html") - .SetContent(sb.ToString()) - .AnswerAsync(); - return; - } - - await e.InvokeNext(); - } -} -``` - + + + ## 九、进阶响应操作 ### 9.1 响应有长度大数据 当响应的数据,在响应时,已知数据长度的话,可以使用此方法。 -```csharp showLineNumbers -var request = e.Context.Request;//http请求体 -var response = e.Context.Response;//http响应体 - -//先设置需要响应的地方 -response.SetStatus(200, "success"); -//然后设置数据总长度 -response.ContentLength = 1024 * 1024; - -for (int i = 0; i < 1024; i++) -{ - //将数据持续写入 - await response.WriteAsync(new byte[1024]); -} -``` + ### 9.2 响应不知长度数据(Chunk模式) 当响应的数据,在响应时,不知数据长度的话,可以使用此方法。 -```csharp showLineNumbers -var request = e.Context.Request;//http请求体 -var response = e.Context.Response;//http响应体 - -//先设置需要响应的地方 -response.SetStatus(200, "success"); -//设置使用Chunk模式 -response.IsChunk = true; - -for (int i = 0; i < 1024; i++) -{ - //将数据持续写入 - await response.WriteAsync(new byte[1024]); -} - -//在正式数据传输完成后,调用此方法,客户端才知道数据结束了 -await response.CompleteChunkAsync(); -``` - + -## 九、创建加密Ssl的HttpsService -Https服务器,和http服务器几乎一样,只不过增加了一个Ssl的配置。具体的Ssl配置,请参考:[TcpService Ssl](./tcpservice.mdx)。 +## 九、HTTPS安全服务 -```csharp showLineNumbers -.SetServiceSslOption(new ServiceSslOption() -{ - Certificate = new X509Certificate2("Socket.pfx", "Socket"), - SslProtocols = SslProtocols.Tls12 -}) -``` +### 9.1 启用SSL/TLS加密 -## 十、本文示例Demo +`HttpService`支持完整的`HTTPS`功能,只需在配置中添加`SSL`选项即可,详细配置可看[Tcp服务器](./tcpservice.mdx) 。 - - \ No newline at end of file +例如: + + + +### 9.2 注意事项 + +- **证书格式** - 支持.pfx、.crt等标准证书格式 +- **端口选择** - HTTPS服务通常使用443端口 +- **混合部署** - 可以同时运行HTTP(80)和HTTPS(443)服务 + +## 十、示例项目 + +以下是完整的`HttpService`使用示例,可以帮助您快速上手: + + + + \ No newline at end of file diff --git a/handbook/docs/httpstaticpageplugin.mdx b/handbook/docs/httpstaticpageplugin.mdx index 57db79c99..577662646 100644 --- a/handbook/docs/httpstaticpageplugin.mdx +++ b/handbook/docs/httpstaticpageplugin.mdx @@ -4,8 +4,9 @@ title: 静态页面插件 --- import { TouchSocketHttpDefinition } from "@site/src/components/Definition.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; + -### 定义 @@ -34,24 +35,7 @@ import { TouchSocketHttpDefinition } from "@site/src/components/Definition.js"; 在创建`HttpService`实例后,只需要使用`UseHttpStaticPage`插件,然后指定根文件夹路径即可。 -```csharp showLineNumbers -var service = new HttpService(); - -var config = new TouchSocketConfig(); -config.SetListenIPHosts(new IPHost[] { new IPHost(7789) }) - .ConfigurePlugins(a => - { - a.UseHttpStaticPage()//添加静态页面文件夹 - .AddFolder("../../../../../api"); - - }); - -await service.SetupAsync(config); - -await service.StartAsync(); - -Console.WriteLine("Http服务器已启动"); -``` + :::tip 提示 @@ -63,19 +47,7 @@ Console.WriteLine("Http服务器已启动"); 在使用`UseHttpStaticPage`插件时,可以指定请求资源定向。例如,当请求的`URL`为`/api`时,将重定向到`/api/index.html`。 -```csharp {4-7} showLineNumbers -a.UseHttpStaticPage() -.SetNavigateAction(request => -{ - if (request.RelativeURL.EndsWith("/")) - { - return $"{request.RelativeURL}/index.html"; - } - //此处可以设置重定向 - return request.RelativeURL; -}) -.AddFolder("api/"); -``` + :::info 信息 @@ -87,14 +59,7 @@ a.UseHttpStaticPage() 可以通过配置响应头,例如添加自定义头。 -```csharp {2-5} showLineNumbers -a.UseHttpStaticPage() -.SetResponseAction(response => -{ - //可以设置响应头 -}) -.AddFolder("api/"); -``` + ### 4.4 配置ContentType @@ -102,14 +67,7 @@ a.UseHttpStaticPage() 但是,也可以通过配置`ContentTypeMapper`来覆盖默认的映射关系。 -```csharp {2-5} showLineNumbers -a.UseHttpStaticPage() -.SetContentTypeProvider(mapper => -{ - mapper.Add(".txt", "text/plain"); -}) -.AddFolder("api/"); -``` + :::info 信息 diff --git a/handbook/docs/ilog.mdx b/handbook/docs/ilog.mdx index c63c76ac5..80bfac83d 100644 --- a/handbook/docs/ilog.mdx +++ b/handbook/docs/ilog.mdx @@ -4,8 +4,9 @@ title: 日志记录器 --- import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; +import CardLink from "@site/src/components/CardLink.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; -### 定义 @@ -67,14 +68,9 @@ import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; ## 四、控制台日志记录器 -控制台日志记录器,是将日志信息输出到控制台。因为直接输出的控制台在当前进程中只会有一个,所以不可以多个创建控制台日志,只能使用其默认实例。 +控制台日志记录器,是将日志信息输出到控制台。因为直接输出的控制台在当前进程中只会有一个,所以不可以多个创建控制台日志,只能使用其默认实例。 -```csharp {1} showLineNumbers -var logger = ConsoleLogger.Default; -logger.Info("Message"); -logger.Warning("Warning"); -logger.Error("Error"); -``` + ### 4.1 可配置属性 @@ -109,20 +105,17 @@ logger.Error("Error"); ### 5.2 默认配置 -在默认配置下,文件日志记录器具有如下特点: +在默认配置下,文件日志记录器具有如下特点: -- **最大日志文件大小**:每个日志文件的最大容量默认为1MB。 -- **日志文件滚动机制**:当达到最大容量时,系统会自动创建一个新的日志文件,并按顺序编号。 -- **日志文件存储路径**:日志文件默认存储在可执行文件所在的目录下。 -- **目录结构**:在默认路径下,首先创建一个名为“logs”的目录;在此目录内,再根据当前日期(格式为“[yyyy-MM-dd]”)创建相应的子目录。 -- **日志文件命名**:最终的日志文件按照“0001.log”的格式命名,并根据需要添加序号区分不同的滚动文件。 +- **最大日志文件大小**:每个日志文件的最大容量默认为1MB。 +- **日志文件滚动机制**:当达到最大容量时,系统会自动创建一个新的日志文件,并按顺序编号。 +- **日志文件存储路径**:日志文件默认存储在可执行文件所在的目录下。 +- **目录结构**:在默认路径下,首先创建一个名为"logs"的目录;在此目录内,再根据当前日期(格式为"[yyyy-MM-dd]")创建相应的子目录。 +- **日志文件命名**:最终的日志文件按照"0001.log"的格式命名,并根据需要添加序号区分不同的滚动文件。 -```csharp showLineNumbers -var logger = new FileLogger(); -logger.Info("Message"); -logger.Warning("Warning"); -logger.Error("Error"); -``` + + +日志文件的输出路径示例: ```csharp showLineNumbers logs\[2024-09-08]\0000.log @@ -132,76 +125,34 @@ logs\[2024-09-08]\0000.log 可以直接对FileLogger实例的MaxSize属性进行设置。 -```csharp {3} showLineNumbers -var logger = new FileLogger() -{ - MaxSize = 1024 * 1024 * 2 -}; -``` + ### 5.4 配置文件路径 可以通过设置FileLogger的CreateLogFolder**回调属性**来指定日志文件的存储路径。 -默认配置的实现如下: +默认配置的实现如下: -```csharp {5} showLineNumbers -var logger = new FileLogger() -{ - CreateLogFolder = (logLevel) => - { - return $"logs\\{DateTime.Now:[yyyy-MM-dd]}"; - } -}; -``` + -例如,你可以实现按**日志类型**,来分类输出日志到单独文件。 +例如,你可以实现按**日志类型**,来分类输出日志到单独文件。 -```csharp {5} showLineNumbers -var logger = new FileLogger() -{ - CreateLogFolder = (logLevel) => - { - return $"logs\\{DateTime.Now:[yyyy-MM-dd]}\\{logLevel}"; - } -}; -``` + ### 5.5 使用容器时配置 -当文件日志被添加到容器中时,可以通过如下方式进行配置。 +当文件日志被添加到容器中时,可以通过如下方式进行配置。 -```csharp {3-7} showLineNumbers -.ConfigureContainer(a => -{ - a.AddFileLogger(fileLogger => - { - fileLogger.MaxSize = 1024 * 1024; - fileLogger.LogLevel = LogLevel.Debug; - }); -}) -``` + 或者 -```csharp {5-9} showLineNumbers -.ConfigureContainer(a => -{ - a.AddLogger(logger => - { - logger.AddFileLogger(fileLogger => - { - fileLogger.MaxSize = 1024 * 1024; - fileLogger.LogLevel = LogLevel.Debug; - }); - }); -}) -``` + ## 六、简易日志记录器 -简易日志记录器,是框架提供的一个便捷的字符串日志记录器,它将日志信息输出到回调委托中,方便使用者在需要时,直接输出日志信息。 +简易日志记录器,是框架提供的一个便捷的字符串日志记录器,它将日志信息输出到回调委托中,方便使用者在需要时,直接输出日志信息。 ### 6.1 可配置属性 @@ -211,174 +162,71 @@ var logger = new FileLogger() | DateTimeFormat | 日志输出时间戳格式 | "yyyy-MM-dd HH:mm:ss ffff" | -```csharp {1} showLineNumbers -var logger = new EasyLogger(LoggerOutput); -logger.Info("Message"); -logger.Warning("Warning"); -logger.Error("Error"); -``` + -```csharp showLineNumbers -private void LoggerOutput(string loggerString) -{ - Console.WriteLine(loggerString); - - //或者如果是winform程序,可以直接输出到TextBox -} -``` + ## 七、自定义日志记录器 -框架提供了ILog接口,使用者可以自定义日志记录器。只需要实现ILog接口即可。 +框架提供了ILog接口,使用者可以自定义日志记录器。只需要实现ILog接口即可。 -```csharp {5-8} showLineNumbers -class MyLogger : ILog -{ - public LogLevel LogLevel { get; set; } = LogLevel.Debug; - - public void Log(LogLevel logLevel, object source, string message, Exception exception) - { - //此处可以自由实现逻辑。 - } -} -``` + :::caution 注意事项 -自定义实现日志,必须要满足,无论任何情况,日志记录处必须无任何异常抛出,因为日志记录时,可能是在框架异常时执行的。如果此时再抛出异常,可能会导致整个程序崩溃。 +自定义实现日志,必须要满足,无论任何情况,日志记录处必须无任何异常抛出,因为日志记录时,可能是在框架异常时执行的。如果此时再抛出异常,可能会导致整个程序崩溃。 ::: ### 7.1 集成Log4net -Log4net是一个非常优秀的日志记录框架,可以非常方便的实现日志记录。接下来,我们以Log4net为例,介绍如何将Log4net集成到框架日志中。 +Log4net是一个非常优秀的日志记录框架,可以非常方便的实现日志记录。接下来,我们以Log4net为例,介绍如何将Log4net集成到框架日志中。 -首先,需要引用Log4net的NuGet包,版本使用最新即可。 +首先,需要引用Log4net的NuGet包,版本使用最新即可。 ``` Install-Package log4net ``` -然后新建一个类文件Mylog4netLogger,继承TouchSocket.Core.ILog接口,并实现其方法。 +然后新建一个类文件Mylog4netLogger,继承TouchSocket.Core.ILog接口,并实现其方法。 -```csharp showLineNumbers -internal class Mylog4netLogger : TouchSocket.Core.ILog -{ - private readonly log4net.ILog m_logger; + - public Mylog4netLogger() - { - this.m_logger = log4net.LogManager.GetLogger("Test"); - } - - public LogLevel LogLevel { get; set; } - - public void Log(LogLevel logLevel, object source, string message, Exception exception) - { - //此处就是实际的日志输出 - - switch (logLevel) - { - case LogLevel.Trace: - this.m_logger.Debug(message, exception); - break; - - case LogLevel.Debug: - this.m_logger.Debug(message, exception); - break; - - case LogLevel.Info: - this.m_logger.Info(message, exception); - break; - - case LogLevel.Warning: - this.m_logger.Warn(message, exception); - break; - - case LogLevel.Error: - this.m_logger.Error(message, exception); - break; - - case LogLevel.Critical: - this.m_logger.Error(message, exception); - break; - - case LogLevel.None: - default: - break; - } - } -} -``` - -然后可能还需要一些关于log4net的日志配置,此处省略了。 +然后可能还需要一些关于log4net的日志配置,此处省略了。 最后就可以直接使用Mylog4netLogger了。 ## 八、使用日志记录器 -框架提供的日志记录器比较简单,可以使用实例直接使用。 +框架提供的日志记录器比较简单,可以使用实例直接使用。 ### 8.1 直接使用 -例如:控制台日志 +例如:控制台日志 -```csharp showLineNumbers -var logger = ConsoleLogger.Default; -logger.Info("Message"); -logger.Warning("Warning"); -logger.Error("Error"); -``` + ### 8.2 注入容器使用 -框架提供了容器注入日志记录器,以TcpService为例,使用方式如下: +框架提供了容器注入日志记录器,以TcpService为例,使用方式如下: -需要在配置容器时(ConfigureContainer),添加日志记录器。 +需要在配置容器时(ConfigureContainer),添加日志记录器。 -```csharp {1,4-6,11} showLineNumbers -var service = new TcpService(); -service.Received = async (client, e) => -{ - //从客户端收到信息 - var mes = e.ByteBlock.Span.ToString(Encoding.UTF8); - service.Logger.Info($"服务器已从{client.Id}接收到信息:{mes}"); + - await client.SendAsync(mes);//将收到的信息直接返回给发送方 -}; +然后在服务中就可以使用日志记录器了。 -await service.SetupAsync(new TouchSocketConfig()//载入配置 - .SetListenIPHosts(7789) - .ConfigureContainer(a => - { - a.AddConsoleLogger(); - //a.AddFileLogger(); - }) - .ConfigurePlugins(a => - { - //a.Add();//此处可以添加插件 - })); -await service.StartAsync();//启动 -``` + :::tip 提示 -使用容器注入日志记录器时,不可以Add多个日志记录器。因为他们使用的同一个接口注册的,后面注册的日志记录器会覆盖前面注册的日志记录器。 +使用容器注入日志记录器时,不可以Add多个日志记录器。因为他们使用的同一个接口注册的,后面注册的日志记录器会覆盖前面注册的日志记录器。 ::: -### 8.2 注入多日志记录器 +### 8.3 注入多日志记录器 -为实现多日志记录器,需要使用日志组(LoggerGroup),方法如下: +为实现多日志记录器,需要使用日志组(LoggerGroup),方法如下: -```csharp {5-6} showLineNumbers -.ConfigureContainer(a => -{ - a.AddLogger(logger => - { - logger.AddConsoleLogger(); - logger.AddFileLogger(); - }); -}) -``` \ No newline at end of file + \ No newline at end of file diff --git a/handbook/docs/independentusedatahandlingadapter.mdx b/handbook/docs/independentusedatahandlingadapter.mdx index b749179ee..d98447297 100644 --- a/handbook/docs/independentusedatahandlingadapter.mdx +++ b/handbook/docs/independentusedatahandlingadapter.mdx @@ -6,7 +6,7 @@ title: 独立使用适配器 import BilibiliCard from '@site/src/components/BilibiliCard.js'; import { TouchSocketDefinition } from "@site/src/components/Definition.js"; -### 定义 + diff --git a/handbook/docs/ioc.mdx b/handbook/docs/ioc.mdx index d688afafb..11c674f20 100644 --- a/handbook/docs/ioc.mdx +++ b/handbook/docs/ioc.mdx @@ -4,265 +4,160 @@ title: 依赖注入容器(IOC) --- import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; +import CardLink from "@site/src/components/CardLink.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; -### 定义 ## 一、说明 -依赖注入,是指程序运行过程中,如果需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部的注入。 +依赖注入(Dependency Injection,简称DI),是指程序运行过程中,如果需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部的注入。 通俗来讲,就是把有依赖关系的类放到容器中,然后在我们需要这些类时,容器自动解析出这些类的实例。 -依赖注入最大的好处时实现类的解耦,利于程序拓展、单元测试、自动化模拟测试等。依赖注入的英文为:`Dependency Injection`,简称 `DI`。(说明来自网络) +依赖注入最大的好处是实现类的解耦,利于程序拓展、单元测试、自动化模拟测试等。 -`TouchSocket`内置了`Container`容器。 +`TouchSocket`内置了`Container`容器,提供了完整的依赖注入功能。 ## 二、特点 -- 支持构造函数、属性、方法三种注入方式,可以选择其中部分生效。 -- 支持`Singleton`、`Transient`两种生命周期。 -- 支持单接口,多实现注入。 -- 支持当获取类型是可实例类型时,即使不注册,也能成功构造。 -- 支持默认参数注入。 -- 支持构建参数注入。 -- 支持标签参数注入。 -- 支持泛型注入。 -- 支持Object注入。 -- 支持**源生成注入**,全面接入AOT模式。 +- 支持构造函数注入 +- 支持三种生命周期:`Singleton`(单例)、`Scoped`(作用域)、`Transient`(瞬态) +- 支持接口到实现类的映射注册 +- 支持泛型类型注册 +- 支持Key标识的多实例注册 +- 支持工厂方法注册 +- 兼容ASP.NET Core的`IServiceCollection` +- 支持作用域解析器(IScopedResolver) +- 支持AOT(Ahead-of-Time Compilation)。 -## 三、注入方式 +## 三、基础使用 ### 3.1 构造函数注入 -【定义类型】 - -```csharp showLineNumbers -class MyClass1 -{ - -} - -class MyClass2 -{ - public MyClass2(MyClass1 myClass1) - { - this.MyClass1 = myClass1; - } - - public MyClass1 MyClass1 { get; } -} -``` - -【注册和获取】 - -```csharp showLineNumbers -var container = new Container(); -container.RegisterSingleton(); -container.RegisterSingleton(); - -var myClass1 = container.Resolve(); -var myClass2 = container.Resolve(); -``` - -### 3.2 属性注入 - -使用`DependencyInject`标记属性,即可注入。 +容器会自动解析构造函数的参数依赖,并注入所需的实例。 【定义类型】 -```csharp showLineNumbers -class MyClass3 -{ - /// - /// 直接按类型,默认方式获取 - /// - [DependencyInject] - public MyClass1 MyClass1 { get; set; } + - /// - /// 获得指定类型的对象,然后赋值到object - /// - [DependencyInject(typeof(MyClass2))] - public object MyClass2 { get; set; } +【注册和解析】 - /// - /// 按照类型+Key获取 - /// - [DependencyInject("key")] - public MyClass1 KeyMyClass1 { get; set; } -} -``` - -【注册和获取】 - -```csharp showLineNumbers -var container = new Container(); -container.RegisterSingleton(); -container.RegisterSingleton("key"); -container.RegisterSingleton(); - -container.RegisterSingleton(); - -var myClass3 = container.Resolve(); -``` + :::tip 提示 -`DependencyInject`特性中,`Type`和`Key`,可以同时使用,也可以只使用其中一个。 +容器会自动选择参数最多的构造函数进行实例化。 -::: +::: -### 3.3 方法注入 +## 四、生命周期 -使用`DependencyInject`标记属性,即可对方法注入。 +`TouchSocket`支持三种生命周期,不同的生命周期决定了实例的创建和销毁策略。 -【定义类型】 +### 4.1 Singleton(单例) -```csharp showLineNumbers -class MyClass4 -{ - public MyClass1 MyClass1 { get;private set; } +单例生命周期意味着在整个容器生命周期内,该类型只会创建一个实例,所有解析操作都会返回同一个实例。 - [DependencyInject] - public void MethodInject(MyClass1 myClass1) - { - this.MyClass1 = myClass1; - } -} -``` + -【注册和获取】 +**使用场景**:适用于无状态的服务、配置对象、日志服务等。 -```csharp showLineNumbers -var container = new Container(); -container.RegisterSingleton(); -container.RegisterSingleton(); +### 4.2 Transient(瞬态) -var myClass4 = container.Resolve(); -``` +瞬态生命周期意味着每次解析都会创建一个新的实例。 -:::tip 提示 + -`DependencyInject`特性,也可以在方法注入时,对参数进行使用。 +**使用场景**:适用于轻量级、有状态的对象,或需要确保每次使用都是全新实例的服务。 -::: +### 4.3 Scoped(作用域) -### 3.4 注入筛选 +作用域生命周期意味着在同一个作用域内,解析会返回同一个实例;在不同作用域内,会创建不同的实例。 -对于一个类,默认情况下,会支持`构造函数`、`属性`、`方法`三种注入方式。但是,当明确知道该类型仅会使用其中部分方式注入时,可以设置注入类型,以此节约性能。 + -```csharp {4} -/// -/// 让MyClass1仅支持构造函数和属性注入 -/// -[DependencyType(DependencyType.Constructor | DependencyType.Property)] -class MyClass1 -{ - -} -``` +**使用场景**:适用于需要在特定范围内共享状态的对象,如数据库上下文(DbContext)、HTTP请求处理器等。 :::info 备注 -`Constructor`构造函数配置不管配不配置,默认都是支持的。 +`Container`默认支持`Scoped`生命周期。如果需要与ASP.NET Core完全兼容的Scoped语义,可以使用`TouchSocket.Core.DependencyInjection`包中的`AspNetCoreContainer`。 -::: +::: -## 四、源生成IOC容器 +## 五、高级功能 -使用源生成IOC容器,能实现100%代码静态生成。能够有效解决运行时代码效率问题和AOT反射问题。 +### 5.1 使用Key注册 -然后声明自己的容器,遵循如下: +支持为同一类型注册多个实例,使用不同的Key进行区分。 -1. 必须继承`ManualContainer`。 -2. 必须使用`partial`修饰为部分类。 -3. 必须添加特性`GeneratorContainer`。 + -```csharp showLineNumbers - [GeneratorContainer] - partial class MyContainer : ManualContainer - { +### 5.2 使用工厂方法注册 - } -``` +可以使用自定义工厂方法来创建实例,获得更灵活的控制。 -### 4.1 注册类型 + -注册类型,可以直接添加特性即可: +### 5.3 接口映射 -```csharp showLineNumbers -[AutoInjectForSingleton]//将声明的类型直接标识为单例注入 -class MyClass12 -{ +支持将接口映射到具体的实现类。 -} + -[AutoInjectForTransient]//将声明的类型直接标识为瞬态注入 -class MyClass13 -{ +### 5.4 泛型类型注册 -} -``` +支持注册泛型类型定义,容器会自动处理具体的泛型参数。 -### 4.2 注册现有类型 + -现有类型已经完成声明,所以无法直接添加特性。所以只能将特性添加在`ManualContainer`的继承类上。 +## 六、使用AspNetCoreContainer -```csharp showLineNumbers -[AddSingletonInject(typeof(IInterface10), typeof(MyClass10))]//直接添加现有类型为单例 -[AddTransientInject(typeof(IInterface11), typeof(MyClass11))]//直接添加现有类型为瞬态 -[GeneratorContainer] -partial class MyContainer : ManualContainer -{ +如果需要与ASP.NET Core的依赖注入系统集成,可以使用`TouchSocket.Core.DependencyInjection`包中的`AspNetCoreContainer`。 -} -``` +首先,安装NuGet包:`TouchSocket.Core.DependencyInjection`。 -:::tip 提示 +然后使用`AspNetCoreContainer`替代`Container`: -源生成IOC容器,同样支持属性、方法、Key等注册配置,使用规则和第三节一致。 + -::: +在TouchSocket配置中使用: + + + +:::tip 优势 + +使用`AspNetCoreContainer`可以: +- 与ASP.NET Core的DI系统完全兼容 +- 支持所有ASP.NET Core的依赖注入特性 +- 实现服务注册的共享和复用 + +::: + +## 七、API参考 + +### 注册方法 + +| 方法 | 说明 | +|------|------| +| `RegisterSingleton()` | 注册单例服务 | +| `RegisterSingleton()` | 注册单例接口映射 | +| `RegisterTransient()` | 注册瞬态服务 | +| `RegisterTransient()` | 注册瞬态接口映射 | +| `RegisterScoped()` | 注册作用域服务 | +| `RegisterScoped()` | 注册作用域接口映射 | + +### 解析方法 + +| 方法 | 说明 | +|------|------| +| `Resolve()` | 解析指定类型的实例 | +| `Resolve(object key)` | 解析指定Key的实例 | +| `CreateScopedResolver()` | 创建作用域解析器 | -## 五、生命周期 +## 八、示例代码 -生命周期是对注入构造的实例的有效性而言的。`TouchSocket`支持两种生命周期。 - -- `Singleton`:单例注入,当注入,并且实例化以后,全局唯一实例。 -- `Transient`:瞬时注入,每次获取的实例都是新实例。 - -对于这两种,熟悉IOC的同学,相信都知道到。 - -:::tip 提示 - -`Container`默认情况下不支持`Scoped`生命周期。所以如果需要,可以安装`TouchSocket.Core.DependencyInjection`实现。 - -::: - -## 六、使用ServiceCollection - -首先,需要安装NuGet包:`TouchSocket.Core.DependencyInjection`。 - -然后直接使用`AspNetCoreContainer`替代`Container`。 - -```csharp showLineNumbers -static IContainer GetContainer() -{ - //return new Container();//默认IOC容器 - - return new AspNetCoreContainer(new ServiceCollection());//使用Aspnetcore的容器 -} -``` - -Config使用 - -```csharp showLineNumbers -var config=new TouchSocketConfig()//载入配置 - .UseAspNetCoreContainer(new ServiceCollection());//ServiceCollection可以使用现有的 -``` - -[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Core/IocConsoleApp) + \ No newline at end of file diff --git a/handbook/docs/ipackage.mdx b/handbook/docs/ipackage.mdx index 6b4bab520..75db61289 100644 --- a/handbook/docs/ipackage.mdx +++ b/handbook/docs/ipackage.mdx @@ -6,7 +6,7 @@ title: 包序列化模式 import CardLink from "@site/src/components/CardLink.js"; import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; -### 定义 + @@ -535,4 +535,4 @@ internal partial class MyGeneratorConvertPackage : PackageBase ## 八、本文示例Demo - \ No newline at end of file + \ No newline at end of file diff --git a/handbook/docs/jsonrpc.mdx b/handbook/docs/jsonrpc.mdx index 0ed23bad6..1cffbf169 100644 --- a/handbook/docs/jsonrpc.mdx +++ b/handbook/docs/jsonrpc.mdx @@ -1,436 +1,241 @@ --- id: jsonrpc -title: 产品及架构介绍 +title: JsonRpc 使用指南 --- import Tag from "@site/src/components/Tag.js"; import CardLink from "@site/src/components/CardLink.js"; import { TouchSocketJsonRpcDefinition } from "@site/src/components/Definition.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; + -### 定义 -## 一、说明 +## 一、概述 -JsonRpc是**通用**的Rpc规范,与**编程语言无关、操作系统无关**。详细说明请参阅[JsonRpc 2.0 官方文档](https://www.jsonrpc.org/specification),在TouchSocket中封装了**前后端**,使其使用更加方便、高效。 +JsonRpc 是一种**通用的 RPC 规范**,具有**跨编程语言、跨操作系统**的特性。详细规范请参阅 [JsonRpc 2.0 官方文档](https://www.jsonrpc.org/specification)。TouchSocket 对 JsonRpc 进行了完整封装,提供了**前后端一体化**的解决方案,使其使用更加方便、高效。 -目前支持`Tcp`、`Http`、`WebSocket`三种协议调用。 +### 1.1 支持的协议 + +目前支持以下三种基础协议进行 JsonRpc 调用: + +- **Tcp** 协议 +- **Http** 协议 +- **WebSocket** 协议 -## 二、特点: +## 二、核心特点 -- **异常反馈** 。 -- 插件支持。 -- 支持自定义类型。 -- 支持类型嵌套。 -- 支持js、Android等调用。 -- 支持服务器主动调用客户端 +- **异常反馈**:完善的异常处理和错误信息反馈机制 +- **插件支持**:灵活的插件扩展体系 +- **自定义类型**:支持自定义类型的序列化和反序列化 +- **类型嵌套**:支持复杂的类型嵌套结构 +- **跨平台调用**:支持 JavaScript、Android 等多平台调用 +- **反向调用**:支持服务器主动调用客户端 -## 三、定义服务 +## 三、定义 RPC 服务 -在**服务器**端中新建一个类,继承于`SingletonRpcServer`类(或实现`ISingletonRpcServer`),然后在该类中写**公共方法**,并用**JsonRpc**特性标签标记。 +### 3.1 服务端实现 + +在**服务器端**创建一个类,继承 `SingletonRpcServer` 类(或实现 `ISingletonRpcServer` 接口),然后在该类中定义**公共方法**,并使用 **JsonRpc** 特性标记。 -```csharp showLineNumbers -public partial class JsonRpcServer : SingletonRpcServer -{ - /// - /// 使用调用上下文。 - /// 可以从上下文获取调用的SessionClient。从而获得IP和Port等相关信息。 - /// - /// - /// - /// - [JsonRpc(MethodInvoke =true)] - public string TestGetContext(ICallContext callContext, string str) - { - if (callContext.Caller is IHttpSessionClient SessionClient) - { - if (SessionClient.Protocol == Protocol.WebSocket) - { - Console.WriteLine("WebSocket请求"); - var client = callContext.Caller as IHttpSessionClient; - var ip = client.IP; - var port = client.Port; - Console.WriteLine($"WebSocket请求{ip}:{port}"); - } - else - { - Console.WriteLine("HTTP请求"); - var client = callContext.Caller as IHttpSessionClient; - var ip = client.IP; - var port = client.Port; - Console.WriteLine($"HTTP请求{ip}:{port}"); - } - } - else if (callContext.Caller is ITcpSessionClient) - { - Console.WriteLine("Tcp请求"); - var client = callContext.Caller as ITcpSessionClient; - var ip = client.IP; - var port = client.Port; - Console.WriteLine($"Tcp请求{ip}:{port}"); - } - return "RRQM" + str; - } + - [JsonRpc(MethodInvoke = true)] - public JObject TestJObject(JObject obj) - { - return obj; - } - - [JsonRpc(MethodInvoke = true)] - public string TestJsonRpc(string str) - { - return "RRQM" + str; - } -} -``` +### 3.2 调用键说明 :::info 备注 -设置`MethodInvoke = true`,即以方法名作为调用键,这也是JsonRpc规范所规定。但同时框架内部还支持另一种方式,即默认情况下会使用方法的**全名称小写**作为调用键(即:命名空间.类名.方法名) +当设置 `MethodInvoke = true` 时,将使用方法名作为调用键,这是 `JsonRpc` 规范的标准方式。 + +框架同时支持另一种方式:默认情况下使用方法的**全名称小写**作为调用键(格式:命名空间+类名+方法名)。 ::: ## 四、启动服务器 -JsonRpc支持多个基本协议的服务器,所以下面将一一介绍。 - -更多注册Rpc的方法请看[注册Rpc服务](./rpcregister.mdx) - -### 4.1 以Tcp为基础协议 - -当以Tcp为基础协议时,支持Tcp的任何操作。包括但不限于`设置适配器`等。 - -下列代码创建的就是一个最普通Tcp协议下的JsonRpc服务器。该服务支持任何未处理的Tcp协议的JsonRpc数据包调用。 - -```csharp showLineNumbers -var service = new TcpService(); -await service.SetupAsync(new TouchSocketConfig() - .SetListenIPHosts(7705) - .ConfigureContainer(a => - { - a.AddRpcStore(store => - { - store.RegisterServer(); - }); - }) - .ConfigurePlugins(a => - { - /* - 使用tcp服务器的时候,默认情况下会把所有连接的协议都转换为JsonRpcUtility.TcpJsonRpc。 - 这样所有的数据都会被尝试解释为JsonRpc。 - 如果不需要该功能,可以调用NoSwitchProtocol()。 - */ - a.UseTcpJsonRpc(); - })); - -await service.StartAsync(); -``` - -:::caution 注意 - -因为上述服务器中没有使用任何适配器,所以在实际使用中,可能会发生数据包粘包、分包等问题。所以不建议直接使用。要想投入生产使用,最简单也建议使用`.SetTcpDataHandlingAdapter(() => new TerminatorPackageAdapter("\r\n"))`换行符分割等适配器。 - -::: - - -### 4.2 使用Http协议服务器 - -创建后,如果想使用Http调用,只需要以Post方式,将调用Json字符串路由到设定路由地址即可(下文示例“/jsonRpc”)。 - -```csharp showLineNumbers -var service = new HttpService(); - -await service.SetupAsync(new TouchSocketConfig() - .SetListenIPHosts(7706) - .ConfigureContainer(a => - { - a.AddRpcStore(store => - { - store.RegisterServer(); - }); - }) - .ConfigurePlugins(a => - { - a.UseHttpJsonRpc() - .SetJsonRpcUrl("/jsonRpc"); - })); - -await service.StartAsync(); -``` - -### 4.3 使用WebSocket协议服务器 - -如果想使用Websocket调用,只需要以**文本**形式,传递到服务器即可。 - -```csharp showLineNumbers -var service = new HttpService(); - -await service.SetupAsync(new TouchSocketConfig() - .SetListenIPHosts(7707) - .ConfigureContainer(a => - { - a.AddRpcStore(store => - { - store.RegisterServer(); - }); - }) - .ConfigurePlugins(a => - { - a.UseWebSocket() - .SetWSUrl("/ws"); - - a.UseWebSocketJsonRpc() - .SetAllowJsonRpc((SessionClient, context) => - { - //此处的作用是,通过连接的一些信息判断该ws是否执行JsonRpc。 - //当然除了此处可以设置外,也可以通过SessionClient.SetJsonRpc(true)直接设置。 - return true; - }); - })); - -await service.StartAsync(); -``` +JsonRpc 支持多种基础协议的服务器,下面将分别介绍不同协议的启动方式。 :::tip 提示 -`WebSocket`协议服务器和`Http`协议服务器可以合并为一个。 +更多注册 RPC 服务的方法请参考:[注册 Rpc 服务](./rpcregister.mdx) -::: +::: -## 五、通用调用 +### 4.1 Tcp 协议服务器 -因为`JsonRpc`是通用调用协议,所以只要**适配基础协议**,即可直接使用`Json`字符串调用。 +当使用 Tcp 作为基础协议时,支持 Tcp 的所有操作特性,包括但不限于**设置适配器**、**配置连接参数**等。 -以下字符串只是示例,具体的method参数,应当遵循当前路由。 +下列代码创建了一个基于 Tcp 协议的 JsonRpc 服务器,该服务器可以处理所有符合 Tcp 协议的 JsonRpc 数据包调用。 -### 5.1 Tcp协议直接调用 - -在`Tcp`协议时,按照适配器,选择性的是否以`\r\n`结尾。 - -```csharp showLineNumbers -{"jsonrpc": "2.0", "method": "testjsonrpc", "params":["RRQM"], "id": 1} -``` - -### 5.2 Http协议直接调用 - -在`Http`协议时,以`Url+Post`方式即可 - -```csharp showLineNumbers -{"jsonrpc": "2.0", "method": "testjsonrpc", "params":["RRQM"], "id": 1} -``` - -### 5.3 Websocket协议直接调用 - -在`Websocket`协议时,以`文本类型`,直接发送到服务器即可。 - -```csharp showLineNumbers -{"jsonrpc": "2.0", "method": "testjsonrpc", "params":["RRQM"], "id": 1} -``` + -## 六、客户端直接调用 +### 4.2 Http 协议服务器 -框架内部提供了`JsonRpc`的专属客户端,可以直接调用,也可以生成代理调用。下列将详细介绍。 +创建 Http 协议服务器后,客户端可以通过 **POST 方式**将 JsonRpc 调用字符串路由到指定的路由地址(下文示例为 "/jsonRpc")。 -### 6.1 Tcp协议 + -```csharp showLineNumbers -var client = new TcpJsonRpcClient(); -await client.SetupAsync(new TouchSocketConfig() - .SetRemoteIPHost("127.0.0.1:7705")); -await client.ConnectAsync(); +### 4.3 WebSocket 协议服务器 -string result = client.InvokeT("TestJsonRpc", InvokeOption.WaitInvoke, "RRQM"); -``` +使用 WebSocket 协议时,只需将 JsonRpc 调用内容以**文本格式**发送到服务器即可。 -### 6.2 Http协议 - -```csharp showLineNumbers -var client = new HttpJsonRpcClient(); -await client.SetupAsync(new TouchSocketConfig() - .SetRemoteIPHost("http://127.0.0.1:7706/jsonrpc")); -await client.ConnectAsync(); - -string result = client.InvokeT("TestJsonRpc", InvokeOption.WaitInvoke, "RRQM"); -``` - -### 6.3 Websocket协议 - -```csharp showLineNumbers -var client = new WebSocketJsonRpcClient(); -await client.SetupAsync(new TouchSocketConfig() - .SetRemoteIPHost("ws://127.0.0.1:7707/ws"));//此url就是能连接到websocket的路径。 -await client.ConnectAsync(); - -string result = client.InvokeT("TestJsonRpc", InvokeOption.WaitInvoke, "RRQM"); -``` - -### 6.4 生成代理调用 - -在服务器端,注册完服务后,就可以生成客户端调用代码了。详细的操作可以查看[服务端代理生成](./rpcgenerateproxy.mdx) - -```csharp {8-9} -a.AddRpcStore(store => -{ - store.RegisterServer(); - #if DEBUG - //下列代码,会生成客户端的调用代码。 - var codeString = store.GetProxyCodes("JsonRpcServerProxy", typeof(JsonRpcAttribute)); - File.WriteAllText("../../../JsonRpcServerProxy.cs", codeString); - #endif -}); -``` - -然后把生成的`.cs`文件复制(或链接)到客户端项目。然后客户端直接使用同名`扩展方法`即可调用。 - -```csharp showLineNumbers -var sum3 = client.TestJsonRpc("RRQM"); -``` - -### 6.5 使用DispatchProxy代理调用 - -使用`DispatchProxy`代理调用,可以实现动态代理,详情请看[DispatchProxy代理生成](./rpcgenerateproxy.mdx) - -首先,需要声明一个基类,用于通讯基础。 - -```csharp showLineNumbers -/// -/// 新建一个类,继承JsonRpcDispatchProxy,亦或者RpcDispatchProxy基类。 -/// 然后实现抽象方法,主要是能获取到调用的IRpcClient派生接口。 -/// -internal class MyJsonRpcDispatchProxy : JsonRpcDispatchProxy -{ - private readonly IJsonRpcClient m_client; - - public MyJsonRpcDispatchProxy() - { - this.m_client = CreateJsonRpcClientByTcp().GetFalseAwaitResult(); - } - - public override IJsonRpcClient GetClient() - { - return this.m_client; - } - - private static async Task CreateJsonRpcClientByTcp() - { - var client = new TcpJsonRpcClient(); - await client.SetupAsync(new TouchSocketConfig() - .SetRemoteIPHost("127.0.0.1:7705") - .SetTcpDataHandlingAdapter(() => new TerminatorPackageAdapter("\r\n"))); - await client.ConnectAsync(); - return client; - } -} -``` + :::tip 提示 -此处其他协议的`JsonRpc`也是完全支持的。 +`WebSocket` 协议服务器和 `Http` 协议服务器可以合并为同一个服务,同时支持两种协议。 ::: +## 五、通用调用方式 -然后按照服务,定义一个相同的代理接口。 +由于 `JsonRpc` 是通用调用协议,只要**适配基础协议**,即可直接使用 `Json` 字符串进行调用。 + +:::info 说明 + +以下字符串仅作示例,实际的 `method` 参数应当遵循当前的路由配置。 + +::: + +### 5.1 Tcp 协议直接调用 + +在 `Tcp` 协议下,根据适配器的配置,可选择是否以 `\r\n` 结尾。 ```csharp showLineNumbers -interface IJsonRpcServer -{ - [JsonRpc(MethodInvoke = true)] - string TestJsonRpc(string str); -} +{"jsonrpc": "2.0", "method": "testjsonrpc", "params":["RRQM"], "id": 1} ``` -最后生成代理,并按照接口调用。 +### 5.2 Http 协议直接调用 -```csharp {1} -var rpc = MyJsonRpcDispatchProxy.Create(); +在 `Http` 协议下,使用 `URL + POST` 方式发送请求。 -while (true) -{ - var result = rpc.TestJsonRpc(Console.ReadLine()); - Console.WriteLine(result); -} +```csharp showLineNumbers +{"jsonrpc": "2.0", "method": "testjsonrpc", "params":["RRQM"], "id": 1} ``` -## 七、反向Rpc(服务器主动调用客户端) +### 5.3 WebSocket 协议直接调用 -框架提供了反向`Rpc`,即**服务器主动调用客户端**。该功可以用于`Web`等多端。 +在 `WebSocket` 协议下,以**文本类型**直接发送到服务器。 -反向Rpc必须在全双工协议下使用,如`WebSocket`、`Tcp`等。 +```csharp showLineNumbers +{"jsonrpc": "2.0", "method": "testjsonrpc", "params":["RRQM"], "id": 1} +``` + + +## 六、专用客户端调用 + +框架内部提供了 `JsonRpc` 的专属客户端,可以直接进行调用,下面将详细介绍各协议客户端的创建和使用方式。 + +### 6.1 创建客户端 + +#### 6.1.1 Tcp 协议客户端 + + + +#### 6.1.2 Http 协议客户端 + + + + +#### 6.1.3 WebSocket 协议客户端 + + + + +### 6.2 直接调用 + +所有的 RPC 客户端都实现了 `IJsonRpcClient` 接口,因此调用方式是统一的。 + +在原生接口中,可以直接使用 `Invoke`、`InvokeT` 等方法进行调用。 + + + +### 6.3 代理调用 + +在服务器端注册完服务后,就可以生成客户端调用代码。详细的操作请查看 [服务端代理生成](./rpcgenerateproxy.mdx)。 + + +然后客户端直接使用同名**扩展方法**即可调用。 + + + +:::tip 提示 + +JsonRpc 支持 RPC 平台的所有功能,如**过滤器**、**调度器**以及所有代理调用方式。 + +::: + +## 七、反向 RPC(服务器主动调用客户端) + +框架提供了反向 `RPC` 功能,即**服务器主动调用客户端**。该功能可以用于 `Web` 等多端场景。 + +:::info 注意 + +反向 RPC 必须在全双工协议下使用,如 `WebSocket`、`Tcp` 等。 + +::: + +### 7.1 使用步骤 具体使用如下: -首先,需要在*客户端*像常规`Rpc`一样声明一个`Rpc`服务。然后需要使用`JsonRpc`特性表示。 +**第一步:** 在客户端像常规 `RPC` 一样声明一个 `RPC` 服务,并使用 `JsonRpc` 特性标记。 -```csharp {3} showLineNumbers -public partial class ReverseJsonRpcServer : SingletonRpcServer -{ - [JsonRpc(MethodInvoke = true)] - public int Add(int a, int b) - { - return a + b; - } -} -``` + -然后注册服务。 +**第二步:** 注册服务。 -```csharp {5-8} showLineNumbers -var jsonRpcClient = new WebSocketJsonRpcClient(); -await jsonRpcClient.SetupAsync(new TouchSocketConfig() - .ConfigureContainer(a => - { - a.AddRpcStore(store => - { - store.RegisterServer(); - }); - }) - .SetRemoteIPHost("ws://127.0.0.1:7707/ws"));//此url就是能连接到websocket的路径。 -await jsonRpcClient.ConnectAsync(); -``` + -在服务器端中,拿到`IHttpSessionClient`对象。然后调用`GetJsonRpcActionClient`扩展方法获取到`IJsonRpcClient`。然后调用`Invoke`等。 +**第三步:** 在服务器端获取 `IHttpSessionClient` 对象,然后调用 `GetJsonRpcActionClient` 扩展方法获取 `IJsonRpcClient`,最后调用 `Invoke` 等方法。 -下列示例演示的是,当`WebSocket`连接上时,服务器主动调用客户端。 +下列示例演示了当 `WebSocket` 连接建立时,服务器主动调用客户端的场景。 -```csharp {8,10} showLineNumbers -class MyPluginClass : PluginBase, IWebSocketHandshakedPlugin -{ - public async Task OnWebSocketHandshaked(IHttpSessionClient client, HttpContextEventArgs e) - { - try - { - //获取JsonRpcActionClient,用于执行反向Rpc - var jsonRpcClient = client.GetJsonRpcActionClient(); + - var result = await jsonRpcClient.InvokeTAsync("Add", InvokeOption.WaitInvoke, 10, 20); - Console.WriteLine($"反向调用成功,结果={result}"); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - await e.InvokeNext(); - } -} -``` +:::tip 提示 -:::tip - -反向JsonRpc也能使用代理。 +反向 JsonRpc 也支持使用代理调用方式。 ::: -## 八、本文示例Demo - +## 八、序列化配置及性能优化 + +### 8.1 使用 Newtonsoft.Json + +框架内部默认使用 `Newtonsoft.Json` 作为序列化工具,如果需要配置序列化选项,可以在配置中直接设置。 + + + +### 8.2 使用 System.Text.Json + +如果您希望使用 `System.Text.Json` 作为序列化工具,可以进行如下配置: + + + + +### 8.3 AOT 支持 + +如果需要在 `AOT`(Ahead-Of-Time)环境下使用 `JsonRpc`,需要注意以下几点: + +1. `JsonRpc` 的参数类型和返回值类型,必须是 `AOT` 支持的类型 +2. 如果是自定义类型,必须在 `AOT` 环境下生成 `Json` 序列化代码 + +#### 配置示例 + + + + + + +## 九、本文示例 Demo + + diff --git a/handbook/docs/modbusdescription.mdx b/handbook/docs/modbusdescription.mdx new file mode 100644 index 000000000..35de5e0b3 --- /dev/null +++ b/handbook/docs/modbusdescription.mdx @@ -0,0 +1,591 @@ +--- +id: modbusdescription +title: Modbus协议介绍 +--- + +## 一、Modbus协议发展历史 + +Modbus协议是由Modicon公司(现为施耐德电气的一部分)在1979年开发的一种应用层通信协议。它是工业自动化领域中最早和最广泛使用的通信协议之一。 + +### 1.1 Modbus总线协议特性 + +Modbus被称为总线协议,主要基于以下几个核心特征: + +#### 1.1.1 主从架构(Master-Slave) +- **单主多从**: 网络中只能有一个主站(Master),可以有多个从站(Slave,最多247个) +- **主站控制**: 只有主站可以主动发起通信,从站只能被动响应 +- **轮询机制**: 主站通过轮询方式与各个从站进行通信 + +#### 1.1.2 共享通信媒介 +```mermaid +graph TB + A[主站 Master] --> B[通信总线] + B --> C[从站1 Slave ID:1] + B --> D[从站2 Slave ID:2] + B --> E[从站3 Slave ID:3] + B --> F[从站N Slave ID:N] + + style A fill:#ff9999 + style B fill:#99ccff + style C fill:#99ff99 + style D fill:#99ff99 + style E fill:#99ff99 + style F fill:#99ff99 +``` + +- **物理总线**: 在串行通信中,所有设备共享同一条物理线路(如RS-485总线) +- **逻辑总线**: 在TCP/IP网络中,通过网络交换机形成逻辑总线结构 +- **地址寻址**: 通过设备地址(Slave ID)来区分不同的从站设备 + +#### 1.1.3 半双工通信特性 +- **时分复用**: 同一时刻只能有一个设备发送数据 +- **冲突避免**: 严格的主从协议避免了数据冲突 +- **确定性通信**: 通信时序完全由主站控制,具有良好的实时性 + +#### 1.1.4 总线拓扑优势 + +| 特性 | 说明 | 优势 | +|------|------|------| +| **成本效益** | 单条线路连接多个设备 | 降低布线和硬件成本 | +| **易于扩展** | 可方便地添加或移除从站 | 系统扩展性好 | +| **故障隔离** | 单个从站故障不影响整个网络 | 提高系统可靠性 | +| **标准化** | 统一的协议规范 | 设备互操作性强 | + +#### 1.1.5 典型总线应用场景 + +**串行总线应用(RS-485)**: +```mermaid +graph LR + A[HMI(人机)/SCADA(数采)] --> B[PLC主站] + B --> C[RS-485总线] + C --> D[变频器] + C --> E[温度传感器] + C --> F[压力变送器] + C --> G[流量计] + + style A fill:#ff9999 + style B fill:#ff9999 + style C fill:#99ccff + style D fill:#99ff99 + style E fill:#99ff99 + style F fill:#99ff99 + style G fill:#99ff99 +``` + +**以太网总线应用(TCP/IP)**: +```mermaid +graph TB + A[上位机] --> B[工业交换机] + B --> C[PLC控制器] + B --> D[智能电表] + B --> E[环境监测设备] + B --> F[执行器] + + style A fill:#ff9999 + style B fill:#99ccff + style C fill:#99ff99 + style D fill:#99ff99 + style E fill:#99ff99 + style F fill:#99ff99 +``` + +#### 1.1.6 与点对点协议的对比 + +| 对比项 | Modbus总线协议 | 点对点协议 | +|--------|----------------|------------| +| **连接方式** | 一条线路连接多设备 | 每两个设备间独立连接 | +| **通信模式** | 主从轮询 | 对等通信 | +| **硬件成本** | 低(共享线路) | 高(独立线路) | +| **扩展性** | 易扩展(添加从站) | 复杂(需增加连接) | +| **实时性** | 确定性轮询 | 可能存在冲突 | +| **故障影响** | 局部影响 | 影响对应连接 | + +### 1.2 发展历程 + +- **1979年**: Modicon公司发布了Modbus协议,最初用于其可编程逻辑控制器(PLC)之间的通信 +- **1980年代**: 协议逐渐被其他厂商采用,成为事实上的工业标准 +- **1996年**: Modbus协议成为开放协议,任何厂商都可以免费使用 +- **2004年**: 成立了Modbus组织(Modbus Organization),负责协议的维护和发展 +- **至今**: 仍然是工业自动化领域最重要的通信协议之一 + +## 二、Modbus协议用途 + +Modbus协议主要用于工业自动化设备之间的通信,具有以下特点和用途: + +### 2.1 应用领域 + +- **工业自动化**: PLC、DCS、SCADA系统之间的通信 +- **能源管理**: 电力监控、能耗管理系统 +- **楼宇自动化**: 暖通空调、照明控制系统 +- **水处理**: 污水处理、供水系统监控 +- **交通运输**: 地铁、机场等基础设施监控 + +### 2.2 协议优势 + +- **简单易用**: 协议结构简单,易于理解和实现 +- **开放免费**: 无需支付授权费用,降低了实施成本 +- **广泛支持**: 几乎所有工控设备都支持Modbus协议 +- **可靠稳定**: 经过数十年的验证,协议稳定可靠 +- **互操作性强**: 不同厂商的设备可以无缝互联 + +## 三、Modbus协议变体 + +Modbus协议有三种主要变体: + +### 3.1 Modbus RTU +- 基于串行通信(RS-232、RS-485) +- 使用二进制编码 +- 数据紧凑,传输效率高 + +### 3.2 Modbus ASCII +- 基于串行通信 +- 使用ASCII字符编码 +- 可读性好,便于调试 + +### 3.3 Modbus TCP/IP +- 基于以太网TCP/IP协议 +- 速度快,支持长距离通信 +- 易于集成到现代网络环境 + +## 四、Modbus协议报文格式 + +### 4.1 Modbus RTU报文格式 + +Modbus RTU报文格式根据是请求报文、正常响应报文还是异常响应报文而有所不同: + +#### 4.1.1 请求报文格式 + +```mermaid +--- +title: "Modbus RTU Request Packet" +--- +packet-beta +0-7: "设备地址(1字节)" +8-15: "功能码(1字节)" +16-47: "数据域(变长)" +48-63: "CRC校验(2字节)" +``` + +**请求报文结构表** + +| 字节位置 | 字段名称 | 长度 | 说明 | 示例值 | +|----------|----------|------|------|--------| +| 0 | 设备地址 | 1字节 | 从站设备地址,范围1-247,0为广播地址 | 0x01 | +| 1 | 功能码 | 1字节 | 指定要执行的操作类型 | 0x03 | +| 2-5 | 数据域 | 变长 | 包含请求的具体参数(地址、数量等) | 0x00 0x00 0x00 0x02 | +| 6-7 | CRC校验 | 2字节 | 循环冗余校验码,低字节在前 | 0xC4 0x0B | + +**读保持寄存器请求报文示例**: +``` +01 03 00 00 00 02 C4 0B +``` +- `01`: 设备地址 +- `03`: 功能码(读保持寄存器) +- `00 00`: 起始地址(0) +- `00 02`: 寄存器数量(2个) +- `C4 0B`: CRC校验 + +#### 4.1.2 正常响应报文格式 + +正常响应的格式根据功能码类型而有所不同: + +##### 读操作响应(功能码01-04) + +```mermaid +--- +title: "Modbus RTU Read Response Packet" +--- +packet-beta +0-7: "设备地址(1字节)" +8-15: "功能码(1字节)" +16-23: "数据字节数(1字节)" +24-47: "数据内容(变长)" +48-63: "CRC校验(2字节)" +``` + +**读操作响应结构表** + +| 字节位置 | 字段名称 | 长度 | 说明 | 示例值 | +|----------|----------|------|------|--------| +| 0 | 设备地址 | 1字节 | 从站设备地址 | 0x01 | +| 1 | 功能码 | 1字节 | 与请求中的功能码相同 | 0x03 | +| 2 | 数据字节数 | 1字节 | 后续数据内容的字节数 | 0x04 | +| 3-6 | 数据内容 | 变长 | 实际的寄存器或线圈数据 | 0x12 0x34 0x56 0x78 | +| 7-8 | CRC校验 | 2字节 | 循环冗余校验码,低字节在前 | 0x?? 0x?? | + +##### 写操作响应(功能码05、06) + +```mermaid +--- +title: "Modbus RTU Write Single Response Packet" +--- +packet-beta +0-7: "设备地址(1字节)" +8-15: "功能码(1字节)" +16-31: "起始地址(2字节)" +32-47: "写入值(2字节)" +48-63: "CRC校验(2字节)" +``` + +**写单个寄存器/线圈响应结构表** + +| 字节位置 | 字段名称 | 长度 | 说明 | 示例值 | +|----------|----------|------|------|--------| +| 0 | 设备地址 | 1字节 | 从站设备地址 | 0x01 | +| 1 | 功能码 | 1字节 | 0x05(写单个线圈)或0x06(写单个寄存器) | 0x06 | +| 2-3 | 起始地址 | 2字节 | 写入的寄存器或线圈地址 | 0x00 0x01 | +| 4-5 | 写入值 | 2字节 | 写入的数据值 | 0x12 0x34 | +| 6-7 | CRC校验 | 2字节 | 循环冗余校验码,低字节在前 | 0x?? 0x?? | + +##### 写多个操作响应(功能码0F、10) + +```mermaid +--- +title: "Modbus RTU Write Multiple Response Packet" +--- +packet-beta +0-7: "设备地址(1字节)" +8-15: "功能码(1字节)" +16-31: "起始地址(2字节)" +32-47: "数量(2字节)" +48-63: "CRC校验(2字节)" +``` + +**写多个寄存器/线圈响应结构表** + +| 字节位置 | 字段名称 | 长度 | 说明 | 示例值 | +|----------|----------|------|------|--------| +| 0 | 设备地址 | 1字节 | 从站设备地址 | 0x01 | +| 1 | 功能码 | 1字节 | 0x0F(写多个线圈)或0x10(写多个寄存器) | 0x10 | +| 2-3 | 起始地址 | 2字节 | 写入的起始地址 | 0x00 0x01 | +| 4-5 | 数量 | 2字节 | 写入的寄存器或线圈数量 | 0x00 0x02 | +| 6-7 | CRC校验 | 2字节 | 循环冗余校验码,低字节在前 | 0x?? 0x?? | + +#### 4.1.3 异常响应报文格式 + +```mermaid +--- +title: "Modbus RTU Exception Response Packet" +--- +packet-beta +0-7: "设备地址(1字节)" +8-15: "异常功能码(1字节)" +16-23: "异常代码(1字节)" +24-39: "CRC校验(2字节)" +``` + +**异常响应结构表** + +| 字节位置 | 字段名称 | 长度 | 说明 | 示例值 | +|----------|----------|------|------|--------| +| 0 | 设备地址 | 1字节 | 从站设备地址 | 0x01 | +| 1 | 异常功能码 | 1字节 | 原功能码 + 0x80(设置最高位为1) | 0x83 | +| 2 | 异常代码 | 1字节 | 具体的错误代码 | 0x02 | +| 3-4 | CRC校验 | 2字节 | 循环冗余校验码,低字节在前 | 0x?? 0x?? | + +**异常响应示例**: +``` +01 83 02 ?? ?? +``` +- `01`: 设备地址 +- `83`: 异常功能码(0x03 + 0x80) +- `02`: 异常代码(非法数据地址) +- `?? ??`: CRC校验 + +### 4.2 Modbus TCP报文格式 + +Modbus TCP协议在标准Modbus协议的基础上增加了MBAP头(Modbus Application Protocol Header),用于在TCP/IP网络上进行通信。 + +#### 4.2.1 MBAP头解释 + +MBAP头是Modbus TCP协议特有的7字节头部,包含以下字段: + +```mermaid +--- +title: "MBAP Header Structure" +--- +packet-beta +0-15: "事务标识符(2字节)" +16-31: "协议标识符(2字节)" +32-47: "长度字段(2字节)" +48-55: "单元标识符(1字节)" +``` + +**MBAP头字段详解**: + +| 字段名称 | 长度 | 作用 | 取值范围 | 说明 | +|----------|------|------|----------|------| +| 事务标识符 | 2字节 | 唯一标识每个事务 | 0x0000-0xFFFF | 客户端生成,用于匹配请求和响应 | +| 协议标识符 | 2字节 | 标识协议类型 | 固定0x0000 | 表示Modbus协议 | +| 长度字段 | 2字节 | 指示后续数据长度 | 0x0001-0x00FF | 不包含MBAP头的前6字节 | +| 单元标识符 | 1字节 | 标识从站设备 | 0x00-0xFF | 相当于串行Modbus的设备地址 | + +#### 4.2.2 完整报文格式 + +```mermaid +--- +title: "Modbus TCP Packet" +--- +packet-beta +0-15: "事务标识符(2字节)" +16-31: "协议标识符(2字节)" +32-47: "长度(2字节)" +48-55: "单元标识符(1字节)" +56-63: "功能码(1字节)" +64-95: "数据域(变长)" +``` + +#### 4.2.3 报文结构表 + +| 字节位置 | 字段名称 | 长度 | 所属部分 | 说明 | 示例值 | +|----------|----------|------|----------|------|--------| +| 0-1 | 事务标识符 | 2字节 | MBAP头 | 用于匹配请求和响应,高字节在前 | 0x00 0x01 | +| 2-3 | 协议标识符 | 2字节 | MBAP头 | Modbus协议固定为0x0000 | 0x00 0x00 | +| 4-5 | 长度 | 2字节 | MBAP头 | 后续字节数(单元标识符+功能码+数据域) | 0x00 0x06 | +| 6 | 单元标识符 | 1字节 | MBAP头 | 从站设备地址(0表示忽略) | 0x01 | +| 7 | 功能码 | 1字节 | PDU | 指定要执行的操作类型 | 0x03 | +| 8-11 | 数据域 | 变长 | PDU | 包含请求或响应的具体数据 | 0x00 0x00 0x00 0x02 | + +**报文组成说明**: +- **MBAP头** (7字节): Modbus Application Protocol Header,提供事务管理和协议识别 +- **PDU** (变长): Protocol Data Unit,包含功能码和数据域,与串行Modbus相同 + +#### 4.2.4 完整报文示例 + +**读保持寄存器请求报文**: +``` +00 01 00 00 00 06 01 03 00 00 00 02 +``` + +**报文解析**: + +**MBAP头部分(前7字节)**: +- `00 01`: 事务标识符(Transaction ID = 1) +- `00 00`: 协议标识符(Protocol ID = 0,表示Modbus协议) +- `00 06`: 长度字段(Length = 6,表示后续有6字节数据) +- `01`: 单元标识符(Unit ID = 1,目标从站地址) + +**PDU部分(后5字节)**: +- `03`: 功能码(读保持寄存器) +- `00 00`: 起始地址(从地址0开始) +- `00 02`: 寄存器数量(读取2个寄存器) + +**响应报文示例**: +``` +00 01 00 00 00 07 01 03 04 12 34 56 78 +``` +- MBAP头:`00 01 00 00 00 07 01`(事务ID=1,长度=7) +- PDU:`03 04 12 34 56 78`(功能码03,4字节数据,寄存器值0x1234和0x5678) + +## 五、常用功能码 + +### 5.1 读操作功能码 + +| 功能码 | 名称 | 说明 | +|--------|------|------| +| 0x01 | 读线圈状态 | 读取离散输出线圈的ON/OFF状态 | +| 0x02 | 读离散输入状态 | 读取离散输入的ON/OFF状态 | +| 0x03 | 读保持寄存器 | 读取保持寄存器中的数据 | +| 0x04 | 读输入寄存器 | 读取输入寄存器中的数据 | + +### 5.2 写操作功能码 + +| 功能码 | 名称 | 说明 | +|--------|------|------| +| 0x05 | 写单个线圈 | 设置单个离散输出线圈的状态 | +| 0x06 | 写单个寄存器 | 设置单个保持寄存器的值 | +| 0x0F | 写多个线圈 | 设置多个离散输出线圈的状态 | +| 0x10 | 写多个寄存器 | 设置多个保持寄存器的值 | + +### 5.3 功能码响应格式详解 + +根据TouchSocket的实现,不同功能码的响应格式处理方式如下: + +#### 5.3.1 读操作响应(功能码01-04及17) + +对于读操作功能码(0x01-0x04)和读写多寄存器功能码(0x17),响应包含数据字节数字段: + +```csharp +if ((byte)functionCode <= 4 || functionCode == FunctionCode.ReadWriteMultipleRegisters) +{ + bodyLength = ReaderExtension.ReadValue(ref reader) + 2; + // +2 是因为还要加上CRC的2个字节 +} +``` + +**响应格式**: +- 设备地址(1字节) + 功能码(1字节) + **数据字节数(1字节)** + 数据内容(变长) + CRC(2字节) + +#### 5.3.2 写单个操作响应(功能码05、06) + +对于写单个线圈(0x05)和写单个寄存器(0x06),响应格式固定: + +```csharp +else if (functionCode == FunctionCode.WriteSingleCoil || functionCode == FunctionCode.WriteSingleRegister) +{ + bodyLength = 6; // 固定长度:地址(2) + 数据(2) + CRC(2) = 6字节 +} +``` + +**响应格式**: +- 设备地址(1字节) + 功能码(1字节) + 起始地址(2字节) + 写入值(2字节) + CRC(2字节) + +#### 5.3.3 写多个操作响应(功能码0F、10) + +对于写多个线圈(0x0F)和写多个寄存器(0x10),响应格式固定: + +```csharp +else if (functionCode == FunctionCode.WriteMultipleCoils || functionCode == FunctionCode.WriteMultipleRegisters) +{ + bodyLength = 6; // 固定长度:地址(2) + 数量(2) + CRC(2) = 6字节 +} +``` + +**响应格式**: +- 设备地址(1字节) + 功能码(1字节) + 起始地址(2字节) + 数量(2字节) + CRC(2字节) + +#### 5.3.4 数据长度计算规则 + +| 功能码范围 | 数据长度字段 | 总长度计算 | 说明 | +|------------|-------------|------------|------| +| 0x01-0x04, 0x17 | 有(1字节) | 3 + 数据字节数 + 2 | 数据字节数字段 + 实际数据 + CRC | +| 0x05, 0x06 | 无 | 固定8字节 | 地址 + 功能码 + 起始地址 + 数据值 + CRC | +| 0x0F, 0x10 | 无 | 固定8字节 | 地址 + 功能码 + 起始地址 + 数量 + CRC | +| 异常响应 | 无 | 固定5字节 | 地址 + 异常功能码 + 异常代码 + CRC | + +## 六、数据模型 + +Modbus协议定义了四种基本数据类型: + +```mermaid +graph TB + A[Modbus数据模型] --> B[线圈 Coils] + A --> C[离散输入 Discrete Inputs] + A --> D[输入寄存器 Input Registers] + A --> E[保持寄存器 Holding Registers] + + B --> B1[1位读写] + B --> B2[地址范围: 00001-09999] + + C --> C1[1位只读] + C --> C2[地址范围: 10001-19999] + + D --> D1[16位只读] + D --> D2[地址范围: 30001-39999] + + E --> E1[16位读写] + E --> E2[地址范围: 40001-49999] +``` + +## 七、典型通信流程 + +### 7.1 读保持寄存器示例 + +```mermaid +sequenceDiagram + participant M as 主站(Master) + participant S as 从站(Slave) + + M->>S: 请求: 读保持寄存器 + Note over M,S: 设备地址: 0x01
功能码: 0x03
起始地址: 0x0000
数量: 0x0002 + + S->>M: 响应: 寄存器数据 + Note over M,S: 设备地址: 0x01
功能码: 0x03
字节数: 0x04
数据: 0x1234 0x5678 +``` + +### 7.2 写单个寄存器示例 + +```mermaid +sequenceDiagram + participant M as 主站(Master) + participant S as 从站(Slave) + + M->>S: 请求: 写单个寄存器 + Note over M,S: 设备地址: 0x01
功能码: 0x06
寄存器地址: 0x0001
寄存器值: 0x1234 + + S->>M: 响应: 确认写入 + Note over M,S: 设备地址: 0x01
功能码: 0x06
寄存器地址: 0x0001
寄存器值: 0x1234 +``` + +## 八、错误处理 + +### 8.1 异常响应处理 + +Modbus协议的异常响应格式在前面的4.1.3节中已经详细描述。异常响应的关键特征: +- **异常功能码**: 原功能码的最高位设置为1(原功能码 + 0x80) +- **异常代码**: 1字节的错误代码,指明具体的错误类型 + +### 8.2 功能码识别 + +在代码实现中,通过检查功能码的最高位来判断是否为异常响应: + +```csharp +var code = ReaderExtension.ReadValue(ref reader); +if ((code & 0x80) == 0) +{ + // 正常响应 + functionCode = (FunctionCode)code; +} +else +{ + // 异常响应 + code = code.SetBit(7, false); // 清除最高位 + functionCode = (FunctionCode)code; + isError = true; +} +``` + +### 8.3 标准异常代码 + +| 异常代码 | 名称 | 说明 | +|----------|------|------| +| 0x01 | 非法功能码 | 从站不支持请求的功能码 | +| 0x02 | 非法数据地址 | 请求的数据地址不存在 | +| 0x03 | 非法数据值 | 请求中包含非法的数据值 | +| 0x04 | 从站设备故障 | 从站设备发生不可恢复的错误 | +| 0x05 | 确认 | 从站已接受请求但需要长时间处理 | +| 0x06 | 从站设备忙 | 从站正忙于处理其他命令 | + +### 8.4 TouchSocket特有错误处理 + +TouchSocket框架在Modbus协议实现中还增加了额外的错误处理机制: + +#### 8.4.1 CRC校验错误处理 + +```csharp +if (crc == newCrc) +{ + // CRC校验成功,正常处理 + request = new ModbusRtuResponse() { ... }; +} +else +{ + // CRC校验失败,返回内存验证错误 + request = new ModbusRtuResponse() + { + SlaveId = slaveId, + FunctionCode = functionCode, + ErrorCode = ModbusErrorCode.ResponseMemoryVerificationError, + }; +} +``` + +#### 8.4.2 TouchSocket扩展错误代码 + +| 错误代码 | 名称 | 说明 | +|----------|------|------| +| ResponseMemoryVerificationError | 响应内存验证错误 | CRC校验失败时返回的自定义错误 | + +#### 8.4.3 错误处理策略 + +TouchSocket在处理CRC校验失败时采用了更加友好的策略: +- **旧版本**: CRC验证失败时直接抛出异常 +- **新版本**: CRC验证失败时不再抛出错误,而是返回特定的错误代码(`ResponseMemoryVerificationError`) + +这种改进使得错误处理更加优雅,避免了因网络干扰导致的程序异常终止。 + +## 九、总结 + +Modbus协议作为工业自动化领域的经典协议,凭借其简单、可靠、开放的特点,在过去40多年中一直占据着重要地位。无论是传统的串行通信还是现代的以太网通信,Modbus协议都能很好地满足工业现场的通信需求。 + +TouchSocket框架提供了完整的Modbus协议实现,支持Modbus RTU、Modbus ASCII和Modbus TCP三种变体,为开发者提供了便捷的工业通信解决方案。 \ No newline at end of file diff --git a/handbook/docs/modbusmaster.mdx b/handbook/docs/modbusmaster.mdx index 7c6e909ce..42078e6ab 100644 --- a/handbook/docs/modbusmaster.mdx +++ b/handbook/docs/modbusmaster.mdx @@ -6,8 +6,9 @@ title: Modbus主站(Master) import Tag from "@site/src/components/Tag.js"; import Pro from "@site/src/components/Pro.js"; import { TouchSocketModbusDefinition } from "@site/src/components/Definition.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; + -### 定义 @@ -48,53 +49,23 @@ import { TouchSocketModbusDefinition } from "@site/src/components/Definition.js" #### 6.1 创建ModbusTcpMaster -```csharp showLineNumbers -var client = new ModbusTcpMaster(); -await client.ConnectAsync("127.0.0.1:502"); -``` + #### 6.2 创建ModbusUdpMaster -```csharp showLineNumbers -var client = new ModbusUdpMaster(); -await client.SetupAsync(new TouchSocketConfig() - .UseUdpReceive() - .SetRemoteIPHost("127.0.0.1:502")); -await client.StartAsync(); -``` + #### 6.3 创建ModbusRtuMaster -```csharp showLineNumbers -var client = new ModbusRtuMaster(); -await client.SetupAsync(new TouchSocketConfig() - .SetSerialPortOption(new SerialPortOption() - { - BaudRate = 9600, - DataBits = 8, - Parity = System.IO.Ports.Parity.Even, - PortName = "COM2", - StopBits = System.IO.Ports.StopBits.One - })); -await client.ConnectAsync(); -``` + #### 6.4 创建ModbusRtuOverTcpMaster -```csharp showLineNumbers -var client = new ModbusRtuOverTcpMaster(); -await client.ConnectAsync("127.0.0.1:502"); -``` + #### 6.5 创建ModbusRtuOverUdpMaster -```csharp showLineNumbers -var client = new ModbusRtuOverUdpMaster(); -await client.SetupAsync(new TouchSocketConfig() - .UseUdpReceive() - .SetRemoteIPHost("127.0.0.1:502")); -await client.StartAsync(); -``` + ## 七、读写操作 @@ -102,89 +73,53 @@ await client.StartAsync(); 所有的`Modbus`主站都支持以下原生接口操作: -```csharp showLineNumbers -//异步发送Modbus请求,并等待响应 -Task SendModbusRequestAsync(ModbusRequest request, int millisecondsTimeout, CancellationToken token); -``` + 以读线圈操作为例: -```csharp showLineNumbers -ModbusRequest modbusRequest = new ModbusRequest(FunctionCode.ReadCoils); -modbusRequest.SetSlaveId(1);//设置站号。如果是Tcp可以不设置 -modbusRequest.SetStartingAddress(0);//设置起始 -modbusRequest.SetQuantity(1);//设置数量 -//modbusRequest.SetValue(false);//如果是写入类操作,可以直接设定值 - -var response =await master.SendModbusRequestAsync(modbusRequest, 1000, CancellationToken.None); - -bool[] bools = response.CreateReader().ToBoolensFromBit().ToArray(); -``` + ### 7.2 快捷扩展实现 因为`Modbus`的操作一般比较固化,所以`ModbusMaster`扩展了以下快捷操作: -读取线圈(FC1)。 +#### 7.2.1 读取线圈(FC1)。 -```csharp showLineNumbers -bool[] bools =await master.ReadCoilsAsync(0, 1); -``` + -读取离散输入(FC2)。 +#### 7.2.2 读取离散输入(FC2)。 -```csharp showLineNumbers -bool[] bools =await master.ReadDiscreteInputsAsync(0, 1); -``` + -读取保持寄存器(FC3)。 +#### 7.2.3 读取保持寄存器(FC3)。 -```csharp showLineNumbers -var response =await master.ReadHoldingRegistersAsync(0, 1); -var reader = response.CreateReader(); -var value=reader.ReadInt16(); -``` + -读取输入寄存器(FC4)。 +#### 7.2.4 读取输入寄存器(FC4)。 -```csharp showLineNumbers -var response =await master.ReadInputRegistersAsync(0, 1); -var reader = response.CreateReader(); -var value=reader.ReadInt16(); -``` + -写入单个线圈(FC5)。 +#### 7.2.5 写入单个线圈(FC5)。 -```csharp showLineNumbers -await master.WriteSingleCoilAsync(0, true); -``` + -写入单个寄存器(FC6)。 +#### 7.2.6 写入单个寄存器(FC6)。 -```csharp showLineNumbers -await master.WriteSingleRegisterAsync(0, (short)100); -``` + -写入多个线圈(FC15)。 +#### 7.2.7 写入多个线圈(FC15)。 -```csharp showLineNumbers -await master.WriteMultipleCoilsAsync(0, new bool[] { true, false, true }); -``` + -写入多个寄存器(FC16)。 +#### 7.2.8 写入多个寄存器(FC16)。 -```csharp showLineNumbers -using (var valueByteBlock = new ValueByteBlock(1024)) -{ - valueByteBlock.WriteUInt16((ushort)2, EndianType.Big);//ABCD端序 - valueByteBlock.WriteUInt16((ushort)2000, EndianType.Little);//DCBA端序 - valueByteBlock.WriteInt32(int.MaxValue, EndianType.BigSwap);//BADC端序 - valueByteBlock.WriteInt64(long.MaxValue, EndianType.LittleSwap);//CDAB端序 + - //写入到寄存器 - await master.WriteMultipleRegistersAsync(1, 2, valueByteBlock.ToArray()); -} -``` +:::caution 注意 + +写入字符串时,应当保证写入后的字节总数为双数。如果是单数,则会报错。 + +::: :::tip 提示 @@ -194,25 +129,17 @@ using (var valueByteBlock = new ValueByteBlock(1024)) ## 八、更多写入与读取 -线圈与离散输入的写入与读取比较单一,上述操作即可满足大部分需求。下面介绍读写保持寄存器、读取输入寄存器的多元化方式。 +线圈与离散输入的写入与读取比较单一,上述操作即可满足大部分需求。 +下面介绍读写保持寄存器、读取输入寄存器的多元化方式。 -读取寄存器到集合。如果该集合中的数据是一类数据,例如全是uint32类型。那么可以使用下列方式: +读取寄存器到集合。如果该集合中的数据是一类数据,例如全是`uint32`类型。那么可以使用下列方式: -```csharp showLineNumbers -//读取寄存器 -var response =await master.ReadHoldingRegistersAsync(1, 0, 10);//站点1,从0开始读取10个寄存器 - -//创建一个读取器 -var reader = response.CreateReader(); - -//将数据全部读为无符号32为,且使用大端序,即ABCD -uint[] values=reader.ToUInt32s(EndianType.Big).ToArray(); -``` + :::tip 提示 -该集合支持全部基础数据类型,以及DataTime和TimeSpan。 +该集合支持全部基础数据类型,和非托管类型,例如:`DataTime`和`TimeSpan`。 ::: @@ -220,54 +147,12 @@ uint[] values=reader.ToUInt32s(EndianType.Big).ToArray(); 当写入下列数据时: -```csharp showLineNumbers -using (var valueByteBlock = new ValueByteBlock(1024)) -{ - valueByteBlock.WriteUInt16((ushort)2, EndianType.Big);//ABCD端序 - valueByteBlock.WriteUInt16((ushort)2000, EndianType.Little);//DCBA端序 - valueByteBlock.WriteInt32(int.MaxValue, EndianType.BigSwap);//BADC端序 - valueByteBlock.WriteInt64(long.MaxValue, EndianType.LittleSwap);//CDAB端序 - - //写入到寄存器 - await master.WriteMultipleRegistersAsync(1, 2, valueByteBlock.ToArray()); -} -``` + 就需要依次读取: -```csharp showLineNumbers -//读取寄存器 -var response =await master.ReadHoldingRegistersAsync(1, 0, 1 + 1 + 2 + 4); + -//创建一个读取器 -var reader = response.CreateReader(); - -//依次读取 -Console.WriteLine(reader.ReadInt16(EndianType.Big)); -Console.WriteLine(reader.ReadInt16(EndianType.Little)); -Console.WriteLine(reader.ReadInt32(EndianType.BigSwap)); -Console.WriteLine(reader.ReadInt64(EndianType.LittleSwap)); -``` - -读写字符串: - -```csharp showLineNumbers -using (var valueByteBlock = new ValueByteBlock(1024)) -{ - //写入字符串,会先用4字节表示字符串长度,然后按utf8编码写入字符串 - valueByteBlock.WriteString("Hello"); - - //写入到寄存器 - await master.WriteMultipleRegistersAsync(1, 0, valueByteBlock.ToArray()); -} - -//读取寄存器 -var response =await master.ReadHoldingRegistersAsync(1, 0, 5);//5个长度,10字节 - -//创建一个读取器 -var reader = response.CreateReader(); -Console.WriteLine(reader.ReadString()); -``` 读写任意类型: @@ -284,6 +169,14 @@ Console.WriteLine(reader.ReadString()); ## 九、ModbusObject操作 +:::danger 注意 + +ModbusObject的操作由于性能和其它设计缺陷问题,以后版本不再维护,也可能会废弃,请谨慎使用。 + +此处更为推荐使用[Plc Modbus](./plcbridgemodbus.mdx)配合[PlcObject](./plcbridgeservice.mdx) + +::: + ### 9.1 基本使用 一般的,我们使用`Modbus`,都是通过Master直接`Read`或`Write`。所以有时候需要维护的代码会非常多,而且容易出错。 diff --git a/handbook/docs/modbusslave.mdx b/handbook/docs/modbusslave.mdx index f343eba04..dbc6e021a 100644 --- a/handbook/docs/modbusslave.mdx +++ b/handbook/docs/modbusslave.mdx @@ -6,16 +6,23 @@ title: Modbus从站(Slave) import Tag from "@site/src/components/Tag.js"; import Pro from "@site/src/components/Pro.js"; import { TouchSocketProModbusDefinition } from "@site/src/components/Definition.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; +import CardLink from "@site/src/components/CardLink.js"; + -### 定义 ## 一、说明 -Modbus是主从通讯的。所以我们开发了Modbus服务器组件,方便大家使用。 +`Modbus`是OSI模型第7层上的应用层报文传输协议,它在连接至不同类型总线或网络的设备之间提供客户机/服务器通信。 +自从 1979 年出现工业串行链路的事实标准以来,`Modbus`使成千上万的自动化设备能够通信。目前,继续增加对简单而雅观的`Modbus`结构支持。互联网组织能够使TCP/IP栈上的保留系统端口502 访问`Modbus`。 + +`Modbus`采用主从(Master-Slave)通信架构,其中主站(Master)发起通信请求,从站(Slave)响应主站的请求。在这种架构中,从站作为数据提供者和执行者,负责维护数据存储区域,并响应主站的读写操作。 + +所以我们开发了这个从站组件,方便大家快速构建`Modbus`从站服务。 ## 二、特点 @@ -35,168 +42,58 @@ Modbus是主从通讯的。所以我们开发了Modbus服务器组件,方便 ## 五、支持插件 +`ModbusSlave`支持插件机制,可以通过插件对Modbus请求进行拦截和处理。 + | 插件方法| 功能 | | --- | --- | | IModbusSlaveExecutingPlugin | 当有主站请求读写该从站时触发。如果想要拒绝请求,可以通过e.IsPermitOperation = false执行。并且e.ErrorCode可以携带返回错误码。| | IModbusSlaveExecutedPlugin | 当有主站完成请求读写该从站时触发 | +### 5.1 插件使用示例 + + + +:::tip 提示 + +插件可以用于实现访问控制、数据验证、日志记录、性能监控等功能。通过插件机制,可以在不修改核心代码的情况下,灵活扩展从站的功能。 + +::: + ## 六、创建 -目前`TouchSokcet.Modbus`从站支持`Tcp`、`Udp`、`Rtu`、`RtuOverTcp`、`RtuOverUdp`等协议。下面会一一介绍创建过程。 +目前`TouchSocket.Modbus`从站支持`Tcp`、`Udp`、`Rtu`、`RtuOverTcp`、`RtuOverUdp`等协议。下面会一一介绍创建过程。 #### 6.1 创建ModbusTcpSlave -```csharp showLineNumbers -var slave = new ModbusTcpSlave(); -await slave.SetupAsync(new TouchSocketConfig() - //监听端口 - .SetListenIPHosts(7808) - .ConfigurePlugins(a => - { - a.AddModbusSlavePoint()//添加一个从站站点 - .SetSlaveId(1)//设置站点号 - //.UseIgnoreSlaveId()//忽略站号验证 - .SetModbusDataLocater(new ModbusDataLocater(10, 10, 10, 10));//设置数据区 - }) - ); -await slave.StartAsync(); -Console.WriteLine("服务已启动"); -``` + #### 6.2 创建ModbusUdpSlave -```csharp showLineNumbers -var slave = new ModbusUdpSlave(); -await slave.SetupAsync(new TouchSocketConfig() - //监听端口 - .SetBindIPHost(7809) - .ConfigurePlugins(a => - { - a.Add(); - - a.AddModbusSlavePoint()//添加一个从站站点 - .SetSlaveId(1)//设置站点号 - .UseIgnoreSlaveId()//忽略站号验证 - .SetModbusDataLocater(new ModbusDataLocater(10, 10, 10, 10));//设置数据区 - }) - ); -await slave.StartAsync(); -Console.WriteLine("服务已启动"); -``` + #### 6.3 创建ModbusRtuSlave -```csharp showLineNumbers -var slave = new ModbusRtuSlave(); -await slave.SetupAsync(new TouchSocketConfig() - //设置串口 - .SetSerialPortOption(new SerialPortOption() - { - BaudRate = 9600, - DataBits = 8, - Parity = System.IO.Ports.Parity.Even, - PortName = "COM1", - StopBits = System.IO.Ports.StopBits.One - }) - .ConfigurePlugins(a => - { - a.Add(); - - a.AddModbusSlavePoint()//添加一个从站站点 - .SetSlaveId(1)//设置站点号 - //.UseIgnoreSlaveId()//如果不调用,默认会进行站号验证 - .SetModbusDataLocater(new ModbusDataLocater(10, 10, 10, 10));//设置数据区 - }) - ); - -await slave.ConnectAsync(); -Console.WriteLine("已连接COM端口"); -``` + #### 6.4 创建ModbusRtuOverTcpSlave -```csharp showLineNumbers -var slave = new ModbusRtuOverTcpSlave(); -await slave.SetupAsync(new TouchSocketConfig() - //监听端口 - .SetListenIPHosts(7810) - .ConfigurePlugins(a => - { - a.Add(); - - a.AddModbusSlavePoint()//添加一个从站站点 - .SetSlaveId(1)//设置站点号 - .UseIgnoreSlaveId()//忽略站号验证 - .SetModbusDataLocater(new ModbusDataLocater(10, 10, 10, 10));//设置数据区 - }) - ); -await slave.StartAsync(); -Console.WriteLine("服务已启动"); -``` + #### 6.5 创建ModbusRtuOverUdpSlave -```csharp showLineNumbers -var slave = new ModbusRtuOverUdpSlave(); -await slave.SetupAsync(new TouchSocketConfig() - //监听端口 - .SetBindIPHost(7811) - .ConfigurePlugins(a => - { - a.Add(); - - a.AddModbusSlavePoint()//添加一个从站站点 - .SetSlaveId(1)//设置站点号 - .UseIgnoreSlaveId()//忽略站号验证 - .SetModbusDataLocater(new ModbusDataLocater(10, 10, 10, 10));//设置数据区 - }) - ); -await slave.StartAsync(); -Console.WriteLine("服务已启动"); -``` + ## 七、添加多个站点 -`Modbus`是一主多从的架构。在实际使用时,一个`ModbusSlave`部署至一个机器(这里不考虑虚拟机),即视为一个从机。但事实上,按照`Modbus`协议,一个`ModbusSlave`可以有多个站点。以`ModbusTcpSlave`为例,他可以通过`IP地址`确定到唯一的设备,同时还可以通过`SlaveId`区分不同的站点。 +`Modbus`是一主多从的架构。在实际使用时,一个`ModbusSlave`部署至一个机器(这里不考虑虚拟机),即视为一个从机。 + +但事实上,按照`Modbus`协议,一个`ModbusSlave`可以有多个站点。 + +以`ModbusTcpSlave`为例,他可以通过`IP地址`确定到唯一的设备,同时还可以通过`SlaveId`区分不同的站点。 所以,我们的`ModbusSlave`也可以有多个站点。以`ModbusTcpSlave`为例,具体操作如下: -```csharp {18-28} showLineNumbers -static ModbusTcpSlave CreateModbusTcpSlave() -{ - var service = new ModbusTcpSlave(); - await service.SetupAsync(new TouchSocketConfig() - //监听端口 - .SetListenIPHosts(7808) - .ConfigurePlugins(a => - { - a.Add(); - - //当添加多个站点时,需要禁用IgnoreSlaveId的设定 - - a.AddModbusSlavePoint()//添加一个从站站点 - .SetSlaveId(1)//设置站点号 - //.UseIgnoreSlaveId()//忽略站号验证 - .SetModbusDataLocater(new ModbusDataLocater(10,10,10,10));//设置数据区 - - a.AddModbusSlavePoint()//再添加一个从站站点 - .SetSlaveId(2)//设置站点号 - //.UseIgnoreSlaveId()//忽略站号验证 - .SetModbusDataLocater(new ModbusDataLocater()//设置数据区 - { - //下列配置表示,起始地址从1000开始,10个长度 - Coils = new BooleanDataPartition(1000, 10), - DiscreteInputs = new BooleanDataPartition(1000, 10), - HoldingRegisters = new ShortDataPartition(1000, 10), - InputRegisters = new ShortDataPartition(1000, 10) - }); - }) - ); - await service.StartAsync(); - Console.WriteLine("服务已启动"); - return service; -} -``` + :::caution 警告 @@ -212,28 +109,36 @@ static ModbusTcpSlave CreateModbusTcpSlave() ## 八、本地读写操作 +### 8.1 获取IModbusSlavePoint实例 + 所有的数据区均在`IModbusSlavePoint`中。所以需要先找到`IModbusSlavePoint`实例。 一般的,如果你使用了插件`IModbusSlaveExecutingPlugin`或者`IModbusSlaveExecutedPlugin`。插件的sender即为`IModbusSlavePoint`。 如果你只能访问到`IModbusSlave`接口(例如:`ModbusTcpSlave`),那么你可以通过`ModbusSlave`的`GetSlavePointBySlaveId`方法获取到`IModbusSlavePoint`。 -```csharp showLineNumbers -var modbusSlavePoint= slave.GetSlavePointBySlaveId(slaveId: 1); -``` + -然后在`IModbusSlavePoint`接口中有`ModbusDataLocater`属性,该属性是`Modbus`数据存储区,即`线圈`、`离散输入`、`保持寄存器`、`输入寄存器`。 +### 8.2 数据区操作 + +在`IModbusSlavePoint`接口中有`ModbusDataLocater`属性,该属性是`Modbus`数据存储区,包含四个基本数据区域: + +- **线圈(Coils)**:可读写的布尔值,对应功能码01、05、15 +- **离散输入(DiscreteInputs)**:只读的布尔值,对应功能码02 +- **保持寄存器(HoldingRegisters)**:可读写的16位寄存器,对应功能码03、06、16 +- **输入寄存器(InputRegisters)**:只读的16位寄存器,对应功能码04 该属性是可读可写的。所以,即使是不同`IModbusSlavePoint`。也可以二次赋值,使其实现更多功能。 -同时。可以通过该属性,创建一个本地`ModbusMaster`,用于直接读写。 +### 8.3 创建本地Master进行读写 -具体读写操作和[ModbusMaster读写](./modbusmaster.mdx)一致。 +可以通过`ModbusDataLocater`属性,创建一个本地`ModbusMaster`,用于直接读写数据区。 -```csharp showLineNumbers -var localMaster = modbusSlavePoint.ModbusDataLocater.CreateDataLocaterMaster(); -var coils = localMaster.ReadCoils(0, 1); -``` + + +### 8.4 更多读写操作 + +具体读写操作和[ModbusMaster读写](./modbusmaster.mdx)一致,支持以下操作: :::tip 提示 @@ -241,6 +146,13 @@ var coils = localMaster.ReadCoils(0, 1); ::: +:::caution 注意 -[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Modbus/ModbusSlaveConsoleApp) +本地读写操作直接操作内存数据,不经过网络通信,适用于从站内部的数据处理和业务逻辑。如果需要通过网络与其他设备通信,请使用标准的`ModbusMaster`。 + +::: + +## 九、示例 + + diff --git a/handbook/docs/mqttclient.mdx b/handbook/docs/mqttclient.mdx index ccc81b7a0..b06bd8457 100644 --- a/handbook/docs/mqttclient.mdx +++ b/handbook/docs/mqttclient.mdx @@ -1,28 +1,31 @@ --- id: mqttclient -title: Mqtt客户端使用 +title: Mqtt客户端 --- import Tag from "@site/src/components/Tag.js"; import { TouchSocketMqttDefinition } from "@site/src/components/Definition.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; + -### 定义 ## 一、说明 -`MqttClient`是遵循Mqtt协议的消息客户端,支持连接Mqtt Broker、订阅主题、发布消息等功能,支持QoS 0/1/2三种消息质量等级。 +`MqttTcpClient` 是遵循Mqtt协议的消息客户端,支持连接Mqtt Broker、订阅主题、发布消息等功能,支持QoS 0/1/2三种消息质量等级,兼容 Mqtt 3.1.1 及 5.0+ 协议版本。 ## 二、特点 - 轻量级协议支持 -- QoS消息质量保障 +- QoS消息质量保障(0/1/2) - 遗嘱消息支持 - 保留消息处理 +- 自动重连机制 - TLS加密通信 - 主题通配符匹配(+/#) +- 插件化扩展体系 ## 三、应用场景 @@ -30,16 +33,17 @@ import { TouchSocketMqttDefinition } from "@site/src/components/Definition.js"; - 跨平台消息推送 - 低带宽环境通信 - 设备状态同步 +- 远程设备控制 ## 四、可配置项 -继承所有[TcpClient](./tcpclient.mdx)的配置。除此之外还支持Mqtt的一些专有配置。 +继承所有[TcpClient](./tcpclient.mdx)的配置。除此之外还支持Mqtt客户端专有配置。
可配置项
-#### SetMqttConnectOptions +### SetMqttConnectOptions `MqttConnectOptions` 类用于配置 Mqtt 客户端连接到 Mqtt 服务端时的参数,支持 Mqtt 3.1.1 及 5.0+ 协议版本。以下是各配置项的详细说明: @@ -91,16 +95,17 @@ import { TouchSocketMqttDefinition } from "@site/src/components/Definition.js"; | `RequestProblemInformation` | 5.0+ | 是否请求服务端在出错时返回详细问题信息(如原因码、原因字符串);默认值为 `true`(建议开启以便排查问题)。 | | `RequestResponseInformation`| 5.0+ | 是否请求服务端返回连接响应的额外信息(如会话过期时间);默认值为 `false`(仅当需要时启用)。 | | `SessionExpiryInterval` | 5.0+ | 会话过期时间间隔(单位:秒,0 表示立即过期);客户端断开后,服务端保留会话的时间(仅当 `CleanSession=false` 时有效)。 | -| `TopicAliasMaximum` | 5.0+ | 客户端允许的最大主题别名值(服务站不能使用超过此值的别名);默认值为 0(表示不使用主题别名)。 | +| `TopicAliasMaximum` | 5.0+ | 客户端允许的最大主题别名值(服务端不能使用超过此值的别名);默认值为 0(表示不使用主题别名)。 | --- -**注意事项** +:::caution 注意 1. **协议版本兼容性**:部分属性(如 `UserProperties`、`AuthenticationMethod`)仅在 Mqtt 5.0+ 中有效,需根据服务端版本选择配置。 2. **遗嘱消息生效条件**:`WillFlag` 必须为 `true`,且 `WillTopic` 和 `WillMessage` 需有效配置,否则遗嘱不会被发送。 3. **认证扩展**:5.0+ 的 `AuthenticationData` 需配合自定义认证插件使用,具体格式由服务端定义。 -4. **只读属性**:`WillMessage` 和 `WillTopic` 标记为 `internal set`,通常通过构造函数或专用方法设置(如 `MqttApplicationMessage`)。 + +:::
@@ -119,177 +124,92 @@ import { TouchSocketMqttDefinition } from "@site/src/components/Definition.js"; ## 六、创建Mqtt客户端 -```csharp showLineNumbers -var client = new MqttTcpClient(); -await client.SetupAsync(new TouchSocketConfig() - .SetRemoteIPHost("tcp://127.0.0.1:7789") - .SetMqttConnectOptions(options => - { - options.ClientId = "TestClient"; - options.UserName = "TestUser"; - options.Password = "TestPassword"; - options.ProtocolName = "MQTT"; - options.Version = MqttProtocolVersion.V311; - options.KeepAlive = 60; - options.CleanSession = true; +### 6.1 简单创建 - // v5可以继续配置其他 +直接创建`MqttTcpClient`实例,配置连接参数和插件: - // options.UserProperties = new[] - // { - // new MqttUserProperty("key1","value1"), - // new MqttUserProperty("key2","value2") - // }; - }) - .ConfigurePlugins(a => - { - a.AddMqttConnectingPlugin(async (mqttSession, e) => - { - Console.WriteLine($"Client Connecting:{e.ConnectMessage.ClientId}"); - await e.InvokeNext(); - }); + - a.AddMqttConnectedPlugin(async (mqttSession, e) => - { - Console.WriteLine($"Client Connected:{e.ConnectMessage.ClientId}"); - await e.InvokeNext(); - }); +:::info 温馨提示 - a.AddMqttReceivingPlugin(async (mqttSession, e) => - { - await e.InvokeNext().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - }); +`client.ConnectAsync()`方法会连接到Mqtt服务器,如果连接失败会抛出异常。 - a.AddMqttReceivedPlugin(async (mqttSession, e) => - { - var message = e.Message; - await e.InvokeNext().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - }); +::: - a.AddMqttClosingPlugin(async (mqttSession, e) => - { - Console.WriteLine($"Client Closing:{e.MqttMessage.MessageType}"); - await e.InvokeNext(); - }); +### 6.2 配置连接选项 - a.AddMqttClosedPlugin(async (mqttSession, e) => - { - Console.WriteLine($"Client Closed:{e.Message}"); - await e.InvokeNext(); - }); + - }));//载入配置 +### 6.3 配置插件 -await client.ConnectAsync();//连接 -``` + +例如,连接相关插件: + + ## 七、订阅与取消订阅 -### 7.1 订阅 +### 7.1 订阅主题 按照Mqtt协议,客户端可以通过`Subscribe`指令订阅一个或多个主题,服务端会返回订阅结果。 -```csharp {10} showLineNumbers -var topic1 = "topic1"; -var topic2 = "topic2"; -SubscribeRequest subscribeRequest1=new SubscribeRequest(topic1, QosLevel.AtLeastOnce);//订阅请求 -SubscribeRequest subscribeRequest2=new SubscribeRequest(topic2, QosLevel.AtMostOnce);//可以设置不同的Qos级别 - -//多个订阅请求 -var mqttSubscribeMessage = new MqttSubscribeMessage(subscribeRequest1, subscribeRequest2); - -//执行订阅 -var mqttSubAckMessage = await client.SubscribeAsync(mqttSubscribeMessage); - -//输出订阅结果 -foreach (var item in mqttSubAckMessage.ReturnCodes) -{ - Console.WriteLine($"ReturnCode:{item}"); -} -``` + ### 7.2 取消订阅 按照Mqtt协议,客户端可以通过`Unsubscribe`指令取消订阅一个或多个主题,服务端会返回所有取消订阅结果。 -```csharp {5} showLineNumbers -var topic1 = "topic1"; -var topic2 = "topic2"; - -//取消订阅 -var mqttUnsubAckMessage = await client.UnsubscribeAsync(new MqttUnsubscribeMessage(topic1,topic2)); -``` + ## 八、接收消息 -Mqtt组件的消息都是通过插件抛出的。你可以订阅`IMqttReceivingPlugin`插件和`IMqttReceivedPlugin`插件来接收消息。 +在Mqtt客户端连接成功之后,Mqtt组件的消息都是通过插件抛出的。你可以订阅`IMqttReceivingPlugin`插件和`IMqttReceivedPlugin`插件来接收消息。 -正如插件说明所示,`IMqttReceivingPlugin`插件可以在收到所有消息时触发,`IMqttReceivedPlugin`插件可以在收到发布消息时触发。 +正如插件说明所示,`IMqttReceivingPlugin`插件可以在收到所有消息时触发,`IMqttReceivedPlugin`插件可以在收到发布消息时触发。 -所以如果你只关心发布成功的消息,那么可以只订阅`IMqttReceivedPlugin`插件即可。 +所以如果你只关心接收(已发布)消息的消息,那么可以只订阅`IMqttReceivedPlugin`插件即可。 -你可以直接使用委托实现订阅: +但是对于客户端来说,也可能需要获取到的消息可能是订阅确认、发布、发布确认、取消订阅确认等。所以你可以通过`IMqttReceivingPlugin`的`e.MqttMessage`来获取到具体的消息类型。 -```csharp {3-17} showLineNumbers -.ConfigurePlugins(a => -{ - a.AddMqttReceivedPlugin(async (client, e) => - { - var mqttMessage = e.MqttMessage; - Console.WriteLine("Reved:" + mqttMessage); +下列将简单演示。 - //订阅消息的主题 - var topicName = mqttMessage.TopicName; +### 8.1 通过插件接收所有消息 - //订阅消息的Qos级别 - var qosLevel = mqttMessage.QosLevel; + - //订阅消息的Payload - var payload = mqttMessage.Payload; - await e.InvokeNext(); - }); -}) -``` +### 8.2 接收发布消息 -或者使用插件类来继承接口实现: +如果你只关心发布成功的消息,那么可以只订阅`IMqttReceivedPlugin`插件即可: -```csharp showLineNumbers -class MyMqttReceivedPlugin : PluginBase, IMqttReceivedPlugin -{ - public async Task OnMqttReceived(IMqttSession client, MqttReceivedEventArgs e) - { - var mqttMessage = e.MqttMessage; - - //订阅消息的主题 - var topicName = mqttMessage.TopicName; - - //订阅消息的Qos级别 - var qosLevel = mqttMessage.QosLevel; - - //订阅消息的Payload - var payload = mqttMessage.Payload; - await e.InvokeNext(); - } -} -``` - -然后把这个类添加到插件即可: - -```csharp {3} showLineNumbers -.ConfigurePlugins(a => -{ - a.Add();//添加自定义插件 -}) -``` + ## 九、发布消息 -```csharp {3} showLineNumbers -MqttPublishMessage message = new(topic1, false, QosLevel.AtLeastOnce, Encoding.UTF8.GetBytes("Hello World")); + -await client.PublishAsync(message); -``` +或者使用高性能的内存池方式发布数据。 + + +## 十、断开连接 + +### 10.1 正常断开 + + + +### 10.2 释放资源 + + + +## 十一、连接管理 + +### 11.1 检查连接状态 + + + +### 11.2 自动重连配置 + + diff --git a/handbook/docs/mqttservice.mdx b/handbook/docs/mqttservice.mdx index 29c3fd9aa..7f64b968b 100644 --- a/handbook/docs/mqttservice.mdx +++ b/handbook/docs/mqttservice.mdx @@ -5,8 +5,9 @@ title: Mqtt服务器 import Tag from "@site/src/components/Tag.js"; import { TouchSocketMqttDefinition } from "@site/src/components/Definition.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; +import CardLink from "@site/src/components/CardLink.js"; -### 定义 @@ -58,31 +59,11 @@ import { TouchSocketMqttDefinition } from "@site/src/components/Definition.js"; ## 六、创建Mqtt服务端 -```csharp showLineNumbers -var service = new MqttTcpService(); -await service.SetupAsync(new TouchSocketConfig() - .SetListenIPHosts(7789) // 监听所有网卡 - .ConfigureContainer(a => - { - a.AddConsoleLogger(); - }) - .ConfigurePlugins(a => - { - a.AddMqttClientConnectedPlugin(async (service, e) => - { - Console.WriteLine($"Client Connected:{e.ClientId}"); - await e.InvokeNext(); - }); +### 6.1 简单创建 - a.AddMqttClientDisconnectedPlugin(async (service, e) => - { - Console.WriteLine($"Client Disconnected:{e.ClientId}"); - await e.InvokeNext(); - }); - })); -await service.StartAsync(); -``` +直接创建MqttTcpService,然后配置基本的监听地址和插件: + ## 七、接收消息 @@ -96,91 +77,57 @@ await service.StartAsync(); 下列将简单演示。 -### 7.1 订阅消息 +### 7.1 通过插件接收所有消息 -```csharp showLineNumbers -.ConfigurePlugins(a => -{ - a.AddMqttReceivingPlugin(async (client, e) => - { - switch (e.MqttMessage) - { - case MqttSubscribeMessage message: - { - //订阅消息 - Console.WriteLine("Reving:" + e.MqttMessage.MessageType); - foreach (var subscribeRequest in message.SubscribeRequests) - { - var topic = subscribeRequest.Topic; - var qosLevel = subscribeRequest.QosLevel; - //或者其他属性 - Console.WriteLine($"Subscribe Topic:{topic},QosLevel:{qosLevel}"); - } - break; - } - default: - break; - } - Console.WriteLine("Reving:" + e.MqttMessage.MessageType); - await e.InvokeNext(); - }); -}) -``` + -### 7.2 取消订阅消息 +### 7.2 接收发布消息 -```csharp showLineNumbers -.ConfigurePlugins(a => -{ - a.AddMqttReceivingPlugin(async (client, e) => - { - switch (e.MqttMessage) - { - case MqttUnsubscribeMessage message: - { +如果你只关心发布成功的消息,那么可以只订阅`IMqttReceivedPlugin`插件即可: - //取消订阅消息 - Console.WriteLine("Reving:" + e.MqttMessage.MessageType); - foreach (var topic in message.TopicFilters) - { - //取消订阅的主题 - Console.WriteLine($"Unsubscribe Topic:{topic}"); - } - break; - } - default: - break; - } - Console.WriteLine("Reving:" + e.MqttMessage.MessageType); - await e.InvokeNext(); - }); -}) -``` + -### 7.3 发布消息 +## 八、发布消息 -所以如果你只关心发布成功的消息,那么可以只订阅`IMqttReceivedPlugin`插件即可。 +暂未实现 +{/* Mqtt服务器可以主动向连接的客户端发布消息。 -你可以直接使用委托实现订阅: +### 8.1 向所有客户端发布消息 -```csharp {3-17} showLineNumbers -.ConfigurePlugins(a => -{ - a.AddMqttReceivedPlugin(async (client, e) => - { - var mqttMessage = e.MqttMessage; - Console.WriteLine("Reved:" + mqttMessage); + - //订阅消息的主题 - var topicName = mqttMessage.TopicName; +### 8.2 向指定客户端发布消息 - //订阅消息的Qos级别 - var qosLevel = mqttMessage.QosLevel; + */} - //订阅消息的Payload - var payload = mqttMessage.Payload; - await e.InvokeNext(); - }); -}) -``` +## 九、客户端管理 +### 9.1 获取所有连接的客户端 + + + +### 9.2 断开指定客户端 + + + +## 十、订阅管理 + +暂未实现 + +{/* ### 10.1 获取客户端订阅信息 + + */} + +## 十一、服务器管理 + +### 11.1 停止服务器 + + + +### 11.2 释放资源 + + + +## 十二、示例Demo + + \ No newline at end of file diff --git a/handbook/docs/namedpipeclient.mdx b/handbook/docs/namedpipeclient.mdx index 85bb7ed81..9438a40e2 100644 --- a/handbook/docs/namedpipeclient.mdx +++ b/handbook/docs/namedpipeclient.mdx @@ -4,20 +4,24 @@ title: 创建NamedPipeClient --- import Tag from "@site/src/components/Tag.js"; +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; import { TouchSocketNamedPipeDefinition } from "@site/src/components/Definition.js"; - -### 定义 +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; +import CardLink from "@site/src/components/CardLink.js"; ## 一、说明 -NamedPipeClient是命名管道系客户端基类,他直接参与管道的连接、发送、接收、处理、断开等,他的业务与服务器的**NamedPipeSessionClient**是一一对应的。 +`NamedPipeClient`是命名管道系客户端基类,他直接参与命名管道的连接、发送、接收、处理、断开等,他的业务与服务器的`NamedPipeSessionClient`是一一对应的。 ## 二、特点 - 简单易用。 +- 单线程处理。 - 内存池支持 - 高性能 - 适配器预处理,一键式解决**分包**、**粘包**、对象解析(即适用于Tcp的一切适配器)等。 @@ -27,22 +31,26 @@ NamedPipeClient是命名管道系客户端基类,他直接参与管道的连 ## 三、产品应用场景 - 所有本机IPC(进程通讯)基础使用场景:可跨平台、跨语言使用。 +- 高性能本地应用程序间通信场景。 +- 需要可靠数据传输的本机服务通信。 ## 四、可配置项 -
-可配置项 -
+### 4.1 管道名称设置 -#### SetMaxPackageSize +`NamedPipeClient`必须指定管道名称,否则无法连接。 -数据包最大值(单位:byte),默认1024×1024×10。该值会在适当时间,直接作用DataHandlingAdapter.MaxPackageSize。 +管道名称支持以下格式: -#### SetPipeName -设置管道名称。 +1. 简单名称:直接传入管道名称,如`"TouchSocketPipe"` -
-
+ + +:::tip 提示 + +建议在配置时,使用规范的管道名称体制,避免使用特殊字符和过长的名称。 + +::: ## 五、支持插件 @@ -64,71 +72,13 @@ NamedPipeClient是命名管道系客户端基类,他直接参与管道的连 代码如下: -```csharp showLineNumbers -var client = new NamedPipeClient(); -client.Connecting = (client, e) => { return EasyTask.CompletedTask; };//即将连接到服务器,此时已经创建管道,但是还未建立连接 -client.Connected = (client, e) => { return EasyTask.CompletedTask; };//成功连接到服务器 -client.Closing = (client, e) => { return EasyTask.CompletedTask; };//即将从服务器断开连接。此处仅主动断开才有效。 -client.Closed = (client, e) => { return EasyTask.CompletedTask; };//从服务器断开连接,当连接不成功时不会触发。 -client.Received = (client, e) => -{ - //从服务器收到信息。但是一般byteBlock和requestInfo会根据适配器呈现不同的值。 - string mes = e.ByteBlock.Span.ToString(Encoding.UTF8); - client.Logger.Info($"客户端接收到信息:{mes}"); - return EasyTask.CompletedTask; -}; - -//载入配置 -await client.SetupAsync(new TouchSocketConfig() - .SetPipeName("TouchSocketPipe")//设置命名管道名称 - .ConfigureContainer(a => - { - a.AddConsoleLogger();//添加一个日志注入 - })); - -await client.ConnectAsync();//调用连接,当连接不成功时,会抛出异常。 - -//Result result =await client.TryConnectAsync();//或者可以调用TryConnect -//if (result.IsSuccess()) -//{ - -//} - -client.Logger.Info("客户端成功连接"); -``` + #### 6.2 继承实现 -一般继承实现的话,可以从NamedPipeClientBase继承。 +一般继承实现的话,可以从`NamedPipeClient`继承。如果有特殊需求,也可以从`NamedPipeClientBase`继承。 -```csharp showLineNumbers -class MyNamedPipeClient : NamedPipeClient -{ - protected override async Task OnNamedPipeReceived(ReceivedDataEventArgs e) - { - //此处逻辑单线程处理。 - - //此处处理数据,功能相当于Received委托。 - string mes = e.ByteBlock.Span.ToString(Encoding.UTF8); - Console.WriteLine($"已接收到信息:{mes}"); - - await base.OnNamedPipeReceived(e); - } -} -``` - -```csharp showLineNumbers -var client = new MyNamedPipeClient(); -//载入配置 -await client.SetupAsync(new TouchSocketConfig() - .SetPipeName("TouchSocketPipe")//设置命名管道名称 - .ConfigureContainer(a => - { - a.AddConsoleLogger();//添加一个日志注入 - })); - -await client.ConnectAsync();//调用连接,当连接不成功时,会抛出异常。 -``` + ## 七、接收数据 @@ -136,21 +86,15 @@ await client.ConnectAsync();//调用连接,当连接不成功时,会抛出 ### 7.1 Received委托处理 -当使用`NamedPipeClient`创建客户端时,内部已经定义好了一个外置委托Received,可以通过该委托直接接收数据。 +当使用`NamedPipeClient`创建客户端时,内部已经定义好了一个外置委托`Received`,可以通过该委托直接接收数据。 -```csharp showLineNumbers -var client = new NamedPipeClient(); -client.Received = (client, e) => -{ - //从服务器收到信息 - string mes = e.ByteBlock.Span.ToString(Encoding.UTF8); - Console.WriteLine($"接收到信息:{mes}"); - return EasyTask.CompletedTask; -}; -await client.ConnectAsync("TouchSocketPipe"); -``` + -### 7.2 插件处理 推荐 +### 7.2 继承NamedPipeClient重写接收逻辑 + +如6.2所示,如果需要更自定义的接收逻辑,可以从`NamedPipeClient`继承,然后重写`OnNamedPipeReceived`方法。 + +### 7.3 插件处理 推荐 按照TouchSocket的设计理念,使用插件处理数据,是一项非常简单,且高度解耦的方式。步骤如下: @@ -160,50 +104,29 @@ await client.ConnectAsync("TouchSocketPipe"); 如果已经有继承类,直接实现`IPlugin`接口即可。 -```csharp showLineNumbers -public class MyPlugin : PluginBase, INamedPipeReceivedPlugin -{ - public async Task OnNamedPipeReceived(INamedPipeSession client, ReceivedDataEventArgs e) - { - //这里处理数据接收 - //根据适配器类型,e.ByteBlock与e.RequestInfo会呈现不同的值,具体看文档=》适配器部分。 - ByteBlock byteBlock = e.ByteBlock; - IRequestInfo requestInfo = e.RequestInfo; - - ////表示该数据已经被本插件处理,无需再投递到其他插件。 - - await e.InvokeNext(); - } -} -``` + (2)创建使用插件处理的客户端 -```csharp showLineNumbers -var client = new NamedPipeClient(); -await client.SetupAsync(new TouchSocketConfig() - .SetPipeName("TouchSocketPipe")//设置命名管道名称 - .ConfigureContainer(a => - { - a.AddConsoleLogger(); - }) - .ConfigurePlugins(a => - { - a.Add(); - })); + -await client.ConnectAsync(); -``` +### 7.4 异步阻塞接收 + +异步阻塞接收,即使用await的方式接收数据。其特点是能在代码上下文中,直接获取到收到的数据。例如: + + + +:::tip 提示 + +异步阻塞接收,在等待接收数据时,不会阻塞线程资源,所以即使大量使用,也不会影响性能。 + +::: ## 八、发送数据 `NamedPipeClient`已经内置了发送方法,直接调用就可以发送,如果发送失败,则会立即抛出异常。 -```csharp showLineNumbers -//原生 -public Task SendAsync(string id, ReadOnlyMemory memory); -public Task SendAsync(string id, IRequestInfo requestInfo); -``` + :::tip 提示 @@ -218,65 +141,37 @@ public Task SendAsync(string id, IRequestInfo requestInfo); ::: -# 九、断线重连 +## 九、断线重连 -断线重连,即tcp客户端在断开服务器后,主动发起的再次连接请求。 +断线重连,即命名管道客户端在断开服务器后,主动发起的再次连接请求。 -### 9.1 触发型重连 +### 9.1 启用断线重连 -触发型重连,依靠的是NamedPipe断开事件(Closed)发生时,再次尝试连接。所以,这就要求客户端在初始时,至少完成一次连接。 +断线重连,依靠的是命名管道断开后,或者初始化后,Online属性为false时,会尝试连接。 -```csharp {8,11} showLineNumbers -var client = new NamedPipeClient(); -await client.SetupAsync(new TouchSocketConfig() - .SetPipeName("TouchSocketPipe")//设置命名管道名称 - .ConfigureContainer(a => - { - a.AddConsoleLogger(); - }) - .ConfigurePlugins(a => - { - a.UseNamedPipeReconnection(); - })); +所以,重连机制在Setup完成后,即会生效。 -await client.ConnectAsync(); -``` -:::caution 注意 + -**触发重连,必须满足以下几个要求:** +### 9.2 暂停和恢复重连 -1. 必须完成第一次连接。 -2. 必须是被动断开,如果是客户端主动调用Close、Disposed等方法主动断开的话,一般不会生效。 -3. 必须有显式的断开信息,也就是说,直接拔网线的话,不会立即生效,会等tcp保活到期后再生效。 - -::: - - -### 9.2 使用Polling轮询连接插件 - -使用Polling断线重连,是一种无人值守的连接方式,它不要求首次连接。 - -```csharp showLineNumbers -.ConfigurePlugins(a => -{ - a.UseNamedPipeReconnection() - .UsePolling(TimeSpan.FromSeconds(1)); -}) -``` + :::caution 注意 -**Polling重连,必须满足以下几个要求:** +**断线重连,必须满足以下几个要求:** -1. 必须有显式的断开信息,不然不会生效。 +1. 必须有显式的断开信息,也就是说,如果管道服务器异常关闭,可能不会立即检测到。 +2. 重连机制依赖于轮询检查,检查间隔不宜过短,以免消耗过多系统资源。 ::: :::tip 提示 -UseReconnection插件,可以通过设置SetActionForCheck,自己规定检查活性的方法。默认情况下,只会检验Online属性,所以无法检验出断网等情况。如果自己控制,则可以发送心跳包,以保证在线状态。 +`UseNamedPipeReconnection`插件,可以通过设置`SetActionForCheck`,自己规定检查活性的方法。默认情况下,只会检验`Online`属性,如果需要更准确的连接状态检查,建议发送心跳包来验证连接活性。 ::: +## 十、示例Demo -[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/NamedPipe) + \ No newline at end of file diff --git a/handbook/docs/namedpipeservice.mdx b/handbook/docs/namedpipeservice.mdx index e4e75ccf6..5836729ed 100644 --- a/handbook/docs/namedpipeservice.mdx +++ b/handbook/docs/namedpipeservice.mdx @@ -1,19 +1,21 @@ --- id: namedpipeservice -title: 创建命名管道服务器 +title: 创建NamedPipeService --- import Tag from "@site/src/components/Tag.js"; +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; import { TouchSocketNamedPipeDefinition } from "@site/src/components/Definition.js"; - -### 定义 +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; +import CardLink from "@site/src/components/CardLink.js"; ## 一、说明 -NamedPipeService是命名管道系服务器基类,它不参与实际的数据交互,只是配置、激活、管理、注销、重建**NamedPipeSessionClient**类实例。而**NamedPipeSessionClient**是当**NamedPipeClient(客户端)**成功连接服务器以后,由服务器新建的一个实例类,后续的所有通信,也都是通过该实例完成的。 +`NamedPipeService`是命名管道系服务器基类,它不参与实际的数据交互,只是配置、激活、管理、注销、重建`NamedPipeSessionClient`类实例。而`NamedPipeSessionClient`是当`NamedPipeClient`(客户端)成功连接服务器以后,由服务器新建的一个实例类,后续的所有通信,也都是通过该实例完成的。 ## 二、特点 @@ -29,12 +31,15 @@ NamedPipeService是命名管道系服务器基类,它不参与实际的数据 ## 三、产品应用场景 - 所有本机IPC(进程通讯)基础使用场景:可跨平台、跨语言使用。 +- 高性能进程间通信:无需网络开销,直接在本机进程间传输数据。 ## 四、服务器架构 +### 4.1 连接架构 + 服务器在收到**新客户端连接**时,会创建一个`NamedPipeSessionClient`的派生类实例,与客户端`NamedPipeClient`一一对应,后续的数据通信均由此实例负责。 -`NamedPipeSessionClient`在Service里面以字典映射。Id为键,`NamedPipeSessionClient`本身为值。 +`NamedPipeSessionClient`在`Service`里面以字典映射。`ID`为键,`NamedPipeSessionClient`本身为值。 ```mermaid flowchart TD; @@ -47,29 +52,52 @@ flowchart TD; ``` +### 4.2 Scoped 生命周期 + +`NamedPipeService`在[支持Scoped](ioc.mdx)的`IOC`容器中工作时,也是支持`Scoped`区域划分的。 + +一般情况下,`NamedPipeService`在`Setup`时,首先会创建一个`Scoped`区域,用于整个`NamedPipeService`的生命周期。在`NamedPipeService`释放(`Dispose`)时释放。 + +然后,当有新客户端连接后,会为每个`NamedPipeSessionClient`的派生类实例也创建一个`Scoped`区域,用于`NamedPipeSessionClient`的生命周期。当连接断开时,会释放此区域。 + ## 五、可配置项 -
-可配置项 -
+### 5.1 配置监听 -#### SetMaxPackageSize -数据包最大值(单位:byte),默认1024×1024×10。该值会在适当时间,直接作用DataHandlingAdapter.MaxPackageSize。 +简单情况下,直接设置管道名称进行监听。 -#### SetPipeName -设置管道名称。 + -#### SetNamedPipeListenOptions -设置独立的管道监听,可以独立控制当前监听的个性化配置。 +简单设置时,无法进行更加个性化的配置。例如:使用何种适配器等。 -#### SetServerName -服务器标识名称,无实际使用意义。 +所以,可以使用`SetNamedPipeListenOptions`方法,进行个性化监听配置。 -#### SetMaxCount -最大可连接数,默认为10000 + -
-
+:::info 温馨提示 + +`SetPipeName`可以和`SetNamedPipeListenOptions`可以同时使用,但是需要注意的是,Config的全局配置仅会对`SetPipeName`单独生效的。`SetNamedPipeListenOptions`的地址配置均是单独配置的。 + +::: + +### 5.2 Id分配策略 + +`NamedPipeService`会在每次新建`NamedPipeSessionClient`时,分配一个Id。默认情况下,Id是随机分配的。 +如果需要自定义Id分配策略,可以使用`SetGetDefaultNewId`方法。 + + + +### 5.3 设置最大连接数量 + +`NamedPipeService`的最大连接数可以通过`SetMaxCount`方法设置。默认情况下,最大连接数为10000。 + + + +### 5.4 服务器名称 + +`NamedPipeService`支持设置服务器名称,可以通过`SetServerName`方法设置。默认情况下,服务器名称为Null。 + + ## 六、支持插件 @@ -94,47 +122,7 @@ flowchart TD; 代码如下: -```csharp showLineNumbers -var service = new NamedPipeService(); -service.Connecting = (client, e) => { return EasyTask.CompletedTask; };//有客户端正在连接 -service.Connected = (client, e) => { return EasyTask.CompletedTask; };//有客户端成功连接 -service.Closing = (client, e) => { return EasyTask.CompletedTask; };//有客户端正在断开连接,只有当主动断开时才有效。 -service.Closed = (client, e) => { return EasyTask.CompletedTask; };//有客户端断开连接 -service.Received = async (client, e) => -{ - //从客户端收到信息 - string mes = e.ByteBlock.Span.ToString(Encoding.UTF8); - client.Logger.Info($"已从{client.Id}接收到信息:{mes}"); - - await client.SendAsync(mes);//将收到的信息直接返回给发送方 - - //await client.SendAsync("id",mes);//将收到的信息返回给特定ID的客户端 - - ////将收到的信息返回给在线的所有客户端。 - ////注意:这只是个设计建议,实际上群发应该使用生产者消费者的设计模式 - //var ids = service.GetIds(); - //foreach (var clientId in ids) - //{ - // if (clientId != client.Id)//不给自己发 - // { - // await service.SendAsync(clientId, mes); - // } - //} -}; - -await service.SetupAsync(new TouchSocketConfig()//载入配置 - .SetPipeName("TouchSocketPipe")//设置命名管道名称 - .ConfigureContainer(a => - { - a.AddConsoleLogger();//添加一个控制台日志注入(注意:在maui中控制台日志不可用) - }) - .ConfigurePlugins(a => - { - //a.Add();//此处可以添加插件 - })); - -await service.StartAsync();//启动 -``` + :::info 温馨提示 @@ -144,146 +132,41 @@ Service.StartAsync()方法并不会阻塞当前运行,所以当在控制台运 ### 7.2 泛型创建 -通过泛型创建服务器,可以实现很多有意思,且能**重写**一些有用的功能。下面就演示,如何通过泛型创建服务器。 +通过泛型创建服务器,可以实现很多有意思,且能**重写**一些有用的功能。下面就演示,如何通过泛型创建自定义服务器。 代码如下: -(1)建立`NamedPipeSessionClient`继承类。 +(1)建立NamedPipeSessionClient继承类。 -```csharp showLineNumbers -public class MySessionClient : NamedPipeSessionClient -{ - protected override async Task OnNamedPipeReceived(ReceivedDataEventArgs e) - { - //此处逻辑单线程处理。 + - //此处处理数据,功能相当于Received委托。 - string mes = e.ByteBlock.Span.ToString(Encoding.UTF8); - Console.WriteLine($"已接收到信息:{mes}"); - await base.OnNamedPipeReceived(e); - } -} -``` +(2)建立`NamedPipeService`继承类。使用**NamedPipeService的泛型**直接创建。 -(2)建立`NamedPipeService`继承类。实际上如果业务不涉及服务器配置的话,可以省略该步骤,使用**NamedPipeService的泛型**直接创建。 + -```csharp showLineNumbers -public class MyService : NamedPipeService -{ - protected override void LoadConfig(TouchSocketConfig config) - { - //此处加载配置,用户可以从配置中获取配置项。 - base.LoadConfig(config); - } +(3)启动服务器。 - protected override MySessionClient NewClient() - { - return new MySessionClient(); - } +自定义服务器的配置和启动与普通服务器一致。 - protected override Task OnNamedPipeConnecting(MySessionClient socketClient, ConnectingEventArgs e) - { - //此处逻辑会多线程处理。 - - //e.Id:对新连接的客户端进行ID初始化,默认情况下是按照设定的规则随机分配的。 - //但是按照需求,您可以自定义设置,例如设置为其IP地址。但是需要注意的是id必须在生命周期内唯一。 - - //e.IsPermitOperation:指示是否允许该客户端链接。 - return base.OnNamedPipeConnecting(socketClient, e); - } -} -``` - -(3)创建服务器(包含MyService)。 - -```csharp showLineNumbers -var service = new MyService(); -await service.StartAsync("TouchSocketPipe");//设置命名管道名称,启动 -``` + :::tip 建议 -由上述代码可以看出,通过继承,可以更加灵活的实现扩展。但实际上,很多业务我们希望大家能通过插件完成。 +由上述代码可以看出,通过继承,可以更加灵活的实现扩展。如有必要,还可以直接从`NamedPipeServiceBase<>`继承,这样可以实现更底层的功能。 ::: -## 八、配置监听 - -### 8.1 Config直接配置 - -服务器在配置监听时,有多种方式实现。其中最简单、最常见的配置方式就是通过Config直接配置。 - -```csharp showLineNumbers -var service = new NamedPipeService(); -await service.SetupAsync(new TouchSocketConfig()//载入配置 - .SetPipeName("TouchSocketPipe"));//设置命名管道名称 - -await service.StartAsync();//启动 -``` - -### 8.2 直接添加监听配置 - -直接添加监听配置是更加个性化的监听配置,它可以单独控制指定监听地址的具体配置,例如:使用何种适配器等。 - -```csharp showLineNumbers -var service = new NamedPipeService(); -await service.SetupAsync(new TouchSocketConfig()//载入配置 - .SetPipeName("TouchSocketPipe")//设置默认命名管道名称 - .SetNamedPipeListenOptions(list => - { - //如果想实现多个命名管道的监听,即可这样设置,一直Add即可。 - list.Add(new NamedPipeListenOption() - { - Adapter = () => new NormalDataHandlingAdapter(), - Name = "TouchSocketPipe2"//管道名称 - }); - - list.Add(new NamedPipeListenOption() - { - Adapter = () => new NormalDataHandlingAdapter(), - Name = "TouchSocketPipe3"//管道名称 - }); - }) - .ConfigureContainer(a =>//容器的配置顺序应该在最前面 - { - a.AddConsoleLogger();//添加一个控制台日志注入(注意:在maui中控制台日志不可用) - }) - .ConfigurePlugins(a => - { - //a.Add();//此处可以添加插件 - })); - -await service.StartAsync();//启动 -``` - -:::info 温馨提示 - -`SetPipeName`可以和`SetNamedPipeListenOptions`可以同时使用,但是需要注意的是,Config的全局配置仅会对`SetPipeName`单独生效的。`SetNamedPipeListenOptions`的地址配置均是单独配置的。 - -::: - - -### 8.3 动态添加、移除监听配置 +## 八、动态添加、移除监听配置 服务器支持在运行时,动态添加,和移除监听配置,这极大的为灵活监听提供了方便,并且还不影响现有连接。可以轻量级的实现Stop操作。 -```csharp {5,16} -var service = new NamedPipeService(); -await service.SetupAsync(new TouchSocketConfig()); + -await service.StartAsync();//启动 +:::info 信息 -service.AddListen(new NamedPipeListenOption()//在Service运行时,可以调用,直接添加监听 -{ - Name = "TouchSocketPipe4",//名称用于区分监听 - Adapter = () => new FixedHeaderPackageAdapter(),//可以单独对当前地址监听,配置适配器,还有其他可配置项,都是单独对当前地址有效。 -}); +在移除监听配置时,已完成连接的客户端不受影响。 -foreach (var item in service.Monitors) -{ - service.RemoveListen(item);//在Service运行时,可以调用,直接移除现有监听 -} -``` +::: ## 九、接收数据 @@ -293,26 +176,16 @@ foreach (var item in service.Monitors) 当使用NamedPipeService(非泛型)创建服务器时,内部已经定义好了一个外置委托Received,可以通过该委托直接接收数据。 -```csharp showLineNumbers -var service = new NamedPipeService(); -service.Received = (client, e) => -{ - //从客户端收到信息 - string mes = e.ByteBlock.Span.ToString(Encoding.UTF8); - client.Logger.Info($"已从{client.Id}接收到信息:{mes}"); - return EasyTask.CompletedTask; -}; - -await service.StartAsync("TouchSocketPipe");//启动 -``` + ### 9.2 重写NamedPipeSessionClient处理 -正如6.2所示,可以直接在MySessionClient的重写**ReceivedData**中直接处理数据。 +正如7.2所示,可以直接在`MyNamedPipeSessionClient`的重写`OnNamedPipeReceived`中直接处理数据。 ### 9.3 插件处理 推荐 -按照TouchSocket的设计理念,使用插件处理数据,是一项非常简单,且高度解耦的方式。步骤如下: + +按照`TouchSocket`的设计理念,使用插件处理数据,是一项非常简单,且高度解耦的方式。步骤如下: (1)声明插件 @@ -320,55 +193,19 @@ await service.StartAsync("TouchSocketPipe");//启动 如果已经有继承类,直接实现`IPlugin`接口即可。 -```csharp showLineNumbers -class MyNamedPipePlugin : PluginBase, INamedPipeConnectedPlugin, INamedPipeClosedPlugin, INamedPipeReceivedPlugin -{ - private readonly ILog m_logger; + - public MyNamedPipePlugin(ILog logger) - { - this.m_logger = logger; - } +(2)配置使用插件处理的服务器 - public async Task OnNamedPipeClosed(INamedPipeSession client, ClosedEventArgs e) - { - this.m_logger.Info("Disconnected"); - await e.InvokeNext(); - } + - public async Task OnNamedPipeConnected(INamedPipeSession client, ConnectedEventArgs e) - { - this.m_logger.Info("Connected"); - await e.InvokeNext(); - } +:::info 信息 - public async Task OnNamedPipeReceived(INamedPipeSession client, ReceivedDataEventArgs e) - { - this.m_logger.Info(e.ByteBlock.ToString()); - await e.InvokeNext(); - } -} -``` +当接收数据时,`Memory`与`RequestInfo`的值会根据适配器类型不同而不同。 -(2)创建使用插件处理的服务器 +::: -```csharp {10} -var service = new NamedPipeService(); -await service.SetupAsync(new TouchSocketConfig() - .SetPipeName("TouchSocketPipe")//设置命名管道名称 - .ConfigureContainer(a => - { - a.AddConsoleLogger(); - }) - .ConfigurePlugins(a => - { - a.Add(); - })); - -await service.StartAsync(); -``` - -### 9.4 异步阻塞接收 推荐 +### 9.4 异步阻塞接收 异步阻塞接收,即使用`await`的方式接收数据。其特点是能在代码上下文中,直接获取到收到的数据。 @@ -376,110 +213,13 @@ await service.StartAsync(); 下列将以插件为例: -```csharp showLineNumbers -class NamedPipeServiceReceiveAsyncPlugin : PluginBase, INamedPipeConnectedPlugin -{ - public async Task OnNamedPipeConnected(INamedPipeSession client, ConnectedEventArgs e) - { - if (client is INamedPipeSessionClient sessionClient) - { - //receiver可以复用,不需要每次接收都新建 - using (var receiver = sessionClient.CreateReceiver()) - { - while (true) - { - //receiverResult每次接收完必须释放 - using (var receiverResult = await receiver.ReadAsync(CancellationToken.None)) - { - //收到的数据,此处的数据会根据适配器投递不同的数据。 - var byteBlock = receiverResult.ByteBlock; - var requestInfo = receiverResult.RequestInfo; - - if (receiverResult.IsCompleted) - { - //断开连接了 - Console.WriteLine($"断开信息:{receiverResult.Message}"); - return; - } - - //如果数据是从ByteBlock投递 - if (byteBlock != null) - { - Console.WriteLine(byteBlock.Span.ToString(Encoding.UTF8)); - } - - //如果是适配器信息,则可以直接处理requestInfo; - } - } - } - } - - } -} -``` + 在异步阻塞接收时,当接收的数据不满足解析条件时,还可以缓存起来,下次一起处理。 例如:下列将演示接收字符串,当没有发现“\r\n”时,将缓存数据,直到发现重要字符。 -其中,`CacheMode`与`MaxCacheSize`是启用缓存的重要属性。`byteBlock.Seek`则是将已读取的数据游标移动至指定位置。 -```csharp showLineNumbers -class NamedPipeServiceReceiveAsyncPlugin : PluginBase, INamedPipeConnectedPlugin -{ - public async Task OnNamedPipeConnected(INamedPipeSession client, ConnectedEventArgs e) - { - if (client is INamedPipeSessionClient sessionClient) - { - //receiver可以复用,不需要每次接收都新建 - using (var receiver = sessionClient.CreateReceiver()) - { - receiver.CacheMode = true; - receiver.MaxCacheSize = 1024 * 1024; - - var rn = Encoding.UTF8.GetBytes("\r\n"); - while (true) - { - //receiverResult每次接收完必须释放 - using (var receiverResult = await receiver.ReadAsync(CancellationToken.None)) - { - //收到的数据,此处的数据会根据适配器投递不同的数据。 - var byteBlock = receiverResult.ByteBlock; - var requestInfo = receiverResult.RequestInfo; - - if (receiverResult.IsCompleted) - { - //断开连接了 - Console.WriteLine($"断开信息:{receiverResult.Message}"); - return; - } - - //在CacheMode下,byteBlock将不可能为null - - var index = 0; - while (true) - { - var r = byteBlock.Span.Slice(index).IndexOf(rn); - if (r < 0) - { - break; - } - - var str = byteBlock.Span.Slice(index, r).ToString(Encoding.UTF8); - Console.WriteLine(str); - - index += rn.Length; - index += r; - } - - byteBlock.Seek(index); - } - } - } - } - } -} -``` :::tip 提示 @@ -489,39 +229,25 @@ class NamedPipeServiceReceiveAsyncPlugin : PluginBase, INamedPipeConnectedPlugin ## 十、发送数据 -按照架构图,每个客户端成功连接后,**服务器**都会创建一个派生自**NamedPipeSessionClient**的实例,并将其存以生成的Id为键,存在一个字典中。 +按照架构图,每个客户端成功连接后,**服务器**都会创建一个派生自`NamedPipeSessionClient`的实例,并将其存以生成的`Id`为键,存在一个字典中。 -所以,service提供了一下原生方法,可以通过id直接将数据发送至指定客户端。 +所以,可以直接使用`NamedPipeSessionClient`直接发送。 -```csharp showLineNumbers -//原生 -public Task SendAsync(string id, ReadOnlyMemory memory); -public Task SendAsync(string id, IRequestInfo requestInfo); -``` +例如,在`Received`委托中,直接回应数据: + + + +或者,如果你知道目标客户端的`Id`,那么可以使用`NamedPipeService`的`SendAsync`方法直接发送数据。 例如: -```csharp showLineNumbers -await service.SendAsync("id",Encoding.UTF8.GetBytes("hello")); -``` + 亦或者,可以先用id查到对应的`NamedPipeSessionClient`,然后用其提供的方法直接发送。 例如: -```csharp showLineNumbers -//尝试性获取 -if (service.TryGetClient("id", out var sessionClient)) -{ - await sessionClient.SendAsync("hello"); -} -``` - -```csharp showLineNumbers -//直接获取,如果id不存在,则会抛出异常 -var sessionClient = service.GetClient("id"); -await sessionClient.SendAsync("hello"); -``` + :::caution 注意 @@ -535,11 +261,86 @@ await sessionClient.SendAsync("hello"); ::: +## 十一、清理和释放 + +### 11.1 清理(断开)连接 + +可以使用`Clear`方法,清理所有连接。 + + + +或者,如果你知道目标客户端的`Id`,那么可以使用`Close`方法,清理指定连接。 + + + +### 11.2 移除监听 + +可以使用`RemoveListen`方法,停止指定命名管道监听器。 + + + :::tip 提示 -框架不仅内置了字节的发送,也扩展了**字符串**等常见数据的发送。而且还包括了`TrySend`等不会抛出异常的发送方法。 +停止监听时,已完成连接的客户端不受影响。 ::: -[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/NamedPipe) +### 11.3 停止服务器 + +可以使用`Stop`方法,停止服务器。 + + + +:::info + +停止服务器时,会断开连接的所有客户端。随后可以重新启动服务器。 + +::: + +### 11.4 释放资源 + +可以使用`Dispose`方法,释放服务器资源。 + + + +## 十二、重置Id + +每个客户端在连接时,服务器都会为连接的客户端**新分配**一个唯一的`Id`。也就是说,在服务器中`Id`与`NamedPipeSessionClient`实例就是一一对应的。 + +### 12.1 配置初始Id策略 + +默认情况下服务器都会根据**历史连接数量**,为连接的客户端新分配`Id`。也就是说,第1个连接的,其Id就是1(表现形式为`01-00-00-00`),以此类推。 + +当然我们可以自由的定义`Id`策略,只需要在`Config`配置中。 + + + +### 12.3 即时修改Id + +上述修改Id的方式,应该还不足以应对所有情况。有时候我们希望,在该连接完成,且经过某种验证之后再设置新的Id,那么我们可以通过`ResetIdAsync`的方法,来实现需求。 + +#### 12.3.1 通过Service直接修改 + + + +#### 12.3.2 通过NamedPipeSessionClient修改 + +首先,需要获取到`NamedPipeSessionClient`。 + +一般使用`Service`,或者在插件中,都可以获取到`NamedPipeSessionClient`。 + +然后需要判断该`NamedPipeSessionClient`是否实现了`IIdClient`接口。一般服务器端的终端,都会实现`IIdClient`接口。 + + + +:::note 备注 + +上述的Id标识,仅仅是服务器`NamedPipeService`和辅助客户端`NamedPipeSessionClient`之间的关联。与客户端`NamedPipeClient`是没有任何关系的。 + +::: + +## 十三、示例Demo + + + diff --git a/handbook/docs/natservice.mdx b/handbook/docs/natservice.mdx index 16dfe4803..d7386d876 100644 --- a/handbook/docs/natservice.mdx +++ b/handbook/docs/natservice.mdx @@ -4,8 +4,9 @@ title: Tcp端口转发 --- import Tag from "@site/src/components/Tag.js"; import { TouchSocketDefinition } from "@site/src/components/Definition.js"; +import CardLink from "@site/src/components/CardLink.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; -### 定义 @@ -28,82 +29,19 @@ import { TouchSocketDefinition } from "@site/src/components/Definition.js"; 创建一个继承自`NatService`的类,并重写必要的方法。在这个例子中,我们创建了一个名为`MyNatService`的类。 -```csharp showLineNumbers -internal class MyNatService : NatService -{ - protected override MyNatSessionClient NewClient() - { - return new MyNatSessionClient(); - } -} -``` + ### 3.2 创建会话客户端类 创建一个继承自`NatSessionClient`的类,并根据需要重写其中的方法。在这个例子中,我们创建了一个名为`MyNatSessionClient`的类。 -```csharp showLineNumbers -class MyNatSessionClient : NatSessionClient -{ - #region 抽象类必须实现 - protected override async Task OnNatConnected(ConnectedEventArgs e) - { - try - { - await this.AddTargetClientAsync(config => - { - config.SetRemoteIPHost("127.0.0.1:7789"); - //还可以配置其他,例如断线重连,具体可看文档tcpClient部分 - }); - } - catch (Exception ex) - { - //目标客户端无法连接,也就是无法转发 - this.Logger.Exception(ex); - } - } - - protected override async Task OnTargetClientClosed(NatTargetClient client, ClosedEventArgs e) - { - //可以自己重连,或者其他操作 - - //或者直接移除 - this.RemoveTargetClient(client); - await EasyTask.CompletedTask; - } - #endregion -} -``` + ## 四、使用 在主函数中初始化服务并开始监听指定端口。这里我们监听的是7788端口,并且设置了日志记录。 -```csharp showLineNumbers -private static async Task Main(string[] args) -{ - var service = new MyNatService(); - await service.SetupAsync(new TouchSocketConfig() - .SetListenIPHosts(7788) - .ConfigureContainer(a => - { - a.AddLogger(logger => - { - logger.AddConsoleLogger(); - logger.AddFileLogger(); - }); - })); - - await service.StartAsync(); - - service.Logger.Info("转发服务器已启动。已将7788端口转发到127.0.0.1:7789地址"); - - while (true) - { - Console.ReadKey(); - } -} -``` + :::tip 提示 @@ -115,34 +53,7 @@ private static async Task Main(string[] args) `NatService`支持将客户端数据转发到多个目标服务器。实现方法也比较简单,只需要使用`NatSessionClient`直接进行添加目标客户端即可。 -```csharp {7-18} showLineNumbers -class MyNatSessionClient : NatSessionClient -{ - protected override async Task OnNatConnected(ConnectedEventArgs e) - { - try - { - await this.AddTargetClientAsync(config => - { - config.SetRemoteIPHost("127.0.0.1:7789"); - //还可以配置其他,例如断线重连,具体可看文档tcpClient部分 - }); - - //也可以再添加个转发端,实现一对多转发 - await this.AddTargetClientAsync(config => - { - config.SetRemoteIPHost("127.0.0.1:7790"); - //还可以配置其他,例如断线重连,具体可看文档tcpClient部分 - }); - } - catch (Exception ex) - { - //目标客户端无法连接,也就是无法转发 - this.Logger.Exception(ex); - } - } -} -``` + ## 六、实现多转一 @@ -150,45 +61,22 @@ class MyNatSessionClient : NatSessionClient 首先,需要独立初始化目标客户端,然后自行管理其创建和释放。 -```csharp showLineNumbers -static class MyClientClass -{ - public static NatTargetClient TargetClient { get; } - - //初始化步骤可以在任意地方先调用 - public static async Task InitAsync() - { - //使用独立模式初始化,这样当NatSessionClient断开时不会释放该资源 - var client = new NatTargetClient(true); - await client.ConnectAsync("127.0.0.1:7789"); - } -} -``` + 然后在`NatSessionClient`中再添加。 -```csharp {5} showLineNumbers -class MultipleToOneNatSessionClient : NatSessionClient -{ - protected override async Task OnNatConnected(ConnectedEventArgs e) - { - await this.AddTargetClientAsync(MyClientClass.TargetClient); - } - - protected override async Task OnTargetClientClosed(NatTargetClient client, ClosedEventArgs e) - { - //不做任何处理 - await e.InvokeNext(); - } -} -``` + :::tip 提示 -使用静态类管理目标客户端,仅仅是演示目的。在实际使用时,可以考虑使用容器,更加方便的管理。 +使用静态类管理目标客户端,仅仅是演示目的。在实际使用时,可以考虑使用容器,更加方便地管理。 ::: -## 六、实现多转多 +## 七、实现多转多 -`NatService`支持将多个客户端数据转发到多个目标服务器。实现方法与实现多对一转发类似,只需要使用`NatSessionClient`将多个固定的目标客户端直接进行添加即可。 \ No newline at end of file +`NatService`支持将多个客户端数据转发到多个目标服务器。实现方法与实现多对一转发类似,只需要使用`NatSessionClient`将多个固定的目标客户端直接进行添加即可。 + +## 八、示例Demo + + \ No newline at end of file diff --git a/handbook/docs/othercore.mdx b/handbook/docs/othercore.mdx index 825bb336f..bc13610e6 100644 --- a/handbook/docs/othercore.mdx +++ b/handbook/docs/othercore.mdx @@ -4,136 +4,134 @@ title: 其他相关功能类 --- import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import CardLink from "@site/src/components/CardLink.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; -### 定义 ## 一、Crc计算 -**TouchSocket**从网上搜集了Crc1-23的计算方法。并封装在了Crc类中。 -以最常用的Crc16为例。 -```csharp showLineNumbers -byte[] data = new byte[10]; -byte[] result = Crc.Crc16(data, 0, data.Length); -``` -## 二、时间测量器(TimeMeasurer) -功能:封装的Stopwatch,测量运行Action的时间。 -```csharp showLineNumbers -TimeSpan timeSpan = TimeMeasurer.Run(() => - { - Thread.Sleep(1000); - }); +**TouchSocket**从网上搜集了Crc1-23的计算方法,并封装在了`Crc`类中。 -``` +以最常用的Crc16为例: + + + +除了返回字节数组的`Crc16`方法外,还提供了`Crc16Value`方法,可以直接返回ushort类型的校验值,更节省内存: + + + +## 二、时间测量器(TimeMeasurer) + +`TimeMeasurer`是对`Stopwatch`的封装,可以方便地测量运行Action或异步操作的时间。 + +**同步操作示例:** + + + +**异步操作示例:** + + ## 三、MD5计算 -```csharp showLineNumbers -string str = MD5.GetMD5Hash("TouchSocket"); -bool b = MD5.VerifyMD5Hash("TouchSocket",str); -``` + +TouchSocket提供了便捷的MD5计算方法,可以快速计算字符串的MD5哈希值,并进行验证。 + + ## 四、16进制相关 -【将16进制的字符转换为数组】 +TouchSocket提供了多个扩展方法,方便进行16进制字符串与字节数组的转换。 + +**将16进制的字符串转换为字节数组:** + ```csharp showLineNumbers - public static byte[] ByHexStringToBytes(this string hexString, string splite = default) +public static byte[] ByHexStringToBytes(this string hexString, string splite = default) ``` -【将16进制的字符转换为int32】 + +**将16进制的字符串转换为int32:** + ```csharp showLineNumbers - public static int ByHexStringToInt32(this string hexString) +public static int ByHexStringToInt32(this string hexString) ``` +**使用示例:** + + + ## 五、雪花Id生成 -雪花Id,会生成long类型的不重复Id。 -```csharp showLineNumbers -SnowflakeIDGenerator generator = new SnowflakeIDGenerator(4); -long id=generator.NextID(); -``` +雪花Id算法(Snowflake)是一种分布式唯一ID生成算法,可以生成long类型的不重复Id。 -## 六、数据压缩 -内部封装了Gzip的压缩。使用静态方法即可完成。 -```csharp showLineNumbers - -byte[] data = new byte[1024]; -new Random().NextBytes(data); - -using (ByteBlock byteBlock=new ByteBlock(1024*64)) -{ - GZip.Compress(byteBlock,data,0,data.Length);//压缩 - var decompressData2 = GZip.Decompress(byteBlock.ToArray());//解压 -} - - -``` - -压缩接口 -内部还定义了一个IDataCompressor的压缩接口。目的是为了向成熟框架传递压缩方法(例如TcpClient)。 -默认配备了一个GZipDataCompressor。可以直接使用。当然大家可以自由扩展其他压缩方法。 - -```csharp showLineNumbers -class MyDataCompressor : IDataCompressor -{ - public byte[] Compress(ArraySegment data) - { - //此处实现压缩 - throw new NotImplementedException(); - } - - public byte[] Decompress(ArraySegment data) - { - //此处实现压缩 - throw new NotImplementedException(); - } -} -``` - -## 七、短时间戳 - -一般的,时间可由long类型作为唯一时间戳,但是有时候,我们也需要短类型的时间戳(uint),所以您可以使用**DateTimeExtensions**类实现,或者使用其扩展方法。 - -```csharp showLineNumbers -uint timestamp= DateTimeExtensions.ConvertTime(DateTime.Now); -timestamp= DateTime.Now.ConvertTime();//扩展方法 -``` - -## 八、读写锁using - -一般的,我们都会使用**ReaderWriterLockSlim**读写锁,进行成对的Enter和Exit。所以我们一般会使用下列代码。 - -```csharp showLineNumbers -ReaderWriterLockSlim lockSlim = new ReaderWriterLockSlim(); -try -{ - lockSlim.EnterReadLock(); - //do something -} -finally -{ - lockSlim.ExitReadLock(); -} -``` - -但是会显得代码非常臃肿。所以我们可以简化使用using实现。 - -```csharp showLineNumbers -ReaderWriterLockSlim lockSlim = new ReaderWriterLockSlim(); - -using (new ReadLock(lockSlim)) -{ - //do something -} - -using (new WriteLock(lockSlim)) -{ - //do something -} -``` + :::tip 提示 -**ReadLock**和**WriteLock**均为struct类型,所以几乎不会影响性能。 +雪花Id生成器适用于分布式系统中需要生成全局唯一ID的场景。通过workerId参数可以区分不同的工作节点,确保在分布式环境下ID的唯一性。 + +::: + +## 六、数据压缩 + +TouchSocket内部封装了GZip压缩功能,可以方便地对数据进行压缩和解压缩。 + +### 6.1 基本压缩示例 + + + +### 6.2 压缩接口 + +内部还定义了一个`IDataCompressor`压缩接口,目的是为了向成熟框架传递压缩方法(例如TcpClient)。默认配备了一个`GZipDataCompressor`,可以直接使用。当然大家也可以自由扩展其他压缩方法。 + + + +**自定义压缩器实现:** + + + +## 七、短时间戳 + +一般的,时间可由long类型作为唯一时间戳,但是有时候,我们也需要短类型的时间戳(uint),TouchSocket提供了`ToUnsignedMillis`扩展方法,可以将`DateTime`或`DateTimeOffset`转换为自1970年1月1日以来的毫秒数的32位无符号整数。 + + + +:::tip 提示 + +短时间戳适用于需要节省存储空间的场景,使用uint类型可以表示从1970年1月1日开始的毫秒数,适用于时间范围不太长的场景。 + +::: + +## 八、读写锁using + +一般的,我们都会使用`ReaderWriterLockSlim`读写锁,进行成对的Enter和Exit。传统方式需要使用try-finally块来确保锁的释放: + + + +但是会显得代码非常臃肿。所以我们可以简化使用using实现: + + + +:::tip 提示 + +`ReadLock`和`WriteLock`均为struct类型,所以几乎不会影响性能。使用using语句可以确保锁在代码块执行完毕后自动释放,使代码更加简洁安全。 ::: + + +## 九、3DES加密 + +TouchSocket提供了3DES(Triple DES)数据加密和解密功能,可以方便地保护敏感数据。 + + + +:::caution 注意 + +- 加密口令长度必须为8个字符 +- 解密时使用的口令必须与加密时一致 +- 3DES是对称加密算法,加密和解密使用相同的密钥 + +::: + diff --git a/handbook/docs/packageadapter.mdx b/handbook/docs/packageadapter.mdx index e875f7f23..d74c955c5 100644 --- a/handbook/docs/packageadapter.mdx +++ b/handbook/docs/packageadapter.mdx @@ -7,7 +7,7 @@ import CardLink from "@site/src/components/CardLink.js"; import BilibiliCard from '@site/src/components/BilibiliCard.js'; import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; -### 定义 + @@ -129,7 +129,7 @@ private static async Task CreateService() service.Received = (client, e) => { //从客户端收到信息 - var mes = e.ByteBlock.Span.ToString(Encoding.UTF8); + var mes = e.Memory.Span.ToString(Encoding.UTF8); client.Logger.Info($"已从{client.Id}接收到信息:{mes}"); return Task.CompletedTask; }; @@ -196,7 +196,7 @@ private static async Task CreateService() service.Received = (client, e) => { //从客户端收到信息 - var mes = e.ByteBlock.Span.ToString(Encoding.UTF8); + var mes = e.Memory.Span.ToString(Encoding.UTF8); client.Logger.Info($"已从{client.Id}接收到信息:{mes}"); return Task.CompletedTask; }; @@ -270,7 +270,7 @@ private static async Task CreateService() service.Received = (client, e) => { //从客户端收到信息 - var mes = e.ByteBlock.Span.ToString(Encoding.UTF8); + var mes = e.Memory.Span.ToString(Encoding.UTF8); client.Logger.Info($"已从{client.Id}接收到信息:{mes}"); return Task.CompletedTask; }; @@ -344,7 +344,7 @@ private static async Task CreateService() service.Received = (client, e) => { //从客户端收到信息 - var mes = e.ByteBlock.Span.ToString(Encoding.UTF8); + var mes = e.Memory.Span.ToString(Encoding.UTF8); client.Logger.Info($"已从{client.Id}接收到信息:{mes}"); return Task.CompletedTask; }; @@ -471,4 +471,4 @@ private static async Task CreateService() ## 六、本文示例Demo - + diff --git a/handbook/docs/plcbridgemodbus.mdx b/handbook/docs/plcbridgemodbus.mdx index 532b08eba..41a9298ff 100644 --- a/handbook/docs/plcbridgemodbus.mdx +++ b/handbook/docs/plcbridgemodbus.mdx @@ -7,8 +7,8 @@ import Pro from "@site/src/components/Pro.js"; import CardLink from "@site/src/components/CardLink.js"; import Definition from "@site/src/components/Definition.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; -### 定义 @@ -39,178 +39,51 @@ TouchSocket PLC Bridge 提供了强大的工业自动化设备集成能力,尤 ### 4.1 初始化 PLC Bridge 服务 -```csharp -var plcBridge = new PlcBridgeService(); -await plcBridge.SetupAsync(new TouchSocketConfig()); -``` + ### 4.2 配置 Modbus TCP 设备桥接 -```csharp -// 连接 TCP Modbus 设备 -var modbusTcpMaster = new ModbusTcpMaster(); -await modbusTcpMaster.ConnectAsync("127.0.0.1:502"); +首先需要连接 TCP Modbus 设备: -// 映射第一个寄存器区段 (0-20) -var plcDrive1 = new MyModbusHoldingRegistersDrive(modbusTcpMaster, new ModbusDriveOption() -{ - Start = 0, // 虚拟起始地址 - Count = 20, // 寄存器数量 - //Group = "Group", // 执行组(同组设备串行操作) - Name = "TcpDevice1", - SlaveId = 1, // Modbus 从站ID - ModbusStart = 0 // 物理设备起始地址 -}); -await plcBridge.AddDriveAsync(plcDrive1); + -// 映射第二个寄存器区段 (50-70) -var plcDrive2 = new MyModbusHoldingRegistersDrive(modbusTcpMaster, new ModbusDriveOption() -{ - Start = 20, // 下一个虚拟起始地址 - Count = 20, - //Group = "Group", - Name = "TcpDevice2", - SlaveId = 1, - ModbusStart = 50 // 物理设备偏移地址 -}); -await plcBridge.AddDriveAsync(plcDrive2); -``` +然后添加设备驱动器,映射第一个寄存器区段(虚拟地址 0-20,对应物理设备地址 0-20): + + + +映射第二个寄存器区段(虚拟地址 20-40,对应物理设备地址 50-70): + + ### 4.3 配置 Modbus UDP 设备桥接 -```csharp -var modbusUdpMaster = new ModbusUdpMaster(); -await modbusUdpMaster.SetupAsync(new TouchSocketConfig() - .UseUdpReceive() - .SetRemoteIPHost("127.0.0.1:503")); -await modbusUdpMaster.StartAsync(); - -var plcDrive3 = new MyModbusHoldingRegistersDrive(modbusUdpMaster, new ModbusDriveOption() -{ - Start = 40, // 虚拟地址偏移 - Count = 20, - //Group = "Group", - Name = "UdpDevice1", - SlaveId = 1, - ModbusStart = 10 // 物理设备起始地址 -}); -await plcBridge.AddDriveAsync(plcDrive3); -``` + ### 4.4 配置 Modbus 串口设备桥接 -```csharp -var modbusRtuMaster = new ModbusRtuMaster(); -await modbusRtuMaster.SetupAsync(new TouchSocketConfig() - .SetSerialPortOption(new SerialPortOption() - { - BaudRate = 9600, - DataBits = 8, - Parity = System.IO.Ports.Parity.Even, - PortName = "COM2", - StopBits = System.IO.Ports.StopBits.One - })); -await modbusRtuMaster.ConnectAsync(); - -var plcDrive4 = new MyModbusHoldingRegistersDrive(modbusRtuMaster, new ModbusDriveOption() -{ - Start = 60, // 虚拟地址偏移 - Count = 20, - //Group = "Group", - Name = "SerialDevice1", - SlaveId = 1, - ModbusStart = 20 // 物理设备起始地址 -}); -await plcBridge.AddDriveAsync(plcDrive4); -``` + ### 4.5 启动桥接服务 -```csharp -await plcBridge.StartAsync(); -``` + ## 五、创建统一访问接口 ### 5.1 定义 PLC 数据对象 -```csharp -partial class MyPlcObject : PlcObject -{ - public MyPlcObject(IPlcBridgeService bridgeService) : base(bridgeService) - { - } +使用 `PlcObject` 类和 `PlcField` 特性定义数据映射: - // 以 short 类型访问所有寄存器 (0-79) - [PlcField(Start = 0, Quantity = 80)] - private ReadOnlyMemory m_allInt16Data; - - // 以 long 类型访问寄存器 (每4个寄存器合并为1个long) - [PlcField(Start = 0, Quantity = 20)] - private ReadOnlyMemory m_allInt64Data; - - // 访问特定 long 数据 (地址59-62) - [PlcField(Start = 59)] - private long m_int64Data; -} -``` + ### 5.2 使用 PLC 数据对象 -```csharp -// 创建PLC数据访问对象 -MyPlcObject myPlcObject = new MyPlcObject(plcBridge); - -// 写入long数据 -var setInt64Result = await myPlcObject.SetInt64DataAsync(1000); -Console.WriteLine($"写入Int64结果: {setInt64Result}"); - -// 读取long数据 -var readInt64Result = await myPlcObject.GetInt64DataAsync(); -Console.WriteLine($"读取Int64结果: {readInt64Result}"); - -// 批量写入short数据 -var data = Enumerable.Range(1, 80).Select(i => (short)i).ToArray(); -var setAllInt16Result = await myPlcObject.SetAllInt16DataAsync(data); - -// 批量读取short数据 -var readAllInt16Result = await myPlcObject.GetAllInt16DataAsync(); - -// 批量读取long数据 -var readAllInt64Result = await myPlcObject.GetAllInt64DataAsync(); -``` + ## 六、自定义驱动器实现 -```csharp -class MyModbusHoldingRegistersDrive : ModbusHoldingRegistersDrive -{ - public MyModbusHoldingRegistersDrive(IModbusMaster master, ModbusDriveOption option) - : base(master, option) - { - } +通过继承 `ModbusHoldingRegistersDrive` 可以实现自定义的读写逻辑,例如添加日志记录、数据校验等功能: - // 自定义读取操作(添加日志等) - protected override async Task ExecuteReadAsync( - ExecuteReadableValue readableValue, - CancellationToken token) - { - var result = await base.ExecuteReadAsync(readableValue, token); - Console.WriteLine($"设备类型={this.Master.GetType().Name},读取地址={readableValue.Start + this.ModbusStart},长度={readableValue.Count},结果:{result.ToJsonString()}"); - return result; - } - - // 自定义写入操作 - protected override async Task ExecuteWriteAsync( - WritableValue writableValue, - CancellationToken token) - { - var result = await base.ExecuteWriteAsync(writableValue, token); - Console.WriteLine($"设备类型={this.Master.GetType().Name},写入地址={writableValue.Start + this.ModbusStart},长度={writableValue.Count},结果:{result.ToJsonString()}"); - return result; - } -} -``` + ## 七、执行流程 @@ -231,13 +104,7 @@ class MyModbusHoldingRegistersDrive : ModbusHoldingRegistersDrive ### 7.3 资源释放 -```csharp -await plcBridge.StopAsync(); -await modbusTcpMaster.CloseAsync(); -await modbusUdpMaster.StopAsync(); -await modbusRtuMaster.CloseAsync(); -// ...释放其他资源 -``` + ## 八、典型应用场景 @@ -268,4 +135,4 @@ TouchSocket PLC Bridge 通过: ## 十一、本文示例 - \ No newline at end of file + \ No newline at end of file diff --git a/handbook/docs/plcbridgeservice.mdx b/handbook/docs/plcbridgeservice.mdx index 05a371865..c0b5d0fb4 100644 --- a/handbook/docs/plcbridgeservice.mdx +++ b/handbook/docs/plcbridgeservice.mdx @@ -7,7 +7,7 @@ import Pro from "@site/src/components/Pro.js"; import CardLink from "@site/src/components/CardLink.js"; import { TouchSocketProPlcBridgesDefinition } from "@site/src/components/Definition.js"; -### 定义 + @@ -247,4 +247,4 @@ using (var byteBlock = new ByteBlock(1024)) ## 八、本文示例 - + diff --git a/handbook/docs/pluginsmanager.mdx b/handbook/docs/pluginsmanager.mdx index 6bcc55419..8308703e2 100644 --- a/handbook/docs/pluginsmanager.mdx +++ b/handbook/docs/pluginsmanager.mdx @@ -4,223 +4,218 @@ title: 插件系统 --- import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import CardLink from "@site/src/components/CardLink.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; -### 定义 ## 一、说明 -插件系统是一组能实现多播订阅的,可中断的触发器,其主要功能就是实现类似事件、委托的通知消息。其设计核心来自于`AspNetCore`的中间件,它有着和中间件一样的使用体验,同时也有着更高的灵活性和自由度。 +插件系统是一组能实现多播订阅的、可中断的触发器,其主要功能就是实现类似事件、委托的通知消息机制。其设计核心来自于`AspNetCore`的中间件,它有着和中间件一样的使用体验,同时也有着更高的灵活性和自由度。 + +插件系统采用责任链模式,允许多个插件按顺序处理同一个事件,每个插件都可以决定是否继续传递给下一个插件,这种设计使得系统具有高度的可扩展性和灵活性。 ## 二、产品特点 -- 简单易用。 -- 易扩展。 +- **简单易用** - 插件接口设计简洁,易于理解和使用 +- **高性能** - 支持IL调用和源生成器,性能接近直接调用 +- **易扩展** - 支持多种添加插件的方式,包括类型、实例和委托 +- **可中断** - 支持在任意插件中中断传递链 +- **可注入** - 完全支持依赖注入容器 +- **模块化** - 每个插件可以独立负责特定功能 ## 三、产品应用场景 -- 所有可以使用事件。委托的场景。 +- 所有可以使用事件、委托的场景 +- 需要中间件模式的场景 +- 需要责任链模式的场景 +- 需要模块化处理的业务逻辑 +- 需要动态扩展功能的系统 ## 四、与事件、委托相比 -1. 订阅的时候可以不用知道被订阅方是谁,只需要知道要订阅什么通知即可。 -2. 订阅可以随时中断。例如:当事件多播的时候,即使其中一个订阅方已经处理,触发也不会停止,这样会造成资源浪费。 -3. 订阅回调。例如:第一个订阅方,想知道本次触发的最终结果是否已被处理时,委托则做不到。而插件则可以。 -4. 插件可以被继承,可以被扩展。 -5. 插件可以被注入。 -6. 插件可以独立负责相关功能,实现功能独立。可模块化功能。 +插件系统相比传统的事件和委托机制有以下优势: + +1. **解耦订阅** - 订阅的时候可以不用知道被订阅方是谁,只需要知道要订阅什么通知即可 +2. **可中断传递** - 订阅可以随时中断。例如:当事件多播的时候,即使其中一个订阅方已经处理,触发也不会停止,这样会造成资源浪费。而插件可以在任意位置中断 +3. **支持回调** - 第一个订阅方想知道本次触发的最终结果是否已被处理时,委托则做不到,而插件则可以 +4. **可继承扩展** - 插件可以被继承,可以被扩展,更符合面向对象设计 +5. **依赖注入** - 插件可以被注入,方便管理生命周期 +6. **功能独立** - 插件可以独立负责相关功能,实现功能独立,可模块化功能 +7. **执行顺序可控** - 可以精确控制插件的执行顺序和传递链 ## 五、创建插件 +创建插件主要包括两个步骤:定义插件接口和实现插件接口。 + #### 5.1 新建插件接口及事件类 -```csharp showLineNumbers -public class MyPluginEventArgs : PluginEventArgs -{ - public string Words { get; set; } -} +首先需要定义一个插件接口和对应的事件参数类。插件接口必须继承`IPlugin`,事件参数类必须继承`PluginEventArgs`。 -/// -/// 定义一个插件接口,使其继承 -/// -public interface ISayPlugin : IPlugin -{ - /// - /// Say。定义一个插件方法,必须遵循: - /// 1.必须是两个参数,第一个参数可以是任意类型,一般表示触发源。第二个参数必须继承 - /// 2.返回值必须是Task。 - /// - /// 触发主体 - /// 传递参数 - /// - Task Say(object sender, MyPluginEventArgs e); -} -``` + + +插件方法的定义必须遵循以下规则: + +1. **参数要求**: 必须有两个参数 + - 第一个参数: 可以是任意类型,一般表示触发源(sender) + - 第二个参数: 必须继承`PluginEventArgs`,用于传递事件数据 +2. **返回值要求**: 必须是`Task`类型,支持异步操作 +3. **唯一性要求**: 一个插件接口中只允许有一个插件方法 :::caution 注意事项 -确定插件唯一的是插件中的`类型`,所以要求一个插件中只允许有一个插件方法。 +确定插件唯一的是插件中的`类型`,所以要求一个插件中只允许有一个插件方法。如果需要处理多种不同的事件,应该定义多个插件接口。 ::: #### 5.2 实现插件接口 -新建一个类,实现`ISayPlugin`接口即可。不过为了方便,也可先继承`PluginBase`基类,然后实现所需的插件接口。 +实现插件接口有两种方式: -```csharp showLineNumbers -public class SayHelloPlugin : PluginBase, ISayPlugin -{ - public async Task Say(object sender, MyPluginEventArgs e) - { - Console.WriteLine($"{this.GetType().Name}------Enter"); - if (e.Words == "hello") - { - Console.WriteLine($"{this.GetType().Name}------Say"); - //当满足的时候输出,且不在调用下一个插件。 +**方式一**: 继承`PluginBase`基类(推荐) - //亦或者设置e.Handled = true,即使调用下一个插件,也会无效 - - return; - } - await e.InvokeNext(); - Console.WriteLine($"{this.GetType().Name}------Leave"); - } -} +新建一个类,先继承`PluginBase`基类,然后实现所需的插件接口。这种方式可以简化实现过程,`PluginBase`提供了一些便捷的基础功能。 -public class SayHiPlugin : PluginBase, ISayPlugin -{ - public async Task Say(object sender, MyPluginEventArgs e) - { - Console.WriteLine($"{this.GetType().Name}------Enter"); - if (e.Words == "hi") - { - Console.WriteLine($"{this.GetType().Name}------Say"); - //当满足的时候输出,且不在调用下一个插件。 +**方式二**: 直接实现插件接口 - //亦或者设置e.Handled = true,即使调用下一个插件,也会无效 - - return; - } +如果类型已经有其他基类,则可以直接实现`IPlugin`接口和插件方法接口的全部内容。 - await e.InvokeNext(); - Console.WriteLine($"{this.GetType().Name}------Leave"); - } -} + -internal class LastSayPlugin : PluginBase, ISayPlugin -{ - public async Task Say(object sender, MyPluginEventArgs e) - { - Console.WriteLine($"{this.GetType().Name}------Enter"); - Console.WriteLine($"您输入的{e.Words}似乎不被任何插件处理"); - await e.InvokeNext(); - Console.WriteLine($"{this.GetType().Name}------Leave"); - } -} -``` +在上面的示例中,我们创建了三个插件: + +- `SayHelloPlugin`: 处理"hello"消息,处理后中断传递 +- `SayHiPlugin`: 处理"hi"消息,处理后中断传递 +- `LastSayPlugin`: 兜底插件,处理所有未被前面插件处理的消息 + +每个插件都遵循以下模式: + +1. **进入插件** - 打印Enter日志 +2. **判断处理** - 检查是否满足处理条件 +3. **处理或传递** - 如果满足条件则处理并设置`e.Handled = true`;否则调用`await e.InvokeNext()`传递给下一个插件 +4. **离开插件** - 打印Leave日志(只有调用了InvokeNext才会执行到这里) :::tip 提示 -实现的插件,建议继承`PluginBase`,然后实现所需的插件接口,这样能简化实现过程。但是如果该类型已经拥有基类,则直接实现所需插件接口的全部内容即可。 +实现的插件建议继承`PluginBase`,然后实现所需的插件接口,这样能简化实现过程。但是如果该类型已经拥有基类,则直接实现所需插件接口的全部内容即可。 ::: ## 六、订阅插件 -在插件触发前,需要先订阅插件。这样才知道哪些插件可以处理该数据。 +在插件触发前,需要先订阅插件。插件管理器会按照添加的顺序依次调用各个插件,形成一个责任链。 ### 6.1 创建插件管理器 -```csharp showLineNumbers -IPluginManager pluginManager = new PluginManager(new Container()) -{ - Enable = true//必须启用 -}; -``` +首先需要创建一个`PluginManager`实例,并设置`Enable = true`启用插件管理器。 + + + +`PluginManager`构造函数需要传入一个`IResolver`容器对象,用于支持依赖注入功能。 ### 6.2 添加订阅插件 +TouchSocket提供了多种添加插件的方式,以适应不同的使用场景。 + #### 6.2.1 按类型添加 -```csharp showLineNumbers -//添加订阅插件 -pluginManager.Add(); -pluginManager.Add(); -pluginManager.Add(); -``` +这是最常用的方式,通过泛型类型参数添加插件。插件实例会由容器自动创建。 + + + +这种方式的优点: +- 代码简洁,不需要手动实例化 +- 支持依赖注入,插件的依赖会自动解析 +- 支持单例模式(通过`PluginOption`特性) #### 6.2.2 按实例添加 -```csharp showLineNumbers +如果需要手动控制插件的创建过程,可以直接传入插件实例。 + +```csharp showLineNumbers pluginManager.Add(new SayHelloPlugin()); pluginManager.Add(new SayHiPlugin()); pluginManager.Add(new LastSayPlugin()); ``` +这种方式的优点: +- 可以在创建插件时传入构造参数 +- 可以对插件实例进行预配置 +- 完全控制插件的生命周期 + #### 6.2.3 按委托添加 -委托添加插件有多个重载,下面一一为例: +委托添加是一种轻量级的方式,适合简单的处理逻辑,无需创建完整的插件类。 -```csharp showLineNumbers -pluginManager.Add(typeof(ISayPlugin), () => -{ - //无参委托,一般做通知 - Console.WriteLine("在Action1中获得"); -}); + -pluginManager.Add(typeof(ISayPlugin), async (MyPluginEventArgs e) => -{ - //只1个指定参数,当参数是事件参数时,需要主动InvokeNext - Console.WriteLine("在Action2中获得"); - await e.InvokeNext(); -}); +委托添加支持多种签名: -pluginManager.Add(typeof(ISayPlugin), async (client, e) => -{ - //2个不指定参数,需要主动InvokeNext - Console.WriteLine("在Action3中获得"); - await e.InvokeNext(); -}); - -pluginManager.Add(typeof(ISayPlugin), async (object client, MyPluginEventArgs e) => -{ - //2个指定参数,需要主动InvokeNext - Console.WriteLine("在Action3中获得"); - await e.InvokeNext(); -}); -``` +1. **无参委托** - `() => { }` - 仅用于通知,不需要处理数据 +2. **单参委托** - `(MyPluginEventArgs e) => { }` - 只接收事件参数,需要手动调用`e.InvokeNext()` +3. **双参委托(不指定类型)** - `(client, e) => { }` - 接收发送者和事件参数,需要手动调用`e.InvokeNext()` +4. **双参委托(指定类型)** - `(object client, MyPluginEventArgs e) => { }` - 明确指定参数类型,需要手动调用`e.InvokeNext()` :::tip 提示 -需不需要`InvokeNext`,只需要记住一点,委托中是否接收了`PluginEventArgs`派生的事件参数,如果是,则需要主动调用。 +**什么时候需要调用InvokeNext?** + +只需要记住一点:委托中是否接收了`PluginEventArgs`派生的事件参数。 +- 如果接收了事件参数,则需要主动调用`await e.InvokeNext()` +- 如果没有接收事件参数(无参委托),则不需要调用,系统会自动继续执行 ::: ## 七、触发插件 -直接使用`RaiseAsync`方法即可触发插件。 +当所有需要的插件都添加完成后,就可以通过`RaiseAsync`方法触发插件链的执行。 -在触发时,`sender`参数和`PluginEventArgs`参数必须和插件中定义的方法参数一致,不然会抛出异常。 + -```csharp {1} -await pluginManager.RaiseAsync(typeof(ISayPlugin), new object(), new MyPluginEventArgs() -{ - Words = Console.ReadLine() -}); -``` +**参数说明:** -### 7.1 执行结果 +- `pluginType`: 要触发的插件接口类型,例如`typeof(ISayPlugin)` +- `sender`: 事件发送者,可以是任意对象,通常是触发事件的对象本身 +- `eventArgs`: 事件参数,必须是插件方法中定义的`PluginEventArgs`派生类型 -按照上述代码代码逻辑,我们声明了一个名为`ISayPlugin`的插件接口,里面包含一个`Say`的方法。然后分别创建了`SayHelloPlugin`、`SayHiPlugin`、`LastSayPlugin`三个类去实现`ISayPlugin`接口。然后将该三个类都添加至插件管理器中,然后触发`Say`方法。同时传入不同的参数。 +:::caution 重要提示 -当Words=test时,`SayHelloPlugin`和`SayHiPlugin`均不满足处理条件,所以会将数据转至下一个插件,直到`LastSayPlugin`插件。然后当`LastSayPlugin`处理结束以后,处理结果又按照`LastSayPlugin`、`SayHiPlugin`、`SayHelloPlugin`的顺序退出插件。这样,即使`SayHelloPlugin`无法处理该数据,也能得知该数据最终有没有被处理。 +触发时传入的`sender`参数类型和`PluginEventArgs`参数类型必须和插件方法定义中的参数类型一致,否则会抛出异常。 -当Words=hello时,`SayHelloPlugin`满足处理条件,并且终止插件的继续传递。 +::: -``` +**执行流程:** + +1. 插件管理器按照添加顺序依次执行每个插件 +2. 每个插件可以选择处理事件或调用`e.InvokeNext()`传递给下一个插件 +3. 如果某个插件设置了`e.Handled = true`或没有调用`InvokeNext()`,则停止传递 +4. 所有插件执行完成后返回处理结果 + +### 7.1 执行结果分析 + +按照上述代码逻辑,我们声明了一个名为`ISayPlugin`的插件接口,里面包含一个`Say`方法。然后分别创建了`SayHelloPlugin`、`SayHiPlugin`、`LastSayPlugin`三个类去实现`ISayPlugin`接口。将这三个类都添加至插件管理器中,然后触发`Say`方法,并传入不同的参数。 + +**场景一: Words="test"** + +当输入"test"时,`SayHelloPlugin`和`SayHiPlugin`均不满足处理条件,所以会将数据转至下一个插件,直到`LastSayPlugin`插件。然后当`LastSayPlugin`处理结束以后,处理结果又按照`LastSayPlugin` → `SayHiPlugin` → `SayHelloPlugin`的顺序退出插件。 + +这体现了责任链模式的特点:即使前面的插件无法处理该数据,也能通过回调得知该数据最终有没有被处理。 + +**场景二: Words="hello"** + +当输入"hello"时,`SayHelloPlugin`满足处理条件,设置`e.Handled = true`并终止插件的继续传递。后续的`SayHiPlugin`和`LastSayPlugin`不会被执行。 + +**场景三: Words="hi"** + +当输入"hi"时,`SayHelloPlugin`不满足条件,调用`InvokeNext()`传递给下一个插件。`SayHiPlugin`满足条件,处理并终止传递。然后执行流程返回到`SayHelloPlugin`的`InvokeNext()`之后继续执行。 + +**执行结果示例:** + +```text 请输入hello,或者hi test SayHelloPlugin------Enter @@ -245,88 +240,363 @@ SayHelloPlugin------Leave 请输入hello,或者hi ``` +从执行结果可以清晰看到插件的执行链和回调机制。 + + ## 八、插件特性 ### 8.1 中断传递 -当某个插件在响应时,如果设置`e.Handled=true`,或者没有调用下一个插件`e.InvokeNext`,则该数据将**不会**再触发后续的插件。 +插件系统的一个重要特性是支持在任意位置中断传递链。有两种方式可以中断传递: + +**方式一: 设置Handled标志** + +```csharp +e.Handled = true; +await e.InvokeNext(); // 即使调用InvokeNext,后续插件也不会执行 +``` + +**方式二: 不调用InvokeNext** + +```csharp +if (满足处理条件) +{ + // 处理逻辑 + return; // 直接返回,不调用e.InvokeNext() +} +``` + +当某个插件中断传递后,该数据将**不会**再触发后续的插件,但是前面已经执行过的插件的"离开"逻辑仍会执行。 + +:::tip 选择建议 + +- 如果只是不想继续传递,使用`return`不调用`InvokeNext()`即可 +- 如果需要明确标记事件已被处理,建议设置`e.Handled = true` +- 两种方式可以组合使用,以提高代码可读性 + +::: + +### 8.2 插件生命周期 + +插件有两个重要的生命周期方法(定义在`IPlugin`接口中): + +**Loaded(IPluginManager pluginManager)** + +当插件成功添加到`IPluginManager`时执行。可以在此方法中进行初始化操作,例如: +- 注册额外的委托处理 +- 订阅其他插件事件 +- 初始化插件状态 + +**Unloaded(IPluginManager pluginManager)** + +当插件通过`IPluginManager.Remove()`被移除时执行。可以在此方法中进行清理操作,例如: +- 释放资源 +- 取消订阅 +- 保存状态 + +如果继承`PluginBase`,这两个方法已经有默认实现,可以通过override重写。 + +### 8.3 插件配置特性 + +可以使用`PluginOption`特性来配置插件的行为: + +```csharp +[PluginOption(Singleton = true)] +public class MySingletonPlugin : PluginBase, IMyPlugin +{ + // 插件实现 +} +``` + +**Singleton属性** + +设置为`true`时,该插件在一个`IPluginManager`中只会有一个实例。即使多次调用`Add()`,也只会使用第一次添加的实例。 + +**FromIoc属性** + +设置为`true`时,插件实例将从IoC容器中解析,而不是每次都创建新实例。这对于需要依赖注入的插件非常有用。 ## 九、提升插件性能 +TouchSocket的插件系统在设计时充分考虑了性能问题,提供了多种优化方案。 + ### 9.1 插件性能测试 -如下图所示,添加10个插件,并且调用10000次。即:单个插件方法被调用10w次。 +如下图所示,添加10个插件,并且调用10000次,即单个插件方法被调用10万次。 -分别在net6.0,netcore3.1,net4.6.1上进行测试。测试项依次为: +测试环境: 分别在net6.0、netcore3.1、net4.6.1上进行测试 -1. DirectRun(直接调用) -2. ActionRun(委托调用) -3. MethodInfoRun(反射调用) -4. ExpressionRun(表达式树调用) -5. PluginRun(插件调用) -6. PluginActionRun(插件委托调用) +测试项目说明: -实际上插件内部使用的是IL调用,所以即使调用上有迭代,性能上也和直接调用的表达式树差不多(这里插件是采用递归的方式迭代,而表达式树测试则是直接for迭代)。但是总体而言性能上是有缺失的。 +1. **DirectRun**(直接调用) - 直接调用方法的基准性能 +2. **ActionRun**(委托调用) - 使用委托调用的性能 +3. **MethodInfoRun**(反射调用) - 使用反射调用的性能 +4. **ExpressionRun**(表达式树调用) - 使用表达式树调用的性能 +5. **PluginRun**(插件调用) - 使用插件接口调用的性能 +6. **PluginActionRun**(插件委托调用) - 使用插件委托调用的性能 -但是当使用插件委托调用时,性能上会有所提升。和直接调用相比,虽然性能降低了20%。但是这已经是委托调用的极限,且没有任何动态代码的生成。这意味着在unity调用也是完全可行的。 +**性能测试结论:** + +- 插件内部使用的是IL调用,即使调用上有迭代,性能上也和直接调用的表达式树差不多 +- 插件采用递归方式迭代,表达式树测试则是直接for迭代,但性能差距不大 +- 使用插件委托调用时,性能可提升至接近直接调用,仅降低约20% +- 插件委托调用没有任何动态代码生成,这意味着在Unity等AOT环境中也完全可行 ### 9.2 注册委托 -注册委托,使用委托,可以提升插件性能。 +注册委托方式可以显著提升插件性能,推荐在性能敏感场景使用。 -```csharp showLineNumbers -//订阅插件,不仅可以使用声明插件的方式,还可以使用委托。 -pluginsManager.Add(typeof(ISayPlugin), () => -{ - Console.WriteLine("在Action1中获得"); -}); -``` +**方式一: 直接添加委托** -其次,不仅可以直接使用委托,还可以在插件里面注册方法为委托。 + -```csharp showLineNumbers -public class SayHelloAction: PluginBase -{ - protected override void Loaded(IPluginsManager pluginsManager) - { - base.Loaded(pluginsManager); +**方式二: 在插件内注册方法为委托** - //注册本地方法为委托 - pluginsManager.Add(typeof(ISayPlugin),this.Say); - } +不仅可以直接使用委托,还可以在插件里面注册方法为委托。 - public async Task Say(object sender, MyPluginEventArgs e) - { - Console.WriteLine($"{this.GetType().Name}------Enter"); - if (e.Words == "helloaction") - { - Console.WriteLine($"{this.GetType().Name}------Say"); - //当满足的时候输出,且不在调用下一个插件。 + - //亦或者设置e.Handled = true,即使调用下一个插件,也会无效 - - return; - } - await e.InvokeNext(); - Console.WriteLine($"{this.GetType().Name}------Leave"); - } -} -``` +这种方式的优势: +- 可以将插件逻辑封装在类中,保持代码组织性 +- 享受委托调用的高性能 +- 可以访问插件的实例成员和状态 :::caution 注意事项 -注册方法为委托时,不要再实现接口,避免重复调用。 +当使用注册方法为委托的方式时,**不要再实现插件接口**,否则会导致插件方法被调用两次: +- 一次通过接口反射调用 +- 一次通过委托直接调用 + +这会造成重复处理和性能下降。 ::: ### 9.3 源生成插件 -使用源生成器,直接可以生成委托调用,但是这要求你的编译器支持,一般(vs2022以上、或者Rider等)。 +使用源生成器可以在编译时直接生成委托调用代码,获得最佳性能。 -一般的,这不需要你的关系,因为当源生成可用时,会自动使用源生成器。 +**使用要求:** +- 编译器支持C# 源生成器(Visual Studio 2022及以上,或Rider) +- 在插件接口上添加`[DynamicMethod]`特性 + + + +**工作原理:** + +源生成器会在编译时分析标记了`[DynamicMethod]`的接口,自动生成高性能的调用代码。生成的代码会: +- 避免反射调用的性能损耗 +- 直接使用委托调用,性能接近直接调用 +- 完全类型安全,编译时检查 + +**使用建议:** + +一般情况下,你不需要关心源生成器的细节。当编译环境支持时,系统会自动使用源生成器优化插件调用。只需要: +1. 在插件接口上添加`[DynamicMethod]`特性 +2. 正常实现和使用插件 +3. 编译器会自动处理优化 + +:::tip 性能建议 + +根据使用场景选择合适的插件添加方式: + +- **开发阶段** - 使用按类型添加,代码简洁,易于调试 +- **性能敏感** - 使用委托添加或源生成插件,获得最佳性能 +- **简单逻辑** - 直接使用匿名委托,无需创建完整的插件类 +- **复杂逻辑** - 创建完整的插件类,便于代码组织和维护 + +::: -[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Core/PluginConsoleApp) \ No newline at end of file +## 十、最佳实践 + +### 10.1 插件设计原则 + +**单一职责原则** + +每个插件应该只负责一个具体的功能,避免在一个插件中处理多个不相关的逻辑。 + +```csharp +// ✅ 好的做法 - 单一职责 +public class LoggingPlugin : PluginBase, IMessagePlugin +{ + public async Task OnMessage(object sender, MessageEventArgs e) + { + // 只负责日志记录 + Logger.Info($"收到消息: {e.Message}"); + await e.InvokeNext(); + } +} + +// ❌ 不好的做法 - 职责混乱 +public class AllInOnePlugin : PluginBase, IMessagePlugin +{ + public async Task OnMessage(object sender, MessageEventArgs e) + { + // 日志、验证、转换...太多职责 + Logger.Info($"收到消息: {e.Message}"); + if (!Validate(e.Message)) return; + e.Message = Transform(e.Message); + await e.InvokeNext(); + } +} +``` + +**职责链顺序** + +合理安排插件的添加顺序,一般遵循: +1. 验证类插件 - 最先执行,快速失败 +2. 转换类插件 - 处理数据格式 +3. 业务类插件 - 执行核心业务逻辑 +4. 日志类插件 - 记录处理结果 +5. 兜底类插件 - 处理未被处理的情况 + +**状态管理** + +使用`PluginEventArgs.State`属性在插件间传递临时状态: + +```csharp +// 在前面的插件中设置状态 +e.State = new { UserId = 123, Timestamp = DateTime.Now }; + +// 在后面的插件中读取状态 +if (e.State is { UserId: int userId }) +{ + // 使用状态信息 +} +``` + +### 10.2 错误处理 + +在插件中应该妥善处理异常,避免影响整个插件链: + +```csharp +public class SafePlugin : PluginBase, IMyPlugin +{ + public async Task Process(object sender, MyEventArgs e) + { + try + { + // 插件逻辑 + await DoSomethingAsync(); + await e.InvokeNext(); + } + catch (Exception ex) + { + // 记录异常 + Logger.Error("插件执行失败", ex); + + // 根据业务决定是否继续 + // 选项1: 中断传递 + e.Handled = true; + + // 选项2: 继续传递给下一个插件 + // await e.InvokeNext(); + } + } +} +``` + +### 10.3 插件管理 + +**移除插件** + +可以通过以下方式移除插件: + +```csharp +// 保存插件引用 +var myPlugin = pluginManager.Add(); + +// 后续需要时移除 +pluginManager.Remove(myPlugin); + +// 或者移除委托 +Action handler = (s, e) => { }; +pluginManager.Add(typeof(IMyPlugin), handler); +pluginManager.Remove(typeof(IMyPlugin), handler); +``` + +**查询插件数量** + +```csharp +// 获取指定类型插件的数量 +var count = pluginManager.GetPluginCount(typeof(IMyPlugin)); + +// 获取从IOC容器创建的插件数量 +var iocCount = pluginManager.GetFromIocCount(typeof(IMyPlugin)); +``` + +**禁用插件管理器** + +```csharp +// 临时禁用所有插件 +pluginManager.Enable = false; + +// 重新启用 +pluginManager.Enable = true; +``` + +### 10.4 与依赖注入集成 + +插件系统完全支持依赖注入: + +```csharp +// 在容器中注册服务 +var container = new Container(); +container.RegisterSingleton(); + +// 创建插件管理器时传入容器 +var pluginManager = new PluginManager(container); + +// 插件可以注入依赖 +public class MyPlugin : PluginBase, IMyPlugin +{ + private readonly IMyService _service; + + // 通过构造函数注入 + public MyPlugin(IMyService service) + { + _service = service; + } + + public async Task Process(object sender, MyEventArgs e) + { + await _service.DoSomethingAsync(); + await e.InvokeNext(); + } +} +``` + +### 10.5 单元测试 + +插件的可测试性很强: + +```csharp +[Test] +public async Task TestMyPlugin() +{ + // Arrange + var container = new Container(); + var pluginManager = new PluginManager(container); + pluginManager.Add(); + + var eventArgs = new MyEventArgs { Data = "test" }; + + // Act + var handled = await pluginManager.RaiseAsync( + typeof(IMyPlugin), + this, + eventArgs + ); + + // Assert + Assert.IsTrue(handled); + Assert.AreEqual("processed", eventArgs.Result); +} +``` + +## 十一、示例Demo + + \ No newline at end of file diff --git a/handbook/docs/rpcactionfilter.mdx b/handbook/docs/rpcactionfilter.mdx index b35f3c0ce..a07ff3335 100644 --- a/handbook/docs/rpcactionfilter.mdx +++ b/handbook/docs/rpcactionfilter.mdx @@ -4,8 +4,9 @@ title: Rpc服务AOP --- import { TouchSocketRpcDefinition } from "@site/src/components/Definition.js"; +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; -### 定义 @@ -14,6 +15,23 @@ import { TouchSocketRpcDefinition } from "@site/src/components/Definition.js"; Rpc服务在被调用时,会触发一系列的Rpc筛选器AOP**IRpcActionFilter**的**特性(Attribute)**,进行相关AOP操作。所以可以利用该特性做很多有关Rpc的AOP操作。 +通过RpcActionFilter,你可以实现: +- **日志记录**:记录方法调用的详细信息 +- **权限验证**:在方法执行前验证用户权限 +- **异常处理**:全局捕获和处理RPC调用异常 +- **性能监控**:统计方法执行时间 +- **参数验证**:在方法执行前验证参数有效性 + +:::tip 提示 + +RpcActionFilter特性基于AOP(面向切面编程)思想,可以在不修改业务代码的情况下,为RPC方法添加横切关注点功能。 + +::: + +import CardLink from "@site/src/components/CardLink"; + + + ## 二、支持的特性方法 | 方法名 | 触发时机 |功能| @@ -25,78 +43,63 @@ Rpc服务在被调用时,会触发一系列的Rpc筛选器AOP**IRpcActionFilte ### 3.1 定义RpcActionFilterAttribute特性 -```csharp showLineNumbers -public class MyRpcActionFilterAttribute : RpcActionFilterAttribute -{ - public override async Task ExecutingAsync(ICallContext callContext, object[] parameters, InvokeResult invokeResult) - { - if (callContext.Caller is ITcpSessionClient client) - { - client.Logger.Info($"即将执行Rpc-{callContext.RpcMethod.Name}"); - } - return await Task.FromResult(invokeResult); - } +要使用RpcActionFilter,首先需要创建一个继承自`RpcActionFilterAttribute`的特性类,并重写相应的方法。 - public override async Task ExecutedAsync(ICallContext callContext, object[] parameters, InvokeResult invokeResult, Exception exception) - { - if (callContext.Caller is ITcpSessionClient client) - { - client.Logger.Info($"执行RPC-{callContext.RpcMethod.Name}结束,状态={invokeResult.Status}"); - } - - return await base.ExecutedAsync(callContext, parameters, invokeResult, exception); - } -} -``` +#### 3.1.1 基础日志记录特性 + + + +#### 3.1.2 权限验证特性 + +通过在`ExecutingAsync`方法中检查用户权限,可以实现访问控制: + + + +#### 3.1.3 全局异常处理特性 + +在`ExecutedAsync`方法中捕获异常,实现统一的异常处理: + + :::tip 提示 -使用RpcActionFilterAttribute特性,不仅可以实现日志记录,还可以实现访问权限限制、全局异常捕捉等。 +使用RpcActionFilterAttribute特性,不仅可以实现日志记录,还可以实现访问权限限制、全局异常捕捉、性能监控等功能。 ::: -### 3.2 使用 +### 3.2 在类上使用特性 -RpcActionFilterAttribute 特性可以应用到任何方法上,也可以应用到类上。 +RpcActionFilterAttribute特性可以应用到RPC服务类上,这样类中的所有方法都会受到该特性的影响: -```csharp {1,19} -[MyRpcActionFilter] -class MyRpcServer : SingletonRpcServer -{ - private readonly ILog m_logger; + - public MyRpcServer(ILog logger) - { - this.m_logger = logger; - } +### 3.3 在接口上使用特性 - /// - /// 将两个数相加 - /// - /// - /// - /// - [DmtpRpc(MethodInvoke = true)]//使用函数名直接调用 - [Description("将两个数相加")]//其作用是生成代理时,作为注释。 - [MyRpcActionFilter] - public int Add(int a, int b) - { - this.m_logger.Info("调用Add"); - var sum = a + b; - return sum; - } -} -``` +特性也可以应用到接口及接口方法上: -### 3.3 规则 + -1. 标签添加在`方法`、`注册接口`或`注册服务`上均会生效。 -2. 标签生效顺序为`接口方法(如果有)`、`服务方法`、`注册接口(如果有`)、`服务`。 -3. 同一类型的标签仅生效一次。 -4. 继承的特性,可以通过`MutexAccessTypes`属性来标识以哪个类型作为同一特性标识。 +### 3.4 启动服务示例 -第1、2条规则,以下代码为例,执行顺序依次为`MyRpcActionFilter1`、`MyRpcActionFilter3`、`MyRpcActionFilter2`、`MyRpcActionFilter4`。 + + +### 3.5 测试调用 + + + +### 3.6 规则 + +RpcActionFilter特性的应用遵循以下规则: + +1. **应用位置**:标签可以添加在`方法`、`注册接口`或`注册服务`上均会生效。 +2. **执行顺序**:标签生效顺序为`接口方法(如果有)` → `服务方法` → `注册接口(如果有)` → `服务类`。 +3. **去重机制**:同一类型的标签仅生效一次。 +4. **互斥特性**:继承的特性,可以通过`MutexAccessTypes`属性来标识以哪个类型作为同一特性标识。 + +#### 3.6.1 执行顺序示例 + +以下代码展示了特性的执行顺序。执行顺序依次为:`MyRpcActionFilter1` → `MyRpcActionFilter3` → `MyRpcActionFilter2` → `MyRpcActionFilter4`。 ```csharp showLineNumbers [MyRpcActionFilter2] @@ -117,7 +120,9 @@ class MyRpcServer :SingletonRpcServer, IMyRpcServer } ``` -第3条规则,以下代码为例,只会将`MyRpcActionFilter`执行一次。 +#### 3.6.2 去重机制示例 + +以下代码中,虽然`MyRpcActionFilter`特性被应用了4次,但只会执行一次: ```csharp showLineNumbers [MyRpcActionFilter] @@ -138,28 +143,78 @@ class MyRpcServer :SingletonRpcServer, IMyRpcServer } ``` -第4条规则,以下代码为例。在`MyBaseAttribute`中指定`MutexAccessTypes`为`MyBaseAttribute`,则`MyAttribute`和`My2Attribute`都继承`MyBaseAttribute`时,即为互斥,在生效时会按照优先等级有且只有一个生效。 +#### 3.6.3 互斥特性示例 -```csharp showLineNumbers -interface IInterface -{ - [My] - [My2] - void Test(); -} +通过设置`MutexAccessTypes`属性,可以让不同的特性类互斥。在以下代码中,`MyAttribute`和`My2Attribute`都继承自`MyBaseAttribute`,由于基类指定了互斥类型,因此在同一个方法上只有一个会生效(按优先级)。 -class MyBaseAttribute:RpcActionFilterAttribute -{ - public override Type[] MutexAccessTypes => new Type[] {typeof(MyBaseAttribute) }; -} +定义互斥特性基类: -class MyAttribute: MyBaseAttribute -{ + -} +定义互斥特性子类: -class My2Attribute : MyBaseAttribute -{ + -} -``` +应用互斥特性: + + + +:::caution 注意 + +互斥特性在实际应用中,应该谨慎使用。确保互斥逻辑符合业务需求,避免出现预期之外的行为。 + +::: + +## 四、常见应用场景 + +### 4.1 日志记录 + +RpcActionFilter最常见的用途是记录RPC调用日志,包括方法名、参数、执行时间等信息。 + +### 4.2 权限验证 + +在方法执行前验证调用者是否有权限访问该方法,可以实现细粒度的访问控制。 + +### 4.3 异常处理 + +统一处理RPC调用过程中的异常,将技术异常转换为用户友好的错误信息。 + +### 4.4 性能监控 + +记录方法的执行时间,用于性能分析和优化。 + +### 4.5 参数验证 + +在方法执行前验证参数的有效性,提前拦截无效请求。 + +### 4.6 缓存处理 + +对于查询类方法,可以在ExecutingAsync中检查缓存,如果命中缓存直接返回,避免执行实际方法。 + +## 五、最佳实践 + +### 5.1 特性命名规范 + +- 特性类名应该以`Attribute`结尾 +- 使用描述性的名称,清晰表达特性的用途 +- 例如:`LoggingFilterAttribute`、`AuthorizationFilterAttribute` + +### 5.2 性能考虑 + +- 避免在ExecutingAsync和ExecutedAsync中执行耗时操作 +- 如果需要执行耗时操作,考虑使用异步方式 +- 注意特性的执行顺序,避免不必要的重复操作 + +### 5.3 异常处理 + +- 在特性方法中妥善处理异常,避免影响正常的RPC调用 +- 记录异常信息,便于问题排查 + +### 5.4 状态管理 + +- 通过`InvokeResult`对象传递状态和结果 +- 合理使用`InvokeStatus`枚举值表示不同的执行状态 + +## 六、总结 + +RpcActionFilter提供了一种优雅的方式来实现RPC服务的横切关注点,通过特性的方式可以在不修改业务代码的情况下,为RPC方法添加各种功能。合理使用RpcActionFilter可以提高代码的可维护性和可扩展性。 \ No newline at end of file diff --git a/handbook/docs/rpcallcontext.mdx b/handbook/docs/rpcallcontext.mdx index a1566857e..8f75128f0 100644 --- a/handbook/docs/rpcallcontext.mdx +++ b/handbook/docs/rpcallcontext.mdx @@ -4,8 +4,9 @@ title: 调用上下文 --- import { TouchSocketRpcDefinition } from "@site/src/components/Definition.js"; +import CardLink from "@site/src/components/CardLink.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; -### 定义 @@ -33,75 +34,33 @@ import { TouchSocketRpcDefinition } from "@site/src/components/Definition.js"; 定义的服务方法的`第一个参数`使用`ICallContext`或其派生类(例如:`DmtpRpc`可以使用`IDmtpRpcCallContext`)。 -```csharp showLineNumbers -public class MyRpcServer : SingletonRpcServer -{ - [Description("登录")] - [DmtpRpc] - public bool Login(ICallContext callContext,string account,string password) - { - if (callContext.Caller is TcpDmtpSessionClient) - { - Console.WriteLine("TcpDmtpRpc请求"); - } - if (account=="123"&&password=="abc") - { - return true; - } + - return false; - } -} -``` +:::tip 提示 + +在单例服务中,通过参数传递调用上下文可以确保在并发调用时,每个调用都能获取到正确的上下文信息。 + +::: ### 2.2 通过瞬时生命周期获取 当服务是瞬态注册时,每次调用服务会创建新的实例,所以当前方法只会被当前调用者拥有,所以,调用上下文会从属性直接注入。 -步骤: +**步骤:** -1. 继承TransientRpcServer或者实现ITransientRpcServer接口。 +1. 继承`TransientRpcServer`或者实现`ITransientRpcServer`接口。 -```csharp showLineNumbers -public class MyRpcServer : TransientRpcServer -{ - [Description("登录")] - [DmtpRpc] - public bool Login(string account,string password) - { - if ( this.CallContext.Caller is TcpDmtpSessionClient) - { - Console.WriteLine("TcpDmtpRpc请求"); - } - if (account=="123"&&password=="abc") - { - return true; - } + - return false; - } -} +**或使用泛型上下文:** -//或使用泛型上下文 -public class MyRpcServer : TransientRpcServer -{ - [Description("登录")] - [DmtpRpc] - public bool Login(string account, string password) - { - if (this.CallContext.Caller is TcpDmtpSessionClient) - { - Console.WriteLine("TcpDmtpRpc请求"); - } - if (account == "123" && password == "abc") - { - return true; - } + - return false; - } -} -``` +:::tip 提示 + +瞬态服务每次调用都会创建新的实例,因此可以直接通过`this.CallContext`属性访问调用上下文,无需通过参数传递。 + +::: ### 2.3 通过IRpcCallContextAccessor服务获取 @@ -118,27 +77,7 @@ public class MyRpcServer : TransientRpcServer 然后,在Rpc服务中,通过`IRpcCallContextAccessor`服务获取到当前调用的上下文。 -```csharp {5,7,16} showLineNumbers -public partial class MyRpcServer : SingletonRpcServer -{ - private readonly IRpcCallContextAccessor m_rpcCallContextAccessor; - - public MyRpcServer(IRpcCallContextAccessor rpcCallContextAccessor) - { - this.m_rpcCallContextAccessor = rpcCallContextAccessor; - } - - [Description("测试从CallContextAccessor中获取当前关联的CallContext")] - [DmtpRpc] - public async Task TestGetCallContextFromCallContextAccessor() - { - //通过CallContextAccessor获取当前关联的CallContext - //此处即使m_rpcCallContextAccessor与当前RpcServer均为单例,也能获取到正确的CallContext - var callContext = this.m_rpcCallContextAccessor.CallContext; - await Task.CompletedTask; - } -} -``` + :::tip 提示 @@ -152,32 +91,7 @@ public partial class MyRpcServer : SingletonRpcServer 下列将以`DmtpRpc`为例,介绍如何取消任务。 -```csharp showLineNumbers -/// -/// 测试取消调用 -/// -/// -/// -[Description("测试取消调用")] -[DmtpRpc] -public async Task TestCancellationToken(ICallContext callContext) -{ - //模拟一个耗时操作 - for (var i = 0; i < 10; i++) - { - //判断任务是否已被取消 - if (callContext.Token.IsCancellationRequested) - { - Console.WriteLine("执行已取消"); - return i; - } - Console.WriteLine($"执行{i}次"); - await Task.Delay(1000); - } - - return -1; -} -``` + :::tip 提示 @@ -199,3 +113,6 @@ public async Task TestCancellationToken(ICallContext callContext) - `RpcMethod`属性就是调用的触发方法。 - 其他参数可以参阅注释帮助理解。 +## 五、示例代码 + + diff --git a/handbook/docs/rpcdispatcher.mdx b/handbook/docs/rpcdispatcher.mdx index a79c5ade7..43ed166f9 100644 --- a/handbook/docs/rpcdispatcher.mdx +++ b/handbook/docs/rpcdispatcher.mdx @@ -3,12 +3,24 @@ id: rpcdispatcher title: Rpc执行调度器 --- +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import CardLink from "@site/src/components/CardLink.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; + ## 一、说明 Rpc执行调度器(`RpcDispatcher`)是`TouchSocket.Rpc`框架中的核心组件之一,用于实现远程过程调用(`Rpc`)的服务执行方式。 目前,内置了`ConcurrencyDispatcher`、`QueueRpcDispatcher`、`ImmediateRpcDispatcher`三种调度器,分别用于实现不同场景下的服务执行方式。 +:::tip + +调度器的选择对RPC服务的性能和行为有重要影响,应根据实际业务场景选择合适的调度器。 + +::: + + + ## 二、调度器说明 `RpcDispatcher`是直接应用于`Rpc Caller`的。所以它在被调用方可能是以多个实例的形式存在。 @@ -19,23 +31,57 @@ Rpc执行调度器(`RpcDispatcher`)是`TouchSocket.Rpc`框架中的核心组 并发调度器(`ConcurrencyDispatcher`)是在收到`Rpc`请求后,直接使用线程池(`Task.Run`)直接执行`Rpc`。所以它是完全并发的。 +**适用场景:** +- 需要高并发处理的场景 +- 各个RPC调用之间无依赖关系 +- 追求最大吞吐量的场景 + +**配置示例:** + + + ### 2.2 队列调度器(QueueRpcDispatcher) 队列调度器(`QueueRpcDispatcher`)是在收到`Rpc`请求后,先将请求放入队列中,再通过线程(`Task.Run`)执行Rpc。所以它在当前实例中(可以简单理解为一个连接)是有顺序的。 +**适用场景:** +- 需要保证同一连接的RPC调用按顺序执行 +- 避免并发执行导致的资源竞争 +- 需要控制执行流程的场景 + +**配置示例:** + + ### 2.3 立即调度器(ImmediateRpcDispatcher) 立即调度器(`ImmediateRpcDispatcher`)是在收到`Rpc`请求后,使用`Rpc`接收线程,直接执行`Rpc`。所以它的同步性是依赖接收线程的。 +**适用场景:** +- 需要最低延迟的场景 +- RPC方法执行时间极短 +- 不希望引入额外线程切换开销 + +**配置示例:** + + + ## 三、使用 `Rpc`调度器并非强制要求,具体还得看`Rpc`框架本身支不支持多样的调度方式。 -例如,`DmtpRpc`框架就支持所有调度方式,但是对于WebApi框架,它只支持`ImmediateRpcDispatcher`。 +例如,`DmtpRpc`框架就支持所有调度方式,但是对于`WebApi`框架,它只支持`ImmediateRpcDispatcher`。 所以实际使用还得看具体框架的支持情况。 +### 3.1 创建客户端并调用 + + + +### 3.2 并发调用示例 + + + ## 四、可重入性 可重入性也表示的是在一个`Caller`的`Rpc`调用中的并发性。不过可以使用`[Reenterable]`特性来控制方法、服务级别的并发。 @@ -46,12 +92,91 @@ Rpc执行调度器(`RpcDispatcher`)是`TouchSocket.Rpc`框架中的核心组 但是对于有的`Rpc`方法,我们希望仅这个方法不支持可重入性,那么我们可以使用`[Reenterable]`特性来控制。 -```csharp {3} showLineNumbers -interface IMyRpcServer : ISingletonRpcServer +### 4.1 使用Reenterable特性 + +通过在接口方法上添加`[Reenterable(false)]`特性,可以禁止该方法的可重入调用,即使使用的是并发调度器,该方法也会按顺序执行。 + + + +### 4.2 调度器与可重入性对照表 + +| 调度器类型 | 默认可重入性 | 支持Reenterable特性 | +|-----------|------------|-------------------| +| ConcurrencyDispatcher | 是 | 是 | +| QueueRpcDispatcher | 否 | 是 | +| ImmediateRpcDispatcher | 否 | 是 | + +:::warning 注意 + +- 队列调度器和立即调度器默认不支持可重入,即使设置`[Reenterable(true)]`也不会生效 +- 只有在并发调度器下,才能通过`[Reenterable(false)]`来禁止特定方法的并发执行 +- 可重入性控制是针对单个连接(Caller)的,不同连接之间不受影响 + +::: + +## 五、全局队列调度器 + +除了上述三种调度器外,TouchSocket还提供了一个全局队列调度器(`GlobalQueueRpcDispatcher`),它与普通队列调度器的区别在于: + +- **普通队列调度器**:每个连接拥有独立的队列实例 +- **全局队列调度器**:所有连接共享同一个队列实例 + +**适用场景:** +- 需要在所有连接之间保证执行顺序 +- 限制系统整体的并发执行数量 +- 实现全局的任务调度 + +**配置方式:** + +```csharp +.ConfigurePlugins(a => { - [Reenterable(false)] - [DmtpRpc(MethodInvoke = true)]//服务注册的函数键,此处为显式指定。默认不传参的时候,为该函数类全名+方法名的全小写。 - Task Output(int value); -} + a.UseDmtpRpc(options => + { + options.UseGlobalQueueRpcDispatcher(); + }); +}) ``` +## 六、自定义调度器 + +如果内置的调度器无法满足需求,可以通过实现`IRpcDispatcher`接口来自定义调度器。 + +**示例:** + +```csharp +public class MyCustomDispatcher : IRpcDispatcher +{ + public async Task ExecuteAsync(IDmtpActor caller, IDmtpRpcCallContext callContext, Func action) + { + // 自定义调度逻辑 + await action(); + } +} + +// 配置使用自定义调度器 +.ConfigurePlugins(a => +{ + a.UseDmtpRpc(options => + { + options.CreateDispatcher = (actor) => new MyCustomDispatcher(); + }); +}) +``` + +## 七、最佳实践 + +1. **高并发场景**:使用`ConcurrencyDispatcher`以获得最大吞吐量 +2. **顺序执行需求**:使用`QueueRpcDispatcher`保证同一连接的调用顺序 +3. **低延迟需求**:使用`ImmediateRpcDispatcher`,但要确保RPC方法执行时间短 +4. **全局控制**:使用`GlobalQueueRpcDispatcher`实现跨连接的顺序控制 +5. **特殊场景**:通过自定义调度器实现特定的业务需求 + +:::tip 性能建议 + +- 避免在`ImmediateRpcDispatcher`中执行耗时操作,否则会阻塞接收线程 +- 对于I/O密集型操作,推荐使用`ConcurrencyDispatcher` +- 对于CPU密集型操作,可以考虑使用`QueueRpcDispatcher`控制并发数 + +::: + diff --git a/handbook/docs/rpcgenerateproxy.mdx b/handbook/docs/rpcgenerateproxy.mdx index ffdf5923f..9ee74e9e5 100644 --- a/handbook/docs/rpcgenerateproxy.mdx +++ b/handbook/docs/rpcgenerateproxy.mdx @@ -4,8 +4,9 @@ title: 生成、获取代理 --- import { TouchSocketRpcDefinition } from "@site/src/components/Definition.js"; +import CardLink from "@site/src/components/CardLink.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; -### 定义 @@ -46,42 +47,14 @@ import { TouchSocketRpcDefinition } from "@site/src/components/Definition.js"; 2. 将生成的.cs文件,复制到客户端项目(如果是同一电脑开发,则可以使用添加链接的方式编译)。 3. 在客户端调用代理。 + + 【示例1】 将代理字符串,写成.cs文件,然后通过链接的形式,将代码添加到客户端项目。 服务器代码,在服务器`启动`后,会在运行路径下,生成一个**RpcProxy.cs**的文件。 -```csharp {16-17} - var service = new TcpDmtpService(); - var config = new TouchSocketConfig()//配置 - .SetListenIPHosts(7789) - .ConfigureContainer(a => - { - a.AddDmtpRouteService(); - a.AddConsoleLogger(); - }) - .ConfigurePlugins(a => - { - a.UseDmtpRpc() - .ConfigureRpcStore(store => - { - store.RegisterServer(); -#if DEBUG - File.WriteAllText("RpcProxy.cs", store.GetProxyCodes("RpcProxy", new Type[] { typeof(DmtpRpcAttribute) })); - ConsoleLogger.Default.Info("成功生成代理"); -#endif - }); - - a.Add(); - }) - .SetDmtpOption(new DmtpOption() - { - VerifyToken = "Rpc"//连接验证口令。 - }); - - await service.SetupAsync(config); - await service.StartAsync(); -``` + :::tip @@ -122,9 +95,7 @@ import { TouchSocketRpcDefinition } from "@site/src/components/Definition.js"; 上述行为,均是导出所有已注册的服务,当需要直接生成多个不同代理的源码时,可通过CodeGenerator静态类的相关方法直接生成。例如: -```csharp {1} -string codes=CodeGenerator.GetProxyCodes("Namespace",new Type[]{typeof(MyRpcServer) },new Type[] { typeof(DmtpRpcAttribute)}); -``` + ::: @@ -141,29 +112,19 @@ Rpc规范了两种方式来添加实体模型的复刻。 #### 2.2.1 直接添加代理类型 -在服务注册之前,任意时刻,可调用CodeGenerator.AddProxyType静态方法,添加代理类型,同时可传入一个bool值,表明是否深度搜索,比如,假如ProxyClass1中还有其他类型,则参数为True时,依然会代理。 +在服务注册之前,任意时刻,可调用CodeGenerator.AddProxyType静态方法,添加代理类型,同时可传入一个bool值,表明是否深度搜索,比如,假如ProxyClass1中还有其他类型,则参数为True时,依然会代理。 -```csharp showLineNumbers -CodeGenerator.AddProxyType(); -CodeGenerator.AddProxyType(deepSearch:true); -``` + 或者直接按程序集添加 -```csharp showLineNumbers -CodeGenerator.AddProxyAssembly(typeof(Program).Assembly); -``` + #### 2.2.2 通过特性标记添加 在需要代理的类上面声明RpcProxy标签,然后也可以重新指定代理类名。 -```csharp showLineNumbers -[RpcProxy("MyArgs")] -public class Args -{ -} -``` + :::tip 提示 @@ -175,15 +136,11 @@ public class Args 默认情况下,与声明服务相同的自定义类型,会被代理复刻成结构相同的类型。但是有时候,我们希望服务端与客户端公用一个dll,所以就不需要复刻,那么可以排除代理类型。 -```csharp showLineNumbers -CodeGenerator.AddIgnoreProxyType(typeof(Program)); -``` + 或者直接按程序集排除 -```csharp showLineNumbers -CodeGenerator.AddIgnoreProxyAssembly(typeof(Program).Assembly); -``` + :::tip 提示 @@ -202,15 +159,7 @@ CodeGenerator.AddIgnoreProxyAssembly(typeof(Program).Assembly); 例如: -```csharp showLineNumbers -class MyRpcAttribute : RpcAttribute -{ - public override Type[] GetGenericConstraintTypes() - { - return new Type[] { typeof(IRpcClient) }; - } -} -``` + 结果: diff --git a/handbook/docs/rpcratelimiting.mdx b/handbook/docs/rpcratelimiting.mdx index 63fe81f04..fe05f026e 100644 --- a/handbook/docs/rpcratelimiting.mdx +++ b/handbook/docs/rpcratelimiting.mdx @@ -4,8 +4,10 @@ title: Rpc访问速率限制 --- import { TouchSocketRpcRateLimitingDefinition } from "@site/src/components/Definition.js"; +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import CardLink from "@site/src/components/CardLink.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; -### 定义 @@ -23,197 +25,89 @@ import { TouchSocketRpcRateLimitingDefinition } from "@site/src/components/Defin ## 二、使用 +:::tip 提示 + +本文所有示例代码均可在示例工程中找到。 + +::: + + + ### 2.1 安装 -nuget安装TouchSocket.Rpc.RateLimiting。 +使用 NuGet 安装 TouchSocket.Rpc.RateLimiting 包。 -```csharp showLineNumbers +```bash showLineNumbers Install-Package TouchSocket.Rpc.RateLimiting ``` ### 2.2 固定窗口限制器 -AddFixedWindowLimiter 方法使用固定的时间窗口来限制请求。 当时间窗口过期时,会启动一个新的时间窗口,并重置请求限制。 +`AddFixedWindowLimiter` 方法使用固定的时间窗口来限制请求。当时间窗口过期时,会启动一个新的时间窗口,并重置请求限制。 -```csharp {3} showLineNumbers -.ConfigureContainer(a => -{ - a.AddRateLimiter(p => - { - p.AddFixedWindowLimiter("FixedWindow", options => - { - options.PermitLimit = 10; - options.Window = TimeSpan.FromSeconds(10); - }); - }); -}) -``` + ### 2.3 滑动窗口限制器 -AddSlidingWindowLimiter 方法使用滑动的时间窗口来限制请求。 当时间窗口过期时,会启动一个新的时间窗口,与固定窗口限制器类似,但为每个窗口添加了段。 窗口在每个段间隔滑动一段。 段间隔的计算方式是:(窗口时间)/(每个窗口的段数)。 +`AddSlidingWindowLimiter` 方法使用滑动的时间窗口来限制请求。当时间窗口过期时,会启动一个新的时间窗口,与固定窗口限制器类似,但为每个窗口添加了段。窗口在每个段间隔滑动一段。段间隔的计算方式是:(窗口时间)/(每个窗口的段数)。 -```csharp {1,4-6,11} showLineNumbers -.ConfigureContainer(a => -{ - a.AddRateLimiter(p => - { - //添加一个名称为SlidingWindow的滑动窗口的限流策略 - p.AddSlidingWindowLimiter("SlidingWindow", options => - { - options.PermitLimit = 10; - options.Window = TimeSpan.FromSeconds(10); - options.SegmentsPerWindow = 5; - }); - }); -}) -``` + ### 2.4 令牌桶限制器 -AddTokenBucketLimiter 方法使用令牌桶来限制请求。 令牌桶限制器将根据指定的令牌生成速率向桶中添加令牌。 如果桶中有足够的令牌,则允许请求,否则拒绝请求。 +`AddTokenBucketLimiter` 方法使用令牌桶来限制请求。令牌桶限制器将根据指定的令牌生成速率向桶中添加令牌。如果桶中有足够的令牌,则允许请求,否则拒绝请求。 -```csharp showLineNumbers -.ConfigureContainer(a => -{ - a.AddRateLimiter(p => - { - //添加一个名称为TokenBucket的令牌桶的限流策略 - p.AddTokenBucketLimiter("TokenBucket", options => - { - options.TokenLimit = 100; - options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; - options.QueueLimit = 10; - options.ReplenishmentPeriod = TimeSpan.FromSeconds(10); - options.TokensPerPeriod = 10; - options.AutoReplenishment = true; - }); - }); -}) -``` + ### 2.5 并发限制器 -并发限制器会限制并发请求数。 每添加一个请求,在并发限制中减去 1。 一个请求完成时,在限制中增加 1。 其他请求限制器限制的是指定时间段的请求总数,而与它们不同,并发限制器仅限制并发请求数,不对一段时间内的请求数设置上限。 +并发限制器会限制并发请求数。每添加一个请求,在并发限制中减去 1。一个请求完成时,在限制中增加 1。其他请求限制器限制的是指定时间段的请求总数,而与它们不同,并发限制器仅限制并发请求数,不对一段时间内的请求数设置上限。 -```csharp showLineNumbers -.ConfigureContainer(a => -{ - a.AddRateLimiter(p => - { - //添加一个名称为Concurrency的并发的限流策略 - p.AddConcurrencyLimiter("Concurrency", options => - { - options.PermitLimit = 10; - options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; - options.QueueLimit = 10; - }); - }); -}) -``` + -### 2.6 使用 +### 2.6 使用限流器 -限流器使用很简单,只需要在需要限流的函数、服务或接口上添加特性即可。 +限流器使用很简单,只需要在需要限流的函数、服务或接口上添加 `[EnableRateLimiting]` 特性即可。 -```csharp {3} showLineNumbers -public partial class MyRpcServer : SingletonRpcServer -{ - [EnableRateLimiting("FixedWindow")] - [Description("登录")]//服务描述,在生成代理时,会变成注释。 - [DmtpRpc(InvokeKey ="Login")]//服务注册的函数键,此处为显式指定。默认不传参的时候,为该函数类全名+方法名的全小写。 - public bool Login(string account, string password) - { - if (account == "123" && password == "abc") - { - return true; - } + - return false; - } -} -``` +## 三、限流算法说明 -## 三、算法 +TouchSocket 的限流算法完全引用 `System.Threading.RateLimiting.dll`,算法实现比较通用且经过充分验证。 -TouchSocket的限流算法,是完全引用`System.Threading.RateLimiting.dll`。所以算是比较通用的限流算法。 +同时,算法的使用方法也完全借鉴 ASP.NET Core 的限流算法使用方法,保持了一致的开发体验。 -同时,算法的使用方法,也是完全借鉴Aspnetcore的限流算法使用方法。 - -所以,具体算法可以参考[ASP.NET Core 中的速率限制](https://learn.microsoft.com/zh-cn/aspnet/core/performance/rate-limit) +如需深入了解具体算法原理和更多配置选项,请参考微软官方文档:[ASP.NET Core 中的速率限制](https://learn.microsoft.com/zh-cn/aspnet/core/performance/rate-limit)。 ## 四、自定义限流分区键 -上述限流器的默认工作分区键都是基于函数的。例如,对于`MyRpcServer`的`Login`函数,分区键为`Login`函数本身,这也就意味着,限流是对`Login`函数限制的。即使调用方来自不同用户,不同地区,只要他们都需要调用`Login`函数,都会被限流器影响。 +上述限流器的默认工作分区键都是基于函数的。例如,对于 `MyRpcServer` 的 `Login` 函数,分区键为 `Login` 函数本身,这意味着限流是对 `Login` 函数整体进行限制的。即使调用方来自不同用户、不同地区,只要他们都需要调用 `Login` 函数,都会被同一个限流器影响。 -但是有时候,我们希望能自定义实现分区键。 +但是有时候,我们希望能自定义实现分区键。例如:实现基于 IP 的限流,那么分区键就是 IP 地址,不同 IP 地址会有独立的限流计数。 -例如:实现IP限流,那么分区键就是`IP`地址。那么我们可以按下列防止做。 +### 4.1 自定义限流策略类 -首先新建一个类`RpcFixedWindowLimiter`,继承`RateLimiterPolicy`,并指定泛型为`string`,因为我们是基于`IP`限流。 +首先新建一个类 `RpcFixedWindowLimiter`,继承 `RateLimiterPolicy`,并指定泛型为 `string`,因为我们是基于 IP(字符串类型)限流。 -```csharp showLineNumbers -// 内部类 RpcFixedWindowLimiter 继承自 RateLimiterPolicy, -// 用于实现基于固定窗口的限流策略,特别适用于远程过程调用 (RPC) 场景。 -internal class RpcFixedWindowLimiter : RateLimiterPolicy -{ - // 私有成员变量,存储了创建限流器时使用的配置选项。 - private readonly FixedWindowRateLimiterOptions m_options; + - // 构造函数接受一个 FixedWindowRateLimiterOptions 类型的参数,并将其赋值给 m_options 成员变量。 - public RpcFixedWindowLimiter(FixedWindowRateLimiterOptions options) - { - this.m_options = options; - } +### 4.2 注册自定义策略 - // 重写基类的方法以返回一个字符串类型的分区键。 - // 根据传入的 ICallContext 对象,尝试从其中获取调用者的 TCP 会话信息。 - // 如果 callContext.Caller 是 ITcpSession 类型,则返回该会话的 IP 地址作为分区键。 - // 如果不是基于 TCP 协议的调用,则返回字符串 "any" 作为分区键。 - protected override string GetPartitionKey(ICallContext callContext) - { - if (callContext.Caller is ITcpSession tcpSession) - { - return tcpSession.IP; - } +然后使用 `AddPolicy` 方法,直接添加自定义限流器。 - // 如果是基于 tcp 协议的调用,理论上不会执行到这里。 - return "any"; - } + - // 重写基类的方法以返回一个新的 RateLimiter 实例。 - // 使用 m_options 创建一个 FixedWindowRateLimiter 实例,并返回它。 - protected override RateLimiter NewRateLimiter(string partitionKey) - { - return new FixedWindowRateLimiter(this.m_options); - } -} -``` +### 4.3 使用自定义策略 -然后使用`AddPolicy`,直接添加自定义限流器。 +最后依然使用 `[EnableRateLimiting]` 特性,指定策略名称即可。 -```csharp showLineNumbers -a.AddRateLimiter(p => -{ - p.AddPolicy("FixedWindow", new RpcFixedWindowLimiter(new System.Threading.RateLimiting.FixedWindowRateLimiterOptions() - { - PermitLimit = 10, - Window = TimeSpan.FromSeconds(10) - })); + -}); -``` - -最后依然使用`EnableRateLimiting`特性即可。 - -```csharp {3} showLineNumbers -public partial class MyRpcServer : SingletonRpcServer -{ - [EnableRateLimiting("FixedWindow")] - ... -} -``` - -[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Rpc/RpcRateLimitingConsoleApp) +通过这种方式,你可以实现各种自定义的限流分区策略,例如: +- 基于 IP 地址限流 +- 基于用户 ID 限流 +- 基于租户 ID 限流 +- 基于设备 ID 限流 +- 等等... diff --git a/handbook/docs/rpcregister.mdx b/handbook/docs/rpcregister.mdx index e01348d85..de6f3e970 100644 --- a/handbook/docs/rpcregister.mdx +++ b/handbook/docs/rpcregister.mdx @@ -8,8 +8,8 @@ import Tabs from "@theme/Tabs"; import TabItem from "@theme/TabItem"; import BilibiliCard from '@site/src/components/BilibiliCard.js'; import { TouchSocketRpcDefinition } from "@site/src/components/Definition.js"; - -### 定义 +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; +import CardLink from '@site/src/components/CardLink'; @@ -20,68 +20,29 @@ import { TouchSocketRpcDefinition } from "@site/src/components/Definition.js"; 当服务仅是一个实例类,则可以在`AddRpcStore`时,可通过`RpcStore`实例,直接注册服务。 -```csharp showLineNumbers -public partial class MyRpcServer : SingletonRpcServer -{ - /// - /// 将两个数相加 - /// - /// - /// - /// - [DmtpRpc(MethodInvoke = true)]//使用函数名直接调用 - [Description("将两个数相加")]//其作用是生成代理时,作为注释。 - public int Add(int a, int b) - { - var sum = a + b; - return sum; - } -} -``` +**服务定义:** -```csharp {5,8} showLineNumbers -.ConfigureContainer(a => -{ - a.AddRpcStore(store => - { - store.RegisterServer(); + - //或者按照类型注册 - //store.RegisterServer(typeof(MyRpcServer)); - }); -}) -``` +**服务注册:** + + + + ### 1.2 注册接口服务 当服务是一个接口时,则可以在`AddRpcStore`时,通过`RpcStore`实例,按注册接口与实例服务。 -```csharp showLineNumbers -public interface IMyRpcServer2 : ISingletonRpcServer -{ - [DmtpRpc(MethodInvoke = true)]//使用函数名直接调用 - int Add(int a, int b); -} +**服务定义:** -public partial class MyRpcServer2 : SingletonRpcServer, IMyRpcServer2 -{ - public int Add(int a, int b) - { - var sum = a + b; - return sum; - } -} -``` + -```csharp {5} showLineNumbers -.ConfigureContainer(a => -{ - a.AddRpcStore(store => - { - store.RegisterServer(); - }); -}) -``` +**服务注册:** + + + + :::tip 提示 @@ -96,102 +57,31 @@ public partial class MyRpcServer2 : SingletonRpcServer, IMyRpcServer2 - 注册为瞬态的服务,在每次调用时,都会创建一个**新的服务实例**。瞬态服务可以直接通过`this.CallContext`获取调用上下文。 - 注册为区域的服务,在每次调用时,也会创建一个**新的服务实例**,并且在区域可用时单例。区域服务也可以直接通过`this.CallContext`获取调用上下文。 -【瞬态服务】 - - -```csharp {7} showLineNumbers -public partial class MyRpcServer : TransientRpcServer -{ - [DmtpRpc(MethodInvoke = true)]//使用函数名直接调用 - [Description("将两个数相加")]//其作用是生成代理时,作为注释。 - public int Add(int a, int b) - { - var callContext= this.CallContext; - var sum = a + b; - return sum; - } -} -``` - - -```csharp {1,7} showLineNumbers -public partial class MyRpcServer : TransientRpcServer -{ - [DmtpRpc(MethodInvoke = true)]//使用函数名直接调用 - [Description("将两个数相加")]//其作用是生成代理时,作为注释。 - public int Add(int a, int b) - { - var callContext= this.CallContext; - var sum = a + b; - return sum; - } -} -``` - - +**瞬态服务定义:** + -【区域服务】 - - -```csharp {7} showLineNumbers -public partial class MyRpcServer : ScopedRpcServer -{ - [DmtpRpc(MethodInvoke = true)]//使用函数名直接调用 - [Description("将两个数相加")]//其作用是生成代理时,作为注释。 - public int Add(int a, int b) - { - var callContext= this.CallContext; - var sum = a + b; - return sum; - } -} -``` - - -```csharp {1,7} showLineNumbers -public partial class MyRpcServer : ScopedRpcServer -{ - [DmtpRpc(MethodInvoke = true)]//使用函数名直接调用 - [Description("将两个数相加")]//其作用是生成代理时,作为注释。 - public int Add(int a, int b) - { - var callContext= this.CallContext; - var sum = a + b; - return sum; - } -} -``` - - +**瞬态服务注册:** + + + +**区域服务定义:** + + + +**区域服务注册:** + + + + ## 二、注册所有服务 ### 2.1 注册指定程序集的服务 -```csharp {5} showLineNumbers -.ConfigureContainer(a => -{ - a.AddRpcStore(store => - { - store.RegisterAllServer(typeof(MyRpcServer).Assembly); - }); -}) -``` + + + ### 2.2 注册已加载程序集中的所有服务 @@ -244,46 +134,21 @@ public partial class MyRpcServer : ScopedRpcServer ### 3.2 注册指定程序集的服务 -```csharp {6,9} showLineNumbers -.ConfigureContainer(a => -{ - a.AddRpcStore(store => - { - //该方法是由源生成提供,可以注册DmtpRpcServerConsoleApp程序集的所有公共Rpc服务 - store.RegisterAllFromDmtpRpcServerConsoleApp(); + - //该方法是由源生成提供,可以注册DmtpRpcServerConsoleApp程序集的所有Rpc服务(包含非公共服务) - store.InternalRegisterAllFromDmtpRpcServerConsoleApp(); - }); -}) -``` + :::tip 提示 -源生成的注册方法名并不固定,而是与**程序集名称**有关,或者如果直接指定的话,则是指定的方法名。例如上程序,默认情况下,程序集名称为DmtpRpcServerConsoleApp,即生成`RegisterAllFromDmtpRpcServerConsoleApp`方法与`InternalRegisterAllFromDmtpRpcServerConsoleApp`方法。 +源生成的注册方法名并不固定,而是与**程序集名称**有关,或者如果直接指定的话,则是指定的方法名。例如上程序,默认情况下,程序集名称为RpcRegisterConsoleApp,即生成`RegisterAllFromRpcRegisterConsoleApp`方法与`InternalRegisterAllFromRpcRegisterConsoleApp`方法。 ::: 源生成的注册,支持接口与实例注册。例如下列服务: -```csharp showLineNumbers -public interface IMyRpcServer : ISingletonRpcServer -{ - [DmtpRpc(MethodInvoke = true)]//使用函数名直接调用 - int Add(int a, int b); -} + -public partial class MyRpcServer : SingletonRpcServer,IMyRpcServer -{ - public int Add(int a, int b) - { - var sum = a + b; - return sum; - } -} -``` - -在生成源代码注册时,会以`IMyRpcServer`接口为注册,以`MyRpcServer`为实现。生成以下注册代码: +在生成源代码注册时,会以`IMyRpcServer2`接口为注册,以`MyRpcServer2`为实现。生成以下注册代码: ```csharp showLineNumbers /* @@ -293,31 +158,31 @@ public partial class MyRpcServer : SingletonRpcServer,IMyRpcServer namespace TouchSocket.Rpc { /// - /// RegisterRpcServerFromDmtpRpcServerConsoleAppExtension + /// RegisterRpcServerFromRpcRegisterConsoleAppExtension /// - public static class RegisterRpcServerFromDmtpRpcServerConsoleAppExtension + public static class RegisterRpcServerFromRpcRegisterConsoleAppExtension { /// - /// 注册程序集DmtpRpcServerConsoleApp中的所有公共Rpc服务。包括: + /// 注册程序集RpcRegisterConsoleApp中的所有公共Rpc服务。包括: /// - /// : + /// : /// /// /// - public static void RegisterAllFromDmtpRpcServerConsoleApp(this RpcStore rpcStore) + public static void RegisterAllFromRpcRegisterConsoleApp(this RpcStore rpcStore) { - rpcStore.RegisterServer(); + rpcStore.RegisterServer(); } /// - /// 注册程序集DmtpRpcServerConsoleApp中的所有Rpc服务。包括: + /// 注册程序集RpcRegisterConsoleApp中的所有Rpc服务。包括: /// - /// : + /// : /// /// /// - internal static void InternalRegisterAllFromDmtpRpcServerConsoleApp(this RpcStore rpcStore) + internal static void InternalRegisterAllFromRpcRegisterConsoleApp(this RpcStore rpcStore) { - rpcStore.RegisterServer(); + rpcStore.RegisterServer(); } } } diff --git a/handbook/docs/serialportclient.mdx b/handbook/docs/serialportclient.mdx index 96dbedd0f..22b200cb2 100644 --- a/handbook/docs/serialportclient.mdx +++ b/handbook/docs/serialportclient.mdx @@ -5,46 +5,122 @@ title: 串口客户端 import Tag from "@site/src/components/Tag.js"; import { TouchSocketSerialPortsDefinition } from "@site/src/components/Definition.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; +import CardLink from "@site/src/components/CardLink.js"; + -### 定义 ## 一、说明 -`SerialPortClient`是串口系客户端,他直接参与串口的连接、发送、接收、处理、断开等。 +`SerialPortClient`是TouchSocket框架提供的高性能串口通信客户端组件,专为.NET平台串口通信场景而设计。它提供了一套完整的串口操作解决方案,涵盖了串口连接建立、数据收发、协议解析、连接管理等全生命周期功能。 -## 二、特点 +### 1.1 核心能力 -- 简单易用。 -- 内存池支持 -- 高性能 -- 适配器预处理,一键式解决**分包**、**粘包**、对象解析(modbus)等。 -- 超简单的同步发送、异步发送、接收等操作。 -- 基于委托、插件驱动,让每一步都能执行AOP。 +- **全生命周期管理**:完整支持串口连接、数据传输、异常处理、连接断开等各个环节 +- **高效数据处理**:内置内存池机制,减少GC压力,提升数据处理性能 +- **灵活适配器系统**:预置多种数据适配器,一键解决分包、粘包、协议解析等常见问题 +- **事件驱动架构**:基于委托和插件的AOP设计,每个操作步骤都可进行拦截和扩展 +- **异步编程支持**:全面支持async/await异步模式,提供同步和异步发送接收API -## 三、产品应用场景 +### 1.2 适用场景 -- 所有串口基础使用场景:可跨平台、跨语言使用。 -- 自定义协议解析场景:可解析任意数据格式的串口数据报文。 +`SerialPortClient`特别适用于以下应用场景: -## 四、可配置项 +- **工业自动化**:PLC通信、传感器数据采集、设备控制等 +- **物联网设备**:串口设备数据采集、远程监控、设备管理 +- **测试测量**:仪器仪表通信、数据记录、自动化测试 +- **嵌入式通信**:单片机通信、开发板调试、产品测试 -
-可配置项 -
+## 二、核心特性 -#### SetMaxPackageSize +### 2.1 🚀 高性能设计 -数据包最大值(单位:byte),默认1024×1024×10。该值会在适当时间,直接作用DataHandlingAdapter.MaxPackageSize。 +- **内存池优化**:采用高效内存池机制,显著降低GC压力,提升数据处理效率 +- **零拷贝传输**:优化数据流转路径,减少不必要的内存拷贝操作 +- **异步优先**:全面拥抱异步编程模式,避免线程阻塞,提升系统并发能力 -#### SetSerialPortOption +### 2.2 🔧 灵活易用 -配置串口相关。例如:端口、波特率、数据位、校验位、停止位等。 +- **即插即用**:简洁的API设计,几行代码即可完成串口通信功能 +- **配置丰富**:支持波特率、数据位、停止位、校验位等全面串口参数配置 +- **适配器生态**:内置多种数据适配器,支持自定义协议解析 -
-
+### 2.3 🛡️ 可靠稳定 + +- **异常容错**:完善的异常处理机制,确保通信稳定性 +- **连接管理**:智能连接状态监控,支持自动重连等高级功能 +- **数据完整性**:内置数据校验和完整性保护机制 + +### 2.4 🔌 插件化扩展 + +- **AOP支持**:基于插件的面向切面编程,可在任何环节注入自定义逻辑 +- **事件驱动**:丰富的事件回调,满足各种业务场景需求 +- **高度解耦**:模块化设计,各组件职责清晰,易于维护和扩展 + +## 三、应用场景 + +### 3.1 🏭 工业自动化领域 + +- **PLC通信**:支持Modbus RTU、自定义工业协议的设备通信 +- **传感器采集**:温度、压力、流量等各类传感器数据实时采集 +- **设备控制**:电机控制、阀门操作、设备状态监控 +- **生产线集成**:MES系统集成、生产数据采集、质量追溯 + +### 3.2 🌐 物联网与智能硬件 + +- **边缘计算网关**:物联网设备数据汇聚、协议转换、云端对接 +- **智能仪表**:电表、水表、气表等智能计量设备通信 +- **环境监测**:空气质量、水质监测、噪音检测等环境数据采集 +- **农业物联网**:土壤监测、温室控制、智能灌溉系统 + +### 3.3 🔬 测试测量应用 + +- **仪器仪表通信**:示波器、万用表、频谱分析仪等测试设备集成 +- **自动化测试**:产品功能测试、老化测试、性能验证 +- **数据记录**:长期数据监控、趋势分析、报告生成 +- **标定校准**:设备标定、参数调整、精度验证 + +### 3.4 💻 嵌入式开发支持 + +- **单片机调试**:STM32、Arduino等开发板程序调试 +- **固件升级**:设备固件远程更新、版本管理 +- **参数配置**:设备参数远程配置、状态查询 +- **协议开发**:自定义通信协议开发、测试验证 + +### 3.5 🎯 跨平台兼容性 + +- **Windows平台**:完整支持.NET Framework/.NET Core/.NET 5+ +- **Linux平台**:支持各种Linux发行版的串口设备访问 +- **嵌入式Linux**:树莓派、工控机等嵌入式设备部署 +- **容器化部署**:支持Docker容器化部署和微服务架构 + +## 四、配置选项 + +### 4.1 串口通信参数的核心配置 + +**主要参数**: + +| 参数名 | 类型 | 默认值 | 说明 | +|--------|------|--------|------| +| `PortName` | string | "COM1" | 串口名称(Windows:COM1-COM256,Linux:/dev/ttyS0等) | +| `BaudRate` | int | 9600 | 波特率(支持1200-921600及自定义值) | +| `DataBits` | int | 8 | 数据位数(通常为5-8位) | +| `Parity` | Parity | None | 校验位(None/Odd/Even/Mark/Space) | +| `StopBits` | StopBits | One | 停止位(One/Two/OnePointFive) | +| `Handshake` | Handshake | None | 流控制(None/XOnXOff/RTS/RTSXOnXOff) | + +**配置示例**: + + + +### 4.2 适配器配置 + +适配器配置及使用详情请看:[单线程流式数据适配器](./singlethreadstreamadapter.mdx) + + ## 五、支持插件 @@ -66,75 +142,18 @@ import { TouchSocketSerialPortsDefinition } from "@site/src/components/Definitio 代码如下: -```csharp showLineNumbers -var client = new SerialPortClient(); -client.Connecting = (client, e) => { return EasyTask.CompletedTask; };//即将连接到端口 -client.Connected = (client, e) => { return EasyTask.CompletedTask; };//成功连接到端口 -client.Closing = (client, e) => { return EasyTask.CompletedTask; };//即将从端口断开连接。此处仅主动断开才有效。 -client.Closed = (client, e) => { return EasyTask.CompletedTask; };//从端口断开连接,当连接不成功时不会触发。 -client.Received = async (c, e) => -{ - await Console.Out.WriteLineAsync(e.ByteBlock.Span.ToString(Encoding.UTF8)); -}; - -await client.SetupAsync(new TouchSocketConfig() - .SetSerialPortOption(new SerialPortOption() - { - BaudRate = 9600,//波特率 - DataBits = 8,//数据位 - Parity = System.IO.Ports.Parity.None,//校验位 - PortName = "COM1",//COM - StopBits = System.IO.Ports.StopBits.One//停止位 - }) - .SetSerialDataHandlingAdapter(() => new PeriodPackageAdapter(){ CacheTimeout = TimeSpan.FromMilliseconds(100) }) - .ConfigurePlugins(a => - { - a.Add(); - })); - -await client.ConnectAsync(); -Console.WriteLine("连接成功"); -``` + :::info 信息 -上述示例中使用了`PeriodPackageAdapter`适配器,具体作用是确保把`100ms`内收到数据,作为一个包解析,在实际使用时,可以根据业务情况自由修改时间值。详情: [PeriodPackageAdapter](packageadapter.mdx) - +上述示例中使用了`PeriodPackageAdapter`适配器,具体作用是确保把`100ms`内收到数据,作为一个包解析,在实际使用时,可以根据业务情况自由修改时间值。详情: [单线程流式数据适配器](./singlethreadstreamadapter.mdx) ::: #### 6.2 继承实现 一般继承实现的话,可以从`SerialPortClientBase`继承。 -```csharp showLineNumbers -class MySerialClient : SerialPortClientBase -{ - protected override async Task OnSerialReceived(ReceivedDataEventArgs e) - { - //此处逻辑单线程处理。 - - //此处处理数据,功能相当于Received委托。 - string mes = e.ByteBlock.Span.ToString(Encoding.UTF8); - Console.WriteLine($"已接收到信息:{mes}"); - - await base.OnSerialReceived(e); - } -} -``` - -```csharp showLineNumbers -var client = new MySerialClient(); -await client.SetupAsync(new TouchSocket.Core.TouchSocketConfig() - .SetSerialPortOption(new SerialPortOption() - { - BaudRate = 9600,//波特率 - DataBits = 8,//数据位 - Parity = System.IO.Ports.Parity.None,//校验位 - PortName = "COM1",//COM - StopBits = System.IO.Ports.StopBits.One//停止位 - })); -await client.ConnectAsync();//调用连接,当连接不成功时,会抛出异常。 -``` + ## 七、接收数据 @@ -144,45 +163,13 @@ await client.ConnectAsync();//调用连接,当连接不成功时,会抛出 当使用`SerialPortClient`创建客户端时,内部已经定义好了一个外置委托`Received`,可以通过该委托直接接收数据。 -```csharp showLineNumbers -var client = new SerialPortClient(); -client.Received = (client, e) => -{ - //收到信息 - string mes = e.ByteBlock.Span.ToString(Encoding.UTF8); - Console.WriteLine($"接收到信息:{mes}"); - return EasyTask.CompletedTask; -}; - -``` + ### 7.2 ReadAsync异步读取 在使用串口时,可能更需要在当前代码上下文读取的情况。所以此处提供了异步读取方法。 -```csharp showLineNumbers -await client.ConnectAsync(); - -using (var receiver = client.CreateReceiver()) -{ - while (true) - { - using (CancellationTokenSource tokenSource=new CancellationTokenSource(TimeSpan.FromSeconds(10))) - { - using (var receiverResult = await receiver.ReadAsync(tokenSource.Token)) - { - if (receiverResult.IsCompleted) - { - //断开 - } - - //按照适配器类型。此处可以获取receiverResult.ByteBlock或者receiverResult.RequestInfo - await Console.Out.WriteLineAsync(receiverResult.ByteBlock.Span.ToString(Encoding.UTF8)); - } - } - } -} -``` + :::tip 提示 @@ -194,49 +181,13 @@ using (var receiver = client.CreateReceiver()) 按照`TouchSocket`的设计理念,使用插件处理数据,是一项非常简单,且高度解耦的方式。步骤如下: -(1)声明插件 +创建串口使用插件接收数据 -插件可以先继承`PluginBase`,然后再实现需要的功能插件接口,可以按需选择泛型或者非泛型实现。 + -如果已经有继承类,直接实现`IPlugin`接口即可。 +使用串口接收插件 -```csharp showLineNumbers -public class MyPlugin : PluginBase, ISerialReceivedPlugin -{ - public async Task OnSerialReceived(ISerialPortSession client, ReceivedDataEventArgs e) - { - //这里处理数据接收 - //根据适配器类型,e.ByteBlock与e.RequestInfo会呈现不同的值,具体看文档=》适配器部分。 - var byteBlock = e.ByteBlock; - var requestInfo = e.RequestInfo; - - ////表示该数据已经被本插件处理,无需再投递到其他插件。 - - await e.InvokeNext(); - } -} -``` - -(2)创建使用插件处理的客户端 - -```csharp {13} showLineNumbers -var client = new SerialPortClient(); -await client.SetupAsync(new TouchSocketConfig() - .SetSerialPortOption(new SerialPortOption() - { - BaudRate = 9600,//波特率 - DataBits = 8,//数据位 - Parity = System.IO.Ports.Parity.None,//校验位 - PortName = "COM1",//COM - StopBits = System.IO.Ports.StopBits.One//停止位 - }) - .ConfigurePlugins(a => - { - a.Add(); - })); - -await client.ConnectAsync(); -``` + ## 八、发送数据 @@ -244,11 +195,7 @@ await client.ConnectAsync(); `SerialPortClient`已经内置了多种发送方法,直接调用就可以发送。 -但需要注意的是,通过该方法发送的数据,会经过**适配器**,如果想要直接发送,请继承以后,使用**DefaultSendAsync**。如果发送失败,则会立即抛出异常。 - -```csharp showLineNumbers -Task SendAsync(ReadOnlyMemory memory); -``` + :::tip 提示 @@ -256,50 +203,26 @@ Task SendAsync(ReadOnlyMemory memory); ::: -### 8.2 同步发送等待 +## 九、请求-响应模式通信 -`WaitingClient`是`TouchSocket`提供的一个等待客户端发送数据的客户端。例如:串口客户端发送一个"Hello",等待对方回信"Hi",此时即可使用`WaitingClient`。 +在使用串口时,有时需要一种请求-响应模式通信组件,专门用于处理需要等待回复的串口通信场景。它实现了发送数据后等待特定响应的同步通信模式,特别适用于命令-应答类型的串口协议。 -```csharp showLineNumbers -await client.ConnectAsync(); +### 9.2 应用场景 -//调用CreateWaitingClient获取到IWaitingClient的对象。 -var waitClient = client.CreateWaitingClient(new WaitingOptions() -{ - FilterFunc = response => //设置用于筛选的fun委托,当返回为true时,才会响应返回 - { - return true; +- **设备查询**:发送查询命令,等待设备状态或参数回复 +- **指令确认**:发送控制指令,等待设备执行确认响应 +- **数据请求**:请求特定数据,等待完整数据包返回 +- **握手通信**:协议握手过程中的请求确认机制 - //if (response.Data.Length == 1) - //{ - // return true; - //} - //return false; - } -}); +### 9.3 基本用法 -//然后使用SendThenReturn。 -byte[] returnData = waitClient.SendThenReturn(Encoding.UTF8.GetBytes("Hello")); -client.Logger.Info($"收到回应消息:{Encoding.UTF8.GetString(returnData)}"); +详情请看:[等待响应组件](./waitingclient.mdx) -//同时,如果适配器收到数据后,返回的并不是字节,而是IRequestInfo对象时,可以使用SendThenResponse. -ResponsedData responsedData = waitClient.SendThenResponse(Encoding.UTF8.GetBytes("Hello")); -IRequestInfo requestInfo = responsedData.RequestInfo;//同步收到的RequestInfo -``` +## 十、关闭和释放 -:::tip 提示 + -在`SendThenReturn`时,通过其他参数,还可以设置`Timeout`,以及可取消的等待`Token`。 +## 十一、Demo示例 -::: - -:::danger 注意事项 - -1. 发送完数据,在等待时,如果收到其他返回数据,则可能得到错误结果。 -2. 发送采用同步锁,一个事务没结束,另一个请求也发不出去。 -3. 在**Net461及以下**版本中,`SendThenReturn`与`SendThenReturnAsync`不能混合使用。即:要么全同步,要么全异步。 - -::: - -[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Serial) + diff --git a/handbook/docs/singlethreadstreamadapter.mdx b/handbook/docs/singlethreadstreamadapter.mdx new file mode 100644 index 000000000..81d603f05 --- /dev/null +++ b/handbook/docs/singlethreadstreamadapter.mdx @@ -0,0 +1,509 @@ +--- +id: singlethreadstreamadapter +title: 单线程流式适配器完整指南 +--- + +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import CardLink from "@site/src/components/CardLink.js"; +import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; + + + + +## 概述 + +单线程流式适配器是`TouchSocket`框架中用于处理流式数据流的核心组件,主要用于解决网络、管道、串口等通信等流式数据中的粘包、分包问题。 + +在流式数据通信中,由于数据传输的连续性和网络传输特性,经常会遇到数据粘包和分包的问题。粘包是指发送方发送的多个数据包在接收方被合并成一个数据包接收;分包则是指一个完整的数据包被拆分成多个片段进行传输。这些问题会导致接收方无法正确解析数据结构,影响应用程序的正常运行。 + +TouchSocket框架提供了一套完整的适配器解决方案来处理这些问题。适配器作为数据处理的中间层,位于原始数据流和应用逻辑之间,负责将连续的字节流按照特定的协议规则解析成完整的数据包。 + +本文档涵盖了TouchSocket框架中所有类型的单线程流式适配器,从最基础的原始适配器到高度封装的模板解析适配器,每种适配器都有其特定的使用场景和优势。通过合理选择和使用这些适配器,开发者可以轻松处理各种复杂的数据协议,确保数据的完整性和准确性。 + +适配器的主要优势包括: + +- **自动化处理**:无需手动处理复杂的数据拆分和组合逻辑 +- **高性能**:采用内存池和零拷贝技术,最大化传输效率 +- **灵活性**:支持自定义数据格式和解析规则 +- **可靠性**:内置错误检测和恢复机制 +- **易用性**:提供多种现成的模板适配器,满足常见的数据协议需求 + +## 示例说明 + +为了更好地理解和使用单线程流式适配器,本文档将通过一个具体的示例来说明适配器的实现和应用。 + +假设我们有一个简单的自定义协议,数据格式如下: + +- 第1个字节表示指令类型 +- 第2字节表示数据类型 +- 第3字节表示后续数据的长度。使用ushort大端表示,最大长度为65535 +- 后续字节表示载荷数据 +- 最后2字节表示CRC16校验码 + +包结构如下: + +```mermaid +--- +title: "Custom Packet Structure" +--- +packet-beta +0-7: "Command Type (指令类型)" +8-15: "Data Type (数据类型)" +16-31: "Data Length (数据长度) - UShort" +32-63: "Payload Data (载荷数据) - Variable Length" +64-79: "CRC16 Checksum (CRC16校验码)" +``` + +## 一、使用适配器 + +单线程流式适配器可以配置在所有流式组件中,例如:Tcp客户端和服务器、管道客户端和服务器、串口等。 + +### 1.1 配置使用 + +在`TouchSocketConfig`配置中,使用`SetTcpDataHandlingAdapter`、`SetNamedPipeDataHandlingAdapter`、`SetSerialDataHandlingAdapter`等方法进行配置。 + + + + + +:::caution 注意 + +适配器在配置时,必须使用`new`关键字创建**新实例**,不能使用单例模式。 + +::: + +### 1.2 直接设置 + +可以在任意时刻,例如在连接成功后,直接通过`SetAdapter`方法直接进行配置。 + + + +:::info 信息 + +1. `SetAdapter`是非公共方法,必须在继承后才可使用。 +2. 一般建议在连接初始化(例如:OnTcpConnecting)时进行配置,避免在数据处理中途更换适配器,导致数据异常。 + +::: + +## 二、使用原始适配器解析数据 + +### 2.1 说明 + +原始适配器则是直接从`SingleStreamDataHandlingAdapter`继承,能够在第一时间,第一手接触到流式源数据。 +可以自定实现数据的继续投递方式。 + +例如:对于最开始假设的数据格式。我们可以这样解析: + + + + + +### 2.2 示例Demo + + + +## 二、内置包适配器 + +### 2.1 说明 + +内置包适配器,是框架内置的,用于直接解决粘、分包问题的现成适配器。 + +它需要客户端和服务器配套使用,能一键式解决粘、分包问题。 + +目前内置的包适配器有以下几种: + +### 2.2 固定包头数据处理适配器 + +1. 最有力的解决粘包。分包问题。 +2. 是自定义协议的不二选择。 +3. 支持指定包头长度,`Byte`、`Ushort`、`Int`三种类型作为包头。 +4. 最好在客户端与服务器均使用`TouchSocket`组件时使用。不然就需要非`TouchSocket`的一方适配包头算法。 + + + +#### 2.2.1 固定包头算法解释 + +- Byte包头算法:以第一个字节作为后续整个数据的长度,整个数据长度区间为[0,255]。 +- Ushort包头算法:前2个字节,且为[默认端序(小端)](./touchsocketbitconverter.mdx)的排列,作为后续整个数据的长度,整个数据长度区间为[0,65535]。 +- Int包头算法(默认配置):前4个字节,且为[默认端序(小端)](./touchsocketbitconverter.mdx)排列,作为后续整个数据的长度,整个数据长度区间为[0,2^31]。 + +#### 2.2.2 使用 + + + +#### 2.2.3 接收数据 + +固定包头数据处理适配器会把数据通过`Memory`进行投递,所以在`Received`中,直接解析`Memory`即可。 + + + +:::tip 提示 + +固定包头数据处理适配器会对发送的数据进行处理,添加包头。 + +::: + +### 2.3 固定长度数据处理适配器 + +1. 无论何时,发送与接收的数据长度永远为设定值。 +2. 算法简单,可以比较轻松的实现跨语言、跨框架。 +3. 一般适用于业务数据固定场景, + + + + +#### 2.3.1 固定长度数据处理算法 + +固定长度数据处理算法比较简单,就是事先约定发送数据的长度无论何时都是一致的。 + +#### 2.3.2 使用 + + + +#### 2.3.3 接收数据 + +固定长度数据处理适配器会把数据通过`Memory`进行投递,所以在`Received`中,直接解析`Memory`即可。 + + + +:::tip 提示 + +固定长度数据处理适配器会对发送的数据进行检查,如果长度不符合要求,则会抛出异常。 + +::: + +### 2.4 终止因子数据处理适配器 + +1. 最适用于字符串类(`Json`,`Xml`等)的信息交互。 +2. 算法简单,非常容易实现跨语言、跨框架。 +3. 发送普通流数据时,有很小的概率发生提前终止的情况(可设置复杂终止因子来解决)。 + + + +#### 2.4.1 终止因子分割数据算法 + +终止因子分割数据算法,就是通过事先约定,发送的数据是以特定数据的组合作为结束的。例如:`redis`协议,就是以`\r\n`作为结束。不过值得注意的是,框架内置的不仅可以用字符串作为终止字符,还能以16进制甚至二进制作为终止字符。 + +#### 2.4.2 使用 + + + +#### 2.4.3 接收数据 + +终止因子数据处理适配器会把数据通过`Memory`进行投递,所以在`Received`中,直接解析`Memory`即可。 + + + +### 2.5 周期数据处理适配器 + +1. 可处理任意数据。 +2. 只能解决分包问题,无法解决粘包问题。 +3. 处理效率会有一定延迟。 + + + +#### 2.5.1 周期数据算法 + +周期数据处理适配器,就是通过判断收到数据的时间间隔,将极短时间内收到的数据进行合并。能够一定程度的解决分包问题。 + +#### 2.5.2 使用 + + + +#### 2.5.3 接收数据 + +周期数据处理适配器会把数据通过`Memory`进行投递,所以在`Received`中,直接解析`Memory`即可。 + + + +### 2.6 Json格式数据处理适配器 + +1. 能够处理任意标准`Json`数据。 +2. 能够提取出信息中的杂质数据。 +3. 支持单个`Object`数据、或者`Array`数据。 +4. 支持类型嵌套格式。 + + + +#### 2.6.1 Json格式数据处理算法 + +Json格式数据处理算法,就是对接收的字符串进行大括号和中括号的计数,当成对的括号组合,来确定一个完整的json数据。 + +#### 2.6.2 使用 + + + +#### 2.6.3 接收数据 + +Json格式数据处理适配器会把数据通过`IRequestInfo`进行投递,所以在`Received`中,需要把`IRequestInfo`转为`JsonPackage`。 + + + +:::tip 提示 + +`Json`格式数据处理适配器不对发送的数据做处理,仅仅对接收到的数据做处理。 + +::: + +### 2.7 示例Demo + + +## 三、用户自定义适配器 + +### 3.1 说明 + +用户自定义适配器,是为了解决当用户的数据是其他情况的问题。不过和原始适配器相比,用户自定义适配器(**CustomDataHandlingAdapter**)要简单很多。因为他提供了很多指令可以组合处理不同情况,而你只需要简单调用即可。 + + + + +### 3.2 运行逻辑 + + + +返回指令类型: + +- FilterResult.Cache:将Reader中的可读数据进行缓存,用于和下次接收数据做拼接。 +- FilterResult.Success:完成本次数据解析,向`Received`投递`IRequestInfo`对象。在返回之前,请一定确保已经消费过数据。不然会发生无限循环的危险情况。 +- FilterResult.GoOn:从当前可读的数据重新投递,所以在返回之前,请一定确保已经推进过至少1位数据。不然会发生无限循环的危险情况。 + +:::danger 注意 + +返回`Success`或者`GoOn`指令时,请一定确保推进过至少1位数据。不然会发生无限循环的危险情况。 + +::: + +### 3.3 创建适配器 + +还是以最开始数据为例: + + + +### 3.4 使用自定义适配器接收数据 + + + +### 3.5 示例Demo + + + +## 四、模板解析"固定包头"数据适配器 + +### 4.1 说明 + +和用户自定义适配器相比,使用模板解析将会更加简单流程。 + +例如在上节所说的数据格式,由于前4个字节总是固定,且至少需要4个字节才能继续解析,所以我们把类似这样的数据格式,叫做"**固定包头**"数据,那么,他就可以使用固定包头数据解析模板。 + +一般来说,绝大多数数据协议都是**固定包头长度**的,例如:modbus协议,或者文本即将解析的数据格式。他们都是经典的固定包头格式,具有Header+Body的明显分割点。但有时候,也有一些数据有好几段,例如:具有Crc校验的数据,也就是Header+Body+Crc的格式,这时候,我们可以把Body+Crc看做一段数据,然后从Header解析BodyLength以后,加上Crc的长度。最后会在OnParsingBody时,将Body和Crc一起投递,届时做好数据分割即可。 + +所以,学会观察数据,是使用模板解析的前提。 + + + +### 4.2 特点 + +1. 可以自由适配**99%**的数据协议(例如:`modbus`,电力控制协议等)。 +2. 可以随意定制数据协议。 +3. 可以与**任意语言、框架**对接数据。 + +### 4.3 创建适配器 + + + +### 4.4 使用 + + + +### 4.5 示例Demo + + +## 五、模板解析"大数据固定包头"数据适配器 + +### 5.1 说明 + +大数据固定包头,是对固定的包头模板的补充,一般来是,固定包头适配器,不能工作于超过2G的数据,但是在少数情况下,会有大量的数据传输需求。所以这部分的业务,可以用大数据固定包头实现。 + + + +### 5.2 特点 + +1. 可以随意定制数据协议。 +2. 可以与**任意语言、框架**对接数据。 +3. 可以接收**理论无限大**的数据。 + +### 5.3 创建适配器 + + + +### 5.4 使用 + + + +### 5.5 示例Demo + + +## 六、模板解析"不固定包头"数据适配器 + +### 6.1 说明 + +有时候,我们需要解析的数据的包头是不定的,例如:`HTTP`数据格式,其数据头和数据体由`\r\n`隔开,而数据头又因为请求者的请求信息的不同,头部数据量不固定,而数据体的长度,也是由数据头的`ContentLength`的值显式指定的,所以,可以考虑使用`CustomUnfixedHeaderDataHandlingAdapter`解析。 + + + +### 6.2 特点 + +1. 可以自由适配**所有**的数据协议。 +2. 可以随意定制数据协议。 +3. 可以与**任意语言、框架**对接数据。 + +### 6.3 创建适配器 + + + +### 6.4 使用 + + + +### 6.5 示例Demo + + +## 七、模板解析"大数据不固定包头"数据适配器 + +### 7.1 说明 + +大数据不固定包头,是对不固定的包头模板的补充,一般来是,不固定包头适配器,不能工作于超过2G的数据,但是在少数情况下,会有大量的数据传输需求。所以这部分的业务,可以用大数据不固定包头实现。 + + + +### 7.2 特点 + +1. 可以随意定制数据协议。 +2. 可以与**任意语言、框架**对接数据。 +3. 可以接收**理论无限大**的数据。 + +### 7.3 创建适配器 + +### 7.4 使用 + + +### 7.5 示例Demo + + +## 八、模板解析"区间数据"数据适配器 + +### 8.1 说明 + +区间适配器,一般用于字符串类的消息,类似"\*\*Hello##",该数据,以\*\*开头,以##结尾。当然,区间适配器也能用于二进制数据,但是会有概率发生标识重复的情况。所以,用于二进制时,应当设置较复杂的区间标识。 + +该适配器与[终止因子分割适配器](./packageadapter.mdx)相比,可以设置开头的字符区间。 + + + +### 8.2 特点 + +1. 可以自由适配**很多**的字符串数据协议。 +2. 可以随意定制数据协议。 +3. 可以与**任意语言、框架**对接数据。 + +### 8.3 创建适配器 + + + +### 8.4 使用 + + + +### 8.5 示例Demo + + +## 九、模板解析"固定数量分隔符"数据适配器 + +### 9.1 说明 + +固定数量分隔符数据适配器,用于解析固定数量分隔符的数据。在此适配器中,我们使用固定数量分隔符来解析数据。相比于区间分隔符数据适配器,此适配器可以处理更复杂的数据。 + + + +### 9.2 特点 + +1. 可以自由适配**很多**的字符串数据协议。 +2. 可以与**任意语言、框架**对接数据。 + +### 9.3 创建适配器 + + +### 9.4 使用 + + +### 9.5 示例Demo + + +## 十、模板解析"Json"数据适配器 + +### 10.1 说明 + +Json适配器,一般用于Json字符串类的消息。可以直接通过Json串解析出对应的数据协议。 + + + +### 10.2 特点 + +1. 可以自由适配**很多**的字符串数据协议。 +2. 可以与**任意语言、框架**对接数据。 + +### 10.3 创建适配器 + + +### 10.4 使用 + + +### 10.5 示例Demo + + +## 十一、适配器可设置参数 + +| 属性 | 描述 |默认值 | +| ---- | ---- |---- | +| MaxPackageSize | 适配器能接收的最大数据包长度 |1024\*1024\*1024字节| +| CanSendRequestInfo | 是否允许发送IRequestInfo对象 |false| +| CacheTimeoutEnable | 是否启用缓存超时。 |true| +| CacheTimeout | 缓存超时时间。 |1秒| + + + + + +## 十二、结语 + +通过本文档的详细介绍,我们全面了解了TouchSocket框架中单线程流式适配器的完整体系和使用方法。从最基础的原始适配器到高度封装的模板解析适配器,每种适配器都为不同的应用场景提供了优雅的解决方案。 + +### 12.1 适配器选择指南 + +在实际开发中,选择合适的适配器至关重要: + +- **性能优先**:需要最高性能和最大灵活性时,使用**原始适配器**,直接处理字节流 +- **快速开发**:对于常见协议格式,优先选择**内置包适配器**(固定包头、终止因子等) +- **自定义协议**:当数据格式复杂但有规律时,使用**模板解析适配器** +- **特殊需求**:对于非标准格式或复杂逻辑,采用**用户自定义适配器** + +### 12.2 最佳实践总结 + +1. **统一性**:确保客户端和服务器使用相同的适配器配置 +2. **性能优化**:合理设置最大包长度和缓存超时参数 +3. **错误处理**:在适配器中实施完善的错误检测和恢复机制 +4. **测试验证**:使用框架提供的`DataHandlerTester`工具验证适配器正确性 +5. **文档记录**:为自定义适配器编写详细的协议文档 + +### 12.3 发展前景 + +TouchSocket的适配器系统不仅解决了网络通信中的粘包分包问题,更为构建可靠、高效的分布式系统奠定了坚实基础。随着物联网、边缘计算等领域的快速发展,这套完善的适配器解决方案将在更多场景中发挥重要作用。 + +掌握了这些适配器的使用方法,您就拥有了处理各种复杂网络协议的强大工具。无论是传统的TCP/IP通信,还是现代的IoT设备互联,TouchSocket的适配器都能为您提供稳定可靠的数据处理能力。 + +:::tip 提示 + +上述创建的适配器客户端与服务器均适用。如有疑问,请参考相关示例Demo或访问官方社区获取技术支持。 + +::: diff --git a/handbook/docs/tcpclient.mdx b/handbook/docs/tcpclient.mdx index 1a57d117a..b3b192561 100644 --- a/handbook/docs/tcpclient.mdx +++ b/handbook/docs/tcpclient.mdx @@ -8,8 +8,8 @@ import BilibiliCard from '@site/src/components/BilibiliCard.js'; import Tabs from "@theme/Tabs"; import TabItem from "@theme/TabItem"; import { TouchSocketDefinition } from "@site/src/components/Definition.js"; - -### 定义 +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; +import CardLink from "@site/src/components/CardLink.js"; @@ -35,59 +35,76 @@ import { TouchSocketDefinition } from "@site/src/components/Definition.js"; ## 四、可配置项 -
-可配置项 -
+### 4.1 目标服务器地址 -#### SetMaxPackageSize -数据包最大值(单位:byte),默认1024×1024×10。该值会在适当时间,直接作用DataHandlingAdapter.MaxPackageSize。 +`TcpClient`必须指定远程服务器地址,否则无法连接。 -#### SetRemoteIPHost 链接到的远程IPHost,支持域名。支持类型: -1. 使用IPv4,传入形如:127.0.0.1:7789的字符串即可。 -2. 使用IPv6,传入形如:[\*::\*]:7789的字符串即可。 -3. 使用域名,必须包含协议类型,形如:http://baidu.com或者https://baidu.com:80 -3. 使用IPv6域名,必须包含协议类型,形如:http://[\*::\*]:80 -#### SetClientSslOption -客户端Ssl配置,为Null时则不启用。 -注意,当RemoteIPHost使用https、wss的域名时,该配置会使用系统默认配置生效。 +1. 使用IPv4,传入形如:`127.0.0.1:7789`的字符串即可。 +2. 使用IPv6,传入形如:`[*::*]:7789`的字符串即可。 +3. 使用域名,必须包含协议类型,形如:`https://touchsocket.net/`或者`http://touchsocket.net:80` +3. 使用IPv6域名,必须包含协议类型,形如:`http://[*::*]:80` + + + +:::tip 提示 + +建议在配置时,使用规范的名称体制,例如:`tcp://127.0.0.1:7789`、`http://127.0.0.1:7789`等。 + +::: + +### 4.2 SSL加密设置 + +`TcpClient`支持SSL加密连接。只需要在配置时,调用`SetClientSslOption`方法,传入`ClientSslOption`实例即可。 -#### SetKeepAliveValue -为Socket设置的属性。 -注意:该配置仅在window平台生效。 + +:::tip 提示 + +当远程服务器使用的证书是信任机构颁发的证书(例如:在阿里云、腾讯云等平台签发的),且在设置远程服务器地址时,使用了`https`、`wss`、`ssl`、`tls`等协议头时(例如:`https://touchsocket.net/`),则会使用默认的Ssl配置。无需再设置`ClientSslOption`。 + +::: + +### 4.3 设置Tcp底层心跳 + +`TcpClient`支持Tcp底层心跳设置。只需要在配置时,调用`SetKeepAliveValue`方法即可。 -#### SetBindIPHost -绑定端口。 -- 在UdpSessionBase中表示本地监听地址 -- 在TcpClient中表示固定客户端端口号。 + + +:::caution 注意 + +1. 此配置项仅在windows下有效。 +2. 非必要请勿开启。这个设置不能代替常规心跳包。因为Tcp心跳包的发送是由操作系统控制的,应用程序无法干预。 + +::: + +### 4.4 固定客户端端口号 + +`TcpClient`支持固定客户端端口号。只需要在配置时,调用`SetBindIPHost`方法即可。 + -#### UseNoDelay -设置Socket的NoDelay属性,默认false。 - +### 4.5 Tcp客户端端口复用 -#### UseReuseAddress -启用端口复用。该配置可在客户端在监听端口时,可以一定程度缓解端口来不及释放的问题。 +`TcpClient`支持端口复用。只需要在配置时,调用`UseReuseAddress`方法即可。 -#### SetMaxBufferSize -设置最大缓存容量,默认自动。 + -#### SetMinBufferSize -设置最小缓存容量,默认自动。 +### 4.6 设置Socket的NoDelay属性 - +`TcpClient`支持设置Socket的NoDelay属性。只需要在配置时,调用`UseNoDelay`方法即可。 -
-
+ + + ## 五、支持插件 @@ -109,73 +126,15 @@ import { TouchSocketDefinition } from "@site/src/components/Definition.js"; 代码如下: -```csharp showLineNumbers -var tcpClient = new TcpClient(); -tcpClient.Connecting = (client, e) => { return EasyTask.CompletedTask; };//即将连接到服务器,此时已经创建socket,但是还未建立tcp -tcpClient.Connected = (client, e) => { return EasyTask.CompletedTask; };//成功连接到服务器 -tcpClient.Closing = (client, e) => { return EasyTask.CompletedTask; };//即将从服务器断开连接。此处仅主动断开才有效。 -tcpClient.Closed = (client, e) => { return EasyTask.CompletedTask; };//从服务器断开连接,当连接不成功时不会触发。 -tcpClient.Received = (client, e) => -{ - //从服务器收到信息。但是一般byteBlock和requestInfo会根据适配器呈现不同的值。 - var mes = e.ByteBlock.Span.ToString(Encoding.UTF8); - tcpClient.Logger.Info($"客户端接收到信息:{mes}"); - return EasyTask.CompletedTask; -}; - -//载入配置 -await tcpClient.SetupAsync(new TouchSocketConfig() - .SetRemoteIPHost("127.0.0.1:7789") - .ConfigureContainer(a => - { - a.AddConsoleLogger();//添加一个日志注入 - })); - -await tcpClient.ConnectAsync();//调用连接,当连接不成功时,会抛出异常。 - -Result result = await tcpClient.TryConnectAsync();//或者可以调用TryConnectAsync -if (result.IsSuccess()) -{ - -} - -tcpClient.Logger.Info("客户端成功连接"); -``` - + + #### 6.2 继承实现 一般继承实现的话,可以从`TcpClient`继承。如果有特殊需求,也可以从`TcpClientBase`继承。 -```csharp showLineNumbers -class MyTcpClient : TcpClient -{ - protected override async Task OnTcpReceived(ReceivedDataEventArgs e) - { - //此处逻辑单线程处理。 - - //此处处理数据,功能相当于Received委托。 - string mes =e.ByteBlock.Span.ToString(Encoding.UTF8); - Console.WriteLine($"已接收到信息:{mes}"); - await base.OnTcpReceived(e); - } -} -``` - -```csharp showLineNumbers -var tcpClient = new MyTcpClient(); -//载入配置 -await tcpClient.SetupAsync(new TouchSocketConfig() - .SetRemoteIPHost("127.0.0.1:7789") - .ConfigureContainer(a => - { - a.AddConsoleLogger();//添加一个日志注入 - })); - -await tcpClient.ConnectAsync();//调用连接,当连接不成功时,会抛出异常。 -``` - + ## 七、接收数据 @@ -183,26 +142,16 @@ await tcpClient.ConnectAsync();//调用连接,当连接不成功时,会抛 ### 7.1 Received委托处理 -当使用TcpClient创建客户端时,内部已经定义好了一个外置委托Received,可以通过该委托直接接收数据。 +当使用`TcpClient`创建客户端时,内部已经定义好了一个外置委托`Received`,可以通过该委托直接接收数据。 -```csharp showLineNumbers -var tcpClient = new TcpClient(); -tcpClient.Received = (client, e) => -{ - //从服务器收到信息 - string mes = e.ByteBlock.Span.ToString(Encoding.UTF8); - Console.WriteLine($"接收到信息:{mes}"); - return EasyTask.CompletedTask; -}; + -await tcpClient.ConnectAsync("127.0.0.1:7789"); -``` +### 7.2 继承TcpClient重写接收逻辑 +如6.2所示,如果需要更自定义的接收逻辑,可以从`TcpClient`继承,然后重写`OnTcpReceived`方法。 ### 7.2 插件处理 推荐 - - (2)创建使用插件处理的客户端 -```csharp showLineNumbers -var client = new TcpClient(); -await client.SetupAsync(new TouchSocketConfig() - .SetRemoteIPHost("127.0.0.1:7789") - .ConfigureContainer(a => - { - a.AddConsoleLogger(); - }) - .ConfigurePlugins(a => - { - a.Add(); - })); + -await client.ConnectAsync(); -``` - -:::danger 注意 - -当接收数据时,ByteBlock与RequestInfo的值会根据适配器类型不同而不同。 - -::: @@ -272,97 +187,11 @@ await client.ConnectAsync(); -### 7.3 异步阻塞接收 推荐 +### 7.3 异步阻塞接收 异步阻塞接收,即使用await的方式接收数据。其特点是能在代码上下文中,直接获取到收到的数据。例如: -```csharp showLineNumbers -var client = new TcpClient(); -await client.ConnectAsync("127.0.0.1:7789");//连接 - -//receiver可以复用,不需要每次接收都新建 -using (var receiver = client.CreateReceiver()) -{ - while (true) - { - //receiverResult必须释放 - using (var receiverResult = await receiver.ReadAsync(CancellationToken.None)) - { - if (receiverResult.IsCompleted) - { - Console.WriteLine($"客户端已断开,信息:{receiverResult.Message}"); - //断开连接了 - return; - } - - //如果是适配器信息,则可以直接获取receiverResult.RequestInfo; - var requestInfo = receiverResult.RequestInfo; - var byteBlock = receiverResult.ByteBlock; - - //从服务器收到信息。 - var mes = byteBlock.Span.ToString(Encoding.UTF8); - client.Logger.Info($"客户端接收到信息:{mes}"); - } - } -} -``` - -在异步阻塞接收时,当接收的数据不满足解析条件时,还可以缓存起来,下次一起处理。 - -例如:下列将演示接收字符串,当没有发现“\r\n”时,将缓存数据,直到发现重要字符。 - -其中,`CacheMode`与`MaxCacheSize`是启用缓存的重要属性。`byteBlock.Seek`则是将已读取的数据游标移动至指定位置。 - -```csharp {7-8,45} showLineNumbers -var client = new TcpClient(); -await client.ConnectAsync("127.0.0.1:7789");//连接 - -//receiver可以复用,不需要每次接收都新建 -using (var receiver = client.CreateReceiver()) -{ - receiver.CacheMode = true; - receiver.MaxCacheSize = 1024 * 1024; - - var rn = Encoding.UTF8.GetBytes("\r\n"); - while (true) - { - //receiverResult每次接收完必须释放 - using (var receiverResult = await receiver.ReadAsync(CancellationToken.None)) - { - //收到的数据,此处的数据会根据适配器投递不同的数据。 - var byteBlock = receiverResult.ByteBlock; - var requestInfo = receiverResult.RequestInfo; - - if (receiverResult.IsCompleted) - { - //断开连接了 - Console.WriteLine($"断开信息:{receiverResult.Message}"); - return; - } - - //在CacheMode下,byteBlock将不可能为null - - var index = 0; - while (true) - { - var r = byteBlock.Span.Slice(index).IndexOf(rn); - if (r < 0) - { - break; - } - - var str = byteBlock.Span.Slice(index, r).ToString(Encoding.UTF8); - Console.WriteLine(str); - - index += rn.Length; - index += r; - } - - byteBlock.Seek(index); - } - } -} -``` + :::tip 提示 @@ -373,13 +202,9 @@ using (var receiver = client.CreateReceiver()) ## 八、发送数据 -TcpClient已经内置了发送方法,直接调用就可以发送,如果发送失败,则会立即抛出异常。 +`TcpClient`已经内置了发送方法,直接调用就可以发送,如果发送失败,则会立即抛出异常。 -```csharp showLineNumbers -//原生 -public Task SendAsync(string id, ReadOnlyMemory memory); -public Task SendAsync(string id, IRequestInfo requestInfo); -``` + :::tip 提示 @@ -398,63 +223,37 @@ public Task SendAsync(string id, IRequestInfo requestInfo); 断线重连,即tcp客户端在断开服务器后,主动发起的再次连接请求。 -### 9.1 触发型重连 +### 9.1 启用断线重连 -触发型重连,依靠的是Tcp断开事件(Closed)发生时,再次尝试连接。所以,这就要求客户端在初始时,至少完成一次连接。 +断线重连,依靠的是Tcp断开后,或者初始化后,Online属性为false时,会尝试连接。 -```csharp {8,11} showLineNumbers -var tcpClient = new TcpClient(); +所以,重连机制在Setup完成后,即会生效。 -//载入配置 -await tcpClient.SetupAsync(new TouchSocketConfig() - .SetRemoteIPHost("127.0.0.1:7789") - .ConfigurePlugins(a => - { - a.UseTcpReconnection(); - })); + -await tcpClient.ConnectAsync();//调用连接 -``` -:::caution 注意 - -**触发重连,必须满足以下几个要求:** - -1. 必须完成第一次连接。 -2. 必须是被动断开,如果是客户端主动调用Close、Disposed等方法主动断开的话,一般不会生效。 -3. 必须有显式的断开信息,也就是说,直接拔网线的话,不会立即生效,会等tcp保活到期后再生效。 - -::: - - - - -### 9.2 使用Polling轮询连接插件 - -使用Polling断线重连,是一种无人值守的连接方式,它不要求首次连接。 - -```csharp showLineNumbers -.ConfigurePlugins(a => -{ - a.UseTcpReconnection() - .UsePolling(TimeSpan.FromSeconds(1)); -}) -``` + :::caution 注意 -**Polling重连,必须满足以下几个要求:** +**断线重连,必须满足以下几个要求:** -1. 必须有显式的断开信息,也就是说,直接拔网线的话,也不会立即生效,会等tcp保活到期后再生效。 +1. 必须有显式的断开信息,也就是说,直接拔网线的话,**不会**立即生效,会等tcp保活到期后再生效。此处可以结合[健康活性检验插件](./tcpcommonplugins.mdx)来绝对保活。 ::: :::tip 提示 -UseReconnection插件,可以通过设置SetActionForCheck,自己规定检查活性的方法。默认情况下,只会检验Online属性,所以无法检验出断网等情况。如果自己控制,则可以发送心跳包,以保证在线状态。 +`UseReconnection`插件,可以通过设置`SetActionForCheck`,自己规定检查活性的方法。默认情况下,只会检验`Online`属性,所以无法检验出断网等情况。如果自己控制,则可以发送心跳包,以保证在线状态。 ::: - +### 9.2 暂停重连 -[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Tcp) +适用于重连的客户端,都提供了`SetPauseReconnection`方法,可以暂停或者恢复重连。 + + + +## 十、示例Demo + + diff --git a/handbook/docs/tcpcommandlineplugin.mdx b/handbook/docs/tcpcommandlineplugin.mdx index 8d65e1a09..79017a781 100644 --- a/handbook/docs/tcpcommandlineplugin.mdx +++ b/handbook/docs/tcpcommandlineplugin.mdx @@ -1,117 +1,121 @@ ---- +--- id: tcpcommandlineplugin title: 命令行执行插件 --- import { TouchSocketDefinition } from "@site/src/components/Definition.js"; +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; +import CardLink from '@site/src/components/CardLink.js'; -### 定义 ## 一、说明 -**TcpCommandLinePlugin**命令行执行插件,是用于TCP的快捷事务实现。该类是抽象类,必须通过继承,在继承类中,声明的具的**公共的**且名称以**Command**结尾的方法,均可被快捷执行。 +**TcpCommandLinePlugin**命令行执行插件,是用于TCP的快捷事务实现。该类是抽象类,必须通过继承使用。在继承类中,声明的**公共的**且名称以**Command**结尾的方法,均可被快捷执行。 + +### 主要特性 + +- **自动命令识别**:所有以"Command"结尾的公共方法都会自动注册为可执行命令 +- **灵活的参数支持**:支持基础类型参数和JSON格式的自定义实体类参数 +- **上下文访问**:命令方法可以通过参数获取当前会话客户端信息 +- **异常处理**:可配置是否将执行异常返回给客户端 +- **数据转换**:内置字符串转换器,支持基础类型和JSON数据格式 ## 二、创建快捷执行插件 -```csharp showLineNumbers -/// -/// 命令执行插件。方法必须以Command结尾。 -/// -class MyCommandLinePlugin : TcpCommandLinePlugin -{ - private readonly ILog logger; +继承`TcpCommandLinePlugin`类,并实现您的命令方法。方法必须是公共的,且名称以**Command**结尾。 - public MyCommandLinePlugin(ILog logger) : base(logger) - { - this.ReturnException = true;//表示执行异常的时候,是否返回异常信息 - this.logger = logger; - } + - /// - /// 加法 - /// - /// - /// - /// - public int AddCommand(int a, int b) - { - this.logger.Info($"执行{nameof(AddCommand)}"); - return a + b; - } +### 命令方法说明 - /// - /// 乘法,并且获取调用者信息 - /// - /// - /// - /// - /// - public int MULCommand(ITcpSessionClient SessionClient,int a, int b) - { - this.logger.Info($"{SessionClient.IP}:{SessionClient.Port}执行{nameof(MULCommand)}"); - return a * b; - } +1. **AddCommand**:简单的加法命令,接收两个整数参数并返回结果 +2. **MULCommand**:乘法命令,同时演示如何获取调用者会话信息 + - 第一个参数`ITcpSessionClient`会自动注入当前会话客户端 + - 通过会话客户端可获取IP、端口等信息 +3. **ExcCommand**:演示异常处理的命令 - /// - /// 测试异常 - /// - /// - public void ExcCommand() - { - throw new Exception("我异常了"); - } -} -``` +### 配置说明 + +- **ReturnException**:设置为`true`时,命令执行出现异常会将异常信息返回给客户端 +- **Converter**:字符串转换器,默认支持基础类型和JSON格式,可自定义扩展 ## 三、创建服务器 -```csharp showLineNumbers -TcpService service = new TcpService(); +配置TCP服务器并添加命令行插件。需要注意数据处理适配器的选择。 -var config = new TouchSocketConfig(); -config.SetListenIPHosts(new IPHost[] { new IPHost("127.0.0.1:7789"), new IPHost(7790) }) //同时监听两个地址 - .SetDataHandlingAdapter(() => - { - //return new TerminatorPackageAdapter(1024, "\r\n");//命令行中使用\r\n结尾 - return new NormalDataHandlingAdapter();//亦或者省略\r\n,但此时调用方不能高速调用,会粘包 - }) - .ConfigureContainer(a => - { - a.AddConsoleLogger(); - }) - .ConfigurePlugins(a => - { - a.Add(); - }); + -//载入配置 -await service.SetupAsync(config); +### 配置要点 -//启动 -await service.StartAsync(); +1. **数据处理适配器**: + - 使用`TerminatorPackageAdapter`时,客户端命令需要以`\r\n`结尾 + - 使用`NormalDataHandlingAdapter`时,可省略终止符,但不能高速连续调用(会粘包) -service.Logger.Info("服务器成功启动。"); -service.Logger.Info("使用:“Add 10 20”测试"); -service.Logger.Info("使用:“MUL 10 20”测试"); -service.Logger.Info("使用:“Exc”测试异常"); +2. **插件注册**:通过`ConfigurePlugins`方法添加自定义的命令行插件 + +## 四、调用方式 + +命令行插件可以被普通TCP客户端、命令提示符(cmd)、telnet等工具调用。 + +### 调用格式 + +```text +命令名 参数1 参数2 ... [终止符] ``` -## 四、调用 +### 示例 -上述快捷执行插件,即可被普通tcp客户端,或cmd/telnet等便捷调用。 - -调用数据格式: - -`Add 10 20 /r/n` **/r/n**非必须,但是当适配器选为[终止字符分割适配器](./packageadapter.mdx)时,则必须。不然,则不可连续调用,会粘包。 +- `Add 10 20\r\n` - 调用加法命令,计算10+20 +- `MUL 5 6\r\n` - 调用乘法命令,计算56 +- `Exc\r\n` - 调用异常测试命令 :::tip 提示 -调用的参数也支持自定义实体类,届时参数使用Json数据格式即可。 +- 命令名称不需要包含"Command"后缀 +- 终止符`\r\n`是否必需取决于所选的数据处理适配器 +- 调用参数支持基础类型和JSON格式的自定义实体类 +- 不同适配器下连续调用的性能表现不同,需根据实际场景选择 ::: +## 五、高级用法 -[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Tcp/TcpCommandLineConsoleApp) +### 自定义数据转换器 + +如果需要支持更复杂的数据类型转换,可以自定义`Converter`: + +```csharp +public MyCommandLinePlugin(ILog logger) : base(logger) +{ + // 自定义转换器逻辑 + this.Converter = new MyCustomConverter(); +} +``` + +### 使用自定义实体类参数 + +命令方法支持接收复杂对象,客户端传递JSON格式数据即可: + +```csharp +public class UserInfo +{ + public string Name { get; set; } + public int Age { get; set; } +} + +public string UserCommand(UserInfo user) +{ + return $"Hello {user.Name}, age {user.Age}"; +} +``` + +调用示例: +``` +User {"Name":"张三","Age":25}\r\n +``` + + \ No newline at end of file diff --git a/handbook/docs/tcpcommonplugins.mdx b/handbook/docs/tcpcommonplugins.mdx index d748ade90..5cacd2bc5 100644 --- a/handbook/docs/tcpcommonplugins.mdx +++ b/handbook/docs/tcpcommonplugins.mdx @@ -5,8 +5,9 @@ title: 常用插件 import BilibiliCard from '@site/src/components/BilibiliCard.js'; import { TouchSocketDefinition } from "@site/src/components/Definition.js"; +import CardLink from '@site/src/components/CardLink.js'; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; -### 定义 @@ -23,33 +24,29 @@ import { TouchSocketDefinition } from "@site/src/components/Definition.js"; 所以需要使用该插件进行健康活性检验,以便及时断开无效连接。 -代码示例: +### 1.1 使用方法 -只需要在插件中添加UseCheckClear即可。其中,可配置: +只需要在插件中添加`UseTcpSessionCheckClear`(或泛型的`UseCheckClear`)即可。该插件支持以下配置选项: -- CheckClearType:健康检验类型,默认是所有类型都进行健康检验,即同时开启发送、接收健康检验,只要有一方有数据交互,即认为连接有效。 -- Tick:健康检验的间隔时间,默认是60秒。 -- OnClose:健康检验失败时的回调函数,默认是直接关闭连接。 +- **CheckClearType**:健康检验类型,默认是`CheckClearType.All`,即同时开启发送、接收健康检验,只要有一方有数据交互,即认为连接有效。 + - `CheckClearType.OnlySend`:仅检测发送方向是否有数据流动 + - `CheckClearType.OnlyReceive`:仅检测接收方向是否有数据流动 + - `CheckClearType.All`:同时检测发送和接收两个方向 +- **Tick**:健康检验的间隔时间,默认是60秒。 +- **OnClose**:健康检验失败时的回调函数,默认是直接关闭连接并输出相应的超时消息。 -```csharp {3} showLineNumbers -.ConfigurePlugins(a => -{ - a.UseCheckClear() - .SetCheckClearType( CheckClearType.All) - .SetTick(TimeSpan.FromSeconds(60)) - .SetOnClose((c,t) => - { - await c.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both); - await c.SafeCloseAsync("超时无数据"); - }); -}) -``` + :::info 温馨提示 -健康检验插件,可能因为操作系定时器原因和检验周期原因,导致检验时间间隔比配置的间隔时间要长。一般来说,最坏情况不会超过设置周期的10%。例如:设置周期为100秒,则最坏情况可能到110秒才会执行清理。 +健康检验插件,可能因为操作系统定时器原因和检验周期原因,导致检验时间间隔比配置的间隔时间要长。一般来说,最坏情况不会超过设置周期的10%。例如:设置周期为100秒,则最坏情况可能到110秒才会执行清理。 ::: +### 1.2 示例代码 + + + +### 1.3 教学视频 \ No newline at end of file diff --git a/handbook/docs/tcpservice.mdx b/handbook/docs/tcpservice.mdx index 81efa4d4e..ea9e16485 100644 --- a/handbook/docs/tcpservice.mdx +++ b/handbook/docs/tcpservice.mdx @@ -8,20 +8,22 @@ import BilibiliCard from '@site/src/components/BilibiliCard.js'; import Tabs from "@theme/Tabs"; import TabItem from "@theme/TabItem"; import { TouchSocketDefinition } from "@site/src/components/Definition.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; +import CardLink from "@site/src/components/CardLink.js"; + -### 定义 ## 一、说明 -TcpService是Tcp系服务器基类,它不参与实际的数据交互,只是配置、激活、管理、注销、重建**SessionClient**类实例。而**SessionClient**是当**TcpClient(客户端)**成功连接服务器以后,由服务器新建的一个实例类,后续的所有通信,也都是通过该实例完成的。 +`TcpService`是`Tcp`系服务器基类,它不参与实际的数据交互,只是配置、激活、管理、注销、重建`SessionClient`类实例。而`SessionClient`是当`TcpClient`(客户端)成功连接服务器以后,由服务器新建的一个实例类,后续的所有通信,也都是通过该实例完成的。 ## 二、特点 - 简单易用。 -- IOCP多线程。 +- `IOCP`多线程。 - 内存池支持 - 高性能(实测服务器单客户端单线程,每秒可接收200w条8字节的信息,接收数据流量可达3GB/s)。 - **多地址监听**(可以一次性监听多个IP及端口) @@ -50,9 +52,9 @@ TcpService是Tcp系服务器基类,它不参与实际的数据交互,只是 ### 4.1 连接架构 -服务器在收到**新客户端连接**时,会创建一个SessionClient的派生类实例,与客户端TcpClient一一对应,后续的数据通信均由此实例负责。 +服务器在收到**新客户端连接**时,会创建一个`SessionClient`的派生类实例,与客户端TcpClient一一对应,后续的数据通信均由此实例负责。 -SessionClient在Service里面以字典映射。ID为键,SessionClient本身为值。 +`SessionClient`在`Service`里面以字典映射。`ID`为键,`SessionClient`本身为值。 ```mermaid flowchart TD; @@ -77,63 +79,96 @@ flowchart TD; ## 五、可配置项 -
-可配置项 -
+### 5.1 配置监听 -#### SetMaxPackageSize -数据包最大值(单位:byte),默认1024×1024×10。该值会在适当时间,直接作用DataHandlingAdapter.MaxPackageSize。 +简单情况下,直接设置统一的监听IP和端口号组,可以一次性设置多个地址。 -#### SetGetDefaultNewId -配置初始Id的分配策略。 + + +简单设置时,无法进行更加个性化的配置。例如:是否启用Ssl加密、使用何种适配器等。 + +所以,可以使用`SetListenOptions`方法,进行个性化监听配置。 + + + +所有配置监听的项,都是从`IPHost`类创建而来。 + +`IPHost`支持以下格式创建: + +- 端口:直接按`int`入参,该操作一般在监听`Ipv4`时使用。 +- IPv4:按"127.0.0.1:7789"入参。 +- IPv6:按"[\*::\*]:7789"入参。 + + + +:::caution 注意 + +在监听时,只能使用IP地址和端口号进行监听,不能使用域名。例如: + +✅ "127.0.0.1:7789" + +❌ "https://touchsocket.net/" + +::: + +### 5.2 Id分配策略 + +`TcpService`会在每次新建`SessionClient`时,分配一个Id。默认情况下,Id是随机分配的。 +如果需要自定义Id分配策略,可以使用`SetGetDefaultNewId`方法。 -#### SetListenIPHosts -设置统一的监听IP和端口号组,可以一次性设置多个地址。 + -#### SetListenOptions -设置独立的监听IP和端口号,可以独立控制当前地址监听的个性化配置。 - -#### SetServerName -服务器标识名称,无实际使用意义。 - -#### SetBacklogProperty -Tcp半连接挂起连接队列的最大长度。默认为30 +### 5.3 Tcp半连接队列 +`TcpService`的半连接队列是指在服务器端,等待完成三次握手的连接请求队列。可以通过`SetBacklogProperty`方法设置。 -#### SetMaxCount -最大可连接数,默认为10000 + +### 5.4 设置最大连接数量 + +`TcpService`的最大连接数可以通过`SetMaxCount`方法设置。默认情况下,最大连接数为10000。 -#### SetServiceSslOption -Ssl配置,为Null时则不启用。 + +### 5.5 Ssl配置 +`TcpService`支持Ssl配置,可以通过`SetServiceSslOption`方法设置。默认情况下,Ssl配置为Null,不启用Ssl加密。 + -#### UseNoDelay -设置Socket的NoDelay属性,默认false。 +### 5.6 设置Socket的NoDelay属性 + +`TcpService`支持设置Socket的NoDelay属性,可以通过`UseNoDelay`方法设置。默认情况下,NoDelay属性为false。 -#### UseReuseAddress -启用端口复用。该配置可在服务器、或客户端在监听端口时,运行监听同一个端口。可以一定程度缓解端口来不及释放的问题。 + + +### 5.7 启用端口复用 + +`TcpService`支持启用端口复用,可以通过`UseReuseAddress`方法设置。默认情况下,端口复用为false。 -#### SetMaxBufferSize -设置最大缓存容量,默认自动。 + -#### SetMinBufferSize -设置最小缓存容量,默认自动。 +### 5.8 设置最大、最小缓存容量 + +`TcpService`支持设置最大、最小缓存容量,可以通过`SetMaxBufferSize`、`SetMinBufferSize`方法设置。默认情况下,最大缓存容量为自动,最小缓存容量为自动。 -
-
+ + +### 5.9 服务器名称 + +`TcpService`支持设置服务器名称,可以通过`SetServerName`方法设置。默认情况下,服务器名称为Null。 + + ## 六、支持插件 @@ -156,34 +191,11 @@ Ssl配置,为Null时则不启用。 直接初始化TcpService,会使用默认的**SessionClient**。 简单的处理逻辑可通过**Connecting**、**Connected**、**Received**等委托直接实现。 + + 代码如下: -```csharp showLineNumbers -var service = new TcpService(); -service.Connecting = (client, e) => { return EasyTask.CompletedTask; };//有客户端正在连接 -service.Connected = (client, e) => { return EasyTask.CompletedTask; };//有客户端成功连接 -service.Closing = (client, e) => { return EasyTask.CompletedTask; };//有客户端正在断开连接,只有当主动断开时才有效。 -service.Closed = (client, e) => { return EasyTask.CompletedTask; };//有客户端断开连接 -service.Received = async (client, e) => -{ - //从客户端收到信息 - var mes = e.ByteBlock.Span.ToString(Encoding.UTF8); - client.Logger.Info($"已从{client.Id}接收到信息:{mes}"); -}; - -await service.SetupAsync(new TouchSocketConfig()//载入配置 - .SetListenIPHosts("tcp://127.0.0.1:7789", 7790)//可以同时监听两个地址 - .ConfigureContainer(a =>//容器的配置顺序应该在最前面 - { - a.AddConsoleLogger();//添加一个控制台日志注入(注意:在maui中控制台日志不可用) - }) - .ConfigurePlugins(a => - { - //a.Add();//此处可以添加插件 - })); - -await service.StartAsync();//启动 -``` + :::info 温馨提示 @@ -191,176 +203,46 @@ Service.StartAsync()方法并不会阻塞当前运行,所以当在控制台运 ::: - - ### 7.2 泛型创建 -通过泛型创建服务器,可以实现很多有意思,且能**重写**一些有用的功能。下面就演示,如何通过泛型创建服务器。 +通过泛型创建服务器,可以实现很多有意思,且能**重写**一些有用的功能。下面就演示,如何通过泛型创建自定义服务器。 代码如下: (1)建立SessionClient继承类。 -```csharp showLineNumbers -public sealed class MySessionClient : TcpSessionClient -{ - protected override async Task OnTcpReceived(ReceivedDataEventArgs e) - { - //此处逻辑单线程处理。 + - //此处处理数据,功能相当于Received委托。 - var mes = e.ByteBlock.Span.ToString(Encoding.UTF8); - Console.WriteLine($"已接收到信息:{mes}"); +(2)建立`TcpService`继承类。使用**TcpService的泛型**直接创建。 - await base.OnTcpReceived(e); - } -} -``` + -(2)建立TcpService继承类。实际上如果业务不涉及服务器配置的话,可以省略该步骤,使用**TcpService的泛型**直接创建。 +(3)启动服务器。 -```csharp showLineNumbers -public class MyService : TcpService -{ - protected override void LoadConfig(TouchSocketConfig config) - { - //此处加载配置,用户可以从配置中获取配置项。 - base.LoadConfig(config); - } +自定义服务器的配置和启动与普通服务器一致。 - protected override MySessionClient NewClient() - { - return new MySessionClient(); - } + - protected override async Task OnTcpConnecting(MySessionClient socketClient, ConnectingEventArgs e) - { - //此处逻辑会多线程处理。 - - //e.Id:对新连接的客户端进行ID初始化,默认情况下是按照设定的规则随机分配的。 - //但是按照需求,您可以自定义设置,例如设置为其IP地址。但是需要注意的是id必须在生命周期内唯一。 - - //e.IsPermitOperation:指示是否允许该客户端链接。 - - await base.OnTcpConnecting(socketClient, e); - } -} -``` - -(3)创建服务器。 - -```csharp showLineNumbers -MyService service = new MyService(); - -await service.SetupAsync(new TouchSocketConfig()//载入配置 - .SetListenIPHosts("tcp://127.0.0.1:7789") - .ConfigureContainer(a =>//容器的配置顺序应该在最前面 - { - a.AddConsoleLogger();//添加一个控制台日志注入(注意:在maui中控制台日志不可用) - }) - .ConfigurePlugins(a => - { - //a.Add();//此处可以添加插件 - })); - -await service.StartAsync();//启动 -``` :::tip 建议 -由上述代码可以看出,通过继承,可以更加灵活的实现扩展。但实际上,很多业务我们希望大家能通过插件完成。 +由上述代码可以看出,通过继承,可以更加灵活的实现扩展。如有必要,还可以直接从`TcpServiceBase<>`继承,这样可以实现更底层的功能。 ::: -## 八、配置监听 - -所有配置监听的项,都是从`IPHost`类创建而来。 - -`IPHost`支持以下格式创建: - -- 端口:直接按`int`入参,该操作一般在监听`Ipv4`时使用。 -- IPv4:按"127.0.0.1:7789"入参。 -- IPv6:按"[\*::\*]:7789"入参。 - - - -### 8.1 Config直接配置 - -服务器在配置监听时,有多种方式实现。其中最简单、最常见的配置方式就是通过Config直接配置。 - -```csharp showLineNumbers -TcpService service = new TcpService(); - -await service.SetupAsync(new TouchSocketConfig()//载入配置 - .SetListenIPHosts("tcp://127.0.0.1:7789", 7790)); - -await service.StartAsync();//启动 -``` - -### 8.2 直接添加监听配置 - -直接添加监听配置是更加个性化的监听配置,它可以单独控制指定监听地址的具体配置,例如:是否启用Ssl加密、使用何种适配器等。 - -```csharp showLineNumbers -var service = new TcpService(); - -await service.SetupAsync(new TouchSocketConfig()//载入配置 - .SetListenOptions(option => - { - option.Add(new TcpListenOption() - { - IpHost = "127.0.0.1:7789", - Name = "server1",//名称用于区分监听 - ServiceSslOption = null,//可以针对当前监听,单独启用ssl加密 - Adapter = () => new NormalDataHandlingAdapter(),//可以单独对当前地址监听,配置适配器 - //还有其他可配置项,都是单独对当前地址有效。 - }); - - option.Add(new TcpListenOption() - { - IpHost = 7790, - Name = "server2",//名称用于区分监听 - ServiceSslOption = null,//可以针对当前监听,单独启用ssl加密 - Adapter = () => new FixedHeaderPackageAdapter(),//可以单独对当前地址监听,配置适配器 - //还有其他可配置项,都是单独对当前地址有效。 - }); - })); - -await service.StartAsync();//启动 -``` - -:::info 温馨提示 - -`SetListenIPHosts`可以和`SetListenOptions`可以同时使用,但是需要注意的是,Config的全局配置仅会对`SetListenIPHosts`单独生效的。`SetListenOptions`的地址配置均是单独配置的。 - -::: - - -### 8.3 动态添加、移除监听配置 +## 八、动态添加、移除监听配置 服务器支持在运行时,动态添加,和移除监听配置,这极大的为灵活监听提供了方便,并且还不影响现有连接。可以轻量级的实现Stop操作。 -```csharp {5,16} -TcpService service = new TcpService(); -await service.SetupAsync(new TouchSocketConfig()); -await service.StartAsync();//启动 + -service.AddListen(new TcpListenOption()//在Service运行时,可以调用,直接添加监听 -{ - IpHost = 7791, - Name = "server3",//名称用于区分监听 - ServiceSslOption = null,//可以针对当前监听,单独启用ssl加密 - Adapter = () => new FixedHeaderPackageAdapter(),//可以单独对当前地址监听,配置适配器 - //还有其他可配置项,都是单独对当前地址有效。 -}); - -foreach (var item in service.Monitors) -{ - service.RemoveListen(item);//在Service运行时,可以调用,直接移除现有监听 -} -``` +:::info 信息 + +在移除监听配置时,已完成连接的客户端不受影响。 + +::: ## 九、接收数据 @@ -372,28 +254,11 @@ foreach (var item in service.Monitors) 当使用TcpService(非泛型)创建服务器时,内部已经定义好了一个外置委托Received,可以通过该委托直接接收数据。 -```csharp showLineNumbers -var service = new TcpService(); -service.Received = (client, e) => -{ - //从客户端收到信息 - var mes = e.ByteBlock.Span.ToString(Encoding.UTF8); - client.Logger.Info($"已从{client.Id}接收到信息:{mes}"); - return EasyTask.CompletedTask; -}; - -await service.SetupAsync(new TouchSocketConfig()//载入配置 - .SetListenIPHosts("tcp://127.0.0.1:7789", 7790)//同时监听两个地址 - .ConfigureContainer(a =>//容器的配置顺序应该在最前面 - { - a.AddConsoleLogger();//添加一个控制台日志注入(注意:在maui中控制台日志不可用) - })); -await service.StartAsync();//启动 -``` + ### 9.2 重写TcpSessionClient处理 -正如6.2所示,可以直接在MySessionClient的重写**OnTcpReceived**中直接处理数据。 +正如7.2所示,可以直接在`MySessionClient`的重写`OnTcpReceived`中直接处理数据。 ### 9.3 插件处理 推荐 @@ -406,7 +271,8 @@ await service.StartAsync();//启动 ]} > -按照TouchSocket的设计理念,使用插件处理数据,是一项非常简单,且高度解耦的方式。步骤如下: + +按照`TouchSocket`的设计理念,使用插件处理数据,是一项非常简单,且高度解耦的方式。步骤如下: (1)声明插件 @@ -414,43 +280,20 @@ await service.StartAsync();//启动 如果已经有继承类,直接实现`IPlugin`接口即可。 -```csharp showLineNumbers -public class MyPlugin : PluginBase, ITcpReceivedPlugin + + +(2)配置使用插件处理的服务器 + +```csharp +.ConfigurePlugins(a => { - public async Task OnTcpReceived(ITcpSession client, ReceivedDataEventArgs e) - { - //这里处理数据接收 - //根据适配器类型,e.ByteBlock与e.RequestInfo会呈现不同的值,具体看文档=》适配器部分。 - ByteBlock byteBlock = e.ByteBlock; - IRequestInfo requestInfo = e.RequestInfo; - - ////表示该数据已经被本插件处理,无需再投递到其他插件。 - - await e.InvokeNext();//如果本插件无法处理当前数据,请将数据转至下一个插件。 - } -} + a.Add(); +}) ``` -(2)创建使用插件处理的服务器 +:::info 信息 -```csharp {10} -var service = new TcpService(); -await service.SetupAsync(new TouchSocketConfig() - .SetListenIPHosts(7789) - .ConfigureContainer(a => - { - a.AddConsoleLogger(); - }) - .ConfigurePlugins(a => - { - a.Add(); - })); -await service.StartAsync(); -``` - -:::danger 注意 - -当接收数据时,ByteBlock与RequestInfo的值会根据适配器类型不同而不同。并且,当数据存于ByteBlock时,其实际的数据长度是ByteBlock.Length(Length)。而不是ByteBlock.Buffer.Length +当接收数据时,`Memory`与`RequestInfo`的值会根据适配器类型不同而不同。 ::: @@ -468,9 +311,7 @@ await service.StartAsync(); - - -### 9.4 异步阻塞接收 推荐 +### 9.4 异步阻塞接收 异步阻塞接收,即使用`await`的方式接收数据。其特点是能在代码上下文中,直接获取到收到的数据。 @@ -478,113 +319,8 @@ await service.StartAsync(); 下列将以插件为例: -```csharp showLineNumbers -class TcpServiceReceiveAsyncPlugin : PluginBase, ITcpConnectedPlugin -{ - public async Task OnTcpConnected(ITcpSession client, ConnectedEventArgs e) - { - if (client is ITcpSessionClient sessionClient) - { - //receiver可以复用,不需要每次接收都新建 - using (var receiver = sessionClient.CreateReceiver()) - { - while (true) - { - //receiverResult每次接收完必须释放 - using (var receiverResult = await receiver.ReadAsync(CancellationToken.None)) - { - //收到的数据,此处的数据会根据适配器投递不同的数据。 - var byteBlock = receiverResult.ByteBlock; - var requestInfo = receiverResult.RequestInfo; + - if (receiverResult.IsCompleted) - { - //断开连接了 - Console.WriteLine($"断开信息:{receiverResult.Message}"); - return; - } - - //如果数据是从ByteBlock投递 - if (byteBlock != null) - { - Console.WriteLine(byteBlock.Span.ToString(Encoding.UTF8)); - } - - //如果是适配器信息,则可以直接处理requestInfo; - } - } - } - } - - await e.InvokeNext(); - } -} -``` - -在异步阻塞接收时,当接收的数据不满足解析条件时,还可以缓存起来,下次一起处理。 - -例如:下列将演示接收字符串,当没有发现“\r\n”时,将缓存数据,直到发现重要字符。 - -其中,`CacheMode`与`MaxCacheSize`是启用缓存的重要属性。`byteBlock.Seek`则是将已读取的数据游标移动至指定位置。 - -```csharp {10-11,48} showLineNumbers -class TcpServiceReceiveAsyncPlugin : PluginBase, ITcpConnectedPlugin -{ - public async Task OnTcpConnected(ITcpSession client, ConnectedEventArgs e) - { - if (client is ITcpSessionClient sessionClient) - { - //receiver可以复用,不需要每次接收都新建 - using (var receiver = sessionClient.CreateReceiver()) - { - receiver.CacheMode = true; - receiver.MaxCacheSize = 1024 * 1024; - - var rn = Encoding.UTF8.GetBytes("\r\n"); - while (true) - { - //receiverResult每次接收完必须释放 - using (var receiverResult = await receiver.ReadAsync(CancellationToken.None)) - { - //收到的数据,此处的数据会根据适配器投递不同的数据。 - var byteBlock = receiverResult.ByteBlock; - var requestInfo = receiverResult.RequestInfo; - - if (receiverResult.IsCompleted) - { - //断开连接了 - Console.WriteLine($"断开信息:{receiverResult.Message}"); - return; - } - - //在CacheMode下,byteBlock将不可能为null - - var index = 0; - while (true) - { - var r = byteBlock.Span.Slice(index).IndexOf(rn); - if (r < 0) - { - break; - } - - var str = byteBlock.Span.Slice(index, r).ToString(Encoding.UTF8); - Console.WriteLine(str); - - index += rn.Length; - index += r; - } - - byteBlock.Seek(index); - } - } - } - } - - await e.InvokeNext(); - } -} -``` :::tip 提示 @@ -594,39 +330,25 @@ class TcpServiceReceiveAsyncPlugin : PluginBase, ITcpConnectedPlugin ## 十、发送数据 -按照架构图,每个客户端成功连接后,**服务器**都会创建一个派生自**TcpSessionClient**的实例,并将其存以生成的Id为键,存在一个字典中。 +按照架构图,每个客户端成功连接后,**服务器**都会创建一个派生自`TcpSessionClient`的实例,并将其存以生成的`Id`为键,存在一个字典中。 -所以,service提供了一下原生方法,可以通过id直接将数据发送至指定客户端。 +所以,可以直接使用`TcpSessionClient`直接发送。 -```csharp showLineNumbers -//原生 -public Task SendAsync(string id, ReadOnlyMemory memory); -public Task SendAsync(string id, IRequestInfo requestInfo); -``` +例如,在`Received`委托中,直接回应数据: + + + +或者,如果你知道目标客户端的`Id`,那么可以使用`TcpService`的`SendAsync`方法直接发送数据。 例如: -```csharp showLineNumbers -await service.SendAsync("id",Encoding.UTF8.GetBytes("hello")); -``` + -亦或者,可以先用id查到对应的TcpSessionClient,然后用其提供的方法直接发送。 +亦或者,可以先用id查到对应的`TcpSessionClient`,然后用其提供的方法直接发送。 例如: -```csharp showLineNumbers -//尝试性获取 -if (service.TryGetClient("id", out var sessionClient)) -{ - await sessionClient.SendAsync("hello"); -} -``` - -```csharp showLineNumbers -//直接获取,如果id不存在,则会抛出异常 -var sessionClient = service.GetClient("id"); -await sessionClient.SendAsync("hello"); -``` + :::caution 注意 @@ -640,11 +362,117 @@ await sessionClient.SendAsync("hello"); ::: + +## 十一、清理和释放 + +### 11.1 清理(断开)连接 + +可以使用`Clear`方法,清理所有连接。 + + + +或者,如果你知道目标客户端的`Id`,那么可以使用`Close`方法,清理指定连接。 + + + +### 11.2 移除监听 + +可以使用`RemoveListen`方法,停止指定Tcp监听器。 + + + :::tip 提示 -框架不仅内置了字节的发送,也扩展了**字符串**等常见数据的发送。而且还包括了`TrySend`等不会抛出异常的发送方法。 +停止监听时,已完成连接的客户端不受影响。 ::: -[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Tcp) +### 11.3 停止服务器 + +可以使用`Stop`方法,停止服务器。 + + + +:::info + +停止服务器时,会断开连接的所有客户端。随后可以重新启动服务器。 + +::: + +### 11.4 释放资源 + +可以使用`Dispose`方法,释放服务器资源。 + + + +## 十二、重置Id + +每个客户端在连接时,服务器都会为连接的客户端**新分配**一个唯一的`Id`。也就是说,在服务器中`Id`与`SessionClient`实例就是一一对应的。 + + + +### 12.1 配置初始Id策略 + +默认情况下服务器都会根据**历史连接数量**,为连接的客户端新分配`Id`。也就是说,第1个连接的,其Id就是1(表现形式为`01-00-00-00`),以此类推。 + +当然我们可以自由的定义`Id`策略,只需要在`Config`配置中。 + + + +### 12.2 创建能代表连接的Id + +上述这种`Id`规范,是与连接信息没有任何关联的,这也就意味着,这种方式是无法关联`SessionClient`的。 + +但往往,有时候,我们希望,`SessionClient`的`Id`,能一定程度的代表一些信息。例如:以客户端的IP和端口,作为唯一id。 + +那这时候,**服务器**可以订阅**Connecting**,然后,为新连接的`SessionClient`,设置与之有关联信息的id。 + + + +### 12.3 即时修改Id + +上述修改Id的方式,应该还不足以应对所有情况。有时候我们希望,在该连接完成,且经过某种验证之后再设置新的Id,那么我们可以通过`ResetIdAsync`的方法,来实现需求。 + +#### 12.3.1 通过Service直接修改 + + + +#### 12.3.2 通过SessionClient修改 + +首先,需要获取到`SessionClient`。 + +一般使用`Service`,或者在插件中,都可以获取到`SessionClient`。 + +然后需要判断该`SessionClient`是否实现了`IIdClient`接口。一般服务器端的终端,都会实现`IIdClient`接口。 + + + +:::note 备注 + +上述的Id标识,仅仅是服务器`TcpService`和辅助客户端`SessionClient`之间的关联。与客户端`TcpClient`是没有任何关系的。 + +::: + +## 十三、AOT支持 + +Tcp使用AOT模式,即在编译时将Tcp的代码全部编译到程序中,而不是在运行时动态加载。 + +实际上,Tcp是完全支持AOT模式, + +### 13.1 项目配置 + +.Net8以上对AOT支持比较好,所以最好使用.Net8以上。然后对项目配置一般如下: + +```xml {3,4} + + ... + true + true + +``` + +## 十四、示例Demo + + + diff --git a/handbook/docs/tcpwaitingclient.mdx b/handbook/docs/tcpwaitingclient.mdx index 54efaf6be..bc5581a86 100644 --- a/handbook/docs/tcpwaitingclient.mdx +++ b/handbook/docs/tcpwaitingclient.mdx @@ -5,7 +5,7 @@ title: 同步请求 import { TouchSocketDefinition } from "@site/src/components/Definition.js"; -### 定义 + diff --git a/handbook/docs/touchsocketbitconverter.mdx b/handbook/docs/touchsocketbitconverter.mdx index 19296a3e9..d2c9033e7 100644 --- a/handbook/docs/touchsocketbitconverter.mdx +++ b/handbook/docs/touchsocketbitconverter.mdx @@ -4,8 +4,8 @@ title: 大小端转换器 --- import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; -### 定义 @@ -15,21 +15,18 @@ import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; ### 1. 大小端模式 - **大端模式 (Big Endian)** 高位字节存储在内存低地址,低位字节存储在高地址。 - *示例:整数 `10` 的4字节大端表示为 `{0, 0, 0, 10}*` + *示例:整数 `10` 的4字节大端表示为 `{0, 0, 0, 10}`* - **小端模式 (Little Endian)** 低位字节存储在内存低地址,高位字节存储在高地址。 - *示例:整数 `10` 的4字节小端表示为 `{10, 0, 0, 0}` + *示例:整数 `10` 的4字节小端表示为 `{10, 0, 0, 0}`* ### 2. 端序类型枚举 -```csharp -public enum EndianType { - Big, // 标准大端 - Little, // 标准小端 - BigSwap, // 交换大端(特殊场景) - LittleSwap // 交换小端(特殊场景) -} -``` +`TouchSocketBitConverter` 支持四种端序类型: +- `EndianType.Big` - 标准大端 +- `EndianType.Little` - 标准小端 +- `EndianType.BigSwap` - 交换大端(特殊场景) +- `EndianType.LittleSwap` - 交换小端(特殊场景) --- @@ -37,22 +34,14 @@ public enum EndianType { ## 二、核心 API 使用 ### 1. 直接指定端序转换器 -```csharp -// 预定义静态实例 -TouchSocketBitConverter.BigEndian // 绝对大端 -TouchSocketBitConverter.LittleEndian // 绝对小端 -TouchSocketBitConverter.BigSwapEndian // 交换大端 -TouchSocketBitConverter.LittleSwapEndian // 交换小端 -``` +`TouchSocketBitConverter` 提供了四个预定义的静态实例,可以直接使用: +- `TouchSocketBitConverter.BigEndian` - 大端转换器 +- `TouchSocketBitConverter.LittleEndian` - 小端转换器 +- `TouchSocketBitConverter.BigSwapEndian` - 交换大端转换器 +- `TouchSocketBitConverter.LittleSwapEndian` - 交换小端转换器 ### 2. 默认端序配置 -```csharp -// 默认小端模式(可全局修改) -TouchSocketBitConverter.DefaultEndianType = EndianType.Big; - -// 获取当前默认转换器 -TouchSocketBitConverter.Default.GetBytes(123); -``` + --- @@ -60,90 +49,55 @@ TouchSocketBitConverter.Default.GetBytes(123); ### 1. 数值类型转换 #### 转换整型 (`int`) -```csharp -// int -> byte[] -byte[] data = TouchSocketBitConverter.BigEndian.GetBytes(0x12345678); - -// byte[] -> int -int value = TouchSocketBitConverter.BigEndian.ToInt32(data, 0); -``` + #### 转换浮点型 (`float`) -```csharp -// float -> byte[] -float num = 3.14f; -byte[] bytes = TouchSocketBitConverter.LittleEndian.GetBytes(num); - -// byte[] -> float -float result = TouchSocketBitConverter.LittleEndian.ToSingle(bytes, 0); -``` + ### 2. 长数据类型处理 #### 转换长整型 (`long`) -```csharp -// long -> byte[] -long bigValue = 0x123456789ABCDEF0; -byte[] data = TouchSocketBitConverter.Default.GetBytes(bigValue); - -// byte[] -> long -long restored = TouchSocketBitConverter.Default.ToInt64(data, 0); -``` + #### 转换高精度小数 (`decimal`) -```csharp -// decimal -> byte[] -decimal money = 123456.78m; -byte[] decimalBytes = TouchSocketBitConverter.BigEndian.GetBytes(money); - -// byte[] -> decimal -decimal result = TouchSocketBitConverter.BigEndian.ToDecimal(decimalBytes, 0); -``` + --- ## 四、高级功能 -### 1. 不安全内存操作 -```csharp -unsafe { - byte[] buffer = new byte[8]; - long value = 0x1122334455667788; - - // 直接内存写入 - fixed (byte* ptr = buffer) { - TouchSocketBitConverter.LittleEndian.UnsafeWriteBytes(ref *ptr, value); - } - - // 直接内存读取 - long readValue = TouchSocketBitConverter.LittleEndian.UnsafeTo(ref buffer[0]); -} -``` +### 1. WriteBytes 写入既有缓冲区 + ### 2. 布尔数组转换 -```csharp -// bool[] -> byte[] -bool[] flags = { true, false, true, true, false, true, false, true }; -byte[] boolBytes = TouchSocketBitConverter.Default.GetBytes(flags); +`TouchSocketBitConverter` 支持将布尔数组按位打包到字节数组中,这在处理位标志时非常有用。 -// byte[] -> bool[] -bool[] decodedFlags = TouchSocketBitConverter.Default.ToBooleans(boolBytes, length: 1); -``` + + +### 3. 批量类型转换 + --- ## 五、特殊场景处理 ### 1. 字节序验证 -```csharp -// 检查当前配置是否与系统字节序一致 -bool isCompatible = TouchSocketBitConverter.Default.IsSameOfSet(); -``` +在跨平台开发时,可以通过 `IsSameOfSet()` 方法检查当前转换器的字节序是否与系统字节序一致。 + + ### 2. 交换端序模式 -```csharp -// 使用 BigSwap 模式处理网络数据包 -byte[] swappedData = TouchSocketBitConverter.BigSwapEndian.GetBytes(0xAABBCCDD); -``` +`TouchSocketBitConverter` 提供了 `BigSwap` 和 `LittleSwap` 两种交换端序模式,用于处理特殊的数据格式。 + + + +### 3. ToValues 字节批量转换 + + +### 4. 布尔位打包 + + +### 5. 跨端批量转换 + --- @@ -153,33 +107,25 @@ byte[] swappedData = TouchSocketBitConverter.BigSwapEndian.GetBytes(0xAABBCCDD); 所有转换方法会检查数组长度,不足时抛出 `ArgumentOutOfRangeException` 2. **跨平台兼容性** - 建议通过 `IsSameOfSet()` 验证端序一致性 + 建议通过 `IsSameOfSet()` 验证端序一致性,确保数据在不同平台间正确传输 3. **性能优化** - 对性能敏感场景推荐使用 `UnsafeWriteBytes` 和 `UnsafeTo` 方法 + - 使用 `WriteBytes` 方法可以直接写入现有缓冲区,避免内存分配 + - 使用 `Span` 和 `ReadOnlySpan` 减少内存拷贝 + - 批量转换时使用 `ConvertValues` 方法提高效率 + +4. **布尔类型特殊处理** + 布尔类型在转换时按位处理,每个布尔值占用1位而不是1字节 --- ## 七、完整示例 -### 网络数据包处理 -```csharp -// 配置全局大端模式 -TouchSocketBitConverter.DefaultEndianType = EndianType.Big; +### 1. 网络数据包处理 + -// 构造数据包 -var packet = new byte[16]; -int header = 0x4D534754; // "MSGT" -float payload = 1024.5f; - -// 写入数据 -TouchSocketBitConverter.Default.WriteBytes(packet.AsSpan(0, 4), header); -TouchSocketBitConverter.Default.WriteBytes(packet.AsSpan(4, 4), payload); - -// 解析数据 -int parsedHeader = TouchSocketBitConverter.Default.ToInt32(packet, 0); -float parsedPayload = TouchSocketBitConverter.Default.ToSingle(packet, 4); -``` +### 2. 自定义结构读写 + --- @@ -187,8 +133,10 @@ float parsedPayload = TouchSocketBitConverter.Default.ToSingle(packet, 4); | 方法 | 说明 | |------|------| -| `GetBytes(T value)` | 将值转换为字节数组 | -| `To(ReadOnlySpan)` | 从字节跨度转换回值 | -| `WriteBytes(Span, T)` | 将值写入字节跨度 | -| `UnsafeWriteBytes(ref byte, T)` | 不安全内存写入 | -| `UnsafeTo(ref byte)` | 不安全内存读取 | +| `GetBytes(T value)` | 将值转换为只读内存 `ReadOnlyMemory` | +| `To(ReadOnlySpan)` | 从字节跨度转换为指定类型的值 | +| `WriteBytes(Span, T)` | 将值写入字节跨度,返回写入的字节数 | +| `ToValues(ReadOnlySpan)` | 将字节批量转换为指定类型的数组 | +| `ConvertValues()` | 在不同类型之间批量转换 | +| `GetConvertedLength()` | 计算转换后的长度 | +| `IsSameOfSet()` | 检查字节序是否与系统一致 | diff --git a/handbook/docs/udpdatahandlingadapter.mdx b/handbook/docs/udpdatahandlingadapter.mdx index d9ad1cf93..ba2ee1280 100644 --- a/handbook/docs/udpdatahandlingadapter.mdx +++ b/handbook/docs/udpdatahandlingadapter.mdx @@ -7,7 +7,7 @@ sidebar_label: a.原始自定义适配器 import { TouchSocketDefinition } from "@site/src/components/Definition.js"; -### 定义 + diff --git a/handbook/docs/udpsession.mdx b/handbook/docs/udpsession.mdx index d84246051..d3abe3c40 100644 --- a/handbook/docs/udpsession.mdx +++ b/handbook/docs/udpsession.mdx @@ -6,8 +6,9 @@ title: 创建UdpSession import Tag from "@site/src/components/Tag.js"; import CardLink from "@site/src/components/CardLink.js"; import { TouchSocketDefinition } from "@site/src/components/Definition.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; + -### 定义 @@ -40,28 +41,11 @@ UDP组件是基于UDP协议的最基础组件,其功能简单,易用。它 ### 5.1 作为服务器使用 -```csharp showLineNumbers -var udpService = new UdpSession(); -udpService.Received = (c,e) => -{ - Console.WriteLine(e.ByteBlock.ToString()); - return EasyTask.CompletedTask; -}; -udpService.Setup(new TouchSocketConfig() - .SetBindIPHost(new IPHost(7789))); -udpService.Start(); -Console.WriteLine("等待接收"); -``` + ### 5.2 作为客户端使用 -```csharp showLineNumbers -var udpClient = new UdpSession(); -udpClient.Setup(new TouchSocketConfig() - //.UseUdpReceive()//作为客户端时,如果需要接收数据,那么需要绑定端口。要么使用SetBindIPHost指定端口,要么调用UseUdpReceive绑定随机端口。 - .SetBindIPHost(new IPHost(7788))); -udpClient.Start(); -``` + :::caution 注意 @@ -88,16 +72,7 @@ public virtual void SendAsync(EndPoint remoteEP, byte[] buffer, int offset, int 例如: -```csharp {5} showLineNumbers -class MyPluginClass : PluginBase, IUdpReceivedPlugin -{ - public async Task OnUdpReceived(IUdpSession client, UdpReceivedDataEventArgs e) - { - await client.SendAsync(e.EndPoint, "RRQM"); - await e.InvokeNext(); - } -} -``` + ### 6.3 发送到默认地址 @@ -115,15 +90,7 @@ public virtual void SendAsync(byte[] buffer, int offset, int length) 委托接收,则是直接订阅Received即可。 -```csharp showLineNumbers -var udpService = new UdpSession(); -udpService.Received = (c, e) => -{ - Console.WriteLine(e.ByteBlock.ToString()); - return EasyTask.CompletedTask; -}; -udpService.Start(7789); -``` + ### 7.2 插件接收 推荐 @@ -131,56 +98,103 @@ udpService.Start(7789); 声明插件 -```csharp showLineNumbers -class MyPluginClass1 : PluginBase, IUdpReceivedPlugin -{ - public async Task OnUdpReceived(IUdpSession client, UdpReceivedDataEventArgs e) - { - var msg = e.ByteBlock.ToString(); - if (msg == "hello") - { - Console.WriteLine("已处理Hello"); - } - else - { - //如果判断逻辑发现此处无法处理,即可转到下一个插件 - await e.InvokeNext(); - } - } -} + -class MyPluginClass2 : PluginBase, IUdpReceivedPlugin -{ - public async Task OnUdpReceived(IUdpSession client, UdpReceivedDataEventArgs e) - { - var msg = e.ByteBlock.ToString(); - if (msg == "hi") - { - Console.WriteLine("已处理Hi"); - } - else - { - //如果判断逻辑发现此处无法处理,即可转到下一个插件 - await e.InvokeNext(); - } - } -} -``` + 使用插件 -```csharp showLineNumbers {6-7} -var udpService = new UdpSession(); -udpService.Setup(new TouchSocketConfig() - .SetBindIPHost(new IPHost(7789)) - .ConfigurePlugins(a => - { - a.Add(); - a.Add(); - })); -udpService.Start(); -``` + -## 八、本文示例Demo +## 八、组播和广播 - +### 8.1 说明 + +- **广播BroadCast**:主机之间"一对所有"的通讯模式,广播者可以向网络中所有主机发送信息。广播禁止在Internet宽带网上传输(广播风暴)。 +- **多播MultiCast**:主机之间"一对一组"的通讯模式,也就是加入了同一个组的主机可以接受到此组内的所有数据。 + +### 8.2 组播使用 + +组播使用非常简单: + +1. 配置中启用广播 **SetEnableBroadcast** (重要) +2. 在UdpSession **启动** 后,调用 **JoinMulticastGroup** 即可加入组播。调用 **DropMulticastGroup** 退出组播。 + +#### 8.2.1 创建组播服务器 + + + +#### 8.2.2 发送组播数据 + + + +### 8.3 广播使用 + +广播使用非常简单: + +1. 配置中启用广播 **SetEnableBroadcast** (重要) + +#### 8.3.1 创建广播服务器 + +广播接收时,是不需要加入组播组的。 + + + +#### 8.3.2 发送广播数据 + + + +:::caution 注意 + +在发送广播数据时,发送端配置 SetEnableBroadcast 是必须的。 + +::: + +## 九、传输大于64K的数据 + +### 9.1 说明 + +UDP由于自身限制,每次发送的数据包最大约64K,但是在局域网内,有时候希望传输更大的数据。所以必须有策略发送。 + +TouchSocket可通过简单设置,实现该功能。 + +### 9.2 使用 + +只需要在配置中,设置其适配器为 **UdpPackageAdapter** 类型即可(默认为 **NormalUdpDataHandlingAdapter**)。同时可以根据传输数据的大小,修改相关属性,如:**MTU**,**Timeout** 等。 + + + +:::caution 注意 + +**此模式下,发送端与接收端均必须为TouchSocket(或实现相同算法),且为相同设置。** + +::: + +### 9.3 原理 + +在发送时,会将要发送的数据分割成MTU长度的数据。然后为其编号,然后发送,最后由接收方重组。 + +#### 9.3.1 数据格式 + +**ID:由雪花算法生成,在并发请求时1毫秒中有400w分之一的概率发生ID重复。但基本可以忽略不计。** + +| Bit | 说明 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| 协议名 | | | | | | | | | | +| byte1 | PackageID为long类型,占用8字节,标识数据包唯一性。 | | | | | | | | | +| byte2 | | | | | | | | | | +| byte3 | | | | | | | | | | +| byte4 | | | | | | | | | | +| byte5 | | | | | | | | | | +| byte6 | | | | | | | | | | +| byte7 | | | | | | | | | | +| byte8 | SN为Ushort占2字节,标识帧序 | | | | | | | | | +| byte9 | | | | | | | | | | +| byte10 | flag,占1字节,最高位标识是否为结束,其他位保留。 | 1 | | | | | | | | +| byte? | 有效载荷数据 | | | | | | | | | +| byte^2 | 当不为终结帧时,此处仍然为载荷数据。当是终结帧时,倒数两个字节为Crc16校验。 | | | | | | | | | +| byte^1 | | | | | | | | | | + +## 十、本文示例Demo + + diff --git a/handbook/docs/upgrade.mdx b/handbook/docs/upgrade.mdx deleted file mode 100644 index ba47bdcdf..000000000 --- a/handbook/docs/upgrade.mdx +++ /dev/null @@ -1,1974 +0,0 @@ ---- -id: upgrade -title: 历史更新 ---- - -import useBaseUrl from "@docusaurus/useBaseUrl"; -import Tag from "@site/src/components/Tag.js"; -import Highlight from '@site/src/components/Highlight.js'; - -:::tip `TouchSocket` 框架升级/发版规则 - -**升级前重点关注可能造成破坏性的标签类型**:修复调整移除升级 - -版本号规则:`主版本号.次版本号.修订版本号` - -- 只要确认为框架 `bug`,则当天修复,下个周日发版,修订版本号 `加 1`。 -- 其余情况,每年发布一个 `主版本`,发布时间跟随Dotnet的发布时间。 - -::: - -## v3.1.15 - -**更新日期:** 2025.8.3 - -**更新描述:** - -- HTTP协议头部解析性能优化。 -- RPC代码生成器命名空间改进。 - -**更新详情:** - -#### TouchSocket.Http - --  优化 `HttpBase.ParsingHeader` 方法中的字节块解析逻辑,优化内存使用和变量命名,提升HTTP头部解析性能。 --  修复 修复头部长度计算错误的问题。 - -#### TouchSocket.SourceGenerator - --  调整 `RpcClientCodeBuilder.GetNamespace` 方法,改进默认命名空间生成策略,支持根据RpcAttributeName自定义命名空间格式。 --  优化 代码生成器的命名空间逻辑,提升生成代码的组织结构。 - -*** - -
-v3.1 -
- - -## v3.1.14 - -**更新日期:** 2025.7.20 - -**更新描述:** - -- HTTP模块性能优化和内存管理改进。 - -**更新详情:** - -#### TouchSocket.Http - --  优化 `HttpResponse.GetContentAsync` 方法中的内存流初始化,使用精确的内容长度预分配内存,提升性能。 --  优化 内存分配策略,减少不必要的内存占用。 --  优化 变量命名和局部变量使用,提升代码可读性。 - -*** - -## v3.1.13 - -**更新日期:** 2025.7.14 - -**更新描述:** - -- 修复和改进升级,提升稳定性和性能。 -- MQTT协议处理优化和错误修复。 - -**更新详情:** - -#### TouchSocket.Mqtt - --  修复 MQTT 协议名称统一为 "MQTT"(大写),修复协议标准一致性问题。 --  修复 `MqttAdapter` 中消息解析位置计算错误,修复数据包边界计算问题。 --  修复 `VariableByteIntegerRecorder` 中长度设置错误,修复变长整数记录器的边界处理。 - -*** - -## v3.1.12 - -**更新日期:** 2025.7.8 - -**更新描述:** - -- 兼容性修复升级。 -- 代码质量和文档规范化改进,以及功能增强。 - -**更新详情:** - -#### TouchSocket.Core - --  优化 代码注释规范化,统一将 "null" 改为 `` 的XML文档格式。 --  新增 `EasyMemoryMarshal` 类增加了更详细的文档和泛型约束。 --  优化 多个核心类的内存管理和线程安全性。 --  优化 `TouchSocketBitConverter` 中的异常文档注释。 - -#### TouchSocket.Mqtt - --  优化 MQTT协议名称从 "MQTT" 统一为 "Mqtt"。 --  重构 `MqttSessionActor` 消息分发机制,提升性能和稳定性。 --  新增 `ThreadSafeTopicSubscriptions` 类,提供线程安全的主题订阅管理。 --  优化 `MqttBroker` 的消息转发逻辑,改进并发处理能力。 --  修复 `MqttReceivedEventArgs` 中属性命名一致性问题。 - -#### TouchSocket.SerialPorts - --  新增 `ISerialPortClient` 和 `SerialPortClient` 实现 `IConnectableClient` 接口。 --  新增 `ConnectAsync` 方法支持异步连接操作。 --  调整 `ISerialPortSession` 接口继承关系,移除冗余接口。 - -#### TouchSocket.Modbus - --  新增 `IModbusRtuMaster` 实现 `IConnectableClient` 接口。 --  新增 `ModbusRtuMaster` 增加 `ConnectAsync` 方法支持。 - -#### TouchSocket.WebApi - --  新增 `RegexRouterAttribute` 类增加详细的XML文档注释。 --  优化 `WebApiAttribute` 中的路由处理逻辑和变量命名。 - -#### TouchSocket.Sockets - --  优化 网络监听器和客户端基类的参数验证和错误处理。 --  修复 `ReconnectionPlugin` 的 Dispose 方法错误,修复无限循环任务的正确释放机制。([#68](https://github.com/RRQM/TouchSocket/pull/68) by [@godchadigo](https://github.com/godchadigo)) - -*** - - -## v3.1.9(10)(11) - -**更新日期:** 2025.6.24 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - -#### TouchSocket.Core - --  优化 `Result`和`Result`在调试时的显示。 --  优化 `IByteBlock`的部分方法传参。 - -#### TouchSocket.Mqtt - --  修复 `IMqttTcpClient`没有继承`ISetupConfigObject`的bug。 - -#### TouchSocket.SerialPort - --  修复 `StreamAsync`异步流模式工作异常的bug。 - -#### TouchSocketPro.Modbus - --  新增 `ModbusCoilsDrive`支持线圈读写。 --  新增 `ModbusDiscreteInputsDrive`支持离散输入读写。 --  新增 `ModbusHoldingRegistersDrive`支持保持寄存器读写。 --  新增 `ModbusInputRegistersDrive`支持输入寄存器读写。 - -#### TouchSocketPro.PlcBridges - --  新增 `PlcObject`组件。 - -*** - -## v3.1.8 - -**更新日期:** 2025.6.15 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - -#### TouchSocket.Core - --  优化 `DependencyProperty`在调试时的显示。 --  调整 `SafeDispose`方法的返回,由`void`改为`Result`。此修改不影响代码编译,但是需要重新编译依赖dll。 - -#### TouchSocket.Http - --  修复 当`Http服务器`在响应后,`HttpResponse`不会清空`Content`的`bug`。 [#ICEVTN](https://gitee.com/RRQM_Home/TouchSocket/issues/ICEVTN) - -#### TouchSocket.Modbus - --  新增 `IModbusResponse`类新增`IsSuccess`属性,可用于快捷获取响应结果。 --  调整 `IModbusResponse`的`Data`属性由`Byte[]`改为`ReadOnlyMemory`。如果仍需使用数组,请使用`Data.Span.ToArray()`方法获取数据。 - -#### TouchSocket.SerialPort - --  新增 `StreamAsync`性能异步流模式,可在`SerialPortOption`中直接启用。 [#PR72](https://gitee.com/RRQM_Home/TouchSocket/pulls/72) - -#### TouchSocketPro.PlcBridges - --  新增 新组件发布。 - -*** - -## v3.1.7 - -**更新日期:** 2025.6.8 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - -#### TouchSocket.Core - --  修复 FileLogger在Windows服务发布时,保存路径错误的bug。 - -#### TouchSocket.Sockets - --  修复 ClientFactory的MinCount失效的bug。 - -#### TouchSocket.Dmtp - --  新增 FileResourceInfo类新增Create方法和Save的其他重载。更方便的支持断点续传方式。 --  修复 FileSectionResult类释放逻辑,减少异常的发生。 - -*** - - -## v3.1.6 - -**更新日期:** 2025.6.2 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - -#### TouchSocket.SerialPort - --  修复 串口组件数据接收回调事件异常。[#ICB8IN](https://gitee.com/RRQM_Home/TouchSocket/issues/ICB8IN) - -*** - - -## v3.1.5 - -**更新日期:** 2025.5.24 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - -#### TouchSocket.Core - --  优化 源生成器提示。 - -*** - - -## v3.1.3(4) - -**更新日期:** 2025.5.18 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - -#### TouchSocket.Core - --  优化 `Method`相关操作,具体请看[新文档](./dynamicmethod.mdx)。 - -#### TouchSocket.Dmtp - --  调整 `Dmtp Channel`的`HoldOnAsync`、`CompleteAsync`、`CancelAsync`等方法返回值改为`Result`,**不再**抛出异常。 - -*** - -## v3.1.2 - -**更新日期:** 2025.5.11 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - -#### All - --  优化 全系语法分析器和源生成器,使用增量源生成器,减少编译时间。 - -#### TouchSocket.Core - --  优化 `Logger`在添加到容器时,支持直接设置日志等级。 --  修复 `ByteBlock`、`ValueByteBlock`在请求的`length`为0时,会抛出异常。 [#69](https://gitee.com/RRQM_Home/TouchSocket/pulls/69) --  修复 `WaitHandlePool在waitData`为`null`无法`Destroy`释放问题。[#IC64WX](https://gitee.com/RRQM_Home/TouchSocket/issues/IC64WX) --  移除 弃用`ManualContainer`。 - -#### TouchSocket.Sockets - --  优化 发送字符串的效率,极大减少GC的产生。 --  优化 `UseCheckClear`的使用体验,使用更为贴切的方法名称。 --  修复 `CheckClearPlugin`在应用到`Client`,第2次检验会失效的`bug`。[#IC5J1I](https://gitee.com/RRQM_Home/TouchSocket/issues/IC5J1I) - -#### TouchSocket.Http - --  新增 `HttpClientBase`请求时,新增直接添加`Host`的逻辑。[#IC6OU2](https://gitee.com/RRQM_Home/TouchSocket/issues/IC6OU2) --  优化 `WebSocket`在断开时,会判断是否带有主动断开消息,如果带有,则投递主动断开的消息。 --  优化 `SetStatus`的使用体验,在默认参数(成功)时,则使用`SetStatusWithSuccess`代替。 --  调整 `WebSocket`的`Ping`与`Pong`使用`Result`进行返回。不再抛出异常。 --  修复 `Http`在处理请求时,阻塞接收执行上下文的bug,这会导致如果收到`http`请求,且需要较长时间执行时,如果连接方已断开,则断开消息不会立即执行的bug。也会间接导致`WebApi`的`CancellationToken`失效。 - - -#### TouchSocket.NamedPipe - --  新增 `UseNamedPipeSessionCheckClear`的插件扩展方法。 - -#### TouchSocket.Rpc - --  新增 `ISingletonRpcServer`的接口。 - -#### TouchSocket.SerialPort - --  新增 `UseSerialPortSessionCheckClear`的插件扩展方法。 - -#### TouchSocket.WebApi - --  修复 `WebApi`调用上下文中`CancellationToken`不起作用的bug。 - -*** - -## v3.1.0(1) - -**更新日期:** 2025.4.29 - -**更新描述:** - -此次升级可能会有部分代码不兼容项,请在升级前做好备份,并且详细阅读[3.1升级指南](https://gitee.com/RRQM_Home/TouchSocket/issues/IC1K3I)。 - -此次升级所有组件在运行时**完全兼容3.0**,所以无论客户端还是服务端,都可以进行差异化更新。 - -**更新详情:** - -#### All - --  调整 `DateTime`调整为`DateTimeOffset`。 --  调整 `Task.Run`全部使用`EasyTask.Run`再次封装运行。 --  优化 所有组件在日志记录时,传入可触发源,可更好的进行日志记录。 - -#### TouchSocket.Core - --  调整 `AsyncBoundedQueue`类调整为`AsyncQueue`。 --  移除 `BytePool`类不再使用,目前使用`ArrayPool`代替。 --  调整 `ByteBlock`类不再提供默认参数构造函数,目前强制传入申请长度。 --  调整 `ResultCode`枚举项`Fail`调整为`Failure`。 --  调整 `FastSerializerContext`默认不再支持`DataTable`和`DataSet`。如有需要,请自行添加`DataTableFastBinaryConverter`和`DataSetFastBinaryConverter`。 --  优化 `PeriodPackageAdapter`接收性能。 --  优化 `BlockSegment`性能。 --  优化 `WaitHandlePool`性能。 --  修复 `PeriodPackageAdapter`接收不判断最大包设定的bug。 --  新增 若干扩展方法。 - - -#### TouchSocket.Sockets - --  调整 `StopAsync`、`CloseAsync`等方法,在调用时不再抛出异常,而是返回`Task`。 --  调整 `IClient`的接口,不再继承自`IDependencyObject`。如有需求请使用`IDependencyClient`。 --  调整 `IServerStopedPlugin`插件,改名为`IServerStoppedPlugin`。 --  优化 `Tcp`、`Udp`等组件的释放逻辑 - -#### TouchSocket.Dmtp - --  优化 添加`IActor`的方式。 - -#### TouchSocket.Hosting - --  优化 日志记录信息。 - -#### TouchSocket.Http - --  优化 性能大幅提高。 --  新增 `HttpStatusCode`静态类,支持常用的状态码。 --  调整 WebSocket的`SwitchProtocolToWebSocketAsync`方法返回值由`bool`改为`Result`。 --  调整 `InitHeaders`、`SetStatus`等方法中设置`Header`的方法,由`Add`调整为`TryAdd`。 --  调整 默认情况下,服务在响应Header中,设置`Server`时,不再显示具体版本。[#IC25RJ](https://gitee.com/RRQM_Home/TouchSocket/issues/IC25RJ) --  修复 HttpClient在断开连接时,不触发`OnTcpClosed`的bug。 - -#### TouchSocket.WebApi.Swagger - --  新增 在Swagger显示时,支持参数属性。[PR68](https://gitee.com/RRQM_Home/TouchSocket/pulls/68) - -#### TouchSocket.Rpc - --  调整 `SingletonRpcServer`名称改为`SingletonRpcServer`。 --  优化 `QueueRpcDispatcher`性能。 - - -#### TouchSocket.Mqtt - --  新增 功能首发。 - -*** - -
-
- -
-v3.0 -
- -## v3.0.26 - -**更新日期:** 2025.4.20 - -**更新描述:** - -兼容性修复升级。 - - -**更新详情:** - -#### TouchSocket.Rpc - --  修复 `RpcActionFilterAttribute`在`RpcImplementation`程序集中使用时无效bug。 [#IC0IB0](https://gitee.com/RRQM_Home/TouchSocket/issues/IC0IB0) - -#### TouchSocket.Http - --  修复 `WebSocket`服务端`WebSocket`使用`AsyncClose()`主动关闭连接时引发`NullReferenceException`异常。 [#IC1FZ8](https://gitee.com/RRQM_Home/TouchSocket/issues/IC1FZ8) - -*** - - -## v3.0.25 - -**更新日期:** 2025.4.12 - -**更新描述:** - -兼容性修复升级。 - - -**更新详情:** - -#### TouchSocket.SerialPort - --  修复 接收数据时,如果业务出现延迟,则会导致接收失败的异常bug。 [#IBZHN2](https://gitee.com/RRQM_Home/TouchSocket/issues/IBZHN2) - -*** - -## v3.0.21(3)(4) - -**更新日期:** 2025.4.6 - -**更新描述:** - -兼容性修复升级。http组件性能优化。 - - -**更新详情:** - -#### TouchSocket.Core - --  新增 `ReadOnlySpan`的`Trim`方法。 --  新增 `Method`新增支持类型的直接构造函数方法。 --  新增 `AsyncResetEvent`新增已释放判断。 --  修复 `DynamicMethod`在ref、out等参数时,无法使用源生成调用的bug。 --  调整 `FlowOperator`的在构造函数使用最大值初始化`MaxSpeed`。 - -#### TouchSocket.Sockets - --  修复 `TcpCore`在特点情况下不释放资源的bug。[PR](https://gitee.com/RRQM_Home/TouchSocket/pulls/65) --  调整 `ShutdownAsync`调整返回值,由`Task`改为`Task`。 - -#### TouchSocket.Http - --  新增 `HttpContent`新增`TryComputeLength`的抽象方法。 --  新增 `HttpResponse`的`FromFileAsync`扩展新增传输限速、进度功能。[#IBYGC7](https://gitee.com/RRQM_Home/TouchSocket/issues/IBYGC7) --  优化 整个`Http`组件性能优化。 --  调整 `HttpHeaders`由枚举改为静态类。(此操作不影响现有代码) - - -*** - - -## v3.0.20 - -**更新日期:** 2025.3.30 - -**更新描述:** - -兼容性修复升级。部分方法名大小写调整。 - - -**更新详情:** - -#### TouchSocket.Core - --  调整 `TouchSocketCoreUtility`类中的所有静态字段大小写调整。 --  调整 `StringExtension`类中的多个方法名大小写调整(此处调用时可能是扩展方法调用的,所以需要注意)。 - -#### TouchSocket.Http - --  修复 `HttpClient`在请求`application/x-www-form-urlencoded`时,内部未进行编码的bug。[#IBVPAD](https://gitee.com/RRQM_Home/TouchSocket/issues/IBVPAD) --  新增 `HttpResponse`新增`FromHtml`扩展方法,用于直接返回`Html`页面。 - -#### TouchSocket.Rpc - --  新增 `IRpcCallContextAccessor`服务,可以在异步调用流中,通过服务直接获取到执行`Rpc`的`CallContext`。 - -#### TouchSocket.WebApi.Swagger - --  更新 `Swagger`页面版本为`v5.20.2`,以支持一键复制url等功能。 [#IBX933](https://gitee.com/RRQM_Home/TouchSocket/issues/IBX933) - -#### TouchSocket.Modbus - --  优化 `Modbus rtu`在响应数据时的严谨性,基本排除了站号,功能码不一致时仍然返回的错误情况。 [#github-issue 54](https://github.com/RRQM/TouchSocket/issues/54) - -*** - - -## v3.0.19 - -**更新日期:** 2025.3.23 - -**更新描述:** - -兼容性修复升级。 - - -**更新详情:** - -#### TouchSocket.Http - --  修复 Http静态内容插件在客户端不支持gzip时仍然会使用gzip的bug。 --  修复 Http静态内容插件在以文件响应时,限速为0的bug。[#IBVCPJ](https://gitee.com/RRQM_Home/TouchSocket/issues/IBVCPJ) - -*** - -## v3.0.18 - -**更新日期:** 2025.3.16 - -**更新描述:** - -兼容性修复升级。少量代码调整,详见 调整 - - -**更新详情:** - -#### TouchSocket.Sockets - --  修复 `TryShutdown`的关闭机制没有考虑异步发送队列的情况。[#IBTKMP](https://gitee.com/RRQM_Home/TouchSocket/issues/IBTKMP) --  调整 `TryShutdown`方法改为`ShutdownAsync`。 - -#### TouchSocket.Http - --  修复 `HttpExtensions`中`GetBoundary`实现存在问题。[#IBT3RO](https://gitee.com/RRQM_Home/TouchSocket/issues/IBT3RO) --  修复 静态页面插件在重新载入时prefix参数丢失问题。[#IBTP38](https://gitee.com/RRQM_Home/TouchSocket/issues/IBTP38) - -*** - - -## v3.0.17 - -**更新日期:** 2025.3.12 - -**更新描述:** - -兼容性修复升级。 - - -**更新详情:** - -#### TouchSocket.Sockets - --  修复 UdpSessionBase的OnUdpReceiving方法添加EndPoint参数。[#IBSQVN](https://gitee.com/RRQM_Home/TouchSocket/issues/IBSQVN) - -#### TouchSocket.SerialPorts - --  修复 SerialPortClient在接收数据时LastReceivedTime一直不会更新。[#IBSXDK](https://gitee.com/RRQM_Home/TouchSocket/issues/IBSXDK) - -*** - - -## v3.0.16 - -**更新日期:** 2025.3.9 - -**更新描述:** - -兼容性修复升级。 - - -**更新详情:** - -#### TouchSocket.Core - --  修复 自定义适配器在接收数据时,不验证`MaxPackageSize`的`bug`。 --  修复 `MakeIdentifier`在获取方法名称时,不显示中文等其他字符的`bug`[#IBQQHY](https://gitee.com/RRQM_Home/TouchSocket/issues/IBQQHY)。 - -#### TouchSocket.Sockets - --  修复 当配置`NoDelay`时,`TcpCore`发送时仍然会把数据放入发送队列,可能会产生细微延迟 [#IBR1I2](https://gitee.com/RRQM_Home/TouchSocket/issues/IBR1I2)。 --  调整 `IWaitingClient`实现了无效的`Dispose`方法,目前已取消。 - -*** - - -## v3.0.15 - -**更新日期:** 2025.3.2 - -**更新描述:** - -兼容性修复升级。 - - -**更新详情:** - -#### All - --  优化 代码规范和相关注释。 - -#### TouchSocket.Core - --  新增 `SystemExtension`新增`ReadAllToByteArray`方法。 - -*** - - -## v3.0.14 - -**更新日期:** 2025.2.15 - -**更新描述:** - -修复升级。区间字符适配器会有不兼容部分,请参阅[区间字符](./custombetweenanddatahandlingadapter.mdx)。 - - -**更新详情:** - -#### All - --  新增 所有的`Plugin`接口。均新增快捷扩展方法,简化使用。 --  调整 使用自定义的`lock`锁对象。以简化使用场景。 - -#### TouchSocket.Core - --  新增 `ILog`接口新增`DateTimeFormat`属性。 [#IBLQBX](https://gitee.com/RRQM_Home/TouchSocket/issues/IBLQBX) --  修复 自定义区间适配器在未找到开始字符的情况下,也会缓存数据的bug。 [#IBKPXU](https://gitee.com/RRQM_Home/TouchSocket/issues/IBKPXU) --  调整 自定义区间适配器的运行逻辑,简化使用方式。 [#IBKPXU](https://gitee.com/RRQM_Home/TouchSocket/issues/IBKPXU) - -#### TouchSocket.Rpc - --  修复 `IRpcActionFilter.ExecutedAsync`在执行异常时,`Exception`参数一直为空的bug。[#IBK579](https://gitee.com/RRQM_Home/TouchSocket/issues/IBK579) - -#### TouchSocket.Dmtp - --  新增 `TokenVerifyException`异常信息中新增Metadata属性。 --  修复 `TcpDmtpService`中使用`e.IsPermitOperation = false;`拒绝客户端无效的bug。[#IBKO6A](https://gitee.com/RRQM_Home/TouchSocket/issues/IBKO6A) - -*** - -## v3.0.13 - -**更新日期:** 2025.1.27 - -**更新描述:** - -兼容性修复升级。 - - -**更新详情:** - - -#### TouchSocket.Core - --  修复 在`net framework`下,如果`Span`为空时,`ToString`会异常的`bug`。 [#IBIYRQ](https://gitee.com/RRQM_Home/TouchSocket/issues/IBIYRQ) - -*** - -## v3.0.12 - -**更新日期:** 2025.1.19 - -**更新描述:** - -兼容性修复升级。 - - -**更新详情:** - - -#### TouchSocket.Core - --  优化 `FileLogger`的路径合并方式。 - -*** - - -## v3.0.11 - -**更新日期:** 2025.1.12 - -**更新描述:** - -兼容性修复升级。 - - -**更新详情:** - -#### All - --  新增 全系新增`EasyTask.ContinueOnCapturedContext`的设定,以解决`Unity3d`在`webgl`平台下,会卡住的问题。 - -#### TouchSocket.Core - --  新增 `IWaitHandlePool`的接口抽象。 - -#### TouchSocket.Http - --  新增 `HttpResponse`新增`SetRedirect`重定向功能。[#IBG1QT](https://gitee.com/RRQM_Home/TouchSocket/issues/IBG1QT) --  优化 `HttpRequest`在请求时的编码效率。 --  修复 `HttpRequest`不会对中文等非`Ascii`编码的字符进行`url encode`的`bug`。[#IBGATN](https://gitee.com/RRQM_Home/TouchSocket/issues/IBGATN) --  修复 在客户端,执行`IWebApiRequestPlugin`插件时,`HttpRequest`无法通过`GetContent`或者`GetBody`获取数据。[#IBGARB](https://gitee.com/RRQM_Home/TouchSocket/issues/IBGARB) - -*** - -## v3.0.10 - -**更新日期:** 2025.1.5 - -**更新描述:** - -兼容性修复升级。 - - -**更新详情:** - -#### TouchSocket.Core - --  新增 `AsyncBoundedQueue`新增`Capacity`、`Count`等属性。 --  新增 `CustomJsonDataHandlingAdapter`自定义适配器,方便自定义继承实现。 --  新增 `CustomCountSpliterDataHandlingAdapter`固定数量分隔符适配器。[#IBF0Z7](https://gitee.com/RRQM_Home/TouchSocket/issues/IBF0Z7) --  新增 `IByteBlock`新增`ReadT`和`WriteT`方法。 - -#### TouchSocket.Sockets - --  修复 `CheckClearPlugin`在客户端断开后,仍然会触发断开的bug。[#IBECPA](https://gitee.com/RRQM_Home/TouchSocket/issues/IBECPA) - -*** - -## v3.0.9 - -**更新日期:** 2024.12.22 - -**更新描述:** - -兼容性修复升级。 - - -**更新详情:** - -#### TouchSocket.Core - --  新增 `SystemTextJsonStringToClassSerializerFormatter`转换器。 --  新增 `IPackage`源生成时,默认支持`Guid`。[#IBC1FH](https://gitee.com/RRQM_Home/TouchSocket/issues/IBC1FH) - -#### TouchSocket.Sockets - --  修复 `TcpService`在`ResetId`后,偶发性客户端集合丢失客户端的问题。[#IBCUHW](https://gitee.com/RRQM_Home/TouchSocket/issues/IBCUHW) --  修复 `WaitingClient`使用`ResponsedData`时第二次接受报错。[#IBC8D2](https://gitee.com/RRQM_Home/TouchSocket/issues/IBC8D2) - -#### TouchSocket.SerialPort - --  修复 `SerialPortClient`串口组件`ProtectedMainSerialPort`属性`null`错误。[#IBBQ4A](https://gitee.com/RRQM_Home/TouchSocket/issues/IBBQ4A) - - -#### TouchSocket.AspNetCore - --  修复 `IWebSocketDmtpService`接口中缺少`Clients`属性。[#IBBQS5](https://gitee.com/RRQM_Home/TouchSocket/issues/IBBQS5) - -#### TouchSocket.Modbus - --  优化 `ModbusRtu`在`crc`校验失败时,会抛出`ResponseMemoryVerificationError = 99`的错误码。[#IBC1J2](https://gitee.com/RRQM_Home/TouchSocket/issues/IBC1J2) - -#### TouchSocket.WebApi - --  新增` WebApi`支持`Put`、`Delete`、`Options`请求方式。 --  修复 `WebApi`当参数解析异常时,`RpcActionFilterAttribute`无法捕获。[#IBCI94](https://gitee.com/RRQM_Home/TouchSocket/issues/IBCI94) --  调整 `WebApiParserPlugin`使用`Mapping`代替`GetRouteMap`和`PostRouteMap`。 - -#### TouchSocket.JsonRpc - --  优化 `JsonRpc`支持`Aot`。 - -*** - -## v3.0.8 - -**更新日期:** 2024.12.15 - -**更新描述:** - -兼容性修复升级。 - - -**更新详情:** - -#### TouchSocket.Sockets - --  修复 `IPHost`类,在`Mono`运行时会异常的bug。 [#IBAG51](https://gitee.com/RRQM_Home/TouchSocket/issues/IBAG51) --  调整 `UdpSessionBase`类调整`ReceivingData`为`OnUdpReceiving`。 --  新增 `Udp`组件,新增`IUdpReceivingPlugin`插件。[#IBB1F6](https://gitee.com/RRQM_Home/TouchSocket/issues/IBB1F6) - -#### TouchSocket.AspNetCore - --  修复 当使用基于`WebSocket`协议,搭建`DmtpServer`服务时 使用`app.UseOutputCache()`缓存中间件导致连接失败后,重连无响应。[#IBAPTQ](https://gitee.com/RRQM_Home/TouchSocket/issues/IBAPTQ) - -#### TouchSocket.Dmtp - --  修复 `DmtpActor`类,在连接成功时,`Handshaking`事件参数`e.Message`无法传回到请求端。 - -#### TouchSocket.SerialPorts - --  修复 `SerialPortClient`类,在关闭或者释放时,资源无法释放的bug。 [#IBB8FD](https://gitee.com/RRQM_Home/TouchSocket/issues/IBB8FD) - -*** - -## v3.0.7 - -**更新日期:** 2024.12.8 - -**更新描述:** - -兼容性修复升级。在.Net9.0中,启用Lock锁代替object锁,提高锁效率。 - - -**更新详情:** - -#### TouchSocket.Core - --  修复 `ValueByteBlock`类,在`WritePackage`时数据无效的bug。 --  优化 开放`PackageFastBinaryConverter`类,以支持二进制数据序列化源生成。 - -#### TouchSocket.Dmtp - --  新增 `DmtpRpc`新增`RpcDispatcher`调度器,支持多线程并发,或者单线程调度。 --  优化 `DefaultSerializationSelector`优化支持`System.Text.Json`源生成模式的序列化。 --  调整 触发`OnFileTransferred`事件的时机调到`SendAsync`前面,目的是保证调用方在收到回复时,响应方已经完成事件处理。此操作理论上不会对现有运行逻辑造成影响。 --  修复 `DmtpRpc`在发送`Rpc`请求时,如果请求模式使用`OnlySend`、或者`WaitSend`,则会抛出异常的bug。[#IB9F8P](https://gitee.com/RRQM_Home/TouchSocket/issues/IB9F8P) - - -#### TouchSocket.Modbus - --  新增 在`IModbusResponse`返回响应时,会同时携带返回响应的当前请求`IModbusRequest`。 - -#### TouchSocket.WebApi - --  优化 `WebApiSerializerConverter`类,以支持更好的`System.Text.Json`源生成模式的序列化。 - -#### TouchSocket.Rpc - --  新增 在`ReenterableAttribute`特性,可以强制设置`Rpc`函数是否为重入模式。 --  新增 `ConcurrencyRpcDispatcher`并发调度器,支持多线程并发。 --  新增 `ImmediateRpcDispatcher`当前调度器,直接在当前线程执行。 --  新增 `QueueRpcDispatcher`队列调度器,把所有请求,放在一个队列中,等待处理。 - -*** - - -## v3.0.5(6) - -**更新日期:** 2024.12.1 - -**更新描述:** - -兼容性修复升级。全面支持`PluginManager`容器化模块和`Scoped`区域划分。 - ->此修改不影响现有运行逻辑。但是如果是Asp.Net Core项目,则可能会影响`Scoped`服务运行结果。 - -**更新详情:** - -#### TouchSocket.Core - --  新增 `FlowOperator`类。以实现流量速度限制。 --  新增 `PluginManager`类。新增`FromIoc`的设定,支持插件容器化。 --  优化 `Result`类。使用`record`修饰,简化使用逻辑。 --  调整 `IResolver`的`Resolve`行为。当服务未注册时,会返回`null`。不会再触发异常。 --  调整 `SetupConfigObject`在Build时,会创建一个新的Scoped生命周期的容器。这个在现有架构下,不会影响运行结果。 --  修复 `Metadata`在添加相同键值时,会异常的bug,正确操作应该是覆盖旧值。 --  移除 `IResolver`移除对`IRegistered`接口的实现,所以无法再使用`IResolver`判断某个服务是否已注册。 --  移除 `IResolver`移除对`TryResolve`扩展方法实现,请使用`Resolve`直接代替。 - -#### TouchSocket.Sockets - --  修复 `IPHost`在`.NET Framework`下,设定任意端口无效的bug。 [#IB7PM1](https://gitee.com/RRQM_Home/TouchSocket/issues/IB7PM1),[#IA8NQ2](https://gitee.com/RRQM_Home/TouchSocket/issues/IA8NQ2) - -#### TouchSocket.Http - --  新增 `HttpBase`新增`ReadCopyToAsync(Stream stream, HttpFlowOperator flowOperator)`方法。支持传输进度、速度显示。[#IB7GIC](https://gitee.com/RRQM_Home/TouchSocket/issues/IB7GIC) --  新增 `HttpFlowOperator`类,支持Http上传、下载文件(流)时,可以方便的实现限速、传输进度、速度显示等。 --  新增 `StreamHttpContent`在传输流数据时,支持`HttpFlowOperator`相关操作。 - - -#### TouchSocket.Rpc --  修复 `Rpc`代码生成器在生成通用泛型类型时,会失败的bug。[#IB7IB2](https://gitee.com/RRQM_Home/TouchSocket/issues/IB7IB2) - - -*** - - -## v3.0.4 - -**更新日期:** 2024.11.24 - -**更新描述:** - -修复升级。**WebSocket有少量代码差异**,下面会详细介绍。 - -**更新详情:** - -#### TouchSocket.Core --  新增 `JsonPackageAdapter`适配器,专门解决纯Json数据格式的粘分包。详情参见:[JsonPackageAdapter](./packageadapter.mdx)。 --  新增 `IByteBlock`新增`WriteNormalString`方法,用于写入普通字符串。 --  新增 `Container`容器新增Scoped生命周期,但是本身容器并未实现功能,如果需使用,请使用[TouchSocket.Core.DependencyInjection](https://www.nuget.org/packages/TouchSocket.Core.DependencyInjection)。 --  新增 `Tcp、NamedPipe、SerialPort、DmtpRpc、JsonRpc、WebApi`等所有组件,支持Scoped容器。(需配合[TouchSocket.Core.DependencyInjection](https://www.nuget.org/packages/TouchSocket.Core.DependencyInjection)容器)。 - -#### TouchSocket.Http --  新增 `HttpStaticPagePlugin`新增`SetContentTypeProvider(Action provider)`方法。 --  新增 `WebSocket`新增`CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription)`方法。 --  修复 `WebSocket`在`Close`时,不符合规范的bug。[#IAAF3U](https://gitee.com/RRQM_Home/TouchSocket/issues/IAAF3U),[#IB5IH0](https://gitee.com/RRQM_Home/TouchSocket/issues/IB5IH0) --  调整 `IWebSocketClosingPlugin`的事件参数,由`ClosedEventArgs`调整为`ClosingEventArgs`。 重新实现接口解决 - - -#### TouchSocket.Sockets - --  修复 `TcpClient`在重连之后,适配器失效的bug。 [#IB6KZJ](https://gitee.com/RRQM_Home/TouchSocket/issues/IB6KZJ) - -#### TouchSocket.Rpc --  新增 `Rpc`服务新增`IScopedRpcServer`。 - - -#### TouchSocket.SerialPort --  修复 `SerialPortClient`在重连之后,适配器失效的bug。 - -#### TouchSocket.NamedPipe - --  新增 `INamedPipeSession`新增`DataHandlingAdapter`属性。 --  修复 `NamedPipeClient`在重连之后,适配器失效的bug。 - -#### TouchSocket.Modbus - --  优化 `Modbus`在写入时,会携带`IModbusResponse`的返回值。 [#IATPWB](https://gitee.com/RRQM_Home/TouchSocket/issues/IATPWB) - - -*** - -## v3.0.3 - -**更新日期:** 2024.11.17 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  优化 `WaitingClient`在收到`ResponsedData`数据时,优先建议使用`ByteBlock`,在高效场景中代替原`Data`属性。 - -*** - - -## v3.0.2 - -**更新日期:** 2024.11.15 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 串口的发送与接收无法通过插件获取到原始数据。[#IB4NF4](https://gitee.com/RRQM_Home/TouchSocket/issues/IB4NF4) - -*** - -## v3.0.0(1) - -**更新日期:** 2024.11.13 - -**更新描述:** - -大版本升级,有部分不兼容性升级。所以请在升级前做好备份,同时在升级之后,请务必阅读[v3.0升级指南](https://gitee.com/RRQM_Home/TouchSocket/issues/IB45VJ)。 - -**本次改动在运行时完全兼容`v2.1`,所以客户端和服务器可以差异版本更新。** - -**更新详情:** - -#### SDK - --  新增 新增`net9.0`支持。 --  移除 移除`net7.0`支持,但是不影响`net7.0`使用,因为最低到`net6.0`的支持。 - - -#### TouchSocket.Core - --  优化 `Plugin`组件默认支持`AOT`,不再借助委托实现。 --  优化 `AppMessenger`组件默认支持`AOT`。 --  优化 `DependencyObject`类使用“懒汉式”加载内部成员,减少内存使用。 --  新增 `PluginManager`支持在运行时移除插件。 --  新增 `Method`类,在方法、或类添加`[DynamicMethod]`特性时,默认支持`AOT`,为动态调用提供极大方便。 - -#### TouchSocket.Core.DependencyInjection - --  修复 `AspNetCoreContainer`不支持`KeysService`的bug。 - -#### TouchSocket.Sockets - --  修复 `IReceiver`在启用缓存模式时,如果已经完成接收,则会抛出异常的bug。 [#IB44LL](https://gitee.com/RRQM_Home/TouchSocket/issues/IB44LL) - -#### TouchSocket.Http - --  调整 `HttpRequest`在请求时,不用传参,直接使用默认构造函数即可。 --  移除 `GetMultifileCollection`相关扩展方法,使用`GetFormCollectionAsync`代替。 - -#### TouchSocket.Rpc - --  新增 `ICallContext`继承`IDependencyObject`,支持**扩展属性**读写,可以更方便的开发。 --  调整 `Rpc`特性取消构造函数入参,使用属性设置。受影响的有:`DmtpRpc`、`JsonRpc`、`XmlRpc`、`WebApi`。 - - -#### TouchSocket.Dmtp - --  修复 `DmtpRpc`在`Avalonia-Web`工作时,连接时间超长的bug。 --  修复 `IWebSocketDmtpClient`不实现`IDmtpClient`的bug。 --  修复 `DmtpHeartbeatPlugin`在长时间运行时,可能会失效的bug。 --  调整 `[DmtpRpc]`特性不再允许继承,所有设置也是通过属性设置。 - -#### TouchSocket.JsonRpc - --  调整 `[JsonRpc]`特性不再允许继承,所有设置也是通过属性设置。 - -#### TouchSocket.WebApi - --  新增 `[FromBody]`特性,支持指定参数来源自Http的请求Body。 --  新增 `[FromForm]`特性,支持指定参数来源自Http的请求Form表单。 --  新增 `[FromHeader]`特性,支持指定参数来源自Http的请求Header。 --  新增 `[FromQuery]`特性,支持指定参数来源自Http的请求Query。 --  调整 `[WebApi]`特性不再允许继承,所有设置也是通过属性设置。 --  调整 WebApi的请求方式,目前全部使用`WebApiRequest`类来表示全部入参,一般如果使用代理,或者源生成的话,只需要重新生成即可。 --  移除 `HttpMethodType.GET`枚举,使用`HttpMethodType.Get`代替。 --  移除 `HttpMethodType.POST`枚举,使用`HttpMethodType.Post`代替。 - -#### TouchSocket.XmlRpc - --  调整 `[XmlRpc]`特性不再允许继承,所有设置也是通过属性设置。 - -*** - -
-
- -
-v2.1 -
- -## v2.1.10 - -**更新日期:** 2024.10.25 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  优化 `CheckClearPlugin`插件频繁输出poll日志的不合理设计。 - -*** - -## v2.1.9 - -**更新日期:** 2024.10.14 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 CheckClearPlugin长时间工作时可能失效的bug。 - - -*** - -## v2.1.8 - -**更新日期:** 2024.10.11 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 TcpService在Stop的时候,有个内部异常打了log。 [#IAWD4N](https://gitee.com/RRQM_Home/TouchSocket/issues/IAWD4N) - - -*** - -## v2.1.7 - -**更新日期:** 2024.10.5 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 TcpService在启动时如果异常,则无法再重新启动的bug。 - -*** - -## v2.1.6 - -**更新日期:** 2024.10.1 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  新增 HttpContent机制,能上传超大数据。 --  新增 StreamHttpContent,能上传流数据,例如:文件流。 --  修复 Task内部异常时没有及时try,导致全局捕获时有无用捕获。 --  修复 TcpServiceBase在调用StopAsync时,IServerStopedPlugin插件无法触发的bug。 --  移除 TriggerQueue无用类。 --  优化 HttpRequest,使其能上传超大数据。 - -*** - -## v2.1.4(5) - -**更新日期:** 2024.9.23 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  新增 DmtpRpc恢复性新增Xml序列化。 --  修复 使用源生成(IPackage)打包时报错。[#IASTWJ](https://gitee.com/RRQM_Home/TouchSocket/issues/IASTWJ) --  修复 Result泛型类中隐式转换错写成显示转换的bug。 --  修复 DmtpRpc序列化选择器没有预留Json序列化配置的bug。 - - -*** - - -## v2.1.3 - -**更新日期:** 2024.9.22 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  优化 部分类,方法注释。 --  优化 多数英文字符串资源。 --  修复 Tcp、NamedPipe、SerialPort等组件在主动调用Close时,ClosedEventArgs参数属性Manual为false的bug,导致重连插件偶发性再次连接。[#IASH1A](https://gitee.com/RRQM_Home/TouchSocket/issues/IASH1A) --  修复 `ByteBlock`类部分bug。 --  移除 `DecimalConver`类,该类功能已完全由`TouchSocketBitConverter`代替,属于无用类。 --  调整 受保护方法`ProtectedResetId`方法,名称更改为`ProtectedResetIdAsync`。 - -*** - - -## v2.1.2 - -**更新日期:** 2024.9.19 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  优化 部分类,方法注释。 --  修复 TcpClient在释放时,重连插件会无限连接的bug,该bug会导致CPU占用过高。[#IAS9NG](https://gitee.com/RRQM_Home/TouchSocket/issues/IAS9NG) - -*** - -## v2.1.1 - -**更新日期:** 2024.9.18 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  新增 在`MemoryCache`中实现新的`TryGetValue`方法。 --  优化 密封`CacheEntry`类并优化缓存管理逻辑。 --  优化 扩展`PackageExtensions`类,增加泛型方法以提高可读性和可重用性。 --  优化 优化`HttpStaticPagePlugin`构造函数和`StaticPageOptions`类以简化静态页面配置。 --  修复 在`FastBinaryFormatter`中改进序列化逻辑,特别是对于**多维数组**的处理。 [#IARKE1](https://gitee.com/RRQM_Home/TouchSocket/issues/IARKE1) --  修复 在`IPackage`中改进打包、解包逻辑,特别是对于**多维数组**的处理。 - -*** - -## v2.1.0 - -**更新日期:** 2024.9.15 - -**更新描述:** - -大版本升级,有部分不兼容性升级。所以请在升级前做好备份,同时在升级之后,请务必阅读[v2.1升级指南](https://gitee.com/RRQM_Home/TouchSocket/issues/I9QSDK)。 - -**更新亮点:** - -本次更新,主要有以下亮点: - -1. 全系支持Span、ValueTask、Memory、Unsafe等依赖。大幅提升并发性能与低GC能力。 -2. 全系组件,尽量多的提供了异步Api,大幅度提升并发能力。 -3. 重构fast序列化,Package包模式、Http、WebSocket等组件,使之更加易用。 -4. 资源国际化。本次更新会在内部使用中英双语信息提示,这在日志记录,堆栈跟踪等场景更加符合区域化。 -5. 增加完整注释。基本上能达到95%的代码注释率。 - -**更新详情:** - -#### TouchSokcet.Core - --  新增 TouchSocketBitConverter新增To、UnsafeTo、WriteBytes、UnsafeWriteBytes等可以直接操作Span。 --  新增 Crc类新增Span相关转换。 --  新增 CustomDataHandlingAdapter新增`bool TryParseRequest(ref TByteBlock byteBlock, out TRequest request)`方法,可以同步完成适配器数据解析。 --  新增 SingleStreamDataAdapterTester泛型测试器,可以对TryParseRequest进行完整性测试。 --  新增 IPackage源生成器新增自定义PackageMember特性,用来定义打包的顺序和自定义转换器。 --  新增 SetupConfigObject的SetupAsync。 --  优化 CustomDataHandlingAdapter支持结构体作为泛型类型。 --  优化 Fast序列化支持自定义FastSerializerContext,这可以极大的利用源生成来决定序列化和反序列化。 --  调整 分离MemoryCache的同步和异步接口。 --  调整 ByteBlock取消Stream的继承,如果需要使用Stream,可以使用ByteBlock.AsStream()。 --  调整 Gzip类调整ByteBlock参数为Stream。 --  调整 CustomDataHandlingAdapter解析的数据,均会以ReadonlySpan的形式投递。 --  调整 IPackage接口,使之既可以在ByteBlock工作,也可以在ValueByteBlock工作。 --  调整 PluginManager使用接口作为唯一键,规定一个接口中有且仅有一个方法。 --  调整 ByteBlock、ValueByteBlock均继承IByteBlock接口规范。 --  移除 ByteBlock、ValueByteBlock移除Buffer属性,如果想获取有效数据,可以通过Memory、Span等获取,如果想获取容量可以使用TotalMemory属性。 - -#### TouchSokcet.Sockets - --  调整 Tcp、Udp组件,默认情况下适配器将为null,并且可以正常工作。 --  调整 SocketClient、ISocketClient等服务器辅助类,改名为TcpSessionClient和ITcpSessionClient。 - - -*** - -
-
- - -
-v2.0 -
- -## v2.0.18 - -**更新日期:** 2024.9.10 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 当Cancel延迟到Reset之后时,新获取的waitData会出现Status为Cancel异常bug [#IAQ2AI](https://gitee.com/RRQM_Home/TouchSocket/issues/IAQ2AI)。 - - -*** - - -## v2.0.17 - -**更新日期:** 2024.8.30 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 使用AspNetCore容器时,默认没有注册ILog的bug。 - -*** - - -## v2.0.16 - -**更新日期:** 2024.8.19 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 tcp在winform中会使用主线程接收的bug,导致各种同步接收异常。 - - -*** - - -## v2.0.15 - -**更新日期:** 2024.8.9 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 当客户端断开连接时,http响应会出现object is null的bug提示。 - - -## v2.0.13(14) - -**更新日期:** 2024.7.24 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 重连插件在长时间运行时,会失效的bug。 --  修复 ssl加密下,连接响应时间过长bug[#IAET6V](https://gitee.com/RRQM_Home/TouchSocket/issues/IAET6V) 。 - -## v2.0.12 - -**更新日期:** 2024.7.17 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 socket在接收连接时,异常无法拦截的bug[#IADIGX](https://gitee.com/RRQM_Home/TouchSocket/issues/IADIGX)。 --  调整 日志记录在执行时,先判断日志组件的可用性。 - -## v2.0.11 - -**更新日期:** 2024.7.12 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 Metadata在Add时不会覆盖原key的bug。 - - - -## v2.0.10 - -**更新日期:** 2024.5.31 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 WebSocket快捷命令行bug。 [#I9TG3V](https://gitee.com/RRQM_Home/TouchSocket/issues/I9TG3V)。 - - -## v2.0.9 - -**更新日期:** 2024.5.29 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 UdpPackage适配器在工作时调时导致的bug。 [#I9SYTR](https://gitee.com/RRQM_Home/TouchSocket/issues/I9SYTR)。 --  修复 Http在GetBoundary时bug。 [#I9PXWT](https://gitee.com/RRQM_Home/TouchSocket/issues/I9PXWT)。 - - - -## v2.0.(7)8 - -**更新日期:** 2024.5.17 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 udp首次建立连接无法接收数据的bug。 [#I9PV7C](https://gitee.com/RRQM_Home/TouchSocket/issues/I9PV7C)。 - -## v2.0.6 - -**更新日期:** 2024.5.12 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 FlowGate的waitTime小于0时bug。 - - -## v2.0.5 - -**更新日期:** 2024.5.2 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 wsclient Received事件与插件触发bug。[#I9L9WI](https://gitee.com/RRQM_Home/TouchSocket/issues/I9L9WI)。 --  修复 ws命令行执行bug。 - -## v2.0.4 - -**更新日期:** 2024.4.30 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 linux平台下,Socket吞吐量大幅降低。[#I9KURV](https://gitee.com/RRQM_Home/TouchSocket/issues/I9KURV)。 --  修复 WebSocket在进行连接时,Host的Header写法错误,没有包含端口。[#I9KUVI](https://gitee.com/RRQM_Home/TouchSocket/issues/I9KUVI)。 - - -## v2.0.3 - -**更新日期:** 2024.4.14 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 Tcp在接收时,内存池释放存在延迟,可能导致内存池快速扩张,浪费内存。[#I9FVAA](https://gitee.com/RRQM_Home/TouchSocket/issues/I9FVAA)。 --  修复 在使用Host模型时,注入瞬态的TcpClient,在第一次获取实例是正常的,第二次就失败。[#I9G3SV](https://gitee.com/RRQM_Home/TouchSocket/issues/I9G3SV)。 --  修复 WebSocketClient连接其他服务器时显示 “操作已被取消”。[#I9GG05](https://gitee.com/RRQM_Home/TouchSocket/issues/I9GG05)。 --  新增 ConcurrentList新增实现IReadOnlyList接口。 - - -## v2.0.2 - -**更新日期:** 2024.4.1 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 TouchSocketBitConverter中的ToBooleans方法存在Bug[#I9C1UY](https://gitee.com/RRQM_Home/TouchSocket/issues/I9C1UY)。 --  修复 SystemExtensions中的GetBit和SetBit方法[#I9C1WM](https://gitee.com/RRQM_Home/TouchSocket/issues/I9C1WM)。 --  修复 HttpService多次响应下载文件时,不会响应的bug。 - - - -## v2.0.1 - -**更新日期:** 2024.3.16 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 HttpClient,无法通过HttpResponse.GetBody()获取响应内容[#I989SI](https://gitee.com/RRQM_Home/TouchSocket/issues/I989SI)。 --  修复 SetNoDelay 异常[#I979B0](https://gitee.com/RRQM_Home/TouchSocket/issues/I979B0)。 --  修复 调用Dmtp服务的大数据传输时,如果循环调用会出现收到的数据和发送的数据不一致。实际上该问题是由`ByteBlock`写入扩容bug而导致的[#I96FNF](https://gitee.com/RRQM_Home/TouchSocket/issues/I96FNF)。 - - -## v2.0.0 - -**更新日期:** 2024.3.9 - -**更新描述:** - -此版本是大版本更新。可能会产生很多不兼容部分,所以升级之前请做好备份,并且请详细阅读下列更新内容。 - -**升级指南:** - -- 升级时请先升级至`2.0.0-beta.190`,再升级至`2.0.0-rc.2`版本,因为正式版对于[Obsolete]特性的成员直接删除了,所以为友好升级,请先升级至此版本。 -- 由2.0.0-beta.200至2.0.0-beta.220 [#I8DE1D](https://gitee.com/RRQM_Home/TouchSocket/issues/I8DE1D) -- 由2.0.0-beta.220至2.0.0-beta.230 [#I8LAX4](https://gitee.com/RRQM_Home/TouchSocket/issues/I8LAX4) - -**更新详情:** - -#### TouchSokcet.Core - --  优化 FileLogger支持指定不同目录。 --  调整 所有自定义插件必须在自身内,主动调用e.InvokeNext()时,才会调用下一个插件。不然会中断插件传递。同时e.Handled功能依然有效。 --  调整 Log项。LogType调整为LogLevel,并且不需要位运算。直接按日志等级输出。 --  调整 修改IPluginsManager名称为IPluginsManager。 --  移除 DependencyProperty中,移除对类型的定义。 --  移除 所有组件的基础插件,强制用户插件必须继承PluginBase,然后实现需要的接口。 --  移除 BytePool在创建ByteBlock时,移除EqualSize的设定,因为这会影响内存池的效率。 --  调整 修改所有委托为异步Task。 --  调整 修改所有Setup返回值为void。 --  修复 Metadata在0个成员长度时,会被反序列化成null的bug。 --  修复 PluginsManager在注册具有继承的插件时,会无法识别的bug。 - -#### TouchSokcet.Sokcets - --  优化 IPHost支持从int、string直接隐式转换。 --  调整 TouchSocket所有“ID”属性,改名为“Id”。 --  调整 TouchSocket所有插件的执行顺序,移动至内部重写方法之后。 --  调整 TouchSocket所有`ResetID`改名为`ResetId`。 --  调整 UseCheckClear项,SetDuration调整名称为SetTick。 --  调整 UseCheckClear项,不仅可以适用服务器,客户端也适用。 --  调整 Config配置中,SetDataHandlingAdapter调整为SetTcpDataHandlingAdapter。 --  调整 适配器项,CustomDataHandlingAdapter中的Filter方法中,byteBlock参数使用in修饰。 --  调整 适配器项,DataHandlingAdapter改名为TcpDataHandlingAdapter。 --  调整 适配器项,DataAdapterTester改名为TcpDataAdapterTester。 --  调整 Config项,所有适配器的相关配置,使用SetAdapterOption配置。 --  移除 UsePlugin的显式配置,当调用ConfigurePlugins时,会自动启用。 - -#### TouchSokcet.Http - --  调整 WSCommandLinePlugin改名为WebSocketCommandLinePlugin。 --  新增 WebSocket添加[同步非阻塞Read](./websocketservice.mdx#52-websocket显式readasync)。 --  新增 WebSocket的WSDataFrame新增IsPing、IsPong、IsText、IsBinary、IsClose等属性。 --  新增 静态网页插件新增NavigateAction与ResponseAction等委托,可以在静态页面请求之前重定向,或者请求返回时设置header等。 - -#### TouchSokcet.Rpc - --  移除 整体功能迁移至TouchSokcet(Pro).Dmtp。 --  调整 [RpcActionFilter](./rpcactionfilter.mdx#33-规则)执行策略和顺序 --  调整 修改ConfigureRpcStore为AddRpcStore。 --  新增 [RealityProxy](./rpcgenerateproxy.mdx)透明代理方式。 --  新增 [DispatchProxy](./rpcgenerateproxy.mdx)添加OnBefore和OnAfter的AOP调用。 - -#### TouchSokcet(Pro).Dmtp - --  调整 原TouchRpc全系改名为Dmtp。例如:原TcpTouchRpcClient改名为TcpDmtpClient。 --  调整 原TouchRpc中InvokeOption,改名为DmtpInvokeOption。InvokeOption依然有效,但是在调用DmtpRpc时,则无法指定序列化方式。所以可能需要使用DmtpInvokeOption。 --  调整 原TouchRpc中Invoke直接调用的方式,改为InvokeT。 --  调整 Dmtp相关配置,使用SetDmtpOption配置。 --  移除 **暂时**移除EventBus功能,后续可能考虑添加。 --  新增 ws协议的TouchRpc服务端,增加HttpContext上下文获取。 --  新增 文件传输项,开放增加SetMaxSpeed功能。 --  新增 DmtpRpc。 --  新增 DmtpRpc组件在调用时,可以通过DmtpInvokeOption传入Metadata元数据。 --  修复 DmtpRpc在调用无ref,out的函数时,参数会为null的bug。 - -#### TouchSokcet.JsonRpc - --  修复 JsonRpc使用内联数组调用[#I79OFZ](https://gitee.com/RRQM_Home/TouchSocket/issues/I79OFZ)。 --  调整 Rpc服务使用调用上下文时不需要再添加`IncludeCallContext`。 - -#### TouchSokcet.WebApi - --  新增 WebApi新增[Swagger页面](./swagger.mdx)。 --  调整 Rpc服务使用调用上下文时不需要再添加`IncludeCallContext`。 - -#### TouchSokcet.XmlRpc - --  调整 Rpc服务使用调用上下文时不需要再添加`IncludeCallContext`。 - -#### TouchSokcet(Pro).Hosting - --  新增 新发布Hosting的包,用于构建更加强壮的运行程序。 - -#### TouchSokcet.SerialPorts - --  新增 新发布串口的包。 - -#### TouchSokcet(Pro).Modbus - --  新增 新发布Modbus的包,支持Tcp、Udp、Rtu、RtuOverTcp、RtuOverUdp协议的主站(Poll)和从站(Slave)。 ---- - - -
-
- -
-v1.3 -
- -## v1.3 - -更新日期:2023.3.1 - -更新描述:兼容性更新。 - - -  优化 整体增加异步方法。 - -  优化 Rpc源代码生成策略,支持接口实例并存。 - -  修复 TcpClient在UseReconnect插件时,Disconnect事件不触发bug。 - -  新增 ws协议的TouchRpc服务端,增加HttpContext上下文获取。 ---- - -
-
- -
-v1.2 -
- -## v1.2 - -更新日期:2023.2.15 - -更新描述:兼容性更新。 - - -  优化 TouchRpc支持命名元组。 - -  优化 Rpc源代码生成策略。 - -  修复 TouchRpc在Websocket协议下,启动,连接异常bug。 - -  修复 TouchRpc在调用WaitSend下失败的bug。 - -  修复 TouchRpc在Handshaked时,调用Rpc超时bug。 - -  修复 序列化、反射在unity中使用il2cpp编译的bug。 - -  修复 反序列化在初次加载时会失败的bug。 - -  修复 BytePool没有公共构造函数的bug。 - -  新增 ws协议的TouchRpc服务端,增加HttpContext上下文获取。 - -  新增 ByteBlock对于int,long等数据,写入和读取的时候支持大小端指定。 - -  新增 IServicePlugin插件,用于显示通知服务器的启动状态。 - -  新增 Rpc支持接口特性标记。 - -  调整 将BytePool由静态调整为实例,且由其Default实例作为默认。 ---- - -
-
- -
-v1.1 -
- -## v1.1 - -更新日期:2023.1.13 - -更新描述:小版本升级,可能会有不兼容。请按下列提示修改。 - - -  优化 TouchRpc系文件传输时,文件夹不存在的提示。 - -  优化 WaitingClient,当客户端断开连接时,可选是否抛出异常。 - -  优化 Fast序列化时。可选序列化只读属性。 - -  修复 多个不稳定Bug。 - -  新增 Tcp客户端新增Disconnecting事件。在主动Close时生效。 - -  调整 多个事件类名称修改,请按照提示修改即可。 - -  移除 多个无用方法参数。 ---- - -
-
- -
-v1.0 -
- -## v1.0.0 - -更新日期:2023.1.1 - -更新描述:大版本升级,请详细阅读下列更新日志。 - - -  升级 将最高版本升级为NET7。 - -  优化 Tcp系异步发送效率。 - -  优化 TouchRpc系Channel的稳健性。 - -  修复 多个不稳定Bug。 - -  新增 ValueByteBlock,在简单代码块里面能有效减少创建的类。 - -  新增 MemoryCache类,其功能类似微软官方。但是支持全部泛型。 - -  新增 [IPackage系](https://www.yuque.com/rrqm/touchsocket/ag9tyar9mmhsme0m)。该系列能以超高效率的进行二进制序列化。 - -  新增 SingleTimer类,不可重入的Timer。 - -  新增 Jsonrpc支持自定义适配器解析(EE) - -  新增 严重TouchRpc系OnRouting通知,所有的客户端之间的通信,都必须经过OnRouting的筛查。 - -  新增 TouchRpc系小文件传输,在文件小于1Mb时,其传输效率是常规传输的10倍以上。 - -  新增 TouchRpc系超大文件多链路传输,支持多个客户端协同传输同一个文件,这在互联网环境中,效率比常规传输提高类3-5倍。 - -  新增 TouchRpc系Redis组件,能实现双端共同存储。 - -  调整 严重精简所有命名空间,删除所有三级命名空间。例如:TouchSocket.Core.ByteManager精简为TouchSocket.Core。 - -  调整 严重删除Newtonsoft.Json的源代码嵌入。全局的Json会根据环境动态调整,详情见[Json工具](https://www.yuque.com/rrqm/touchsocket/emqy43#PfVh1) - -  调整 严重框架默认日志由ConsoleLogger,替换为EmptyLogger(不输出任何东西)。 - -  调整 严重Tcp全系,在连接时,ID的初始值使用long类型从0递增。 - -  调整 严重Tcp服务器,将定时清理无数据交互的选项替换为UseCheckClear插件。并且默认没有启用,需要手动加入。 - -  调整 Tcp系适配器,取消部分参数。 - -  调整 DataLock改名为DataSecurity。 - -  调整 EasyAction改名EasyTask。 - -  调整 IMessage改名IMessageObject。 - -  调整 TokenInstance改名MessageInstance。 - -  调整 TouchRpc系,精简常规文件传输操作。 - -  调整 严重TouchRpc系,所有插件通知参数,默认都设为不允许操作,需要手动设置e.IsPermitOperation=true。 - -  移除 Newtonsoft.Json的源代码嵌入。全局的Json会根据环境动态调整,详情见[Json工具](https://www.yuque.com/rrqm/touchsocket/emqy43#PfVh1)。 - -*** 更新示例指南 *** - -(1)适配器参数报错:直接删除isAsync参数,以及isAsync为**True**的所有逻辑。 -![image.png](@site/static/img/docs/upgrade-1.png) -(2)依赖属性的声明报错:增加泛型约束即可,详情查看[依赖属性](https://www.yuque.com/rrqm/touchsocket/ubk57o#jyzSl) -![image.png](@site/static/img/docs/upgrade-2.png) -(3)服务端定时清理警告:在配置插件中使用UseCheckClear,并且进行相关配置。 -![image.png](@site/static/img/docs/upgrade-3.png) -![image.png](@site/static/img/docs/upgrade-4.png) - ---- - -
-
- -
-v0 -
- -## 版本号: 0.7.0 -更新日期:2022.9.21 -更新描述:兼容性更新,增强型更新。**RPC内容需要客户端与服务器同步更新**。 -更新详情: - -优化 -1. Fast二进制序列化,支持自定义序列化。 -2. TouchRpc全系,在文件传输等大型IO时,由于心跳失败而断开连接。 -3. 优化AspNetCore的IContainer。 -4. TcpCommandLinePlugin与WSCommandLinePlugin支持获取客户端参数。 - -新增 -1. 插件实例会以单例注入容器。 -2. 所有适配器支持[缓存超时](https://www.yuque.com/rrqm/touchsocket/83526e6320dfc85fef317d850aa51e92#Z0S0g)设定。 -3. 修改所有事件为委托。 -4. 开放[AspnetCore](https://www.yuque.com/rrqm/touchsocket/55e5bbf58745fa639dba511c7bcd54d1#WqOmh)创建Tcp,Http等服务器的配置。 -5. IClient增加发送、接收的最后时间记录。 -6. Http支持多文件上传(目前仅支持小文件,具体大小以实际运行内存为准,实测100Mb没问题)。 -7. Websocket插件默认会处理Close报文。且插件支持Close。 -8. Rpc支持模板代码重写。 -9. TouchRpc支持元组。 -10. JsonRpc支持Websocket协议。 - -修改 -1. IScopedContainer修改为IContainerProvider - -修复 -1. BytePool回收内存时不判断大小的bug。 - -删除 -1. 无。 - - ---- - -## 版本号: 0.6.0 -更新日期:2022.9.10 -更新描述:兼容性更新,增强型更新。**专为Unity 3D适配**。 -更新详情: - -优化 -1. Gzip的压缩效率。 -2. 发送效率。 - -新增 -1. IDataCompressor数据传输压缩接口。 -2. [RemoteStream](https://www.yuque.com/rrqm/touchsocket/ukq0mu)支持数据读写压缩。 -3. WaitResultPackageBase类,专属非序列化的数据格式化。 -4. DelaySender[延迟缓存发送](https://www.yuque.com/rrqm/touchsocket/1f21a56ee75f896a5b5b38b37b071881#RL0kx)。 - -修改 -1. 无 - -修复 -1. Rpc注册服务为单例时,实际上是瞬时服务的bug。 - -删除 -1. 独立线程发送。 - ---- - -## 版本号: 0.5.0 -更新日期:2022.9.1 -更新描述:兼容性更新,增强型更新。 -更新详情: - -优化 -1. 全局资源的获取逻辑。 - -新增 -1. Container增加卸载注册功能。 -2. FilePool新增FileStorageStream的获取。 -3. http客户端(及websocket)支持代理和验证代理。 -4. TouchRpc全系新增[远程文件操作](https://www.yuque.com/rrqm/touchsocket/pearz0) -5. TouchRpc(除udp)新增[远程流访问](https://www.yuque.com/rrqm/touchsocket/ukq0mu) - -修改 -1. 无 - -修复 -1. 修复Http客户端请求重复Header时的bug。 - -删除 -1. TouchRpc全系的事件操作,推荐直接插件的方式,或者使用TouchRpcActionPlugin然后添加委托。 - - -更新示例 -TouchRpc的相关事件均已使用插件代替。所以请使用插件实现操作。如果需要事件等功能的话,可以用TouchRpcActionPlugin的插件实现。例如: -```csharp showLineNumbers -.UsePlugin() -.ConfigurePlugins(a=> -{ - a.Add>()//此处的逻辑可用插件替代完成。 - .SetFileTransfering((client, e) => - { - //有可能是上传,也有可能是下载 - client.Logger.Info($"服务器请求传输文件,ID={client.ID},请求类型={e.TransferType},文件名={e.FileInfo.FileName}"); - }) - .SetFileTransfered((client, e) => - { - //传输结束,但是不一定成功,需要从e.Result判断状态。 - client.Logger.Info($"服务器传输文件结束,ID={client.ID},请求类型={e.TransferType},文件名={e.FileInfo.FileName},请求状态={e.Result}"); - }); -}) -``` - ---- - -## 版本号: 0.4.5 -更新日期:2022.8.25 -更新描述:兼容性更新,增强型更新。 -更新详情: - -优化 -1. FileLogger的写入逻辑,大大地提升了写入效率。 - -新增 -1. [Pipeline适配器](https://www.yuque.com/rrqm/touchsocket/ofnliu) -2. [TLV适配器](https://www.yuque.com/rrqm/touchsocket/wug4bv) -3. WaitingClient支持按条件等待返回。 -4. 日志系统可以筛选日志的输出类型 -5. Rpc系统,可以使用单例、瞬时生命周期的服务。 -6. Rpc系统,可定义持久化模型。 -7. Rpc在使用瞬时生命周期的服务时,可以直接获取调用上下文。 -8. XmlRpc增加调用上下文。 - -修改 -1. 日志系统。 -2. Rpc的调用上下文均采用接口,例如:JsonRpc改为IJsonRpcCallContext,WebApi为IWebApiCallContext。 -3. IRpcActionFilter的参数列表。 - -修复 -1. UdpSession资源不释放的Bug。 - -删除 -1. 冗余元素。 - - ---- - -## 版本号: 0.3.5 -更新日期:2022.8.12 -更新描述:兼容性更新,增强型更新。 -更新详情: - -优化 -1. 各类客户端发送逻辑。 -2. Method类的调用逻辑。 - -新增 -1. 适配器可以设定发送IRequestInfo对象。 -2. 插件新增UseWebSocket的快捷方式。 -3. ReconnectionPlugin插件可以获得重连次数的重载设置。 -4. ProTcpService的服务注入。 -5. ProHttpService的服务注入。 -6. ProIOC容器的共享使用。 - -修改 -1. 各类发送逻辑,以最小化发送方法为基础,其余方法改为扩展方法。 -2. 相关接口的实现。 -3. 由网友[修改GetInfo](https://gitee.com/dotnetchina/TouchSocket/pulls/11) - -修复 -1. Container获取泛型失败bug。 -2. BetweenAnd适配器适配器部分bug。 -3. Router标签无法路由的bug。 -4. 修复TouchRpc推送文件状态不正确bug -5. 修复独立线程在断线重连后发送bug。 - -删除 -1. 冗余的发送方法,不影响上版本任何使用。 - - ---- - - -## 版本号: 0.2.4 -更新日期:2022.7.28 -更新描述:兼容性更新。 -更新详情: - -优化 -1. 优化IOC容器。 -2. 优化Metadata的写入方式。 -3. FileLogger,当日志文件达到1Mb时,会再新增文件序号。 - -新增 -1. Mapper类,支持简单类型映射 -2. Tcp服务器、客户端、udp等增加端口复用配置。 -3. Pro轮询式断线重连。 -4. ProNATService转发客户端重连。 - -修改 -1. RRQM二进制序列化,改名为Fast。 -2. TouchRpcClient连接时的Metadata,改为由Config配置注入。 -3. FilePool,取消延迟释放机制。 - -修复 -1. 修复WebSocket连接问题 - -删除 -1. 客户端直接调用的短线重连方式。仅保留在Config注入的功能。 - ---- - -## 版本号: 0.1.0 -更新日期:2022.7.16 -更新描述:初始化版本发布。由RRQMSocket迁移而来。 - -迁移指南: -### 1.所有类的命名空间修改,此处如果类型名未修改的话,可由vs智能提示解决。 -### 2.类型名称修改 -| 原类型名称 | 新类型名称 | -| --- | --- | -| RRQMBitConverter | TouchSocketBitConverter | -| RRQMConfig | TouchSocketConfig | -| RRQMConverter | TouchSocketConverter | -| RRQMDependencyObject | DependencyObject | -| MsgEventArgs | MsgEventArgs | -| RRQMEventAgrs | TouchSocketEventArgs | -| IServerProvider | IRpcServer | -| ServerProvider | SingletonRpcServer | -| RRQMOverlengthException | OverlengthException | - -### 3.使用逻辑修改 -1)原RRQMConfig设置Logger的方法,改为容器注入: -![image.png](@site/static/img/docs/upgrade-5.png) -断线重连逻辑 -![image.png](@site/static/img/docs/upgrade-6.png) -RpcStore使用变更 -如果是仅有一个Rpc解析器,那么可以直接删除RpcStore的声明,从而使用对应的**解析器实例**,直接注册服务。然后可以通过其属性RpcStore,获取到具体的RpcStore实例。 - -如果是有多个解析器,那么,首先可以使用任意一个解析器的RpcStore属性实例,作为主RpcStore,然后添加其他解析器。当然也可以直接new RpcStore,然后统一管理解析器。其中构造函数中的Container容器,可以直接new Container(),但是更建议使用和解析器相同的容器,这样注入的服务会变得全局可用。 - - -
-
- diff --git a/handbook/docs/video.mdx b/handbook/docs/video.mdx index 697849e06..5905a239b 100644 --- a/handbook/docs/video.mdx +++ b/handbook/docs/video.mdx @@ -3,6 +3,10 @@ id: video title: 视频教学 --- +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + ## 🚀【零基础逆袭】B站爆款课程《C# TouchSocket!零基础网络通信入门》重磅来袭!助你成为领域大神!🎉 ✨ **为什么选择这门课?看这里!** ✨ @@ -40,57 +44,150 @@ title: 视频教学 ✅ QQ群答疑 + 直播答疑 📝 --- -⏳ **限时福利(以下活动截止2025.6.22)** 🚨 -✨TouchSocket VIP感恩回馈活动✨ - -🎁【已授权客户专属】 - -截至**2025.6.22**凡购买过Pro版(个人/企业)的新老朋友 -🎉立享课程5折(实际为100¥)返现优惠! - -💎【打赏达人专属】 - -**2025.5.1**前打赏过的朋友看这里! -📸凭打赏截图联系若汝棋茗(QQ505554090) -💰双倍金额抵扣!上限100元! -(例:累计打赏10元→购课减20元) - -🚀【开源贡献者专属】 - -**2025.5.1**前提交过PR的开发者大大请注意! -📎已合并的代码/文档PR均可 -✨每次贡献兑换20元优惠!上限100元! -(例:合并2次→购课减40元) - -🐛【Bug猎手专属】 - -**2025.5.1**前提交过框架Bug的小伙伴看过来! -🔗已证实的Bug类Issue均可(必须提交过issue,不限git平台) -💥每次Issue兑换10元优惠!上限100元! -(例:提交2次→购课减20元) - - -📢【活动规则】 - - 1. 所有优惠可叠加使用,总上限100元 - 2. 优惠凭证仅限本人使用,禁止转让 - - -💸【返现/优惠操作指南】 - - 1. 先到B站课堂下单:https://www.bilibili.com/cheese/play/ss489296905 - 2. 保留购课截图+对应凭证(打赏/PR/Issue) - 3. 添加QQ505554090提交材料 - 4. **次日**18点后核对订单,无误后立即返现 - 5. 支持微信/支付宝收款码 - -💖感谢每一位伙伴的支持与陪伴! -如有疑问可随时联系客服哦~ (๑•̀ㅂ•́)و✧ - ---- 📱 **马上行动** 👇 点击课程 👉 [立即抢位](https://www.bilibili.com/cheese/play/ss489296905) -🎯 **今日投资自己,明日惊艳所有人!** 💪 \ No newline at end of file +🎯 **今日投资自己,明日惊艳所有人!** 💪 + +## 主要课程内容 + +课程的大多数内容,都和本文档内容是关联的,所以都嵌入在文档的相关章节中,方便学习和查阅。 + +此处展示的仅仅是课程中,独立体系的内容。 + +## Tcp在WPF、Winform中的企业级应用 + + + +### 📋 课程概述 + +本课程是一门深度实战型的企业级桌面应用开发教程,专注于TCP网络通信在WPF和Winform应用中的高级应用实践。💻✨ + +课程以实际项目开发为导向,从零开始构建一套完整的企业级通信框架,涵盖了从基础架构设计到高级功能实现的全过程。通过32个精心设计的章节,学员将掌握如何在桌面应用中实现稳定、高效的网络通信解决方案。🏗️ + +### 🎯 核心技术栈 + +#### 🔧 开发框架 +- **WPF (Windows Presentation Foundation)** - 现代Windows桌面应用开发 +- **Winform** - 传统Windows桌面应用开发 +- **Baboon框架** - 轻量级IoC依赖注入框架 +- **TouchSocket** - 高性能TCP通信库 + +#### 🌐 网络通信技术 +- **TCP协议深度应用** - 企业级网络通信实现 +- **自定义通信协议设计** - TLV格式协议架构 +- **数据适配器模式** - 高效数据解析与封装 +- **断线重连机制** - 网络异常自动恢复 + +#### 💬 业务功能模块 +- **用户登录认证系统** - 安全的用户身份验证 +- **即时聊天通信** - 实时消息传输与转发 +- **好友列表管理** - 用户关系维护 +- **文件传输系统** - 大文件分块上传下载 + +### 📚 课程结构 + +#### 🏛️ 第一部分:架构设计与框架搭建 +- 解决方案架构设计与项目框架介绍 +- 服务器端解决方案创建与配置 +- 客户端与通用模块解决方案设计 +- Baboon框架集成与IoC容器配置 + +#### 🔌 第二部分:通信协议与适配器开发 +- 自定义TCP通信协议设计(TLV格式) +- 数据适配器实现与性能优化 +- 服务器与客户端通信框架完善 +- 一问一答通信模式设计与Ping业务实现 + +#### 🔐 第三部分:用户认证与连接管理 +- 断线重连机制实现 +- 界面通知系统设计 +- 登录通信逻辑开发 +- WPF与Winform登录界面实现 + +#### 💭 第四部分:即时聊天系统开发 +- 客户端聊天通信功能实现 +- 服务器消息队列与转发机制 +- 消息接收通信机制完善 +- 好友列表获取与管理 + +#### 📁 第五部分:文件传输系统 +- 文件传输架构设计 +- 文件分块下载技术实现 +- 服务器端文件传输逻辑 +- 下载进度与速度显示 + +### 🎓 学习收获 + +通过本课程的学习,您将获得: + +- 🏢 **企业级开发经验**:掌握真实项目中的架构设计思路和开发模式 +- 🔧 **核心技术能力**:深入理解TCP通信、数据协议、异步编程等关键技术 +- 💡 **解决方案思维**:学会分析复杂业务需求并设计可扩展的技术方案 +- 🚀 **实战项目经验**:完整的聊天系统和文件传输系统开发经历 + +### 👥 适用人群 + +- 有一定C#基础的.NET开发者 👨‍💻 +- 希望学习网络编程的桌面应用开发者 🖥️ +- 需要开发企业级通信系统的工程师 🔧 +- 想要提升架构设计能力的程序员 🏗️ + +### 🔥 课程亮点 + +- ✅ **实战导向**:每个章节都基于真实项目需求 +- ✅ **技术深度**:深入TCP协议底层实现细节 +- ✅ **架构思维**:培养企业级系统设计能力 +- ✅ **跨平台支持**:同时支持WPF和Winform开发 + +--- + +💪 准备好开启您的企业级TCP通信开发之旅了吗?让我们一起构建高性能的桌面通信应用!🚀✨ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/handbook/docs/waitingclient.mdx b/handbook/docs/waitingclient.mdx index 4b903c4a8..727f2950c 100644 --- a/handbook/docs/waitingclient.mdx +++ b/handbook/docs/waitingclient.mdx @@ -4,11 +4,11 @@ title: 同步请求 --- import CardLink from "@site/src/components/CardLink.js"; - import BilibiliCard from '@site/src/components/BilibiliCard.js'; import { TouchSocketDefinition } from "@site/src/components/Definition.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; + -### 定义 @@ -28,63 +28,22 @@ import { TouchSocketDefinition } from "@site/src/components/Definition.js"; 例如:`TcpClient`、`TcpService`、`NamedPipeClient`、`NamedPipeService`、`SerialPortClient`等。 +::: + ## 二、在客户端使用 在客户端工作时,支持很多组件,例如:`TcpClient`、`NamedPipeClient`、`SerialPortClient`,下面仅以`TcpClient`为例。 -```csharp showLineNumbers -var client = new TcpClient(); -await client.ConnectAsync("tcp://127.0.0.1:7789"); -//调用CreateWaitingClient获取到IWaitingClient的对象。 -var waitClient = client.CreateWaitingClient(new WaitingOptions() -{ - FilterFunc = response => //设置用于筛选的fun委托,当返回为true时,才会响应返回 - { - return true; - } -}); - -//然后使用SendThenReturn。 -byte[] returnData = await waitClient.SendThenReturnAsync(Encoding.UTF8.GetBytes("RRQM")); -Console.WriteLine($"收到回应消息:{Encoding.UTF8.GetString(returnData)}"); - -//同时,如果适配器收到数据后,返回的并不是字节,而是IRequestInfo对象时,可以使用SendThenResponse. -ResponsedData responsedData = await waitClient.SendThenResponseAsync(Encoding.UTF8.GetBytes("RRQM")); -IRequestInfo requestInfo = responsedData.RequestInfo;//同步收到的RequestInfo -``` + ## 三、在服务器使用 同理,在客户端工作时,支持很多组件,例如:`TcpService`、`NamedPipeService`,下面仅以`TcpService`为例。 -```csharp showLineNumbers -var service = new TcpService(); -await service.StartAsync(7789);//启动服务器 - -//在服务器中,找到指定Id的会话客户端 -if (service.TryGetClient("targetId", out var tcpSessionClient)) -{ - //调用CreateWaitingClient获取到IWaitingClient的对象。 - var waitClient = tcpSessionClient.CreateWaitingClient(new WaitingOptions() - { - FilterFunc = response => //设置用于筛选的fun委托,当返回为true时,才会响应返回 - { - return true; - } - }); - - //然后使用SendThenReturn。 - byte[] returnData = await waitClient.SendThenReturnAsync(Encoding.UTF8.GetBytes("RRQM")); - Console.WriteLine($"收到回应消息:{Encoding.UTF8.GetString(returnData)}"); - - //同时,如果适配器收到数据后,返回的并不是字节,而是IRequestInfo对象时,可以使用SendThenResponse. - ResponsedData responsedData = await waitClient.SendThenResponseAsync(Encoding.UTF8.GetBytes("RRQM")); - IRequestInfo responseRequestInfo = responsedData.RequestInfo;//同步收到的RequestInfo -} -``` + :::tip 提示 @@ -98,30 +57,11 @@ if (service.TryGetClient("targetId", out var tcpSessionClient)) 在默认情况下,超时时间是5秒。 -```csharp showLineNumbers -await waitingClient.SendThenResponseAsync("hello");//默认5秒超时 -``` - -所以可以直接传参,设置超时时间。 - -```csharp showLineNumbers -await waitingClient.SendThenResponseAsync("hello", 1000*10);//设置10秒超时 -``` + 但是有时候,我们希望**不设置超时时间**,而是由用户自己控制超时时间,也就是能有**取消**的等待。 - -```csharp showLineNumbers -var cts = new CancellationTokenSource(); - -_=Task.Run(async () => -{ - await Task.Delay(5000); - cts.Cancel();//5秒后取消等待,不再等待服务端的消息。这里模拟的是客户端主动取消等待 -}); - -await waitingClient.SendThenResponseAsync("hello", cts.Token); -``` + ### 4.2 筛选配置 @@ -129,42 +69,19 @@ await waitingClient.SendThenResponseAsync("hello", cts.Token); 在默认情况下,**筛选函数**为空,即**不筛选**。 -```csharp {3} showLineNumbers -var waitingClient = client.CreateWaitingClient(new WaitingOptions() -{ - FilterFunc=default -}); -``` - 所以可以设置筛选函数。 -```csharp {3-10} showLineNumbers -var waitingClient = client.CreateWaitingClient(new WaitingOptions() -{ - FilterFunc = response => //设置用于筛选的fun委托,当返回为true时,才会响应返回 - { - var requestInfo=response.RequestInfo; - var byteBlock=response.ByteBlock; - - //这里可以根据服务端返回的信息,判断是否响应 - return true; - } -}); -``` + :::tip 异步筛选函数 -可以设置异步筛选函数,可以返回一个`Task`,用于异步筛选。 - -```csharp -FilterFuncAsync=async response=>await Task.FromResult(true) -``` +如果需要异步判断,则可以设置异步筛选函数,可以返回一个`FilterFuncAsync`,用于异步筛选。 ::: :::info 备注 -筛选函数的参数是`Response`,它包含两个属性,分别为`RequestInfo`和`ByteBlock`,具体使用哪个属性,看[适配器类型](./adapterdescription.mdx) +筛选函数的参数是`Response`,它包含两个属性,分别为`RequestInfo`和`Memory`,具体使用哪个属性,看[适配器类型](./singlethreadstreamadapter.mdx) ::: @@ -188,33 +105,7 @@ FilterFuncAsync=async response=>await Task.FromResult(true) 如果想要实现,当筛选函数返回`false`时,将数据从插件再次传递,那么就需要在筛选函数中,自行触发插件。 -例如: - -```csharp {18} showLineNumbers -var waitingClient = this.m_tcpClient.CreateWaitingClient(new WaitingOptions() -{ - FilterFuncAsync = async (response) => - { - var byteBlock = response.ByteBlock; - var requestInfo = response.RequestInfo; - - if (true)//如果满足某个条件,则响应WaitingClient - { - return true; - } - else - { - //否则 - //数据不符合要求,waitingClient继续等待 - //如果需要在插件中继续处理,在此处触发插件 - - await this.m_tcpClient.PluginManager.RaiseAsync(typeof(ITcpReceivedPlugin), this.m_tcpClient, new ReceivedDataEventArgs(byteBlock, requestInfo)); - - return false; - } - } -}); -``` + ### 5.4 关于SendThenReturn与SendThenReturnAsync @@ -230,40 +121,11 @@ var waitingClient = this.m_tcpClient.CreateWaitingClient(new WaitingOptions() `WaitingClient`的机制是发送一个数据,然后等待响应,所以,使用时机,绝对不可以在`Received`事件(或者插件)中调用。这将导致死锁。 -例如: - -```csharp {8} showLineNumbers -var tcpClient = new TcpClient(); - -var tcpClient.Received =async (client,e) => -{ - var waitingClient = client.CreateWaitingClient(new WaitingOptions()); - - //这里将导致死锁 - var bytes = await waitingClient.SendThenReturnAsync("hello"); -}; - -... -``` + 如果确实需要使用,请使用`Task.Run`来异步处理。 -例如: - -```csharp {4-9} showLineNumbers -this.m_tcpClient.Received =async (client,e) => -{ - //此处不能await,否则也会导致死锁 - _ = Task.Run(async () => - { - var waitingClient = client.CreateWaitingClient(new WaitingOptions()); - - var bytes = await waitingClient.SendThenReturnAsync("hello"); - }); -}; - -... -``` + ### 5.6 其他 @@ -310,4 +172,4 @@ this.m_tcpClient.Received =async (client,e) => ## 七、示例代码 - \ No newline at end of file + \ No newline at end of file diff --git a/handbook/docs/webapi-aot.mdx b/handbook/docs/webapi-aot.mdx new file mode 100644 index 000000000..8229dd576 --- /dev/null +++ b/handbook/docs/webapi-aot.mdx @@ -0,0 +1,201 @@ +--- +id: webapi-aot +title: WebAPI AOT支持 +--- + +## 一、AOT 支持 + +TouchSocket.WebApi 完整支持 Native AOT 编译。 + +### 1.1 基础配置 + +在项目文件中启用 AOT: + +```xml showLineNumbers + + + net9.0 + true + true + + + + + + + + + +``` + +### 1.2 JSON 序列化上下文 + +AOT 需要定义 JSON 序列化上下文: + +```csharp showLineNumbers +[JsonSerializable(typeof(int))] +[JsonSerializable(typeof(string))] +[JsonSerializable(typeof(UserDto))] +[JsonSerializable(typeof(List))] +public partial class AppJsonSerializerContext : JsonSerializerContext +{ +} + +public class UserDto +{ + public int Id { get; set; } + public string Name { get; set; } + public int Age { get; set; } +} +``` + +### 1.3 配置序列化器 + +使用 System.Text.Json 并指定序列化上下文: + +```csharp showLineNumbers +.ConfigurePlugins(a => +{ + a.UseWebApi() + .ConfigureConverter(converter => + { + converter.Clear(); + + // 使用 AOT 友好的序列化器 + converter.AddSystemTextJsonSerializerFormatter(options => + { + options.TypeInfoResolver = AppJsonSerializerContext.Default; + options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + }); + }); +}) +``` + +### 1.4 服务注册 + +使用源生成器注册服务: + +```csharp showLineNumbers +[GenerateRegister] +public partial class ApiServer : SingletonRpcServer +{ + [WebApi(Method = HttpMethodType.Get)] + public int Sum(int a, int b) + { + return a + b; + } + + [WebApi(Method = HttpMethodType.Get)] + public List GetUsers() + { + return new List + { + new UserDto { Id = 1, Name = "Alice", Age = 25 }, + new UserDto { Id = 2, Name = "Bob", Age = 30 } + }; + } + + [WebApi(Method = HttpMethodType.Post)] + public string CreateUser([FromBody] UserDto user) + { + return $"Created: {user.Name}"; + } +} +``` + +## 二、完整示例 + +### 2.1 Program.cs + +```csharp showLineNumbers +using TouchSocket.Core; +using TouchSocket.Http; +using TouchSocket.Sockets; +using TouchSocket.WebApi; +using System.Text.Json; +using System.Text.Json.Serialization; + +var builder = WebApplication.CreateBuilder(args); + +// 配置 TouchSocket 服务 +builder.Services.AddServiceHostedService(config => +{ + config.SetListenIPHosts(7789); + config.ConfigurePlugins(a => + { + a.UseWebApi() + .ConfigureConverter(converter => + { + converter.Clear(); + converter.AddSystemTextJsonSerializerFormatter(options => + { + options.TypeInfoResolver = AppJsonSerializerContext.Default; + options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + }); + }); + + a.UseDefaultHttpServicePlugin(); + }); +}); + +var app = builder.Build(); +await app.RunAsync(); + +// JSON 序列化上下文 +[JsonSerializable(typeof(int))] +[JsonSerializable(typeof(string))] +[JsonSerializable(typeof(UserDto))] +[JsonSerializable(typeof(List))] +public partial class AppJsonSerializerContext : JsonSerializerContext +{ +} + +// API 服务 +[GenerateRegister] +public partial class ApiServer : SingletonRpcServer +{ + [WebApi(Method = HttpMethodType.Get)] + public int Sum(int a, int b) + { + return a + b; + } + + [WebApi(Method = HttpMethodType.Get)] + public List GetUsers() + { + return new List + { + new UserDto { Id = 1, Name = "Alice", Age = 25 }, + new UserDto { Id = 2, Name = "Bob", Age = 30 } + }; + } + + [WebApi(Method = HttpMethodType.Post)] + public string CreateUser([FromBody] UserDto user) + { + return $"Created: {user.Name}"; + } +} + +public class UserDto +{ + public int Id { get; set; } + public string Name { get; set; } + public int Age { get; set; } +} +``` + +### 2.2 发布 + +```bash +dotnet publish -c Release +``` + +生成的可执行文件体积小、启动快、无需运行时。 + +## 三、注意事项 + +1. **避免反射**:使用源生成器而不是反射 +2. **序列化类型**:所有序列化类型必须添加到 `JsonSerializable` +3. **第三方库**:确保所有依赖库支持 AOT +4. **测试**:AOT 发布前充分测试 diff --git a/handbook/docs/webapi-auth.mdx b/handbook/docs/webapi-auth.mdx new file mode 100644 index 000000000..b5c8b388f --- /dev/null +++ b/handbook/docs/webapi-auth.mdx @@ -0,0 +1,250 @@ +--- +id: webapi-auth +title: WebAPI 鉴权与授权 +--- + +## 一、鉴权与授权 + +TouchSocket.WebApi 支持多种鉴权和授权方式。 + +### 1.1 基于插件的鉴权 + +使用插件进行全局鉴权: + +```csharp showLineNumbers +public class AuthPlugin : PluginBase, IWebApiPlugin +{ + public async Task OnWebApiRequest(IWebApiClientBase client, WebApiEventArgs e) + { + var request = e.Request; + var token = request.Headers["Authorization"]; + + if (string.IsNullOrEmpty(token) || !IsValidToken(token)) + { + // 未授权 + e.Response.SetStatus(401, "Unauthorized"); + await e.Response.AnswerAsync(); + e.Handled = true; // 阻止继续处理 + return; + } + + await e.InvokeNext(); + } + + private bool IsValidToken(string token) + { + // 验证 token 逻辑 + return token == "Bearer valid-token"; + } +} +``` + +注册插件: + +```csharp showLineNumbers +.ConfigurePlugins(a => +{ + a.Add(); // 鉴权插件 + a.UseWebApi(); + a.UseDefaultHttpServicePlugin(); +}) +``` + +### 1.2 基于 RPC AOP 的鉴权 + +使用 RPC 过滤器实现方法级鉴权: + +```csharp showLineNumbers +public class AuthorizeAttribute : RpcActionFilterAttribute +{ + public override async Task ExecutingAsync( + ICallContext callContext, + object[] parameters, + InvokeResult invokeResult) + { + if (callContext is IWebApiCallContext webApiContext) + { + var request = webApiContext.HttpContext.Request; + var token = request.Headers["Authorization"]; + + if (string.IsNullOrEmpty(token) || !IsValidToken(token)) + { + return new InvokeResult + { + Status = InvokeStatus.UnEnable, + Message = "Unauthorized" + }; + } + } + + return invokeResult; + } + + private bool IsValidToken(string token) + { + return token == "Bearer valid-token"; + } +} +``` + +使用: + +```csharp showLineNumbers +public partial class ApiServer : SingletonRpcServer +{ + // 公开接口,无需鉴权 + [WebApi(Method = HttpMethodType.Get)] + public string PublicApi() + { + return "Public data"; + } + + // 需要鉴权的接口 + [Authorize] + [WebApi(Method = HttpMethodType.Get)] + public string SecureApi() + { + return "Secure data"; + } +} +``` + +### 1.3 基于角色的授权 + +实现基于角色的授权: + +```csharp showLineNumbers +public class RoleAuthorizeAttribute : RpcActionFilterAttribute +{ + private readonly string[] _roles; + + public RoleAuthorizeAttribute(params string[] roles) + { + _roles = roles; + } + + public override async Task ExecutingAsync( + ICallContext callContext, + object[] parameters, + InvokeResult invokeResult) + { + if (callContext is IWebApiCallContext webApiContext) + { + var request = webApiContext.HttpContext.Request; + var token = request.Headers["Authorization"]; + + if (!TryGetUserRole(token, out var userRole)) + { + return new InvokeResult + { + Status = InvokeStatus.UnEnable, + Message = "Unauthorized" + }; + } + + if (!_roles.Contains(userRole)) + { + return new InvokeResult + { + Status = InvokeStatus.UnEnable, + Message = "Forbidden: Insufficient permissions" + }; + } + } + + return invokeResult; + } + + private bool TryGetUserRole(string token, out string role) + { + role = null; + if (string.IsNullOrEmpty(token)) return false; + + // 从 token 中解析角色 + if (token == "Bearer admin-token") + { + role = "Admin"; + return true; + } + else if (token == "Bearer user-token") + { + role = "User"; + return true; + } + + return false; + } +} +``` + +使用: + +```csharp showLineNumbers +public partial class ApiServer : SingletonRpcServer +{ + // 仅管理员可访问 + [RoleAuthorize("Admin")] + [WebApi(Method = HttpMethodType.Delete)] + public string DeleteUser(int id) + { + return $"Deleted user {id}"; + } + + // 管理员和普通用户都可访问 + [RoleAuthorize("Admin", "User")] + [WebApi(Method = HttpMethodType.Get)] + public string GetUser(int id) + { + return $"User {id} info"; + } +} +``` + +## 二、完整示例 + +```csharp showLineNumbers +// 服务器端 +var service = new HttpService(); +await service.SetupAsync(new TouchSocketConfig() + .SetListenIPHosts(7789) + .ConfigurePlugins(a => + { + a.Add(); // 全局鉴权 + a.UseWebApi(); + a.UseDefaultHttpServicePlugin(); + })); + +await service.StartAsync(); + +// API 服务 +public partial class SecureApiServer : SingletonRpcServer +{ + [WebApi(Method = HttpMethodType.Get)] + public string PublicInfo() + { + return "This is public information"; + } + + [Authorize] + [WebApi(Method = HttpMethodType.Get)] + public string UserInfo(IWebApiCallContext context) + { + var token = context.HttpContext.Request.Headers["Authorization"]; + return $"User info (token: {token})"; + } + + [RoleAuthorize("Admin")] + [WebApi(Method = HttpMethodType.Post)] + public string AdminAction() + { + return "Admin action executed"; + } +} + +// 客户端调用 +var client = new HttpClient(); +client.DefaultRequestHeaders.Add("Authorization", "Bearer valid-token"); + +var result = await client.GetStringAsync("http://localhost:7789/secureapiserver/userinfo"); +Console.WriteLine(result); +``` \ No newline at end of file diff --git a/handbook/docs/webapi-client.mdx b/handbook/docs/webapi-client.mdx new file mode 100644 index 000000000..5bd324e72 --- /dev/null +++ b/handbook/docs/webapi-client.mdx @@ -0,0 +1,258 @@ +--- +id: webapi-client +title: WebAPI 客户端调用 +--- + +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; + +## 一、概述 + +TouchSocket.WebApi 支持多种客户端调用方式,包括浏览器、标准 HttpClient、TouchSocket 专用客户端以及代理方式调用。 + + +## 二、浏览器调用 + +最简单的方式是直接通过浏览器访问 WebApi 接口: + +**直接访问:** +``` +http://localhost:7789/apiserver/sum?a=10&b=20 +``` + +**使用 JavaScript/Fetch API:** + +```javascript +// GET 请求 +fetch('http://localhost:7789/apiserver/sum?a=10&b=20') + .then(response => response.json()) + .then(data => console.log(data)); + +// POST 请求 +fetch('http://localhost:7789/apiserver/create', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ name: 'Alice', age: 25 }) +}) + .then(response => response.json()) + .then(data => console.log(data)); +``` + +## 三、使用原生 HttpClient + +对于简单场景,可以使用 .NET 标准库的 `HttpClient` 或任何支持 HTTP 协议的客户端工具直接调用: + + + +:::info 说明 + +原生 HttpClient 调用简单直接,但需要手动处理 URL 拼接、序列化/反序列化、异常处理等。对于更复杂的场景,推荐使用 TouchSocket 提供的专用客户端。 + +::: + +## 四、使用 TouchSocket WebApiClient(推荐) + +TouchSocket 提供的 `WebApiClient` 是专为 WebApi 设计的客户端,提供了更强大的功能和更好的开发体验。 + + + +### 4.1 核心优势 + +| 特性 | WebApiClient | 原生 HttpClient | +|------|-------------|----------------| +| **强类型调用** | ✅ 支持泛型返回 | ❌ 需要手动反序列化 | +| **代码生成** | ✅ 自动生成调用代码 | ❌ 手动编写 | +| **插件系统** | ✅ 丰富的插件支持 | ❌ 需要自行实现 | +| **超时控制** | ✅ 灵活的超时配置 | ⚠️ 全局超时 | +| **异常处理** | ✅ 统一的异常体系 | ⚠️ 需要自行包装 | + +### 4.2 创建 WebApiClient + + + +**配置说明**: +- `SetRemoteIPHost`:设置服务器地址和端口 +- `ConfigurePlugins`:配置插件(如日志、认证、重连等) +- 支持链式配置,简洁易用 + +### 4.3 GET 请求调用 + + + +**优势**: +- 使用 `WebApiRequest` 对象构建请求,避免字符串拼接错误 +- `Querys` 参数自动编码,防止 URL 注入 +- `InvokeTAsync` 直接返回强类型结果,无需手动反序列化 + +### 4.4 POST 请求调用 + + + +**优势**: +- `Body` 属性自动序列化对象 +- 支持复杂类型参数,无需手动处理 JSON +- 自动设置 Content-Type 头 + +### 4.5 使用代理调用 + + + +**优势**: +- 使用生成的扩展方法,调用更简洁 +- 编译时类型检查,避免运行时错误 +- IDE 智能提示,提高开发效率 + +## 五、使用 WebApiClientSlim + +`WebApiClientSlim` 是基于 `System.Net.Http.HttpClient` 的轻量级封装,结合了原生 HttpClient 的兼容性和 TouchSocket 的便利性。 + +### 5.1 创建 WebApiClientSlim + + + +### 5.2 字符串模板调用 + + + +**适用场景**: +- ✅ 需要兼容现有的 `HttpClient` 代码 +- ✅ 希望使用 `IHttpClientFactory` 管理连接池 +- ✅ 需要在 .NET 6.0+ 或 .NET 4.8.1 环境中运行 +- ✅ 想要轻量级封装,同时保留原生 HttpClient 的灵活性 + +**核心特点**: +- 支持字符串模板调用:`GET:/ApiServer/Sum?a={0}&b={1}` +- 自动参数替换和 URL 编码 +- 兼容标准 `HttpClient` 生态(如 Polly、HttpClientFactory 等) +- 支持插件系统,可扩展功能 + +:::tip 提示 + +`WebApiClientSlim` 可以复用已有的 `HttpClient` 实例,遵循微软最佳实践。仅在 .NET 6.0+ 和 .NET 4.8.1 可用。 + +::: + +## 六、使用 DispatchProxy 代理 + +使用 DispatchProxy 代理可以像调用本地方法一样调用远程 WebApi。 + + +### 6.1 定义代理接口 + + + +**说明**: +- 接口方法需要添加 `[WebApi]` 特性,指定 HTTP 方法 +- 使用 `[Router]` 特性自定义路由规则 +- 支持多个 `[Router]` 特性,实现多路由 + +### 6.2 实现代理类 + + + +**说明**: +- 继承 `WebApiDispatchProxy` 或 `RpcDispatchProxy` 基类 +- 实现 `GetClient()` 方法,返回用于通信的客户端 +- 建议复用客户端实例,避免频繁创建连接 + +### 6.3 使用代理调用 + + + +**优点**: +- 类型安全,编译时检查 +- 调用简洁,像本地方法一样 +- 支持 async/await +- 便于单元测试(可以 mock 接口) + +## 七、代码生成 + +TouchSocket 支持通过源生成器或代码生成自动生成客户端代理,避免手动编写调用代码。 + + + +### 7.1 服务端代码生成 + +在服务端注册服务时生成客户端代理代码: + + + +### 7.2 使用生成的代理 + +生成的代理代码包含了所有 WebApi 方法的客户端扩展方法,可以直接使用: + + + +### 7.3 源生成器 + +使用源生成器在编译时生成代理: + +```csharp +[GenerateWebApiProxy] +public partial class ApiServer : SingletonRpcServer +{ + [WebApi(Method = HttpMethodType.Get)] + public int Sum(int a, int b) + { + return a + b; + } +} +``` + +源生成器会自动生成客户端接口,可以用于 DispatchProxy 代理。 + +:::tip 提示 + +更多代码生成的详细信息,请参考 [RPC 代理生成](./rpcgenerateproxy.mdx) 文档。 + +::: + +## 八、客户端插件 + +### 8.1 请求拦截 + +使用插件可以在发送请求前后进行拦截处理,例如添加认证 Token、日志记录等。 + + + +**说明**: +- `IWebApiRequestPlugin`:请求发送前拦截 +- `IWebApiResponsePlugin`:响应接收后拦截 +- `e.IsHttpMessage`:判断是否使用 `System.Net.Http.HttpClient` 作为通讯主体 +- `e.RequestMessage`:`System.Net.Http.HttpRequestMessage` 对象 +- `e.Request`:TouchSocket 的 `HttpRequest` 对象 +- 必须调用 `await e.InvokeNext()` 继续执行链 + +### 8.2 使用插件 + + + +### 8.3 常见应用场景 + +**添加认证 Token:** + +在 `MyWebApiPlugin` 的 `OnWebApiRequest` 方法中添加Token即可(参见上面的插件拦截代码)。 + +**日志记录:** + +在插件的请求和响应方法中添加日志记录即可。 + +**错误重试:** + +在插件的响应方法中根据状态码实现重试逻辑即可。 + +## 九、异常处理 + +### 9.1 基本异常处理 + + + +### 9.2 设置超时 + + + +### 9.3 使用 CancellationToken + + \ No newline at end of file diff --git a/handbook/docs/webapi-context.mdx b/handbook/docs/webapi-context.mdx new file mode 100644 index 000000000..905cb6fed --- /dev/null +++ b/handbook/docs/webapi-context.mdx @@ -0,0 +1,116 @@ +--- +id: webapi-context +title: WebAPI 调用上下文 +--- + +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; + +## 一、调用上下文 + +在 WebAPI 方法中,可以通过 `IWebApiCallContext` 访问请求的详细信息和进行高级操作。 + + + + +### 1.1 获取调用上下文 + + + +**调用上下文提供的信息:** + +- `callContext.Caller` - 客户端连接对象(IP、Port、Id 等) +- `callContext.HttpContext` - HTTP 上下文 +- `callContext.HttpContext.Request` - HTTP 请求对象 +- `callContext.HttpContext.Response` - HTTP 响应对象 + +## 二、客户端信息 + +### 2.1 获取客户端 IP 和端口 + +通过 `Caller` 属性获取客户端连接信息: + + + +**可用信息**: +- `callContext.Caller.IP` - 客户端 IP 地址 +- `callContext.Caller.Port` - 客户端端口 +- `callContext.Caller.Id` - 客户端连接 ID + +## 三、HTTP 上下文 + +### 3.1 访问 HTTP 请求 + +通过 `HttpContext.Request` 访问请求信息: + +- `request.Method` - HTTP 方法(GET、POST 等) +- `request.URL` - 完整 URL +- `request.RelativeURL` - 相对路径 +- `request.Headers` - 请求头集合 +- `request.Query` - 查询参数集合 + +### 3.2 访问 HTTP 响应 + +通过 `HttpContext.Response` 设置响应: + +- `response.SetStatus(code, message)` - 设置状态码 +- `response.Headers[name] = value` - 设置响应头 +- `response.ContentType` - 设置内容类型 +- `response.SetContent(data)` - 设置响应内容 + +## 四、响应定制 + +### 4.1 自定义响应状态码 + + + +### 4.2 设置响应头 + + + +### 4.3 设置 Content-Type + + + +## 五、高级场景 + +### 5.1 WebSocket 升级 + +可以将 HTTP 连接升级为 WebSocket: + + + +### 5.2 流式响应 + +对于大型数据或实时数据,可以使用流式响应: + + + +## 六、常见场景示例 + +### 6.1 获取客户端信息和请求详情 + + + +### 6.2 条件响应 + +根据条件返回不同的响应: + + + +## 七、注意事项 + +### 7.1 响应写入时机 + +- 如果方法有返回值,框架会自动序列化并写入响应 +- 如果使用 `Response.AnswerAsync()` 手动写入,方法应返回 `Task` 或 `void` + +### 7.2 异步操作 + +- 文件操作、流操作建议使用异步方法(`async/await`) +- 避免在 WebAPI 方法中执行长时间阻塞操作 + +### 7.3 资源释放 + +- 对于文件流、数据库连接等资源,确保正确释放 +- 使用 `using` 语句或 `try-finally` 块 diff --git a/handbook/docs/webapi-cors.mdx b/handbook/docs/webapi-cors.mdx new file mode 100644 index 000000000..41df670fa --- /dev/null +++ b/handbook/docs/webapi-cors.mdx @@ -0,0 +1,253 @@ +--- +id: webapi-cors +title: WebAPI 跨域配置 +--- + +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; + +## 一、CORS 跨域 + +CORS(Cross-Origin Resource Sharing,跨域资源共享)允许浏览器跨域访问资源。在 WebAPI 中,可以通过配置 CORS 策略来控制哪些来源可以访问 API。 + +## 二、配置方式 + +### 2.1 方法级 CORS + +在特定方法上启用 CORS: + + + +**说明**: +- `[EnableCors("cors")]` 特性需要配合容器中注册的跨域策略名称使用 +- "cors" 是在容器中注册的跨域策略名称 + +### 2.2 类级 CORS + +在整个类上启用 CORS,所有方法都应用该策略: + +```csharp +[EnableCors("cors")] +public partial class ApiServer : SingletonRpcServer +{ + [WebApi(Method = HttpMethodType.Get)] + public string GetData() + { + return "Data"; + } + + [WebApi(Method = HttpMethodType.Post)] + public string PostData(string data) + { + return $"Received: {data}"; + } +} +``` + +### 2.3 全局 CORS 配置 + +使用插件启用全局 CORS,所有 API 都允许跨域: + +```csharp +.ConfigurePlugins(a => +{ + // 启用全局CORS,允许所有来源 + a.UseCors(policy => + { + policy.AllowAnyOrigin() + .AllowAnyMethod() + .AllowAnyHeader(); + }); + + a.UseWebApi(); + a.UseDefaultHttpServicePlugin(); +}) +``` + +## 三、注册 CORS 策略 + +在容器中注册命名的 CORS 策略: + +```csharp +.ConfigureContainer(a => +{ + // 添加跨域服务 + a.AddCors(corsOption => + { + // 添加名为 "cors" 的跨域策略 + corsOption.Add("cors", corsBuilder => + { + corsBuilder.AllowAnyMethod() + .AllowAnyOrigin(); + }); + }); +}) +``` + +## 四、CORS 策略配置 + +### 4.1 允许所有来源 + +```csharp +corsBuilder.AllowAnyOrigin() + .AllowAnyMethod() + .AllowAnyHeader(); +``` + +### 4.2 限制特定来源 + +```csharp +corsBuilder.AllowOrigins("http://localhost:3000", "https://example.com") + .AllowMethods("GET", "POST") + .AllowHeaders("Content-Type", "Authorization"); +``` + +### 4.3 允许携带凭证 + +```csharp +corsBuilder.AllowOrigins("http://localhost:3000") + .AllowMethods("GET", "POST", "PUT", "DELETE") + .AllowHeaders("Content-Type", "Authorization") + .AllowCredentials(); // 允许携带凭证(Cookie、Authorization header 等) +``` + +### 4.4 设置预检请求缓存时间 + +```csharp +corsBuilder.AllowOrigins("http://localhost:3000") + .AllowMethods("GET", "POST") + .AllowHeaders("Content-Type") + .SetMaxAge(3600); // 预检请求缓存时间(秒) +``` + +## 五、完整示例 + +```csharp +// 服务器端配置 +var service = new HttpService(); +await service.SetupAsync(new TouchSocketConfig() + .SetListenIPHosts(7789) + .ConfigureContainer(a => + { + a.AddRpcStore(store => + { + store.RegisterServer(); + }); + + // 注册跨域策略 + a.AddCors(corsOption => + { + // 策略1:允许所有来源 + corsOption.Add("allowAll", corsBuilder => + { + corsBuilder.AllowAnyOrigin() + .AllowAnyMethod() + .AllowAnyHeader(); + }); + + // 策略2:限制特定来源 + corsOption.Add("restricted", corsBuilder => + { + corsBuilder.AllowOrigins("http://localhost:3000") + .AllowMethods("GET", "POST") + .AllowHeaders("Content-Type", "Authorization") + .AllowCredentials(); + }); + }); + + a.AddConsoleLogger(); + }) + .ConfigurePlugins(a => + { + a.UseWebApi(); + a.UseDefaultHttpServicePlugin(); + })); + +await service.StartAsync(); + +// API 服务 +public partial class ApiServer : SingletonRpcServer +{ + // 使用 allowAll 策略 + [EnableCors("allowAll")] + [WebApi(Method = HttpMethodType.Get)] + public List GetUsers() + { + return new List { "Alice", "Bob", "Charlie" }; + } + + // 使用 restricted 策略 + [EnableCors("restricted")] + [WebApi(Method = HttpMethodType.Post)] + public string CreateUser([FromBody] UserDto user) + { + return $"Created: {user.Name}"; + } +} +``` + +## 六、客户端调用示例 + +### 6.1 JavaScript/Fetch API + +```javascript +// 简单请求 +fetch('http://localhost:7789/apiserver/getusers', { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + } +}) +.then(response => response.json()) +.then(data => console.log(data)); + +// 携带凭证的请求 +fetch('http://localhost:7789/apiserver/createuser', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer token123' + }, + credentials: 'include', // 携带凭证 + body: JSON.stringify({ name: 'Alice', age: 25 }) +}) +.then(response => response.json()) +.then(data => console.log(data)); +``` + +### 6.2 jQuery/AJAX + +```javascript +$.ajax({ + url: 'http://localhost:7789/apiserver/getusers', + type: 'GET', + dataType: 'json', + xhrFields: { + withCredentials: true // 携带凭证 + }, + success: function(data) { + console.log(data); + } +}); +``` + +## 七、注意事项 + +### 7.1 安全考虑 + +- **生产环境**:避免使用 `AllowAnyOrigin()`,应明确指定允许的来源 +- **凭证传递**:使用 `AllowCredentials()` 时,不能使用 `AllowAnyOrigin()`,必须指定具体来源 + +### 7.2 预检请求 + +浏览器对于"非简单请求"会先发送 OPTIONS 预检请求: + +- **简单请求**:GET、HEAD、POST(Content-Type 为 text/plain、multipart/form-data 或 application/x-www-form-urlencoded) +- **非简单请求**:使用了自定义 Header、Content-Type 为 application/json 等 + +`UseDefaultHttpServicePlugin` 会自动处理 OPTIONS 请求。 + +### 7.3 策略选择建议 + +- **开发环境**:可以使用 `AllowAnyOrigin()` 简化开发 +- **测试环境**:使用具体的测试域名 +- **生产环境**:严格限制允许的来源、方法和头部 diff --git a/handbook/docs/webapi-parameter.mdx b/handbook/docs/webapi-parameter.mdx new file mode 100644 index 000000000..acd3c3c25 --- /dev/null +++ b/handbook/docs/webapi-parameter.mdx @@ -0,0 +1,193 @@ +--- +id: webapi-parameter +title: WebAPI 参数绑定 +--- + +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; + +## 一、参数绑定 + +TouchSocket.WebApi 支持从多个来源获取参数。 + + + + +### 1.1 默认参数绑定规则 + +WebAPI 会根据 HTTP 方法和参数类型自动选择绑定源: + +**GET 请求**: +- 所有参数默认从 **Query String** 获取 + +**POST 请求**: +- **简单类型**(int、string、DateTime 等)从 **Query String** 获取 +- **复杂类型**(自定义类、集合等)从 **Request Body** 获取 + +### 1.2 FromQuery - 从查询字符串 + +显式指定从 Query String 获取参数: + + + +**说明**: +- 可以通过 `Name` 属性自定义参数名 +- `[FromQuery(Name = "aa")]` 将参数 `a` 映射到查询字符串的 `aa` + +访问示例:`/ApiServer/SumFromQuery?aa=10&b=20` + +### 1.3 FromHeader - 从请求头 + +从 HTTP Header 获取参数: + + + +请求示例: +```http +GET /ApiServer/SumFromHeader +a: 10 +b: 20 +``` + +### 1.4 FromForm - 从表单 + +从表单数据获取参数: + + + +请求示例: +```http +GET /ApiServer/SumFromForm +Content-Type: application/x-www-form-urlencoded + +a=10&b=20 +``` + +### 1.5 FromBody - 从请求体 + +显式指定从 Request Body 获取参数(JSON 格式): + + + +请求示例: +```http +POST /ApiServer/TestPost +Content-Type: application/json + +{ + "a": 10, + "b": 20 +} +``` + +## 二、参数类型支持 + +### 2.1 基础类型 + +支持所有 C# 基础类型: +- 整数:`int`, `long`, `short`, `byte` +- 浮点:`double`, `float`, `decimal` +- 其他:`bool`, `string`, `DateTime`, `DateTimeOffset`, `Guid` +- 可空类型:`int?`, `DateTime?` 等 + +### 2.2 集合类型 + +支持数组和集合,通过重复参数名传递: + +**示例**: +``` +/api/sumarray?numbers=1&numbers=2&numbers=3 +``` + +### 2.3 复杂对象 + +支持嵌套对象和复杂类型: + + + +## 三、参数特性说明 + +### 3.1 FromQuery + +- **用途**:从 URL 查询字符串获取参数 +- **支持 GET/POST**:是 +- **自定义名称**:支持 `Name` 属性 + +### 3.2 FromHeader + +- **用途**:从 HTTP 请求头获取参数 +- **支持 GET/POST**:是 +- **自定义名称**:支持 `Name` 属性 + +### 3.3 FromForm + +- **用途**:从表单数据获取参数 +- **Content-Type**:`application/x-www-form-urlencoded` 或 `multipart/form-data` +- **支持文件上传**:是(multipart/form-data) + +### 3.4 FromBody + +- **用途**:从请求体获取参数 +- **Content-Type**:通常为 `application/json` +- **序列化**:使用配置的序列化器(JSON/XML) + +## 四、常见场景 + +### 4.1 GET 请求获取简单参数 + +简单参数无需特性标注,自动从 Query 获取: + + + +### 4.2 POST 请求获取对象 + +复杂对象自动从 Body 获取: + + + +### 4.3 混合参数来源 + +同时从多个来源获取参数: + +```csharp +[WebApi(Method = HttpMethodType.Post)] +public string MixedParams( + [FromQuery] int id, + [FromHeader] string authorization, + [FromBody] MyClass data) +{ + return $"ID: {id}, Data: {data.A + data.B}"; +} +``` + +请求示例: +```http +POST /apiserver/mixedparams?id=100 +Authorization: Bearer token123 +Content-Type: application/json + +{ + "a": 10, + "b": 20 +} +``` + +## 五、注意事项 + +### 5.1 参数名称匹配 + +- 参数名称**不区分大小写** +- Query 参数 `?Name=Alice` 可以匹配参数 `string name` + +### 5.2 复杂类型序列化 + +- POST 的复杂对象默认使用 JSON 反序列化 +- 可以通过配置更改序列化器(详见[序列化文档](./webapi-serialization.mdx)) + +### 5.3 必需参数与可选参数 + +- 使用可空类型或默认值定义可选参数: + ```csharp + public int Sum(int a, int b = 0) // b 是可选的 + public string GetUser(int? id = null) // id 是可选的 + ``` diff --git a/handbook/docs/webapi-route.mdx b/handbook/docs/webapi-route.mdx new file mode 100644 index 000000000..c98836178 --- /dev/null +++ b/handbook/docs/webapi-route.mdx @@ -0,0 +1,208 @@ +--- +id: webapi-route +title: WebAPI 路由系统 +--- + +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; + +## 一、路由概述 + +TouchSocket.WebApi 提供了强大且灵活的路由系统,支持多种路由配置方式,包括默认路由、自定义路由模板和正则表达式路由。路由系统能够精确匹配请求 URL 到对应的 API 方法,并提供友好的错误处理机制。 + + + +## 二、默认路由 + +### 2.1 路由格式 + +当没有显式指定路由时,系统会自动使用默认路由规则: + +**默认路由格式**:`/{服务类名}/{方法名}` + +**特点**: +- 不区分大小写 +- 服务类名使用完整类名(包括 "Server" 后缀) +- 方法名即为实际方法名称 + +### 2.2 示例 + + + +**访问路径**: +- `/DemoApiServer/Sum?a=10&b=20` +- `/demoapiserver/sum?a=10&b=20` (大小写不敏感) + +## 三、自定义路由 + +### 3.1 使用 Router 特性 + +使用 `[Router]` 特性可以自定义路由规则,支持占位符替换: + +**占位符说明**: +- `[api]` - 服务类名(使用完整类名) +- `[action]` - 方法名 + +### 3.2 多路由配置 + +一个方法可以配置多个路由路径: + + + +**访问路径**: +- `/ApiServer/Sumab?a=10&b=20` +- `/ApiServer/Sum?a=10&b=20` + +### 3.3 自定义路由路径 + +可以完全自定义路由路径,不使用占位符: + + + +**访问路径**: +- `/api/custom/calculate?x=5&y=3` + +### 3.4 类级别路由 + +`[Router]` 特性可以应用于类级别,为类中的所有方法设置统一的路由前缀: + +```csharp +[Router("/api/v1/[api]")] +public class UserApiServer : SingletonRpcServer +{ + [Router("[action]")] + [WebApi(Method = HttpMethodType.Get)] + public string GetUser(int id) + { + return $"User: {id}"; + } +} +``` + +**访问路径**:`/api/v1/UserApiServer/GetUser?id=1` + +**说明**: +- 方法级别的 `[Router]` 会覆盖类级别的设置 +- 如果方法没有设置 `[Router]`,则会继承类级别的路由模板 + +## 四、正则表达式路由 + +### 4.1 RegexRouter 特性 + +使用 `[RegexRouter]` 特性可以定义基于正则表达式的路由规则,适用于需要动态路径参数的场景。 + + + +**说明**: +- 正则表达式不区分大小写 +- 支持复杂的路由模式匹配 +- 可以通过 `IWebApiCallContext` 获取实际请求的 URL 进行解析 + +### 4.2 正则路由示例 + +**常见正则路由模式**: + +```csharp +// 匹配数字 ID +[RegexRouter(@"^/users/\d+$")] // /users/123 + +// 匹配 GUID +[RegexRouter(@"^/orders/[0-9a-f-]+$")] // /orders/123e4567-e89b + +// 匹配多段路径 +[RegexRouter(@"^/api/v\d+/products/\d+$")] // /api/v1/products/100 +``` + +:::caution 注意 + +正则路由标记的方法,如果没有其他的常规路由的话,在Swagger中将不会被列出。 + +::: + +## 五、路由匹配规则 + +### 5.1 匹配优先级 + +路由系统按照以下优先级进行匹配: + +1. **精确匹配**(字典查询,O(1) 性能) +2. **正则表达式匹配**(按注册顺序依次尝试) + +### 5.2 匹配流程 + +1. 首先尝试精确路由匹配(包括默认路由和 Router 特性定义的路由) +2. 如果精确匹配失败,则尝试正则表达式路由匹配 +3. 如果路由匹配成功但 HTTP 方法不匹配,返回 405 Method Not Allowed +4. 如果没有找到任何匹配的路由,返回 404 Not Found + +### 5.3 路由规范化 + +- **自动补齐斜杠**:路由模板如果没有以 `/` 开始,系统会自动补齐 +- **大小写不敏感**:所有路由匹配均不区分大小写 +- **URL 规范化**:请求 URL 会被转换为小写进行匹配 + +## 六、路由配置最佳实践 + +### 6.1 命名建议 + +- 使用清晰、语义化的路由路径 +- 遵循 RESTful 风格(推荐但非强制) +- 避免过于复杂的路由规则 + +### 6.2 占位符使用 + +- `[api]` 占位符适用于需要包含服务类名的场景 +- `[action]` 占位符适用于需要包含方法名的场景 +- 如果不需要服务类名或方法名,可以使用完全自定义的路由 + +### 6.3 正则路由使用 + +- 仅在需要动态路径参数时使用正则路由 +- 正则表达式应尽量简洁明了 +- 考虑性能影响,正则匹配比精确匹配慢 + +### 6.4 多路由配置 + +- 可以为向后兼容或别名提供多个路由 +- 避免过多的路由别名,保持 API 的简洁性 + +## 七、常见问题 + +### 7.1 路由冲突 + +**问题**:多个方法配置了相同的路由路径和 HTTP 方法。 + +**解决方案**: +- 确保每个路由路径和 HTTP 方法的组合是唯一的 +- 使用不同的 HTTP 方法(GET、POST、PUT、DELETE 等) +- 使用不同的路由路径 + +### 7.2 占位符不生效 + +**问题**:使用了 `[api]` 或 `[action]` 占位符,但路由没有按预期生成。 + +**解决方案**: +- 检查占位符是否被正确包含在方括号中:`[api]` 而不是 `{api}` +- 确认路由模板以 `/` 开始 +- 验证服务类名和方法名是否符合预期 + +### 7.3 正则路由不匹配 + +**问题**:正则表达式路由无法匹配预期的 URL。 + +**解决方案**: +- 检查正则表达式语法是否正确 +- 记住正则路由是不区分大小写的 +- 使用在线正则测试工具验证表达式 +- 确保正则表达式以 `^` 开始,以 `$` 结束(完整匹配) + +## 八、总结 + +TouchSocket.WebApi 的路由系统提供了: + +- **灵活性**:支持默认路由、自定义路由和正则路由 +- **高性能**:精确匹配使用字典查询,性能优异 +- **易用性**:简洁的特性配置,占位符支持 +- **可扩展性**:支持类级别和方法级别的路由配置 + +通过合理使用路由系统,可以构建清晰、易维护的 Web API 接口。 diff --git a/handbook/docs/webapi-serialization.mdx b/handbook/docs/webapi-serialization.mdx new file mode 100644 index 000000000..3401cdf61 --- /dev/null +++ b/handbook/docs/webapi-serialization.mdx @@ -0,0 +1,98 @@ +--- +id: webapi-serialization +title: WebAPI 数据序列化 +--- + +## 一、数据序列化 + +数据序列化用于将对象转换为传输格式(如 JSON、XML)。TouchSocket.WebApi 支持多种序列化格式。 + +### 1.1 默认序列化器 + +框架默认支持以下格式化器,根据 `Accept` 请求头自动选择: + +- **application/json** 或 **text/json**:使用 JSON 格式化(默认:Newtonsoft.Json) +- **application/xml** 或 **text/xml**:使用 XML 格式 +- **text/plain**:使用文本格式化 + +### 1.2 配置序列化器 + +在添加 WebApi 插件时,可以配置序列化器: + +```csharp showLineNumbers +.ConfigurePlugins(a => +{ + a.UseWebApi() + .ConfigureConverter(converter => + { + // 可选:清空现有所有格式化器 + // converter.Clear(); + + // 添加 Json 格式化器(Newtonsoft.Json) + converter.AddJsonSerializerFormatter(new Newtonsoft.Json.JsonSerializerSettings() + { + Formatting = Newtonsoft.Json.Formatting.None + }); + + // 添加 Xml 格式化器 + converter.AddXmlSerializerFormatter(); + }); +}) +``` + +### 1.3 使用 System.Text.Json + +对于需要 AOT 支持的场景,推荐使用 `System.Text.Json`: + +```csharp showLineNumbers +.ConfigurePlugins(a => +{ + a.UseWebApi() + .ConfigureConverter(converter => + { + converter.Clear(); + + converter.AddSystemTextJsonSerializerFormatter(options => + { + options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + options.WriteIndented = false; + }); + }); +}) +``` + +### 1.4 自定义序列化器 + +可以实现自定义的序列化器: + +```csharp showLineNumbers +class MySerializerFormatter : ISerializerFormatter +{ + public int Order { get; set; } + + public bool TryDeserialize(HttpContext state, in string source, Type targetType, out object target) + { + // 实现反序列化逻辑 + throw new NotImplementedException(); + } + + public bool TrySerialize(HttpContext state, in object target, out string source) + { + // 实现序列化逻辑 + throw new NotImplementedException(); + } +} +``` + +添加自定义格式化器: + +```csharp showLineNumbers +.ConfigurePlugins(a => +{ + a.UseWebApi() + .ConfigureConverter(converter => + { + converter.Add(new MySerializerFormatter()); + }); +}) +``` diff --git a/handbook/docs/webapi-staticfiles.mdx b/handbook/docs/webapi-staticfiles.mdx new file mode 100644 index 000000000..0c6b9f567 --- /dev/null +++ b/handbook/docs/webapi-staticfiles.mdx @@ -0,0 +1,641 @@ +--- +id: webapi-staticfiles +title: WebAPI 静态文件服务 +--- + +import Tag from "@site/src/components/Tag.js"; + +## 一、概述 + +TouchSocket.WebApi 可以与静态文件服务插件结合使用,实现 WebAPI 和静态资源托管的一体化解决方案。这对于构建前后端分离应用、提供文档页面、托管资源文件等场景非常有用。 + +:::tip 提示 +静态文件服务功能由 `HttpStaticPagePlugin` 插件提供,详细文档请参考 [HTTP静态页面插件](./httpstaticpageplugin.mdx)。 +::: + +## 二、基本使用 + +### 2.1 同时启用 WebAPI 和静态文件服务 + +```csharp showLineNumbers +var service = new HttpService(); +await service.SetupAsync(new TouchSocketConfig() + .SetListenIPHosts(7789) + .ConfigurePlugins(a => + { + // 启用 WebAPI + a.UseWebApi(); + + // 启用静态文件服务 + a.UseHttpStaticPage() + .AddFolder("wwwroot/"); // 添加静态文件根目录 + + // 默认HTTP插件应最后添加 + a.UseDefaultHttpServicePlugin(); + })); + +await service.StartAsync(); +``` + +目录结构示例: +``` +项目根目录/ +├── wwwroot/ +│ ├── index.html +│ ├── css/ +│ │ └── style.css +│ ├── js/ +│ │ └── app.js +│ └── images/ +│ └── logo.png +└── Program.cs +``` + +访问方式: +- API: `http://localhost:7789/api/users` +- 静态文件: `http://localhost:7789/index.html` +- CSS: `http://localhost:7789/css/style.css` + +## 三、配置静态文件服务 + +### 3.1 添加多个文件夹 + +```csharp showLineNumbers +a.UseHttpStaticPage() + .AddFolder("wwwroot/") // 主要静态文件目录 + .AddFolder("public/") // 公共资源目录 + .AddFolder("uploads/"); // 上传文件目录 +``` + +### 3.2 设置默认文件 + +```csharp showLineNumbers +a.UseHttpStaticPage() + .SetDefaultFile("index.html") // 设置默认首页 + .AddFolder("wwwroot/"); +``` + +访问 `http://localhost:7789/` 会自动返回 `index.html`。 + +### 3.3 URL 重写 + +使用 `SetNavigateAction` 实现 URL 重写: + +```csharp showLineNumbers +a.UseHttpStaticPage() + .SetNavigateAction(request => + { + // 重写规则:访问 /home 时实际返回 /index.html + if (request.RelativeURL.Equals("/home", StringComparison.OrdinalIgnoreCase)) + { + return "/index.html"; + } + + // SPA 应用路由处理:所有未匹配的路由都返回 index.html + if (!Path.HasExtension(request.RelativeURL)) + { + return "/index.html"; + } + + return request.RelativeURL; + }) + .AddFolder("wwwroot/"); +``` + +### 3.4 自定义响应处理 + +使用 `SetResponseAction` 添加自定义响应头或处理: + +```csharp showLineNumbers +a.UseHttpStaticPage() + .SetResponseAction(response => + { + // 添加缓存控制 + response.Headers.Add("Cache-Control", "public, max-age=31536000"); + + // 添加安全头 + response.Headers.Add("X-Content-Type-Options", "nosniff"); + response.Headers.Add("X-Frame-Options", "SAMEORIGIN"); + + // 根据文件类型设置不同的缓存策略 + if (response.ContentType?.Contains("text/html") == true) + { + response.Headers["Cache-Control"] = "no-cache"; + } + }) + .AddFolder("wwwroot/"); +``` + +## 四、与 WebAPI 结合使用场景 + +### 4.1 前后端分离应用 + +#### 后端 API 服务 + +```csharp showLineNumbers +public partial class UserApiServer : SingletonRpcServer +{ + [WebApi(HttpMethodType.Get, Route = "/api/users")] + public List GetUsers() + { + return userService.GetAll(); + } + + [WebApi(HttpMethodType.Post, Route = "/api/users")] + public UserInfo CreateUser([FromBody] UserInfo user) + { + return userService.Create(user); + } +} +``` + +#### 前端静态文件 + +`wwwroot/index.html`: +```html + + + + 用户管理系统 + + + +
+ + + +``` + +`wwwroot/js/app.js`: +```javascript +// 调用后端 API +fetch('/api/users') + .then(response => response.json()) + .then(data => { + console.log('用户列表:', data); + // 渲染用户列表 + }); +``` + +### 4.2 API 文档托管 + +将 Swagger UI 或其他 API 文档作为静态文件托管: + +```csharp showLineNumbers +.ConfigurePlugins(a => +{ + a.UseWebApi(); + + // 托管 API 文档 + a.UseHttpStaticPage() + .SetNavigateAction(request => + { + // /docs 路由重定向到文档首页 + if (request.RelativeURL == "/docs" || request.RelativeURL == "/docs/") + { + return "/docs/index.html"; + } + return request.RelativeURL; + }) + .AddFolder("wwwroot/"); + + a.UseDefaultHttpServicePlugin(); +}) +``` + +### 4.3 文件上传与访问 + +#### 上传 API + +```csharp showLineNumbers +public partial class FileApiServer : SingletonRpcServer +{ + [WebApi(HttpMethodType.Post, Route = "/api/upload")] + public async Task Upload([FromCallContext] IHttpCallContext callContext) + { + var request = callContext.HttpContext.Request; + + // 处理文件上传 + if (request.ContentType?.Contains("multipart/form-data") == true) + { + var file = await request.GetFirstFileAsync(); + var fileName = $"{Guid.NewGuid()}{Path.GetExtension(file.FileName)}"; + var filePath = Path.Combine("uploads", fileName); + + // 保存文件 + await File.WriteAllBytesAsync(filePath, file.Data); + + return new UploadResult + { + Success = true, + Url = $"/uploads/{fileName}", + FileName = fileName + }; + } + + throw new Exception("无效的请求"); + } +} +``` + +#### 配置静态文件访问 + +```csharp showLineNumbers +a.UseHttpStaticPage() + .AddFolder("uploads/"); // 允许访问上传的文件 +``` + +上传后可以通过 `http://localhost:7789/uploads/xxx.jpg` 访问文件。 + +## 五、高级配置 + +### 5.1 MIME 类型配置 + +```csharp showLineNumbers +a.UseHttpStaticPage() + .SetMimeMapping(new Dictionary + { + { ".json", "application/json" }, + { ".wasm", "application/wasm" }, + { ".md", "text/markdown" } + }) + .AddFolder("wwwroot/"); +``` + +### 5.2 文件过滤 + +只允许访问特定类型的文件: + +```csharp showLineNumbers +a.UseHttpStaticPage() + .SetNavigateAction(request => + { + var ext = Path.GetExtension(request.RelativeURL).ToLower(); + + // 只允许特定文件类型 + var allowedExtensions = new[] { ".html", ".css", ".js", ".png", ".jpg", ".gif" }; + + if (!allowedExtensions.Contains(ext)) + { + return null; // 返回 null 表示拒绝访问 + } + + return request.RelativeURL; + }) + .AddFolder("wwwroot/"); +``` + +### 5.3 访问控制 + +```csharp showLineNumbers +a.UseHttpStaticPage() + .SetNavigateAction(request => + { + // 禁止访问敏感目录 + if (request.RelativeURL.StartsWith("/config/") || + request.RelativeURL.StartsWith("/private/")) + { + return null; + } + + return request.RelativeURL; + }) + .SetResponseAction(response => + { + // 添加访问控制头 + response.Headers.Add("Access-Control-Allow-Origin", "*"); + }) + .AddFolder("wwwroot/"); +``` + +### 5.4 压缩支持 + +对静态文件启用压缩: + +```csharp showLineNumbers +a.UseHttpStaticPage() + .SetResponseAction(response => + { + // 对可压缩的内容类型启用压缩 + var compressibleTypes = new[] { "text/", "application/json", "application/javascript" }; + + if (compressibleTypes.Any(t => response.ContentType?.StartsWith(t) == true)) + { + response.Headers.Add("Content-Encoding", "gzip"); + // 实际压缩处理 + } + }) + .AddFolder("wwwroot/"); +``` + +## 六、SPA 应用支持 + +### 6.1 Vue/React/Angular 应用 + +对于单页应用,需要处理路由回退: + +```csharp showLineNumbers +.ConfigurePlugins(a => +{ + a.UseWebApi(); + + a.UseHttpStaticPage() + .SetNavigateAction(request => + { + var url = request.RelativeURL; + + // API 路由直接放行 + if (url.StartsWith("/api/")) + { + return null; // 交给 WebAPI 处理 + } + + // 静态资源文件直接返回 + if (Path.HasExtension(url)) + { + return url; + } + + // 其他所有路由都返回 index.html(SPA 路由) + return "/index.html"; + }) + .SetResponseAction(response => + { + // 对 index.html 禁用缓存 + if (response.ContentType?.Contains("text/html") == true) + { + response.Headers["Cache-Control"] = "no-cache, no-store, must-revalidate"; + } + }) + .AddFolder("wwwroot/"); + + a.UseDefaultHttpServicePlugin(); +}) +``` + +### 6.2 路由优先级 + +确保 API 路由优先于静态文件: + +```csharp showLineNumbers +.ConfigurePlugins(a => +{ + // 1. 先添加 WebAPI(优先匹配 API 路由) + a.UseWebApi(); + + // 2. 再添加 Swagger(如果使用) + a.UseSwagger(); + + // 3. 最后添加静态文件服务 + a.UseHttpStaticPage() + .AddFolder("wwwroot/"); + + // 4. 兜底插件 + a.UseDefaultHttpServicePlugin(); +}) +``` + +## 七、性能优化 + +### 7.1 缓存策略 + +```csharp showLineNumbers +a.UseHttpStaticPage() + .SetResponseAction(response => + { + var url = response.Request.RelativeURL; + + // 对带版本号的资源设置长期缓存 + if (Regex.IsMatch(url, @"\.([\w]+)\.\w+$")) + { + response.Headers["Cache-Control"] = "public, max-age=31536000, immutable"; + } + // 对 HTML 文件不缓存 + else if (url.EndsWith(".html")) + { + response.Headers["Cache-Control"] = "no-cache"; + } + // 其他静态资源短期缓存 + else + { + response.Headers["Cache-Control"] = "public, max-age=86400"; + } + + // 添加 ETag + response.Headers["ETag"] = $"\"{response.GetHashCode()}\""; + }) + .AddFolder("wwwroot/"); +``` + +### 7.2 CDN 配置 + +```csharp showLineNumbers +a.UseHttpStaticPage() + .SetNavigateAction(request => + { + var url = request.RelativeURL; + + // 将特定资源重定向到 CDN + if (url.StartsWith("/assets/")) + { + request.Response.StatusCode = 301; + request.Response.Headers["Location"] = $"https://cdn.example.com{url}"; + return null; + } + + return url; + }) + .AddFolder("wwwroot/"); +``` + +## 八、安全考虑 + +### 8.1 防止目录遍历 + +```csharp showLineNumbers +a.UseHttpStaticPage() + .SetNavigateAction(request => + { + var url = request.RelativeURL; + + // 防止路径穿越攻击 + if (url.Contains("..") || url.Contains("~")) + { + return null; + } + + // 规范化路径 + url = Path.GetFullPath(url).Replace("\\", "/"); + + return url; + }) + .AddFolder("wwwroot/"); +``` + +### 8.2 添加安全响应头 + +```csharp showLineNumbers +a.UseHttpStaticPage() + .SetResponseAction(response => + { + // 防止 XSS + response.Headers["X-Content-Type-Options"] = "nosniff"; + response.Headers["X-XSS-Protection"] = "1; mode=block"; + + // 防止点击劫持 + response.Headers["X-Frame-Options"] = "SAMEORIGIN"; + + // CSP 策略 + response.Headers["Content-Security-Policy"] = "default-src 'self'"; + + // HTTPS 强制 + response.Headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"; + }) + .AddFolder("wwwroot/"); +``` + +### 8.3 文件大小限制 + +```csharp showLineNumbers +a.UseHttpStaticPage() + .SetNavigateAction(request => + { + var filePath = Path.Combine("wwwroot", request.RelativeURL.TrimStart('/')); + + if (File.Exists(filePath)) + { + var fileInfo = new FileInfo(filePath); + + // 限制单个文件大小(例如 10MB) + if (fileInfo.Length > 10 * 1024 * 1024) + { + request.Response.StatusCode = 413; // Payload Too Large + return null; + } + } + + return request.RelativeURL; + }) + .AddFolder("wwwroot/"); +``` + +## 九、常见问题 + +### 9.1 静态文件不显示 + +**问题**:访问静态文件返回 404 + +**解决方案**: +1. 确认文件路径正确,相对于程序运行目录 +2. 检查文件是否被正确复制到输出目录(设置为"始终复制") +3. 确认静态文件插件在 WebAPI 之后添加 +4. 检查 `SetNavigateAction` 是否正确返回路径 + +### 9.2 SPA 路由刷新 404 + +**问题**:单页应用刷新页面时返回 404 + +**解决方案**: +```csharp showLineNumbers +a.UseHttpStaticPage() + .SetNavigateAction(request => + { + // 对于没有扩展名的请求(SPA路由),返回 index.html + if (!Path.HasExtension(request.RelativeURL)) + { + return "/index.html"; + } + return request.RelativeURL; + }) + .AddFolder("wwwroot/"); +``` + +### 9.3 API 和静态文件冲突 + +**问题**:API 路由被静态文件服务拦截 + +**解决方案**: +确保正确的插件顺序: +```csharp showLineNumbers +.ConfigurePlugins(a => +{ + a.UseWebApi(); // 1. 先添加 WebAPI + a.UseHttpStaticPage() // 2. 后添加静态文件 + .AddFolder("wwwroot/"); + a.UseDefaultHttpServicePlugin(); // 3. 最后添加兜底插件 +}) +``` + +或在 `SetNavigateAction` 中排除 API 路由: +```csharp showLineNumbers +a.UseHttpStaticPage() + .SetNavigateAction(request => + { + if (request.RelativeURL.StartsWith("/api/")) + { + return null; // 交给 WebAPI 处理 + } + return request.RelativeURL; + }) + .AddFolder("wwwroot/"); +``` + +## 十、最佳实践 + +### 10.1 文件组织 + +``` +wwwroot/ +├── index.html # 入口文件 +├── favicon.ico # 网站图标 +├── assets/ # 资源目录 +│ ├── css/ +│ ├── js/ +│ ├── images/ +│ └── fonts/ +├── docs/ # 文档目录 +└── downloads/ # 下载文件 +``` + +### 10.2 版本化资源 + +在构建时为资源添加版本号: +```html + + +``` + +### 10.3 开发与生产环境分离 + +```csharp showLineNumbers +#if DEBUG + a.UseHttpStaticPage() + .SetResponseAction(response => + { + // 开发环境:禁用缓存 + response.Headers["Cache-Control"] = "no-cache"; + }) + .AddFolder("wwwroot/"); +#else + a.UseHttpStaticPage() + .SetResponseAction(response => + { + // 生产环境:启用缓存 + response.Headers["Cache-Control"] = "public, max-age=31536000"; + }) + .AddFolder("wwwroot/"); +#endif +``` + +## 十一、总结 + +TouchSocket.WebApi 与静态文件服务的结合提供了完整的 Web 应用解决方案: + +✅ **一体化部署**:API 和静态资源统一部署 +✅ **灵活配置**:支持 URL 重写、响应定制 +✅ **SPA 友好**:完美支持单页应用路由 +✅ **高性能**:内置缓存策略和优化选项 +✅ **安全可靠**:提供多层安全防护 + +这使得 TouchSocket 成为构建现代 Web 应用的理想选择,无论是简单的静态网站还是复杂的前后端分离应用。 + diff --git a/handbook/docs/webapi-swagger.mdx b/handbook/docs/webapi-swagger.mdx new file mode 100644 index 000000000..691589f9a --- /dev/null +++ b/handbook/docs/webapi-swagger.mdx @@ -0,0 +1,62 @@ +--- +id: webapi-swagger +title: WebAPI Swagger文档 +--- + +import Tag from "@site/src/components/Tag.js"; +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import { TouchSocketWebApiSwaggerDefinition } from "@site/src/components/Definition.js"; + +## 一、Swagger 文档支持 + + + +### 1.1 什么是 Swagger + +Swagger是一个用于生成、描述和调用RESTful接口的Web服务。通俗的来讲,Swagger就是将项目中所有(想要暴露的)接口展现在页面上,并且可以进行接口调用和测试的服务。 + + + + +### 1.2 启用 Swagger + +使用非常简单,先安装`TouchSocket.WebApi.Swagger`的包,然后直接UseSwagger即可。 + +```csharp showLineNumbers +var service = new HttpService(); +await service.SetupAsync(new TouchSocketConfig() + .SetListenIPHosts(7789) + .ConfigurePlugins(a => + { + a.UseTcpSessionCheckClear(); + + a.UseWebApi(); + + // 添加 Swagger 支持 + a.UseSwagger(options => + { + options.UseLaunchBrowser(); // 启动时自动打开浏览器 + }); + + // 此插件是http的兜底插件,应该最后添加 + a.UseDefaultHttpServicePlugin(); + })); + +await service.StartAsync(); + +Console.WriteLine("测试swagger http://127.0.0.1:7789/swagger/index.html"); +``` + +访问 Swagger UI: +``` +http://127.0.0.1:7789/swagger +``` + +### 1.3 Swagger 配置选项 + +```csharp showLineNumbers +a.UseSwagger(options => +{ + options.UseLaunchBrowser(); // 自动打开浏览器 +}); +``` \ No newline at end of file diff --git a/handbook/docs/webapi.mdx b/handbook/docs/webapi.mdx index bec70cd4c..cd4ddb5a5 100644 --- a/handbook/docs/webapi.mdx +++ b/handbook/docs/webapi.mdx @@ -1,765 +1,94 @@ ---- +--- id: webapi -title: 产品及架构介绍 +title: WebAPI 概述 --- -import CardLink from "@site/src/components/CardLink.js"; +import Tag from "@site/src/components/Tag.js"; import BilibiliCard from '@site/src/components/BilibiliCard.js'; import { TouchSocketWebApiDefinition } from "@site/src/components/Definition.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; -### 定义 ## 一、说明 -WebApi是**通用**的Rpc调用,与**编程语言无关**,与**操作系统无关**。其路由机制模仿AspNetCore,可实现很多路由机制。但是因为http兼容性错综复杂,所以目前TouchSocket的WebApi仅支持**GET**、**POST**函数。使用体验接近于AspNetCore。 +TouchSocket.WebApi 是一款高性能、轻量级的 WebAPI 框架,提供类似于 ASP.NET Core 的开发体验,同时具有更高的性能表现。它支持多种调用方式,包括浏览器访问、HttpClient 调用、代理生成等,是构建高性能 Web API 服务的理想选择。 - + -## 二、特点 +## 二、核心特性 -- 高性能,100个客户端,10w次调用,仅用时17s。 -- **全异常反馈** 。 -- 支持大部分路由规则。 -- 支持js、Android等调用。 +- **高性能** + 性能优于传统 HTTP 框架,采用高效的请求处理机制 +- **轻量级** + 最小化依赖,快速启动,资源占用少 -## 三、定义服务 +- **简单易用** + 类似 ASP.NET Core 的 API 设计,降低学习成本 -在**服务器**端中新建一个类,继承于**SingletonRpcServer**类(或实现IRpcServer),然后在该类中写**公共方法**,并用**WebApi**属性标签标记。 +- **灵活路由** + 支持特性路由、默认路由、正则路由等多种路由方式 -```csharp showLineNumbers -public partial class ApiServer : SingletonRpcServer -{ - private readonly ILog m_logger; +- **多种调用方式** + 支持浏览器、HttpClient、代理生成等多种调用方式 - public ApiServer(ILog logger) - { - this.m_logger = logger; - } +- **完整功能** + 内置鉴权、跨域、文件上传下载等常用功能 - [WebApi(Method = HttpMethodType.Get)] - public int Sum(int a, int b) - { - return a + b; - } -} +- **AOT 支持** + 完整支持 Native AOT 编译,进一步提升性能 + +- **Swagger 集成** + 内置 Swagger 支持,自动生成 API 文档 + +## 三、快速开始 + +### 3.1 安装 NuGet 包 + +```bash +Install-Package TouchSocket.WebApi ``` -## 四、启动服务器 +### 3.2 定义 WebAPI 服务类 -更多注册Rpc的方法请看[注册Rpc服务](./rpcregister.mdx) +创建一个服务类,继承自 `SingletonRpcServer`、`ScopedRpcServer` 或 `TransientRpcServer`,然后在方法上添加 `[WebApi]` 特性。 -```csharp showLineNumbers -var service = new HttpService(); -await service.SetupAsync(new TouchSocketConfig() - .SetListenIPHosts(7789) - .ConfigureContainer(a => - { - a.AddConsoleLogger(); - a.AddRpcStore(store => - { - store.RegisterServer();//注册服务 - }); - }) - .ConfigurePlugins(a => - { - a.UseCheckClear(); + - a.UseWebApi(); + - //此插件是http的兜底插件,应该最后添加。作用是当所有路由不匹配时返回404.且内部也会处理Option请求。可以更好的处理来自浏览器的跨域探测。 - a.UseDefaultHttpServicePlugin(); - })); -await service.StartAsync(); +**服务生命周期说明:** -Console.WriteLine("以下连接用于测试webApi"); -Console.WriteLine($"使用:http://127.0.0.1:7789/ApiServer/Sum?a=10&b=20"); -``` +- **SingletonRpcServer** - 单例服务,整个应用程序生命周期内只创建一个实例 +- **ScopedRpcServer** - 作用域服务,每个连接的请求创建一个新实例(⚠️注意:此处的 scope 划分和 ASP.NET 中的 scope 不同) +- **TransientRpcServer** - 瞬态服务,每次调用都创建一个新实例 + + + +### 3.3 启动 WebAPI 服务器 + + + +**配置说明:** + +- `SetListenIPHosts(7789)` - 设置监听端口 +- `AddConsoleLogger()` - 添加控制台日志 +- `AddRpcStore()` - 配置 RPC 存储,用于注册服务 +- `RegisterServer()` - 注册服务类 +- `UseWebApi()` - 启用 WebAPI 插件 +- `UseDefaultHttpServicePlugin()` - 添加默认 HTTP 插件,处理 404 等情况 - -## 五、参数规则 +### 3.4 访问 API -### 5.1 Get规则 +服务启动后,可以通过浏览器或其他 HTTP 客户端访问: -使用`Get`进行请求时,服务方法可以声明多个参数,但是每个参数都必须是**简单类型**(例如:int、string、DateTime等)。 - -```csharp showLineNumbers -[WebApi(Method = HttpMethodType.Get)] -public int Get(int a) -{ - return a; -} - -[WebApi(Method = HttpMethodType.Get)] -public int Sum(int a, int b) -{ - return a + b; -} ``` - -### 5.2 Post规则 - -使用`Post`进行请求时,服务方法可以声明多个参数,但是当参数是基础类型或者字符串类型时,它也会来源于`Query`参数。 -同时,有且只有当最后一个参数为**其他类型**时,才会从`Body`解析。 - -例如: - -以下参数依然来自`Query`,`Body`为空也可以。 - -```csharp showLineNumbers -[WebApi(Method = HttpMethodType.Post)] -public int Sum(int a, int b) -{ - return a + b; -} +http://127.0.0.1:7789/DemoApiServer/Sum?a=10&b=20 ``` - -当最后一个参数为**其他类型**时,它将会从`Body`解析。 - -例如: - -以下参数,前两个来自`Query`,`MyClass`将从`Body`解析。 - -```csharp showLineNumbers -[WebApi(Method = HttpMethodType.Post)] -public int Sum(int a, int b, MyClass myClass) -{ - return a + b; -} -``` - - - -### 5.3 特性规则 - -`WebApi`支持使用特性来定制参数规则。目前支持`[FromQuery]`、`[FromHeader]`、`[FromForm]`、`[FromBody]`等。 - -例如: - -[FromQuery] - -```csharp showLineNumbers -[WebApi(Method = HttpMethodType.Get)] -public int SumFromQuery([FromQuery] int a, [FromQuery] int b) -{ - return a + b; -} -``` - -[FromHeader] - -```csharp showLineNumbers -[WebApi(Method = HttpMethodType.Get)] -public int SumFromHeader([FromHeader] int a, [FromHeader] int b) -{ - return a + b; -} -``` - -[FromForm] - -```csharp showLineNumbers -[WebApi(Method = HttpMethodType.Post)] -public int SumFromForm([FromForm] int a, [FromForm] int b) -{ - return a + b; -} -``` - -[FromBody] - -```csharp showLineNumbers -[WebApi(Method = HttpMethodType.Post)] -public int SumFromBody([FromBody] MyClass myClass) -{ - return myClass.A + myClass.B; -} -``` - - - -:::tip 提示 - -当使用特性时,可以重新指定参数名,例如: - -```csharp showLineNumbers -[WebApi(Method = HttpMethodType.Get)] -public int SumFromQuery([FromQuery(Name ="aa")] int a, [FromQuery] int b) -{ - return a + b; -} -``` - -::: - -## 六、路由规则 - -框架的路由规则比较简单,默认情况下,以服务的名称+方法名称作为路由。 - -例如下列: - -将会以`/ApiServer/Sum`为请求url(不区分大小写)。 - -```csharp showLineNumbers -public class ApiServer : SingletonRpcServer -{ - [WebApi(Method = HttpMethodType.Get)] - public int Sum(int a, int b) - { - return a + b; - } -} -``` - -当需要定制路由消息时,可用`[api]`替代服务名,`[action]`替代方法名。 - -例如下列: - -将会以`user/ApiServer/test/Sum`为请求url(不区分大小写)。 - -```csharp showLineNumbers -[Router("/user/[api]/test/[action]")] -public class ApiServer : SingletonRpcServer -{ - [WebApi(Method = HttpMethodType.Get)] - public int Sum(int a, int b) - { - return a + b; - } -} -``` - - - -:::tip 提示 - -`Router`特性不仅可以用于服务,也可以用于方法。而且可以多个使用。 - -::: - -## 七、调用上下文 - -框架中,每个请求都会产生一个**调用上下文(ICallContext)**,这个上下文可以获取到当前请求的**客户端**、**服务**、**方法**、**参数**等。 - -通用调用上下文可以参阅[通用Rpc调用上下文](./rpcallcontext.mdx)。 - -下列将介绍`WebApi`的专用调用上下文。 - -首先,在服务方法中,只要有参数类型为`IWebApiCallContext`(或者`ICallContext`),框架会自动注入当前调用上下文。 - -例如: - -```csharp {2} showLineNumbers -[WebApi(Method = HttpMethodType.Get)] -public int SumCallContext(IWebApiCallContext callContext, int a, int b) -{ - return a + b; -} -``` - -:::tip 提示 - -调用上下文的位置没有限制,但是建议在方法参数的**最前面**,这样即使对`Post`,也不会有额外的判断压力。 - -::: - - - - - -### 7.1 获取当前客户端信息 - -一般来说,调用上下文的`Caller`就是实际通信的客户端,也就是`HttpSessionClient`。 - -```csharp showLineNumbers -if (callContext.Caller is IHttpSessionClient httpSessionClient) -{ - Console.WriteLine($"IP:{httpSessionClient.IP}"); - Console.WriteLine($"Port:{httpSessionClient.Port}"); - Console.WriteLine($"Id:{httpSessionClient.Id}"); -} -``` - -### 7.2 获取HttpContext - -`WebApi`的调用上下文,除了`Caller`,还有`HttpContext`。 - -```csharp showLineNumbers -//http内容 -var httpContext = callContext.HttpContext; - -//http请求 -var request = httpContext.Request; -//http响应 -var response = httpContext.Response; -``` - -:::tip 提示 - -当获取到`HttpContext`后,就可以做`HttpService`的所有功能。例如:读取请求头,修改响应头,修改响应内容,修改响应状态码、响应WebSocket连接等。具体可以参考[创建HttpService](./httpservice.mdx)和[WebSocketService](./websocketservice.mdx)。 - -::: - -## 八、调用服务 - -### 8.1 直接调用 - -直接调用,则是不使用**任何代理**,直接Call Rpc,使用比较简单,**浏览器**也能直接调用实现。 - -【Url请求】 - -```scheme -http://127.0.0.1:7789/ApiServer/Sum?a=10&b=20 -``` - -### 8.2 框架HttpClient调用 - -框架内置的`HttpClient`,可以参考[HttpClient](./httpclient.mdx)的使用,此处做一个简单使用示例。 - -```csharp {4} showLineNumbers -var client = new HttpClient(); -await client.ConnectAsync("127.0.0.1:7789"); - -string responseString = await client.GetStringAsync("/ApiServer/Sum?a=10&b=20"); -``` - -### 8.3 内置WebApiClient调用 - -内置`WebApi`的客户端和`HttpClient`基本一致,但是封装了一些`Rpc`的调用接口,可以更加方便的执行一些操作。 - -【创建WebApi客户端】 - -```csharp {8} showLineNumbers -var client = new WebApiClient(); -await client.SetupAsync(new TouchSocketConfig() - .SetRemoteIPHost("127.0.0.1:7789") - .ConfigurePlugins(a => - { - a.Add(); - })); -await client.ConnectAsync(); -``` - -【GET调用】 - -在使用`GET`进行调用时,其`InvokeKey`直接使用Url即可,然后还需要构建一个`WebApiRequest`,然后使用`Invoke`(或者`InvokeT`)进行调用。 - -例如: - -```csharp showLineNumbers -var request = new WebApiRequest(); -request.Method = HttpMethodType.Get; -request.Querys = new KeyValuePair[] { new KeyValuePair("a", "10"), new KeyValuePair("b", "20") }; - -var sum1 = client.InvokeT("/ApiServer/Sum", invokeOption_30s, request); -Console.WriteLine($"Get调用成功,结果:{sum1}"); -``` - -【POST调用】 - -例如: - -```csharp showLineNumbers -var requestForPost = new WebApiRequest(); -requestForPost.Method = HttpMethodType.Post; -requestForPost.Body = new MyClass() { A = 10, B = 20 }; - -var sum2 = client.InvokeT("/ApiServer/TestPost", invokeOption_30s, requestForPost); -Console.WriteLine($"Post调用成功,结果:{sum2}"); -``` - -### 8.4 Dotnet自带HttpClient调用 - -`Dotnet`自带`HttpClient`则是通过连接池的方式访问。详情看[HttpClient](https://learn.microsoft.com/zh-cn/dotnet/api/system.net.http.httpclient) - -【创建客户端】 - -```csharp showLineNumbers -var client = new WebApiClientSlim(new System.Net.Http.HttpClient()); -await client.SetupAsync(new TouchSocketConfig() - .SetRemoteIPHost("http://127.0.0.1:7789")); -``` - -调用方法同上。 - -:::info 备注 - -按照微软建议,`HttpClient`应该保证整个程序中单实例使用,所以可以在`WebApiClientSlim`构造函数中传入已存在的对象。 - -::: - -:::caution 注意 - -`WebApiClientSlim`仅在net6.0+,net481可用。 - -::: - - -### 8.5 生成代理调用 - -使用`WebApi`客户端进行调用时,其使用规则相较比较复杂的。但是在实际使用时,基本上是不需要手动书写调用代码的。下面将介绍代理生成调用。 - -在服务器端,注册完服务后,就可以生成客户端调用代码了。详细的操作可以查看[服务端代理生成](./rpcgenerateproxy.mdx) - -```csharp {8-9} -a.UseWebApi() -.ConfigureRpcStore(store => -{ - store.RegisterServer();//注册服务 - -#if DEBUG - //下列代码,会生成客户端的调用代码。 - var codeString = store.GetProxyCodes("WebApiProxy", typeof(WebApiAttribute)); - File.WriteAllText("../../../WebApiProxy.cs", codeString); -#endif -}); -``` - -然后把生成的.cs文件复制(或链接)到客户端项目。然后客户端直接使用同名`扩展方法`即可调用。 - -```csharp showLineNumbers -var sum3 =await client.SumAsync(10,20); -``` - - - -### 8.6 使用DispatchProxy代理调用 - -使用DispatchProxy代理调用,可以实现动态代理,详情请看[DispatchProxy代理生成](./rpcgenerateproxy.mdx) - -首先,需要声明一个基类,用于通讯基础。 - -```csharp showLineNumbers -/// -/// 新建一个类,继承WebApiDispatchProxy,亦或者RpcDispatchProxy基类。 -/// 然后实现抽象方法,主要是能获取到调用的IRpcClient派生接口。 -/// -class MyWebApiDispatchProxy : WebApiDispatchProxy -{ - private readonly WebApiClient m_client; - - public MyWebApiDispatchProxy() - { - this.m_client = CreateWebApiClient(); - } - - private static WebApiClient CreateWebApiClient() - { - var client = new WebApiClient(); - await client.SetupAsync(new TouchSocketConfig() - .SetRemoteIPHost("127.0.0.1:7789") - .ConfigurePlugins(a => - { - a.UseReconnection(); - })); - await client.ConnectAsync(); - Console.WriteLine("连接成功"); - return client; - } - - public override IWebApiClientBase GetClient() - { - return m_client; - } -} -``` - -然后按照服务,定义一个相同的代理接口。 - -```csharp showLineNumbers -interface IApiServer -{ - [Router("ApiServer/[action]")] - [WebApi(Method = HttpMethodType.Get)] - int Sum(int a, int b); -} -``` - -:::tip 提示 - -路由规则和服务端相同。 - -::: - -最后生成代理,并按照接口调用。 - -```csharp {1} -IApiServer api = MyWebApiDispatchProxy.Create(); -while (true) -{ - Console.WriteLine("请输入两个数,中间用空格隔开,回车确认"); - string str = Console.ReadLine(); - var strs = str.Split(' '); - int a = int.Parse(strs[0]); - int b = int.Parse(strs[1]); - - var sum = api.Sum(a, b); - Console.WriteLine(sum); -} -``` - -:::tip - -`WebApi`的调用,除了上述,还支持源生成调用,更多请看[源生成调用](./rpcgenerateproxy.mdx)。 - -::: - - -## 九、数据格式化 - -数据格式化,就是对`WebApi`执行前后的数据进行序列化和反序列化。一般来说,常用的格式化类型有两种,一种是`JSON`,另一种是`XML`。具体的,应该根据`Accept`请求头中的数据格式来选择。默认情况下: - -- 如果Accept请求头中包含`application/json`、`text/json`,则使用JSON格式化。 -- 如果Accept请求头中包含`application/xml`、`text/xml`,则使用XML格式化。 -- 如果Accept请求头中包含`text/plain`,则使用文本格式化(如果是复杂类型,则依然会按照Json或则Xml)。 - -### 9.1 格式化配置 - -在添加`WebApi`插件时,可以通过`ConfigureConverter`方法来配置数据格式化。 - -```csharp {4} showLineNumbers -.ConfigurePlugins(a => -{ - a.UseWebApi() - .ConfigureConverter(converter => - { - //配置转换器 - - //converter.Clear();//可以选择性的清空现有所有格式化器 - - //添加Json格式化器,可以自定义Json的一些设置 - converter.AddJsonSerializerFormatter(new Newtonsoft.Json.JsonSerializerSettings() {Formatting= Newtonsoft.Json.Formatting.None } ); - - //添加Xml格式化器 - converter.AddXmlSerializerFormatter(); - }); -}) -``` - -### 9.2 自定义格式化器 - -TouchSocket的`WebApi`插件支持自定义格式化器。 - -新建一个类,实现`ISerializerFormatter`接口,并实现相关方法。 - -```csharp showLineNumbers -class MySerializerFormatter : ISerializerFormatter -{ - public int Order { get; set; } - - public bool TryDeserialize(HttpContext state, in string source, Type targetType, out object target) - { - //反序列化 - throw new NotImplementedException(); - } - - public bool TrySerialize(HttpContext state, in object target, out string source) - { - //序列化 - throw new NotImplementedException(); - } -} -``` - -添加格式化器 - -```csharp {6} showLineNumbers -.ConfigurePlugins(a => -{ - a.UseWebApi() - .ConfigureConverter(converter => - { - converter.Add(new MySerializerFormatter()); - }); -}) -``` - -## 十、鉴权、授权 - -### 10.1 请求插件实现 - -在AspNetCore中,鉴权与授权是通过中间件实现的。而TouchSocket的WebApi(HttpService)在设计时也可以使用类似方式实现该功能。下列就以伪代码jwt鉴权示例。 - -首先声明一个鉴权插件。用于判断当前请求header中是否包含授权header。 - -```csharp showLineNumbers -/// -/// 鉴权插件 -/// -class AuthenticationPlugin : PluginBase, IHttpPlugin -{ - public async Task OnHttpRequest(IHttpSessionClient client, HttpContextEventArgs e) - { - string aut = e.Context.Request.Headers["Authorization"]; - if (aut.IsNullOrEmpty())//授权header为空 - { - await e.Context.Response - .SetStatus(401, "授权失败") - .AnswerAsync(); - return; - } - - //伪代码,假设使用jwt解码成功。那就执行下一个插件。 - //if (jwt.Encode(aut)) - //{ - // 此处可以做一些授权相关的。 - //} - await e.InvokeNext(); - } -} -``` - -然后添加使用插件即可。 - -```csharp showLineNumbers -.ConfigurePlugins(a => -{ - a.UseCheckClear(); - - a.Add(); - - a.UseWebApi(); - - ... -}) -``` - -:::caution 注意 - -鉴权插件的添加,应该在UseWebApi之前。这样才能保证api的安全性。 - -::: - - -### 10.2 Rpc Aop实现 - -WebApi也属于Rpc的行列,所以在执行时,也可以在Rpc的Aop中实现鉴权。具体请看[Rpc服务AOP](./rpcactionfilter.mdx) - - -## 十一、跨域 - -在`WebApi`中的跨域,除了[Cors跨域](./cors.mdx)全局设置之外,还支持特性设置,进行更细粒度的控制。 - -所以,首先添加跨域服务是必须的。 - -```csharp {4} showLineNumbers -.ConfigureContainer(a => -{ - //添加跨域服务 - a.AddCors(corsOption => - { - //添加跨域策略,后续使用policyName即可应用跨域策略。 - corsOption.Add("cors", corsBuilder => - { - corsBuilder.AllowAnyMethod() - .AllowAnyOrigin(); - }); - }); -}) -``` - -然后,在WebApi中使用特性进行跨域设置。 - -```csharp {3} showLineNumbers -public partial class ApiServer : SingletonRpcServer -{ - [EnableCors("cors")]//使用跨域 - [WebApi(Method = HttpMethodType.Get)] - public int Sum(int a, int b) - { - return a + b; - } -} -``` - -:::tip 提示 - -`EnableCors`特性,不仅可以用于方法,还支持服务类,接口直接使用。 - -::: - -## 十二、AOT相关 - -`WebApi`组件目前已**完全支持**使用AOT进行编译。具体使用步骤如下: - -首先,使用通用主机模型进行新建项目。详情查看[通用主机模型](./generichost.mdx)。 - -然后基本代码如下: - -```csharp showLineNumbers -var builder = Host.CreateApplicationBuilder(args); - -builder.Services.ConfigureContainer(a => -{ - a.AddRpcStore(store => - { - store.RegisterServer();//注册服务 - }); - - a.AddAspNetCoreLogger(); -}); - -builder.Services.AddServiceHostedService(config => -{ - config.SetListenIPHosts(7789) - .ConfigurePlugins(a => - { - a.UseCheckClear(); - - a.UseWebApi(); - }); -}); - -var host = builder.Build(); -host.Run(); -``` - -然后需要解决支持Aot的序列化问题。目前仅支持`System.Text.Json`。所以需要新建一个序列化上下文。然后把需要序列化的类型添加到`JsonSerializable`中。 - -```csharp showLineNumbers -[JsonSerializable(typeof(MyClass))]//实际类型1 -[JsonSerializable(typeof(MySum))]//实际类型2 -internal partial class AppJsonSerializerContext : JsonSerializerContext -{ - -} -``` - -:::info 信息 - -`JsonSerializable`中,可以添加多个类型。具体要添加哪些类型要看你服务中端需要序列化的类型。**一般来说需要包含服务方法中的所有参数类型和返回值类型**。 - -::: - -然后使用序列化上下文。 - -```csharp {5-12} showLineNumbers -.ConfigurePlugins(a => -{ - ... - a.UseWebApi() - .ConfigureConverter(converter => - { - converter.Clear(); - converter.AddSystemTextJsonSerializerFormatter(options => - { - options.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default); - }); - }); -}) -``` - - - -## 十三、本文示例Demo - - - - diff --git a/handbook/docs/websocketclient.mdx b/handbook/docs/websocketclient.mdx index 7348a2cdb..13ae7c336 100644 --- a/handbook/docs/websocketclient.mdx +++ b/handbook/docs/websocketclient.mdx @@ -6,671 +6,245 @@ title: 创建WebSocket客户端 import Tag from "@site/src/components/Tag.js"; import CardLink from "@site/src/components/CardLink.js"; import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; import { TouchSocketHttpDefinition } from "@site/src/components/Definition.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; + -### 定义 +## 一、说明 -## 一、可配置项 +`WebSocketClient`是WebSocket系客户端基类,它直接参与WebSocket的连接、握手、发送、接收、处理、断开等。它基于HTTP协议升级而来,与服务器的`HttpSessionClient`中的`WebSocket`实例是一一对应的通信关系。 -继承[HttpClient](./httpclient.mdx) +## 二、特点 -## 二、支持插件接口 +- 简单易用,支持ws和wss协议。 +- 支持自定义握手请求头。 +- 内置心跳机制(Ping/Pong)。 +- 支持文本和二进制数据传输。 +- 支持大数据包的中继传输。 +- 支持断线重连机制。 +- 内存池支持,高性能处理。 +- 基于委托、插件驱动,支持AOP编程。 + +## 三、产品应用场景 + +- 实时通信应用:聊天室、在线客服、即时消息推送。 +- 在线协作:多人在线编辑、实时数据同步。 +- 物联网通信:设备状态监控、远程控制。 +- 游戏开发:实时游戏数据交换。 +- 金融交易:实时股票行情、交易数据推送。 +- 实时监控:系统监控、数据大屏展示。 + +## 四、可配置项 + +`WebSocketClient`继承自[HttpClient](./httpclient.mdx),所以支持所有[HttpClient](./httpclient.mdx)的配置项,无特有配置。 + +## 五、支持插件接口 | 插件方法| 功能 | | --- | --- | -| IWebSocketHandshakingPlugin | 当收到握手请求之前,可以进行连接验证等 | -| IWebSocketHandshakedPlugin | 当成功握手响应之后 | -| IWebSocketReceivedPlugin | 当收到Websocket的数据报文 | -| IWebSocketClosingPlugin | 当收到关闭请求时,如果对方直接断开连接,此方法则不会触发。 | -| IWebSocketClosedPlugin | 当WebSocket连接断开时触发,无论是否正常断开。但如果是断网等操作,可能不会立即执行,需要结合心跳操作和`UseCheckClear`插件来进行清理。 | +| IWebSocketConnectingPlugin | 当收到握手请求之前,可以进行连接验证、自定义请求头等 | +| IWebSocketConnectedPlugin | 当成功握手响应之后触发 | +| IWebSocketReceivedPlugin | 当收到WebSocket的数据报文时触发 | +| IWebSocketClosingPlugin | 当收到关闭请求时触发,如果对方直接断开连接,此方法则不会触发 | +| IWebSocketClosedPlugin | 当WebSocket连接断开时触发,无论是否正常断开。但如果是断网等操作,可能不会立即执行,需要结合心跳操作和CheckClear插件来进行清理 | -## 三、创建客户端 +## 六、创建WebSocket客户端 -### 3.1 创建常规客户端 +### 6.1 简单创建 -```csharp showLineNumbers -var client = new WebSocketClient(); -await client.SetupAsync(new TouchSocketConfig() - .SetRemoteIPHost("ws://127.0.0.1:7789/ws") - .ConfigureContainer(a => - { - a.AddConsoleLogger(); - })); -await client.ConnectAsync(); -client.Logger.Info("连接成功"); -``` +简单的处理逻辑可通过**Received**等委托直接实现。 -### 3.2 创建WSs客户端 + -**当需要连接到由证书机构颁发的网址(例如:小程序、物联网等)时,仅需要设置带有`wss`的url即可。** + -```csharp showLineNumbers -wss://127.0.0.1:7789/ws -``` +### 6.2 继承实现 -**当连接自定义证书的Ssl:wss://127.0.0.1:7789/ws** +一般继承实现的话,可以从`WebSocketClient`继承。 -```csharp showLineNumbers -var client = new WebSocketClient(); + -await client.SetupAsync(new TouchSocketConfig() - .SetRemoteIPHost(new IPHost("wss://127.0.0.1:7789/ws")) - .SetClientSslOption( - new ClientSslOption() - { - ClientCertificates = new X509CertificateCollection() { new X509Certificate2("RRQMSocket.pfx", "RRQMSocket") }, - SslProtocols = SslProtocols.Tls12, - TargetHost = "127.0.0.1", - CertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => { return true; } - })); +### 6.3 创建WSS客户端 -await client.ConnectAsync(); +当需要连接到由证书机构颁发的网址(例如:小程序、物联网等)时,仅需要设置带有`wss`的url即可。当需要连接到自签名证书的网站时,则需要额外设置,详情请参考[HttpClient证书配置](./httpclient.mdx)。 -Console.WriteLine("连接成功"); -``` - -:::caution 注意 - -当使用域名连接时,`TargetHost`为域名,例如连接到`IPHost("wss://baidu.com")`时,`TargetHost`应当填写:`baidu.com` - -::: - -## 四、连接服务器 +## 七、连接服务器 `WebSocketClient`可以使用默认配置直接连接到服务器,同时也支持使用多种方法定义连接。 -### 4.1 直接连接 +### 7.1 直接连接 使用`url`直接建立连接,这一般是服务器也只是普通的`ws`服务器的情况下。 -```csharp showLineNumbers -var client = new WebSocketClient(); -await client.SetupAsync(new TouchSocketConfig() - .ConfigureContainer(a => - { - a.AddConsoleLogger(); - }) - .SetRemoteIPHost("ws://127.0.0.1:7789/ws")); -await client.ConnectAsync(); + -client.Logger.Info("通过ws://127.0.0.1:7789/ws连接成功"); -``` - -### 4.2 带Query参数连接 +### 7.2 带Query参数连接 带`Query`参数连接,实际上还是通过`url`直接连接。 -```csharp showLineNumbers -var client = new WebSocketClient(); -await client.SetupAsync(new TouchSocketConfig() - .ConfigureContainer(a => - { - a.AddConsoleLogger(); - }) - .SetRemoteIPHost("ws://127.0.0.1:7789/wsquery?token=123456")); -await client.ConnectAsync(); + -client.Logger.Info("通过ws://127.0.0.1:7789/wsquery?token=123456连接成功"); - -``` - -### 4.3 使用特定Header连接 +### 7.3 使用特定Header连接 一般的,当某些服务器安全级别较高时,可能会定制特定的`header`用于验证连接。 -```csharp showLineNumbers -var client = new WebSocketClient(); -await client.SetupAsync(new TouchSocketConfig() - .ConfigureContainer(a => - { - a.AddConsoleLogger(); - }) - .ConfigurePlugins(a => - { - a.Add(typeof(IWebSocketHandshakingPlugin), async (IWebSocket client, HttpContextEventArgs e) => - { - e.Context.Request.Headers.Add("token", "123456"); - await e.InvokeNext(); - }); - }) - .SetRemoteIPHost("ws://127.0.0.1:7789/wsheader")); -await client.ConnectAsync(); + -client.Logger.Info("通过ws://127.0.0.1:7789/wsheader连接成功"); -``` - -:::tip 提示 - -实际上`OnWebSocketHandshaking`就是插件委托,也可以自己封装到插件使用。 - -::: - -### 4.4 使用Post方式连接 +### 7.4 使用Post方式连接 `WebSocket`默认情况下是基于`Get`方式连接的,但是在一些更特殊的情况下,需要以`Post`,甚至其他方式连接,那么可以使用以下方式实现。 -```csharp showLineNumbers -using var client = new WebSocketClient(); -await client.SetupAsync(new TouchSocketConfig() - .ConfigureContainer(a => - { - a.AddConsoleLogger(); - }) - .ConfigurePlugins(a => - { - a.Add(typeof(IWebSocketHandshakingPlugin), async (IWebSocket client, HttpContextEventArgs e) => - { - e.Context.Request.Method = HttpMethod.Post;//将请求方法改为Post - await e.InvokeNext(); - }); - }) - .SetRemoteIPHost("ws://127.0.0.1:7789/postws")); -await client.ConnectAsync(); - -client.Logger.Info("通过ws://127.0.0.1:7789/postws连接成功"); -``` + :::tip 提示 使用此方式时,基本上就能完全定制请求连接了。比如一些`Cookie`等。 +::: + +## 八、接收数据 + +在`WebSocketClient`中,接收数据的方式有很多种。多种方式可以组合使用。 + +### 8.1 Received委托处理 + +当使用`WebSocketClient`创建客户端时,内部已经定义好了一个外置委托`Received`,可以通过该委托直接接收数据。 + + + +### 8.2 继承WebSocketClient重写接收逻辑 + +如6.2所示,如果需要更自定义的接收逻辑,可以从`WebSocketClient`继承,然后重写`OnWebSocketReceived`方法。 + +### 8.3 使用插件接收数据 + +使用插件接收消息是最推荐的方式,它提供了高度解耦和灵活的数据处理能力。 + +**(1)定义插件:** + + + +**(2)配置使用插件:** + + + +:::tip 提示 + +在客户端端,默认情况下插件的所有函数都是线程安全的。 + +::: + +### 8.4 异步阻塞接收 + +异步阻塞接收是通过直接调用`WebSocket`的`ReadAsync`方法来同步阻塞式读取数据。这种方式的特点是能在代码上下文中直接获取数据,便于处理复杂的数据逻辑。 + +一般的,在刚连接成功调用`ReadAsync`方法来接收数据。 + + + + + +:::info 信息 + +`ReadAsync`方式是**异步非阻塞**的接收方式,不会占用线程资源,只会阻塞当前`Task`。因此可以大量使用,不需要考虑性能问题。 + +::: + +:::caution 注意 + +使用`ReadAsync`方式会终止触发`IWebSocketReceivedPlugin`插件。 + +::: + +### 8.5 接收中继数据 + +WebSocket在接收大数据时,可能会分包接收。可以通过`WSDataFrame.Opcode`的值是不是`WSDataType.Cont`来判断是不是分包数据。 + +分包数据的处理方式有很多,下面提供一种内存缓存的方式: + + + +:::caution 注意 + +内存缓存的方式适合数据量不大的场景,如果数据量较大,建议使用其他缓存等方式。 + ::: - -## 五、发送数据 +## 九、发送数据 客户端定义了一些发送方法,方便开发者快速发送数据。 -### 5.1 发送文本类消息 +### 9.1 发送文本消息 -```csharp showLineNumbers -await client.SendAsync("Text"); -``` + -### 5.2 发送二进制消息 +### 9.2 发送二进制消息 -```csharp showLineNumbers -await client.SendAsync(new byte[10]); -``` + + +### 9.3 发送自定义数据帧 + + -### 5.3 直接发送自定义构建的数据帧 -```csharp showLineNumbers -using (var frame = new WSDataFrame()) -{ - frame.Opcode = WSDataType.Text; - frame.FIN = true; - frame.RSV1 = true; - frame.RSV2 = true; - frame.RSV3 = true; - frame.AppendText("I"); - frame.AppendText("Love"); - frame.AppendText("U"); - await client.SendAsync(frame); -} -``` :::info 备注 -此部分功能就需要你对`WebSocket`有充分了解才可以操作。 +此部分功能需要对`WebSocket`协议有充分了解才可以操作。 -::: +::: -## 六、接收数据 +### 9.4 发送Ping、Pong消息 -### 6.1 订阅Received事件实现 - -```csharp showLineNumbers -client.Received = async (c, e) => -{ - switch (e.DataFrame.Opcode) - { - case WSDataType.Cont: - break; - case WSDataType.Text: - break; - case WSDataType.Binary: - break; - case WSDataType.Close: - break; - case WSDataType.Ping: - break; - case WSDataType.Pong: - break; - default: - break; - } - - await e.InvokeNext(); -}; -``` - -### 6.2 使用插件实现 推荐 - -【定义插件】 -```csharp showLineNumbers -public class MyWebSocketPlugin : PluginBase, IWebSocketReceivedPlugin -{ - private readonly ILog m_logger; - - public MyWebSocketPlugin(ILog logger) - { - this.m_logger = logger; - } - public async Task OnWebSocketReceived(IWebSocket client, WSDataFrameEventArgs e) - { - switch (e.DataFrame.Opcode) - { - case WSDataType.Cont: - m_logger.Info($"收到中间数据,长度为:{e.DataFrame.PayloadLength}"); - - return; - - case WSDataType.Text: - m_logger.Info(e.DataFrame.ToText()); - - if (!client.Client.IsClient) - { - client.SendAsync("我已收到"); - } - return; - - case WSDataType.Binary: - if (e.DataFrame.FIN) - { - m_logger.Info($"收到二进制数据,长度为:{e.DataFrame.PayloadLength}"); - } - else - { - m_logger.Info($"收到未结束的二进制数据,长度为:{e.DataFrame.PayloadLength}"); - } - return; - - case WSDataType.Close: - { - m_logger.Info("远程请求断开"); - client.Close("断开"); - } - return; - - case WSDataType.Ping: - break; - - case WSDataType.Pong: - break; - - default: - break; - } - - await e.InvokeNext(); - } -} -``` - -【使用】 - -```csharp {10} -var client = new WebSocketClient(); -await client.SetupAsync(new TouchSocketConfig() - .SetRemoteIPHost("ws://127.0.0.1:7789/ws") - .ConfigureContainer(a => - { - a.AddConsoleLogger(); - }) - .ConfigurePlugins(a => - { - a.Add(); - })); -await client.ConnectAsync(); -``` - -### 6.3 使用WebSocket显式ReadAsync - -```csharp showLineNumbers -using (var client = GetClient()) -{ - //当WebSocket想要使用ReadAsync时,需要设置此值为true - client.AllowAsyncRead = true; - - while (true) - { - using (var receiveResult = await client.ReadAsync(CancellationToken.None)) - { - if (receiveResult.IsClosed) - { - //断开连接了 - break; - } - - //判断是否为最后数据 - //例如发送方发送了一个10Mb的数据,接收时可能会多次接收,所以需要此属性判断。 - if (receiveResult.DataFrame.FIN) - { - if (receiveResult.DataFrame.IsText) - { - Console.WriteLine($"WebSocket文本:{receiveResult.DataFrame.ToText()}"); - } - } - } - } -} -``` - -:::info 信息 - -`ReadAsync`的方式是属于**同步不阻塞**的接收方式。他不会单独占用线程,只会阻塞当前`Task`。所以可以大量使用,不需要考虑性能问题。同时,`ReadAsync`的好处就是单线程访问上下文,这样在处理ws分包时是非常方便的。 - -::: - -:::caution 注意 - -使用该方式,会阻塞`IWebSocketHandshakedPlugin`的插件传递。在收到`WebSocket`消息的时候,不会再触发插件。 - -::: - - - - -### 6.4 接收中继数据 - -`WebSocket`协议本身是支持超大数据包的,但是这些包不会一次性接收,而是分多次接收的,同时会通过`Opcode`来表明其为中继数据。 - - - -下面将演示接收文本数据。 - -【方法1】 - -```csharp {22-46} showLineNumbers -//当WebSocket想要使用ReadAsync时,需要设置此值为true -client.AllowAsyncRead = true; - -//此处即表明websocket已连接 - -MemoryStream stream = default;//中继包缓存 -var isText = false;//标识是否为文本 -while (true) -{ - using (var receiveResult = await client.ReadAsync(CancellationToken.None)) - { - if (receiveResult.IsCompleted) - { - break; - } - - var dataFrame = receiveResult.DataFrame; - var data = receiveResult.DataFrame.PayloadData; - - switch (dataFrame.Opcode) - { - case WSDataType.Cont: - { - //如果是非net6.0即以上,即:NetFramework平台使用。原因是stream不支持span写入 - //var segment = data.AsSegment(); - //stream.Write(segment.Array, segment.Offset, segment.Count); - - //如果是net6.0以上,直接写入span即可 - stream.Write(data.Span); - - //收到的是中继包 - if (dataFrame.FIN)//判断是否为最终包 - { - //是 - - if (isText)//判断是否为文本 - { - this.m_logger.Info($"WebSocket文本:{Encoding.UTF8.GetString(stream.ToArray())}"); - } - else - { - this.m_logger.Info($"WebSocket二进制:{stream.Length}长度"); - } - } - } - break; - case WSDataType.Text: - { - if (dataFrame.FIN)//判断是不是最后的包 - { - //是,则直接输出 - //说明上次并没有中继数据缓存,直接输出本次内容即可 - this.m_logger.Info($"WebSocket文本:{dataFrame.ToText()}"); - } - else - { - isText = true; - - //否,则说明数据太大了,分中继包了。 - //则,初始化缓存容器 - stream ??= new MemoryStream(); - - //下面则是缓存逻辑 - - //如果是非net6.0即以上,即:NetFramework平台使用。原因是stream不支持span写入 - //var segment = data.AsSegment(); - //stream.Write(segment.Array, segment.Offset, segment.Count); - - //如果是net6.0以上,直接写入span即可 - stream.Write(data.Span); - } - } - break; - case WSDataType.Binary: - { - if (dataFrame.FIN)//判断是不是最后的包 - { - //是,则直接输出 - //说明上次并没有中继数据缓存,直接输出本次内容即可 - this.m_logger.Info($"WebSocket二进制:{data.Length}长度"); - } - else - { - isText = false; - - //否,则说明数据太大了,分中继包了。 - //则,初始化缓存容器 - stream ??= new MemoryStream(); - - //下面则是缓存逻辑 - - //如果是非net6.0即以上,即:NetFramework平台使用。原因是stream不支持span写入 - //var segment = data.AsSegment(); - //stream.Write(segment.Array, segment.Offset, segment.Count); - - //如果是net6.0以上,直接写入span即可 - stream.Write(data.Span); - } - } - break; - case WSDataType.Close: - break; - case WSDataType.Ping: - break; - case WSDataType.Pong: - break; - default: - break; - } - } -} -``` - -或者可以使用内置的扩展方法来进行一次性接收。 - -例如一次性接收文本: - -```csharp showLineNumbers - var str = await client.ReadStringAsync(); -``` - -一次性接收二进制数据: - -```csharp showLineNumbers -using (MemoryStream stream=new MemoryStream()) -{ - var str = await client.ReadBinaryAsync(stream); -} -``` - -:::caution 注意 - -`ReadStringAsync`或者`ReadBinaryAsync`,都只接收对应的数据类型,如果收到非匹配数据则会抛出异常。 - -::: - -【方法2】 - -使用消息组合器。如果想在`OnWebSocketReceived`进行合并消息,则可以使用该方法。 - -```csharp {30-74} showLineNumbers -public class MyWebSocketPlugin : PluginBase, IWebSocketReceivedPlugin -{ - public MyWebSocketPlugin(ILog logger) - { - this.m_logger = logger; - } - - private readonly ILog m_logger; - - public async Task OnWebSocketReceived(IWebSocket client, WSDataFrameEventArgs e) - { - switch (e.DataFrame.Opcode) - { - case WSDataType.Close: - { - this.m_logger.Info("远程请求断开"); - await client.CloseAsync("断开"); - } - return; - - case WSDataType.Ping: - this.m_logger.Info("Ping"); - await client.PongAsync();//收到ping时,一般需要响应pong - break; - - case WSDataType.Pong: - this.m_logger.Info("Pong"); - break; - - default: - { - - //其他报文,需要考虑中继包的情况。所以需要手动合并 WSDataType.Cont类型的包。 - //或者使用消息合并器 - - //获取消息组合器 - var messageCombinator = client.GetMessageCombinator(); - - try - { - //尝试组合 - if (messageCombinator.TryCombine(e.DataFrame, out var webSocketMessage)) - { - //组合成功,必须using释放模式 - using (webSocketMessage) - { - //合并后的消息 - var dataType = webSocketMessage.Opcode; - - //合并后的完整消息 - var data = webSocketMessage.PayloadData; - - if (dataType == WSDataType.Text) - { - //按文本处理 - } - else if (dataType == WSDataType.Binary) - { - //按字节处理 - } - else - { - //可能是其他自定义协议 - } - } - } - } - catch (Exception ex) - { - this.m_logger.Exception(ex); - messageCombinator.Clear();//当组合发生异常时,应该清空组合器数据 - } - - } - break; - } - - await e.InvokeNext(); - } -} -``` - -:::caution 注意 - -使用消息组合器,实际上是由框架缓存了数据,所以,整体数据不要太大,不然可能会有爆内存的风险。 - -::: - - -## 七、其他操作 - -### 7.1 握手机制 - -`WebSocket`拥有独立的握手机制,直接获取`Online`属性即可。 - -### 7.2 Ping机制 - -`WebSocket`有自己的`Ping`、`Pong`机制。所以直接调用已有方法即可。 - -```csharp showLineNumbers -await client.PingAsync(); -await client.PongAsync(); -``` + + :::tip 建议 `WebSocket`是双向通讯,所以支持客户端和服务器双向操作`Ping`和`Pong`报文。但是一般来说都是客户端执行`Ping`,服务器回应`Pong`。 -::: +::: -### 7.3 断线重连 +### 9.5 发送大数据(分包) -`WebSocket`断线重连,可以直接使用插件。 +发送大数据时,可能需要分包发送,可以使用`SendAsync`的重载方法,设置`FIN`标志。 -```csharp showLineNumbers -.ConfigurePlugins(a => -{ - a.UseWebSocketReconnection(); -}) -``` + - +## 十、连接管理 -## 八、关闭连接 +### 10.1 连接状态检查 + +`WebSocket`拥有独立的握手机制,可以通过`Online`属性检查连接状态。 + +### 10.2 关闭连接 在使用`WebSocket`时,如果想主动关闭连接,可以使用`CloseAsync`方法,同时可以携带一个关闭原因。 -默认关闭状态码为1000。意为:正常关闭。 +默认关闭状态码为1000,意为:正常关闭。 -```csharp showLineNumbers -await webSocket.CloseAsync("关闭"); -``` - -如果你想使用其他状态码,可以参考如下代码。 - -```csharp showLineNumbers -await webSocket.CloseAsync(WebSocketCloseStatus.EndpointUnavailable,"关闭");//状态码为1001,意为:服务端不可用。 -``` + -## 九、本文示例Demo +### 10.3 断线重连 - +`WebSocket`断线重连,可以直接使用插件。 + + + + + + + +## 十一、示例Demo + + diff --git a/handbook/docs/websocketdescription.mdx b/handbook/docs/websocketdescription.mdx index 86cd2e4e6..ab7bd586c 100644 --- a/handbook/docs/websocketdescription.mdx +++ b/handbook/docs/websocketdescription.mdx @@ -6,7 +6,7 @@ title: 产品及架构介绍 import BilibiliCard from '@site/src/components/BilibiliCard.js'; import { TouchSocketHttpDefinition } from "@site/src/components/Definition.js"; -### 定义 + diff --git a/handbook/docs/websocketservice.mdx b/handbook/docs/websocketservice.mdx index 06e65c753..fb5f34673 100644 --- a/handbook/docs/websocketservice.mdx +++ b/handbook/docs/websocketservice.mdx @@ -3,919 +3,229 @@ id: websocketservice title: 创建WebSocket服务器 --- -import CardLink from "@site/src/components/CardLink.js"; +import Tag from "@site/src/components/Tag.js"; import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; import { TouchSocketHttpDefinition } from "@site/src/components/Definition.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; +import CardLink from "@site/src/components/CardLink.js"; + -### 定义 ## 一、说明 -`WebSocket`是基于`Http`协议的升级协议,所以应当挂载在`http`服务器执行。 +`WebSocket`服务器是基于`HTTP`协议升级而来的长连接通信协议服务器。它继承自`HttpService`,在完成HTTP握手后,通过协议升级建立WebSocket连接。每个成功连接的客户端都会在服务器端创建一个对应的`HttpSessionClient`实例,后续的所有WebSocket通信都通过该实例完成。 +## 二、特点 -## 二、可配置项 +- 基于HTTP协议升级,支持标准WebSocket协议。 +- 支持文本、二进制以及其他Type数据传输。 +- 内置心跳机制(Ping/Pong)。 +- 支持数据帧分包和组合。 +- 支持WSS(WebSocket Secure)加密连接。 +- 支持多种连接验证方式。 +- 高性能异步处理。 +- 基于插件驱动,支持AOP编程。 -继承[HttpService](./httpservice.mdx) +## 三、产品应用场景 +- 实时通信应用:聊天室、在线客服、实时协作等。 +- 实时数据推送:股票行情、游戏数据、监控数据等。 +- 物联网设备通信:传感器数据上报、设备控制等。 +- Web应用实时交互:在线编辑器、实时画板等。 -## 三、支持插件接口 +## 四、服务器架构 + +### 4.1 连接架构 + +WebSocket服务器基于HTTP服务器,当收到WebSocket握手请求时,会将HTTP连接升级为WebSocket连接。每个WebSocket连接对应一个`HttpSessionClient`实例。 + +```mermaid +flowchart TD; + HttpService-->HttpSessionClient-1; + HttpService-->HttpSessionClient-2; + HttpService-->HttpSessionClient-3; + HttpSessionClient-1-->WebSocketClient-1; + HttpSessionClient-2-->WebSocketClient-2; + HttpSessionClient-3-->WebSocketClient-3; +``` + +### 4.2 协议升级流程 + +1. 客户端发送HTTP握手请求 +2. 服务器验证握手请求 +3. 服务器响应握手成功 +4. 连接升级为WebSocket协议 +5. 开始WebSocket数据帧通信 + +## 五、可配置项 + +继承[HttpService](./httpservice.mdx)的所有配置项,无特殊配置。 + +## 六、支持插件接口 | 插件方法| 功能 | | --- | --- | -| IWebSocketHandshakingPlugin | 当收到握手请求之前,可以进行连接验证等 | -| IWebSocketHandshakedPlugin | 当成功握手响应之后 | +| IWebSocketConnectingPlugin | 当收到握手请求之前,可以进行连接验证等 | +| IWebSocketConnectedPlugin | 当成功握手响应之后 | | IWebSocketReceivedPlugin | 当收到Websocket的数据报文 | | IWebSocketClosingPlugin | 当收到关闭请求时触发。如果对方直接断开连接,则此方法则不会触发。 | | IWebSocketClosedPlugin | 当WebSocket连接断开时触发,无论是否正常断开。但如果是断网等操作,可能不会立即执行,需要结合心跳操作和CheckClear插件来进行清理。 | -## 四、创建WebSocket服务 +## 七、创建WebSocket服务器 -### 4.1 简单直接创建 +### 7.1 简单直接创建 -可以使用WebSocket插件,直接指定一个特殊`url`路由,来完全接收WebSocket连接。 - -然后通过插件来接收数据。(例:下列接收数据) - -```csharp showLineNumbers -var service = new HttpService(); -await service.SetupAsync(new TouchSocketConfig()//加载配置 - .SetListenIPHosts(7789) - .ConfigureContainer(a => - { - a.AddConsoleLogger(); - }) - .ConfigurePlugins(a => - { - a.UseWebSocket()//添加WebSocket功能 - .SetWSUrl("/ws")//设置url直接可以连接。 - .UseAutoPong();//当收到ping报文时自动回应pong - })); - -await service.StartAsync(); - -service.Logger.Info("服务器已启动"); -``` +最简单的方式是使用`WebSocket`插件,直接指定`URL`路由来接收`WebSocket`连接。 -### 4.2 验证连接 + -可以对连接的`Url`、`Query`、`Header`等参数进行验证,然后决定是否执行`WebSocket`连接。 +### 7.2 验证连接 -```csharp showLineNumbers -var service = new HttpService(); -await service.SetupAsync(new TouchSocketConfig()//加载配置 - .SetListenIPHosts(7789) - .ConfigureContainer(a => - { - a.AddConsoleLogger(); - }) - .ConfigurePlugins(a => - { - a.UseWebSocket()//添加WebSocket功能 - .SetVerifyConnection(VerifyConnection) - .UseAutoPong();//当收到ping报文时自动回应pong - })); - -await service.StartAsync(); - -service.Logger.Info("服务器已启动"); -``` - -```csharp showLineNumbers -/// -/// 验证websocket的连接 -/// -/// -/// -/// -private static Task VerifyConnection(IHttpSessionClient client, HttpContext context) -{ - if (!context.Request.IsUpgrade())//如果不包含升级协议的header,就直接返回false。 - { - return false; - } - if (context.Request.UrlEquals("/ws"))//以此连接,则直接可以连接 - { - return true; - } - else if (context.Request.UrlEquals("/wsquery"))//以此连接,则需要传入token才可以连接 - { - if (context.Request.Query.Get("token") == "123456") - { - return true; - } - else - { - await context.Response - .SetStatus(403, "token不正确") - .AnswerAsync(); - } - } - else if (context.Request.UrlEquals("/wsheader"))//以此连接,则需要从header传入token才可以连接 - { - if (context.Request.Headers.Get("token") == "123456") - { - return true; - } - else - { - await context.Response - .SetStatus(403, "token不正确") - .AnswerAsync(); - } - } - return false; -} -``` - -### 4.3 通过WebApi创建 - -通过WebApi的方式会更加灵活,也能很方便的获得Http相关参数。还能实现多个Url的连接路由。 - -实现步骤: - -1. 配置`WebApi`相关,详情请看[WebApi](./webapi.mdx) -2. 在插件中接收`WebSocket`连接。 - -```csharp {8-11,15} showLineNumbers -var service = new HttpService(); -await service.SetupAsync(new TouchSocketConfig()//加载配置 - .SetListenIPHosts(7789) - .ConfigureContainer(a => - { - a.AddConsoleLogger(); - - a.AddRpcStore(store => - { - store.RegisterServer();//注册服务 - }); - }) - .ConfigurePlugins(a => - { - a.UseWebApi(); - })); - -await service.StartAsync(); - -service.Logger.Info("服务器已启动"); -``` +可以对连接的`URL`、`Query`参数、`Header`等进行验证,决定是否允许WebSocket连接。 - -【接收连接】 - -```csharp {16} showLineNumbers -public class MyApiServer : SingletonRpcServer -{ - private readonly ILog m_logger; - - public MyApiServer(ILog logger) - { - this.m_logger = logger; - } - - [Router("/[api]/[action]")] - [WebApi(Method = HttpMethodType.Get)] - public async Task ConnectWS(IWebApiCallContext callContext) - { - if (callContext.Caller is HttpSessionClient sessionClient) - { - var result=await sessionClient.SwitchProtocolToWebSocketAsync(callContext.HttpContext); - if (result.IsSuccess) - { - m_logger.Info("WS通过WebApi连接"); - var webSocket = sessionClient.WebSocket; - } - } - } -} -``` - -或者接收到连接后,直接使用`WebSocket`接收。这样如果想直接在`WebApi`返回数据也比较方便。 - -```csharp {16-43} showLineNumbers -public class MyApiServer : SingletonRpcServer -{ - private readonly ILog m_logger; - - public MyApiServer(ILog logger) - { - this.m_logger = logger; - } - - [Router("/[api]/[action]")] - [WebApi(Method = HttpMethodType.Get)] - public async Task ConnectWS(IWebApiCallContext callContext) - { - if (callContext.Caller is HttpSessionClient sessionClient) - { - if (await sessionClient.SwitchProtocolToWebSocketAsync(callContext.HttpContext)) - { - m_logger.Info("WS通过WebApi连接"); - var webSocket = sessionClient.WebSocket; - - webSocket.AllowAsyncRead = true; - - while (true) - { - using (var tokenSource=new CancellationTokenSource(TimeSpan.FromSeconds(30))) - { - using (var receiveResult = await webSocket.ReadAsync(tokenSource.Token)) - { - if (receiveResult.IsCompleted) - { - //webSocket已断开 - return; - } - - //webSocket数据帧 - var dataFrame = receiveResult.DataFrame; - - //此处可以处理数据 - } - } - - } - } - } - } -} -``` - -### 4.4 通过Http上下文直接创建 - -使用上下文直接创建的优点在于能更加个性化的实现`WebSocket`的连接。 - -```csharp showLineNumbers -class MyHttpPlugin : PluginBase, IHttpPlugin -{ - public async Task OnHttpRequest(IHttpSessionClient client, HttpContextEventArgs e) - { - if (e.Context.Request.UrlEquals("/GetSwitchToWebSocket")) - { - var result = await client.SwitchProtocolToWebSocketAsync(e.Context); - return; - } - await e.InvokeNext(); - } -} -``` - -### 4.5 创建基于Ssl的WebSocket服务 - -创建`WSs`服务器时,其他配置不变,只需要在`config`中配置`SslOption`代码即可,放置了一个自制`Ssl`证书,密码为“RRQMSocket”以供测试。使用配置非常方便。 - -```csharp showLineNumbers -var config = new TouchSocketConfig(); -config.SetServiceSslOption(new ServiceSslOption() //Ssl配置,当为null的时候,相当于创建了ws服务器,当赋值的时候,相当于wss服务器。 - { - Certificate = new X509Certificate2("RRQMSocket.pfx", "RRQMSocket"), - SslProtocols = SslProtocols.Tls12 - }); -``` - - -## 五、接收消息 - -WebSocket服务器接收消息,目前有两种方式。第一种就是通过订阅`IWebSocketReceivedPlugin`插件完全异步的接收消息。第二种就是调用`WebSocket`,然后调用`ReadAsync`方法异步阻塞式读取。 - - - -### 5.1 简单接收消息 - -【定义插件】 -```csharp showLineNumbers -public class MyWebSocketPlugin : PluginBase, IWebSocketReceivedPlugin -{ - private readonly ILog m_logger; - - public MyWebSocketPlugin(ILog logger) - { - this.m_logger = logger; - } - public async Task OnWebSocketReceived(IWebSocket client, WSDataFrameEventArgs e) - { - switch (e.DataFrame.Opcode) - { - case WSDataType.Cont: - m_logger.Info($"收到中间数据,长度为:{e.DataFrame.PayloadLength}"); - - return; - - case WSDataType.Text: - m_logger.Info(e.DataFrame.ToText()); - - if (!client.Client.IsClient) - { - client.SendAsync("我已收到"); - } - return; - - case WSDataType.Binary: - if (e.DataFrame.FIN) - { - m_logger.Info($"收到二进制数据,长度为:{e.DataFrame.PayloadLength}"); - } - else - { - m_logger.Info($"收到未结束的二进制数据,长度为:{e.DataFrame.PayloadLength}"); - } - return; - - case WSDataType.Close: - { - m_logger.Info("远程请求断开"); - client.Close("断开"); - } - return; - - case WSDataType.Ping: - break; - - case WSDataType.Pong: - break; - - default: - break; - } - - await e.InvokeNext(); - } -} -``` - -【使用】 - -```csharp {12} -var service = new HttpService(); -await service.SetupAsync(new TouchSocketConfig()//加载配置 - .SetListenIPHosts(7789) - .ConfigureContainer(a => - { - a.AddConsoleLogger(); - }) - .ConfigurePlugins(a => - { - a.UseWebSocket()//添加WebSocket功能 - .SetWSUrl("/ws"); - a.Add();//自定义插件。 - })); - -await service.StartAsync(); -``` + :::tip 提示 -插件的所有函数,都是可能被并发执行的,所以应当做好线程安全。 +在验证过程中,如果`url`不匹配,或者不包含升级协议头的话,一般不需要额外处理,直接返回`false`即可。随后这个请求会被当作普通的`HTTP`请求处理。 + +如果`url`正确,但是其他鉴权没通过时,才需要直接进行`Http`响应。 ::: -### 5.2 WebSocket显式ReadAsync +### 7.3 其他方式创建 -`WebSocket`显式`ReadAsync`数据,实际上是直接通过`WebSocket`直接循环读取数据。 +实际上,只要在升级协议后,能访问到`HttpContext`,即可通过`SwitchProtocolToWebSocketAsync`方式创建`WebSocket`连接更加灵活,可以方便地获取HTTP参数,实现多个URL的连接路由。 -一般的,如果`WebSocket`是常规连接,则可能需要使用`IWebSocketHandshakedPlugin`插件,在`握手成功`的后直接读取数据。 - -如果连接是通过`WebApi`、或者`Http上下文`创建的连接,则可以直接使用`ReadAsync`。 - -总之要能拿到`WebSocket`对象,则可以直接使用。 - -和`简单接收消息`相比,使用`ReadAsync`接收消息可以直接访问代码上下文资源,这可能在处理`中继数据`时是方便的。 - -```csharp showLineNumbers -class MyReadWebSocketPlugin : PluginBase, IWebSocketHandshakedPlugin -{ - private readonly ILog m_logger; - - public MyReadWebSocketPlugin(ILog logger) - { - this.m_logger = logger; - } - public async Task OnWebSocketHandshaked(IWebSocket client, HttpContextEventArgs e) - { - //当WebSocket想要使用ReadAsync时,需要设置此值为true - client.AllowAsyncRead = true; - - //此处即表明websocket已连接 - while (true) - { - using (var receiveResult = await client.ReadAsync(CancellationToken.None)) - { - - if (receiveResult.IsCompleted) - { - break; - } - - //判断是否为最后数据 - //例如发送方发送了一个10Mb的数据,接收时可能会多次接收,所以需要此属性判断。 - if (receiveResult.DataFrame.FIN) - { - if (receiveResult.DataFrame.IsText) - { - m_logger.Info($"WebSocket文本:{receiveResult.DataFrame.ToText()}"); - } - } - - } - } - - //此处即表明websocket已断开连接 - m_logger.Info("WebSocket断开连接"); - await e.InvokeNext(); - } -} -``` - - - -【使用】 - -```csharp {16} -private static HttpService CreateHttpService() -{ - var service = new HttpService(); - await service.SetupAsync(new TouchSocketConfig()//加载配置 - .SetListenIPHosts(7789) - .ConfigureContainer(a => - { - a.AddConsoleLogger(); - }) - .ConfigurePlugins(a => - { - a.UseWebSocket()//添加WebSocket功能 - .SetWSUrl("/ws")//设置url直接可以连接。 - .UseAutoPong();//当收到ping报文时自动回应pong - - a.Add(); - })); - - await service.StartAsync(); - - service.Logger.Info("服务器已启动"); - service.Logger.Info("直接连接地址=>ws://127.0.0.1:7789/ws"); - return service; -} -``` + :::info 信息 -`ReadAsync`的方式是属于**同步不阻塞**的接收方式(和当下Aspnetcore模式一样)。他不会单独占用线程,只会阻塞当前`Task`。所以可以大量使用,不需要考虑性能问题。同时,`ReadAsync`的好处就是单线程访问上下文,这样在处理ws分包时是非常方便的。 +`SwitchProtocolToWebSocketAsync`后的`WebSocket`实例还是会放置在所在的`HttpSessionClient`中,可以通过`HttpSessionClient.WebSocket`获取到。 ::: +### 7.4 创建基于SSL的WebSocket服务(WSS) + +创建WSS(WebSocket Secure)服务器,只需在配置中添加SSL选项。详情: [Http服务器配置Ssl](./httpservice.mdx) + + +## 八、接收消息 + +WebSocket服务器有多种接收消息的方式,可以根据不同的使用场景选择合适的方法。 + + + +### 8.1 插件方式接收消息 + +使用插件接收消息是最推荐的方式,它提供了高度解耦和灵活的数据处理能力。 + +**(1)定义插件:** + + + +**(2)配置使用插件:** + + + +:::tip 提示 + +在服务器端,默认情况下插件的所有函数都可能被并发执行,因此应当做好线程安全处理。更多详情请参考:[插件开发使用指南](./pluginsmanager.mdx) + +::: + +### 8.2 异步阻塞接收 + +异步阻塞接收是通过直接调用`WebSocket`的`ReadAsync`方法来同步阻塞式读取数据。这种方式的特点是能在代码上下文中直接获取数据,便于处理复杂的数据逻辑。 + +一般的,在`IWebSocketConnectedPlugin`插件中(即刚连接成功)调用`ReadAsync`方法来接收数据。 + + + + + +:::info 信息 + +`ReadAsync`方式是**异步非阻塞**的接收方式,不会占用线程资源,只会阻塞当前`Task`。因此可以大量使用,不需要考虑性能问题。 + +::: + :::caution 注意 -使用该方式,会阻塞`IWebSocketHandshakedPlugin`的插件传递。在收到`WebSocket`消息的时候,不会再触发插件。 +使用`ReadAsync`方式会阻塞`IWebSocketConnectedPlugin`的插件传递链,在收到`WebSocket`消息时不会触发`IWebSocketReceivedPlugin`插件。 -::: +::: -### 5.3 接收中继数据 +### 8.3 接收中继数据 -`WebSocket`协议本身是支持超大数据包的,但是这些包不会一次性接收,而是分多次接收的,同时会通过`Opcode`来表明其为中继数据。 +WebSocket在接收大数据时,可能会分包接收。可以通过`WSDataFrame.Opcode`的值是不是`WSDataType.Cont`来判断是不是分包数据。 - +分包数据的处理方式有很多,下面提供一种内存缓存的方式: -下面将演示接收文本数据。 - -【方法1】 - -使用`ReadAsync`,在`OnWebSocketHandshaked`组合消息。 - -```csharp {33-52} showLineNumbers -internal class MyReadTextWebSocketPlugin : PluginBase, IWebSocketHandshakedPlugin -{ - private readonly ILog m_logger; - - public MyReadTextWebSocketPlugin(ILog logger) - { - this.m_logger = logger; - } - - public async Task OnWebSocketHandshaked(IWebSocket client, HttpContextEventArgs e) - { - //当WebSocket想要使用ReadAsync时,需要设置此值为true - client.AllowAsyncRead = true; - - //此处即表明websocket已连接 - - MemoryStream stream = default;//中继包缓存 - var isText = false;//标识是否为文本 - while (true) - { - using (var receiveResult = await client.ReadAsync(CancellationToken.None)) - { - if (receiveResult.IsCompleted) - { - break; - } - - var dataFrame = receiveResult.DataFrame; - var data = receiveResult.DataFrame.PayloadData; - - switch (dataFrame.Opcode) - { - case WSDataType.Cont: - { - //先缓存中继数据 - stream.Write(data.Span); - - //然后判断中继包是否为最终包 - if (dataFrame.FIN) - { - //是 - - if (isText)//判断是否为文本 - { - this.m_logger.Info($"WebSocket文本:{Encoding.UTF8.GetString(stream.ToArray())}"); - } - else - { - this.m_logger.Info($"WebSocket二进制:{stream.Length}长度"); - } - } - } - break; - case WSDataType.Text: - { - if (dataFrame.FIN)//判断是不是最后的包 - { - //是,则直接输出 - //说明上次并没有中继数据缓存,直接输出本次内容即可 - this.m_logger.Info($"WebSocket文本:{dataFrame.ToText()}"); - } - else - { - isText = true; - - //否,则说明数据太大了,分中继包了。 - //则,初始化缓存容器 - stream ??= new MemoryStream(); - - //下面则是缓存逻辑 - - //如果是非net6.0即以上,即:NetFramework平台使用。原因是stream不支持span写入 - //var segment = data.AsSegment(); - //stream.Write(segment.Array, segment.Offset, segment.Count); - - //如果是net6.0以上,直接写入span即可 - stream.Write(data.Span); - } - } - break; - case WSDataType.Binary: - { - if (dataFrame.FIN)//判断是不是最后的包 - { - //是,则直接输出 - //说明上次并没有中继数据缓存,直接输出本次内容即可 - this.m_logger.Info($"WebSocket二进制:{data.Length}长度"); - } - else - { - isText = false; - - //否,则说明数据太大了,分中继包了。 - //则,初始化缓存容器 - stream ??= new MemoryStream(); - - //下面则是缓存逻辑 - - //如果是非net6.0即以上,即:NetFramework平台使用。原因是stream不支持span写入 - //var segment = data.AsSegment(); - //stream.Write(segment.Array, segment.Offset, segment.Count); - - //如果是net6.0以上,直接写入span即可 - stream.Write(data.Span); - } - } - break; - case WSDataType.Close: - break; - case WSDataType.Ping: - break; - case WSDataType.Pong: - break; - default: - break; - } - } - } - - //此处即表明websocket已断开连接 - this.m_logger.Info("WebSocket断开连接"); - await e.InvokeNext(); - } -} -``` - -或者可以使用内置的扩展方法来进行一次性接收。 - -例如一次性接收文本: - -```csharp showLineNumbers - var str = await client.ReadStringAsync(); -``` - -一次性接收二进制数据: - -```csharp showLineNumbers -using (MemoryStream stream=new MemoryStream()) -{ - var str = await client.ReadBinaryAsync(stream); -} -``` + :::caution 注意 -`ReadStringAsync`或者`ReadBinaryAsync`,都只接收对应的数据类型,如果收到非匹配数据则会抛出异常。 +内存缓存的方式适合数据量不大的场景,如果数据量较大,建议使用其他缓存等方式。 ::: -【方法2】 +## 九、发送数据 -使用消息组合器。如果想在`OnWebSocketReceived`进行合并消息,则可以使用该方法。 +按照`WebSocket`服务器架构,每个成功连接的客户端都会在服务器端创建一个`HttpSessionClient`实例。要发送WebSocket消息,需要通过这些实例进行操作。 -```csharp {30-74} showLineNumbers -public class MyWebSocketPlugin : PluginBase, IWebSocketReceivedPlugin -{ - public MyWebSocketPlugin(ILog logger) - { - this.m_logger = logger; - } +### 9.1 获取客户端实例 - private readonly ILog m_logger; +一般的,如果在插件中收到信息时,可以直接拿到`HttpSessionClient`实例。或者`IWebSocket`对象。此时直接操作即可。 - public async Task OnWebSocketReceived(IWebSocket client, WSDataFrameEventArgs e) - { - switch (e.DataFrame.Opcode) - { - case WSDataType.Close: - { - this.m_logger.Info("远程请求断开"); - await client.CloseAsync("断开"); - } - return; +如果在服务器的其他地方想要发送消息,可以通过下面代码获取所有在线客户端,然后选择需要发送的客户端。 - case WSDataType.Ping: - this.m_logger.Info("Ping"); - await client.PongAsync();//收到ping时,一般需要响应pong - break; + - case WSDataType.Pong: - this.m_logger.Info("Pong"); - break; +### 9.2 发送文本消息 - default: - { + - //其他报文,需要考虑中继包的情况。所以需要手动合并 WSDataType.Cont类型的包。 - //或者使用消息合并器 +### 9.3 发送二进制消息 - //获取消息组合器 - var messageCombinator = client.GetMessageCombinator(); + - try - { - //尝试组合 - if (messageCombinator.TryCombine(e.DataFrame, out var webSocketMessage)) - { - //组合成功,必须using释放模式 - using (webSocketMessage) - { - //合并后的消息 - var dataType = webSocketMessage.Opcode; +### 9.4 发送自定义数据帧 - //合并后的完整消息 - var data = webSocketMessage.PayloadData; + - if (dataType == WSDataType.Text) - { - //按文本处理 - } - else if (dataType == WSDataType.Binary) - { - //按字节处理 - } - else - { - //可能是其他自定义协议 - } - } - } - } - catch (Exception ex) - { - this.m_logger.Exception(ex); - messageCombinator.Clear();//当组合发生异常时,应该清空组合器数据 - } - - } - break; - } +### 9.5 发送Ping、Pong消息 - await e.InvokeNext(); - } -} -``` - -:::caution 注意 - -使用消息组合器,实际上是由框架缓存了数据,所以,整体数据不要太大,不然可能会有爆内存的风险。 - -::: - -## 六、回复、响应数据 - -要回复`Websocket`消息,必须使用**HttpSessionClient**对象。如果要获取到所有的`HttpSessionClient`对象,则必须通过`HttpService`对象获取。 - -所以,首先,得确保能访问到`HttpService`对象。 - -如果是在`HttpService`的创建过程中,你可以直接访问到`HttpService`对象。 - -但是如果在插件中,或者其他地方,你需要使用其他方法来实现。 - -### 6.1 在插件中获取`HttpService`对象 - -插件是支持依赖注入的,所以可以直接注入`HttpService`对象。然后在插件中获取。 - -例如: - -【注册服务】 - -```csharp {6} showLineNumbers -var service = new HttpService(); -await service.SetupAsync(new TouchSocketConfig()//加载配置 - ... - .ConfigureContainer(a => - { - a.RegisterSingleton(service); - ... - })); -await service.StartAsync(); -``` - -【获取服务】 - -```csharp {3,5,8} showLineNumbers -public class MyWebSocketPlugin : PluginBase,... -{ - private readonly IHttpService m_httpService; - - public MyWebSocketPlugin(ILog logger,IHttpService httpService) - { - this.m_logger = logger; - this.m_httpService = httpService; - } -} -``` - -除了使用容器,还可以在插件触发时,通过`WebSocket`获取`HttpService`对象。 - -```csharp {6-9} showLineNumbers -public class MyWebSocketPlugin : PluginBase, IWebSocketReceivedPlugin -{ - ... - public async Task OnWebSocketReceived(IWebSocket client, WSDataFrameEventArgs e) - { - if (client.Client is IHttpSessionClient httpSessionClient) - { - var service = httpSessionClient.Service as IHttpService - } - await e.InvokeNext(); - } -} -``` - - -### 6.2 获取SessionClient - -#### (1)直接获取所有在线客户端 - -通过`service.Clients`属性,获取当前在线的所有客户端。 - -```csharp showLineNumbers -var clients = service.Clients; -foreach (var client in clients) -{ - if (client.Protocol == Protocol.WebSocket)//先判断是不是websocket协议 - { - if (client.Id == "id")//再按指定id发送,或者直接广播发送 - { - await client.WebSocket.SendAsync("hello"); - } - } -} -``` - -:::caution 注意 - -由于`HttpSessionClient`的生命周期是由框架控制的,所以最好尽量不要直接引用该实例,可以引用`HttpSessionClient.Id`,然后再通过服务器查找。 - -::: - -#### (2)通过Id获取 - -先调用`service.GetIds`方法,获取当前在线的所有客户端的Id,然后选择需要的Id,通过`TryGetClient`方法,获取到想要的客户端。 - -```csharp showLineNumbers -string[] ids = service.GetIds(); -if (service.TryGetClient(ids[0], out HttpSessionClient sessionClient)) -{ - await sessionClient.WebSocket.SendAsync("hello"); -} -``` - -### 6.3 发送文本类消息 - -```csharp showLineNumbers -await webSocket.SendAsync("Text"); -``` - -### 6.4 发送二进制消息 - -```csharp showLineNumbers -await webSocket.SendAsync(new byte[10]); -``` - -### 6.5 直接发送自定义构建的数据帧 - -```csharp showLineNumbers -using (var frame = new WSDataFrame()) -{ - frame.Opcode = WSDataType.Text; - frame.FIN = true; - frame.RSV1 = true; - frame.RSV2 = true; - frame.RSV3 = true; - frame.AppendText("I"); - frame.AppendText("Love"); - frame.AppendText("U"); - await webSocket.SendAsync(frame); -} -``` -:::info 备注 - -此部分功能就需要你对`WebSocket`有充分了解才可以操作。 - -::: - -### 6.6 发送Ping - -```csharp showLineNumbers -await webSocket.PingAsync(); -``` - -### 6.7 发送Pong - -```csharp showLineNumbers -await webSocket.PongAsync(); -``` + -### 6.8 发送大数据 +### 9.7 发送大数据(分包) 发送大数据时,需要分包发送,可以使用`SendAsync`的重载方法,设置`FIN`标志。 -```csharp showLineNumbers -// 发送 "hello" 消息 100 次,最后一次设置 FIN 标志 -var i = 0; -while (true) -{ - if (i++ == 100) - { - await sessionClient.WebSocket.SendAsync("hello", true); - break; - } - await sessionClient.WebSocket.SendAsync("hello", false); -} -``` + +## 十、连接管理 -## 七、关闭连接 +### 10.1 主动关闭连接 在使用`WebSocket`时,如果想主动关闭连接,可以使用`CloseAsync`方法,同时可以携带一个关闭原因。 默认关闭状态码为1000。意为:正常关闭。 -```csharp showLineNumbers -await webSocket.CloseAsync("关闭"); -``` - -如果你想使用其他状态码,可以参考如下代码。 - -```csharp showLineNumbers -await webSocket.CloseAsync(WebSocketCloseStatus.EndpointUnavailable,"关闭");//状态码为1001,意为:服务端不可用。 -``` + -## 八、本文示例Demo +## 十一、示例Demo - \ No newline at end of file + diff --git a/handbook/docs/wscommandlineplugin.mdx b/handbook/docs/wscommandlineplugin.mdx index 52990aa4f..5d199b61b 100644 --- a/handbook/docs/wscommandlineplugin.mdx +++ b/handbook/docs/wscommandlineplugin.mdx @@ -5,15 +5,16 @@ title: 快捷事务命令行 import BilibiliCard from '@site/src/components/BilibiliCard.js'; import { TouchSocketHttpDefinition } from "@site/src/components/Definition.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; +import CardLink from "@site/src/components/CardLink.js"; -### 定义 ## 一、说明 -快捷事务命令行,是用于**WebSocket**的快捷事务实现,让WS在**Text**文本中,用最简单的文字消息即可完成相关事务的执行。 +快捷事务命令行,是用于**WebSocket**的快捷事务实现,让WS在**Text**文本中,用最简单的文字消息即可完成相关事务的执行。该功能通过继承`WebSocketCommandLinePlugin`基类实现,可以让开发者以最简单的方式定义和调用WebSocket事务方法。 @@ -21,97 +22,86 @@ import { TouchSocketHttpDefinition } from "@site/src/components/Definition.js"; ### 2.1 声明事务插件 -新建类型,继承自`WebSocketCommandLinePlugin`。然后在里面写一些需要的事务方法。 +新建类型,继承自`WebSocketCommandLinePlugin`基类。然后在里面声明需要执行的事务方法。 -声明的事务方法必须满足:只能是实例方法、必须是公开方法、方法名以`Command`结尾。 +声明的事务方法必须满足以下要求: +- 只能是**实例方法** +- 必须是**公开方法**(public) +- 方法名必须以**Command结尾** +- 支持基础类型参数和Json字符串参数 +- 参数之间用空格隔开 -```csharp showLineNumbers -/// -/// 命令行插件。 -/// 声明的方法必须为公开实例方法、以"Command"结尾,且支持json字符串,参数之间空格隔开。 -/// -public class MyWSCommandLinePlugin : WebSocketCommandLinePlugin -{ - public MyWSCommandLinePlugin(ILog logger) : base(logger) - { - } - - public int AddCommand(int a, int b) - { - return a + b; - } - - //当第一个参数,直接或间接实现ITcpSession接口时,会收集到当前请求的客户端,从而可以获取IP等。 - public SumClass SumCommand(IHttpClientBase client, SumClass sumClass) - { - sumClass.Sum = sumClass.A + sumClass.B; - return sumClass; - } -} -``` - -### 3.1 注册使用 - -直接通过插件添加即可。 - -```csharp {14} -var service = new HttpService(); -await service.SetupAsync(new TouchSocketConfig()//加载配置 - .SetListenIPHosts(7789) - .ConfigureContainer(a => - { - a.AddConsoleLogger(); - }) - .ConfigurePlugins(a => - { - a.UseWebSocket()//添加WebSocket功能 - //.SetWSUrl("/ws")//设置url直接可以连接。 - .SetVerifyConnection(VerifyConnection) - .UseAutoPong();//当收到ping报文时自动回应pong - a.Add(); - })); - -await service.StartAsync(); -``` - -### 3.2 调用 - -调用数据格式: - -以事务方法的**方法名**为第一个参数(不包含Command),后续参数直接写,多个参数用**空格**隔开。例如:`Add 10 20` - -```csharp {18} -var client = new WebSocketClient(); -await client.SetupAsync(new TouchSocketConfig() - .ConfigureContainer(a => - { - a.AddConsoleLogger(); - }) - .ConfigurePlugins(a => - { - a.Add(typeof(IWebSocketReceivedPlugin), async (IHttpClientBase c, WSDataFrameEventArgs e) => - { - client.Logger.Info($"收到Add的计算结果:{e.DataFrame.ToText()}"); - await e.InvokeNext(); - }); - }) - .SetRemoteIPHost("ws://127.0.0.1:7789/ws")); -await client.ConnectAsync(); - -client.SendWithWS("Add 10 20"); -``` + :::tip 提示 -快捷事务命令行不仅可以添加在服务器,客户端也可以添加使用。 +在事务方法中,如果第一个参数的类型实现了`IHttpSession`或`IWebSocket`接口,框架会自动注入当前请求的客户端实例,从而可以获取客户端的IP地址、ID等信息。 + +::: + +### 2.2 注册使用 + +在服务端配置插件时,直接通过`Add`方法添加命令行插件即可。 + + + +### 2.3 客户端调用 + +客户端调用命令行事务方法时,需要按照特定的格式发送文本消息: + +**调用格式:** `方法名 参数1 参数2 ...` + +- 第一个参数是事务方法的**方法名**(不包含`Command`后缀) +- 后续参数直接写,多个参数用**空格**隔开 +- 例如调用`AddCommand`方法:`Add 10 20` + + + +:::tip 提示 + +快捷事务命令行不仅可以添加在服务器端,客户端也可以添加使用。 ::: :::tip 提示 -快捷事务参数支持Json字符串,但是需要注意的是,调用的Json字符串也不能包含空格。 +快捷事务参数支持Json字符串,但是需要注意的是,Json字符串内部不能包含空格,否则会被识别为多个参数。 ::: +## 三、参数类型支持 -[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/WebSocket/WebSocketConsoleApp) \ No newline at end of file +命令行插件支持以下几种参数类型: + +### 3.1 基础类型参数 + +支持所有C#基础类型,包括:`int`、`long`、`double`、`string`、`bool`等。 + +### 3.2 Json字符串参数 + +支持将Json字符串自动反序列化为对象参数。例如: +``` +Sum {"A":10,"B":20} +``` + +:::caution 注意 + +Json字符串内部不能包含空格,否则会被错误解析为多个参数。 + +::: + +### 3.3 客户端注入参数 + +如果方法的第一个参数类型实现了`IHttpSession`或`IWebSocket`接口,框架会自动注入当前请求的客户端实例,无需在调用时传递。 + +## 四、异常处理 + +默认情况下,当事务方法执行发生异常时,插件会将异常信息返回给客户端。可以通过`NoReturnException()`方法关闭异常返回: + +```csharp +a.Add().NoReturnException(); +``` + +## 五、示例代码 + + \ No newline at end of file diff --git a/handbook/docs/xmlrpc.mdx b/handbook/docs/xmlrpc.mdx index d07f1cf83..38ee59c67 100644 --- a/handbook/docs/xmlrpc.mdx +++ b/handbook/docs/xmlrpc.mdx @@ -4,227 +4,123 @@ title: 产品及架构介绍 --- import { TouchSocketXmlRpcDefinition } from "@site/src/components/Definition.js"; - -### 定义 +import Tag from "@site/src/components/Tag.js"; +import CardLink from "@site/src/components/CardLink.js"; +import CustomCodeBlock from './CodeBlocks/CustomCodeBlock'; +## 一、概述 -## 一、说明 +XmlRpc 是一种历史悠久的、跨语言、跨平台的远程过程调用(RPC)协议。其核心特征是: -XmlRpc是**通用**的工作在**Internet**上的RPC。一个[XML-RPC](https://baike.baidu.com/item/XML-RPC/0) 消息就是一个请求体为xml的**http-post**请求,被调用的方法在服务器端执行并将执行结果以xml格式编码后返回。这与**编程语言无关**,与**操作系统无关**。在此处封装了**前后端**,使其使用更加方便、高效。 +- 采用标准的 HTTP POST 作为传输承载 +- 请求与响应体使用 XML 进行结构化编码 +- 服务端按方法名称解析并执行,返回结果以 XML 封装 +协议规范参考(外部资料):[XML-RPC 百度百科](https://baike.baidu.com/item/XML-RPC/0) -## 二、特点: +TouchSocket 在此基础上提供了: -- **异常反馈** 。 -- 支持自定义类型。 -- 支持类型嵌套。 -- 支持Web等调用。 +- 统一的 RPC 服务注册与调度体系 +- 代理代码自动生成 +- 与其它 RPC(JsonRpc、DmtpRpc、WebApi)一致的调用体验 +- 插件化扩展能力 -## 三、创建服务 +## 二、核心特点 -### 3.1 定义服务 +- 异常反馈:出现业务或框架异常时能获得完整错误信息 +- 自定义类型:支持复杂对象序列化(内部完成 XML 解析映射) +- 类型嵌套:支持多层级对象树结构 +- 统一调用范式:同一套 Invoke / 代理 调用模式 +- 动态代理:支持 DispatchProxy 方式快速构建接口式调用 -在**服务器**端中新建一个类,继承于**SingletonRpcServer**类(或实现IRpcServer),然后在该类中写**公共方法**,并用**XmlRpc**属性标签标记。 +## 三、定义 RPC 服务 -```csharp showLineNumbers -public partial class XmlServer : SingletonRpcServer -{ - [XmlRpc(MethodInvoke = true)] - public int Sum(int a, int b) - { - return a + b; - } +### 3.1 服务端实现 - [XmlRpc(MethodInvoke = true)] - public int TestClass(MyClass myClass) - { - return myClass.A + myClass.B; - } -} +在服务器端创建一个类,继承 `SingletonRpcServer`(或实现 `IRpcServer`),对外暴露的公共方法使用 `XmlRpc` 特性标记。设置 `MethodInvoke = true` 时,将以方法名作为调用键(与标准 XmlRpc 方式一致)。 -public class MyClass -{ - public int A { get; set; } - public int B { get; set; } -} + -``` +### 3.2 启动 Http 服务器 -### 3.2 启动服务 +XmlRpc 在本框架中主要基于 Http 承载。下面示例展示如何注册服务并启动: -更多注册Rpc的方法请看[注册Rpc服务](./rpcregister.mdx) + -```csharp showLineNumbers -var service = new HttpService(); +:::tip 提示 -await service.SetupAsync(new TouchSocketConfig() - .ConfigureContainer(a => - { - a.AddConsoleLogger(); - a.AddRpcStore(store => - { - store.RegisterServer(); +更多注册与组合用法请参阅:[注册 Rpc 服务](./rpcregister.mdx) - //下列为生成客户端的代理代码 - File.WriteAllText("../../../RpcProxy.cs", store.GetProxyCodes("RpcProxy", new Type[] { typeof(XmlRpcAttribute) })); - ConsoleLogger.Default.Info("成功生成代理"); +::: - }); - }) - .ConfigurePlugins(a => - { - a.UseXmlRpc() - .SetXmlRpcUrl("/xmlRpc"); - }) - .SetListenIPHosts(7789)); -await service.StartAsync(); +## 四、通用调用方式(原生 XmlRpc) -service.Logger.Info("服务器已启动"); -``` +作为通用协议,任何支持 HTTP 的语言/平台都可以直接构造标准 XmlRpc 报文: -## 四、客户端调用 - -### 4.1 直接调用 - -因为XmlRpc是一套已经约定俗成的通讯协议。所以可以通过常规方法直接调用。 - -【Http访问】 - -使用Post方式,url=http://127.0.0.1:7789/xmlRpc - -然后使用XmlRpc的规范传参。 +POST http://127.0.0.1:7789/xmlRpc ```xml - Sum - - - - 10 - - - - - 20 - - - - + Sum + + + + 10 + + + + + 20 + + + + ``` -### 4.2 使用客户端调用 +:::info 说明 -使用客户端调用,则是使用RPC的范式进行调用,使用比较简单。 +方法名需与服务端暴露的方法匹配,并满足 `MethodInvoke = true` 的调用键策略。 -```csharp showLineNumbers -private static async Task Main(string[] args) -{ - var client =await GetXmlRpcClientAsync(); +::: - //直接调用 - var result1 = client.InvokeT("Sum", InvokeOption.WaitInvoke, 10, 20); - Console.WriteLine($"直接调用,返回结果:{result1}"); +## 五、专用客户端调用 - var result2 = client.Sum(10, 20);//此Sum方法是服务端生成的代理。 - Console.WriteLine($"代理调用,返回结果:{result2}"); +框架提供 `XmlRpcClient`,支持: - Console.ReadKey(); -} +1. 直接 `Invoke / InvokeT` 调用 +2. 代理扩展方法调用(由生成的代理代码提供) -private static async Task GetXmlRpcClientAsync() -{ - var jsonRpcClient = new XmlRpcClient(); - await jsonRpcClient.ConnectAsync("http://127.0.0.1:7789/xmlRpc"); - Console.WriteLine("连接成功"); - return jsonRpcClient; -} -``` +### 5.1 创建客户端 -### 4.3 生成代理调用 + -在服务器端,注册完服务后,就可以生成客户端调用代码了。详细的操作可以查看[服务端代理生成](./rpcgenerateproxy.mdx) +### 5.2 直接 / 代理调用示例 -```csharp {6-7} -a.AddRpcStore(store => -{ - store.RegisterServer(); + -#if DEBUG - File.WriteAllText("../../../RpcProxy.cs", store.GetProxyCodes("RpcProxy", new Type[] { typeof(XmlRpcAttribute) })); - ConsoleLogger.Default.Info("成功生成代理"); -#endif -}); -``` +## 六、代理调用 -然后把生成的.cs文件复制(或链接)到客户端项目。然后客户端直接使用同名`扩展方法`即可调用。 +首先,需要先生成代理调用代码,详情请参阅:[代理生成](./rpcgenerateproxy.mdx) -```csharp showLineNumbers -var sum3 = client.Sum(10,20); -``` +将生成的 `RpcProxy.cs` 引入客户端项目,即可获得强类型扩展方法: -### 4.4 使用DispatchProxy代理调用 + -使用DispatchProxy代理调用,可以实现动态代理,详情请看[DispatchProxy代理生成](./rpcgenerateproxy.mdx) +:::tip 提示 -首先,需要声明一个基类,用于通讯基础。 +XmlRpc 支持 RPC 平台的所有功能,如**过滤器**、**调度器**以及所有代理调用方式。 -```csharp showLineNumbers -/// -/// 新建一个类,继承XmlRpcDispatchProxy,亦或者RpcDispatchProxy基类。 -/// 然后实现抽象方法,主要是能获取到调用的IRpcClient派生接口。 -/// -internal class MyXmlRpcDispatchProxy : XmlRpcDispatchProxy -{ - private readonly IXmlRpcClient m_client; +::: - public MyXmlRpcDispatchProxy() - { - this.m_client = GetXmlRpcClientAsync().GetFalseAwaitResult(); - } +## 八、错误与调试 - public override IXmlRpcClient GetClient() - { - return this.m_client; - } +- 服务端异常会封装为 XmlRpc Fault 返回 +- 建议开启控制台日志:`a.AddConsoleLogger()` +- 可结合过滤器与插件进行性能统计、限流、鉴权(与其它 RPC 模块一致) - private static async Task GetXmlRpcClientAsync() - { - var jsonRpcClient = new XmlRpcClient(); - await jsonRpcClient.ConnectAsync("http://127.0.0.1:7789/xmlRpc"); - Console.WriteLine("连接成功"); - return jsonRpcClient; - } -} -``` +## 九、示例工程 -然后按照服务,定义一个相同的代理接口。 - -```csharp showLineNumbers -interface IXmlRpcServer -{ - [XmlRpc(MethodInvoke = true)] - int Sum(int a, int b); -} -``` - -最后生成代理,并按照接口调用。 - -```csharp {1} -var rpc = MyXmlRpcDispatchProxy.Create(); -while (true) -{ - Console.WriteLine("请输入两个数,中间用空格隔开,回车确认"); - string str = Console.ReadLine(); - var strs = str.Split(' '); - int a = int.Parse(strs[0]); - int b = int.Parse(strs[1]); - - var sum = rpc.Sum(a, b); - Console.WriteLine(sum); -} -``` - -[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/XmlRpc) \ No newline at end of file + \ No newline at end of file diff --git a/handbook/docusaurus.config.ts b/handbook/docusaurus.config.ts index f56538fa8..edd1892f1 100644 --- a/handbook/docusaurus.config.ts +++ b/handbook/docusaurus.config.ts @@ -13,7 +13,6 @@ const config = { url: "https://touchsocket.net/", baseUrl: '/', onBrokenLinks: "throw", - onBrokenMarkdownLinks: "throw", favicon: "img/favicon.ico", projectName: "TouchSocket", scripts: [], @@ -37,20 +36,20 @@ const config = { // Please change this to your repo. // Remove this to remove the "edit this page" links. editUrl: - 'https://gitee.com/rrqm_home/touchsocket/tree/master/handbook/', + 'https://github.com/RRQM/TouchSocket/tree/master/handbook', showLastUpdateTime: true, showLastUpdateAuthor: true, sidebarCollapsible: true, sidebarCollapsed: true, - lastVersion: 'current', + lastVersion: '3.1', versions: { current: { - label: '3.1', + label: '4.0-rc', path: '/current', }, // next: { - // label: '2.1-rc', - // path: '/next', + // label: '4.0-beta', + // path: '/current', // banner: 'none' // } }, @@ -67,7 +66,7 @@ const config = { // Please change this to your repo. // Remove this to remove the "edit this page" links. editUrl: - 'https://gitee.com/rrqm_home/touchsocket/tree/master/handbook/', + 'https://github.com/RRQM/TouchSocket/tree/master/handbook', }, theme: { customCss: './src/css/custom.css', @@ -109,7 +108,7 @@ const config = { { label: "更新日志", position: "left", - to: "docs/current/upgrade" + to: "/upgrade" }, { label: "博客", @@ -138,7 +137,7 @@ const config = { position: "right", items: [ { - label: "Gitee(主库)", + label: "Gitee", href: "https://gitee.com/rrqm_home/touchsocket", }, { @@ -213,6 +212,9 @@ const config = { markdown: { mermaid: true, + hooks: { + onBrokenMarkdownLinks: "throw", + }, }, themes: [ ['@docusaurus/theme-mermaid', @@ -232,7 +234,11 @@ const config = { explicitSearchResultPath: true, }, ], - ] + ], + // future: { + // v4: true, // opt-in for Docusaurus v4 planned changes + // experimental_faster: true, // turns Docusaurus Faster on globally + // }, }; export default config; diff --git a/handbook/package.json b/handbook/package.json index 6265d43fe..3db81403b 100644 --- a/handbook/package.json +++ b/handbook/package.json @@ -1,38 +1,40 @@ { "name": "touchsocket", - "version": "3.1", + "version": "4.0", "private": true, "scripts": { "docusaurus": "docusaurus", - "start": "docusaurus start", - "build": "docusaurus build", + "start": "npm run generate-codes && docusaurus start", + "build": "npm run generate-codes -- --build && docusaurus build", "swizzle": "docusaurus swizzle", "deploy": "docusaurus deploy", "clear": "docusaurus clear", "serve": "docusaurus serve", "write-translations": "docusaurus write-translations", "write-heading-ids": "docusaurus write-heading-ids", - "typecheck": "tsc" + "typecheck": "tsc", + "generate-codes": "node docs/CodeBlocks/generateCodesModule.js" }, "dependencies": { - "@docusaurus/core": "^3.8.1", - "@docusaurus/preset-classic": "^3.8.1", - "@docusaurus/theme-mermaid": "^3.8.1", - "@easyops-cn/docusaurus-search-local": "^0.40.1", + "@docusaurus/core": "^3.9.1", + "@docusaurus/faster": "^3.9.1", + "@docusaurus/preset-classic": "^3.9.1", + "@docusaurus/theme-mermaid": "^3.9.1", + "@easyops-cn/docusaurus-search-local": "^0.52.1", "@giscus/react": "^3.0.0", "@mdx-js/react": "^3.0.0", "clsx": "^2.0.0", "mermaid": "^11.4.0", - "node-gyp": "^10.1.0", + "node-gyp": "^11.4.2", "prism-react-renderer": "^2.3.0", - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "^19.2.0", + "react-dom": "^19.2.0" }, "devDependencies": { - "@docusaurus/module-type-aliases": "^3.8.1", - "@docusaurus/tsconfig": "^3.8.1", - "@docusaurus/types": "^3.8.1", - "typescript": "~5.2.2" + "@docusaurus/module-type-aliases": "^3.9.1", + "@docusaurus/tsconfig": "^3.9.1", + "@docusaurus/types": "^3.9.1", + "typescript": "^5.9.3" }, "browserslist": { "production": [ @@ -48,10 +50,5 @@ }, "engines": { "node": ">=18.0" - }, - "overrides": { - "@cmfcmf/docusaurus-search-local": { - "@docusaurus/core": "^3.2.1" - } } -} +} \ No newline at end of file diff --git a/handbook/scripts/debug-definition.js b/handbook/scripts/debug-definition.js deleted file mode 100644 index d56ade033..000000000 --- a/handbook/scripts/debug-definition.js +++ /dev/null @@ -1,46 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -// 精准匹配定义部分的正则表达式 -const definitionRegex = /### 定义\s*([\s\S]*?)(?=\n## |$)/; - -// 解析定义部分内容 -function parseDefinition(definitionText) { - const lines = definitionText.split('\n').map(line => line.trim()).filter(line => line); - - let namespace = ''; - const assemblies = []; - - for (const line of lines) { - if (line.startsWith('命名空间:')) { - namespace = line.replace('命名空间:', '').replace('
', '').trim(); - } else if (line.startsWith('程序集:')) { - // 解析程序集链接 - const assemblyMatch = line.match(/\[([^\]]+)\]/); - if (assemblyMatch) { - const assemblyName = assemblyMatch[1]; - const isPro = line.includes(''); - assemblies.push({ name: assemblyName, isPro }); - } - } - } - - return { namespace, assemblies }; -} - -const filePath = 'd:/CodeOpen/TouchSocket/handbook/docs/dmtpclient.mdx'; -const content = fs.readFileSync(filePath, 'utf8'); - -console.log('文件内容:'); -console.log(content.substring(0, 500)); - -const definitionMatch = content.match(definitionRegex); -if (definitionMatch) { - console.log('\n匹配到的定义部分:'); - console.log(definitionMatch[1]); - - const result = parseDefinition(definitionMatch[1]); - console.log('\n解析结果:', result); -} else { - console.log('\n未找到定义部分'); -} diff --git a/handbook/scripts/fix-definition-imports.js b/handbook/scripts/fix-definition-imports.js deleted file mode 100644 index 98f87c88f..000000000 --- a/handbook/scripts/fix-definition-imports.js +++ /dev/null @@ -1,115 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -// 从 Definition.js 中提取预定义的程序集映射 -const definitionJsPath = path.join(__dirname, '..', 'src', 'components', 'Definition.js'); -const definitionJsContent = fs.readFileSync(definitionJsPath, 'utf8'); - -// 解析程序集到 type 的映射 -const assemblyToTypeMap = {}; -const typeDefRegex = /export const (\w+)Definition = \([^)]*\) =>\s*/g; - let componentMatch; - - while ((componentMatch = componentRegex.exec(content)) !== null) { - usedComponents.add(componentMatch[1]); - } - - if (usedComponents.size === 0) { - return { processed: false, reason: '没有使用Definition组件' }; - } - - // 检查当前的导入语句 - const currentImportRegex = /import\s+(?:{\s*([^}]+)\s*}|(\w+))\s+from\s+["']@site\/src\/components\/Definition\.js["'];?/; - const importMatch = content.match(currentImportRegex); - - if (!importMatch) { - return { processed: false, reason: '没有找到Definition导入语句' }; - } - - const componentsArray = Array.from(usedComponents); - const newImportStatement = `import { ${componentsArray.join(', ')} } from "@site/src/components/Definition.js";`; - - // 替换导入语句 - const newContent = content.replace(currentImportRegex, newImportStatement); - - // 写回文件 - fs.writeFileSync(filePath, newContent); - - return { - processed: true, - components: componentsArray, - oldImport: importMatch[0], - newImport: newImportStatement - }; -} - -// 主函数 -function main() { - const docsDir = path.join(__dirname, '..', 'docs'); - const versionedDocsDir = path.join(__dirname, '..', 'versioned_docs'); - - const allFiles = [ - ...findMdxFiles(docsDir), - ...findMdxFiles(versionedDocsDir) - ]; - - console.log(`找到 ${allFiles.length} 个 mdx 文件`); - - let processed = 0; - let skipped = 0; - - for (const file of allFiles) { - const relativePath = path.relative(path.join(__dirname, '..'), file); - const result = fixImportInFile(file); - - if (result.processed) { - processed++; - console.log(`✓ ${relativePath}: ${result.components.join(', ')}`); - } else { - skipped++; - // console.log(`- ${relativePath}: ${result.reason}`); - } - } - - console.log(`\n修复完成:`); - console.log(`修复了 ${processed} 个文件`); - console.log(`跳过了 ${skipped} 个文件`); -} - -if (require.main === module) { - main(); -} - -module.exports = { fixImportInFile }; diff --git a/handbook/scripts/replace-definitions-precise.js b/handbook/scripts/replace-definitions-precise.js deleted file mode 100644 index 8b4be251e..000000000 --- a/handbook/scripts/replace-definitions-precise.js +++ /dev/null @@ -1,255 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -// 从 Definition.js 中提取预定义的程序集映射 -const definitionJsPath = path.join(__dirname, '..', 'src', 'components', 'Definition.js'); -const definitionJsContent = fs.readFileSync(definitionJsPath, 'utf8'); - -// 解析程序集到 type 的映射 -const assemblyToTypeMap = {}; -const typeDefRegex = /export const (\w+)Definition = \([^)]*\) =>\s* line.trim()).filter(line => line); - - let namespace = ''; - const assemblies = []; - - for (const line of lines) { - if (line.startsWith('命名空间:')) { - namespace = line.replace('命名空间:', '').replace('
', '').trim(); - } else if (line.startsWith('程序集:')) { - // 解析程序集链接 - const assemblyMatch = line.match(/\[([^\]]+)\]/); - if (assemblyMatch) { - const assemblyName = assemblyMatch[1]; - const isPro = line.includes(''); - assemblies.push({ name: assemblyName, isPro }); - } - } - } - - return { namespace, assemblies }; -} - -// 生成 Definition 组件 -function generateDefinitionComponent(namespace, assemblies) { - // 将程序集名称转换为类型名称 (去掉.dll并转换格式) - const assemblyTypes = assemblies.map(assembly => { - const type = assembly.name.replace('.dll', ''); - // 去掉点,转换为驼峰式 - return type.replace(/\./g, ''); - }); - - // 检查是否有对应的预定义组件 - if (assemblies.length === 1) { - // 单个程序集 - const type = assemblyTypes[0]; - if (assemblyToTypeMap[type]) { - return `<${assemblyToTypeMap[type]} />`; - } - } else if (assemblies.length === 2) { - // 双程序集组合,检查是否有 pro 版本的组合 - const regularAssembly = assemblies.find(a => !a.isPro); - const proAssembly = assemblies.find(a => a.isPro); - - if (regularAssembly && proAssembly) { - const regularType = regularAssembly.name.replace('.dll', '').replace(/\./g, ''); - const proType = proAssembly.name.replace('.dll', '').replace(/\./g, ''); - - // 检查是否有 pro 版本的预定义组件 - if (assemblyToTypeMap[proType]) { - return `<${assemblyToTypeMap[proType]} />`; - } - // 或者检查是否有常规版本的预定义组件 - else if (assemblyToTypeMap[regularType]) { - return `<${assemblyToTypeMap[regularType]} />`; - } - } - } - - // 如果没有找到预定义组件,使用自定义参数 - const props = [`namespace="${namespace}"`]; - - if (assemblies.length === 1) { - props.push(`assembly="${assemblies[0].name}"`); - if (assemblies[0].isPro) { - props.push(`isPro={true}`); - } - } else { - const assemblyNames = assemblies.map(a => a.name); - props.push(`assemblies={${JSON.stringify(assemblyNames)}}`); - - const proAssemblies = assemblies.filter(a => a.isPro).map(a => a.name); - if (proAssemblies.length > 0) { - props.push(`proAssemblies={${JSON.stringify(proAssemblies)}}`); - } - } - - return ``; -} - -// 处理单个文件 -function processFile(filePath) { - const content = fs.readFileSync(filePath, 'utf8'); - - // 检查是否包含定义部分 - const definitionMatch = content.match(definitionRegex); - if (!definitionMatch) { - return { processed: false, reason: '无定义部分' }; - } - - const definitionText = definitionMatch[1]; - const { namespace, assemblies } = parseDefinition(definitionText); - - if (!namespace || assemblies.length === 0) { - return { processed: false, reason: '无法解析命名空间或程序集' }; - } - - // 生成 Definition 组件 - const definitionComponent = generateDefinitionComponent(namespace, assemblies); - - // 检查是否需要添加 import - let newContent = content; - - // 确定需要导入的组件名称 - const componentName = definitionComponent.match(/<(\w+)/)[1]; - const hasCorrectImport = content.includes(`import { ${componentName} }`) || - content.includes(`import {${componentName}}`) || - (content.includes(`import { `) && content.includes(`${componentName}`)); - - if (!hasCorrectImport) { - // 检查是否已经有 Definition 相关的导入 - const existingImportRegex = /import\s+(?:{\s*([^}]+)\s*}|(\w+))\s+from\s+["']@site\/src\/components\/Definition\.js["'];?/; - const existingImportMatch = content.match(existingImportRegex); - - if (existingImportMatch) { - // 替换现有的导入 - const newImportLine = `import { ${componentName} } from "@site/src/components/Definition.js";`; - newContent = content.replace(existingImportRegex, newImportLine); - } else { - // 添加新的导入 - const importLine = `import { ${componentName} } from "@site/src/components/Definition.js";`; - - // 查找现有的 import 语句 - const importMatches = content.match(/import .* from .*;/g) || []; - - if (importMatches.length > 0) { - // 找到最后一个 import 语句的位置 - const lastImport = importMatches[importMatches.length - 1]; - const lastImportIndex = content.indexOf(lastImport); - const afterLastImport = lastImportIndex + lastImport.length; - newContent = content.slice(0, afterLastImport) + '\n' + importLine + content.slice(afterLastImport); - } else { - // 在文件开头添加 import(在 frontmatter 之后) - const frontmatterMatch = content.match(/^---[\s\S]*?---/); - if (frontmatterMatch) { - const frontmatterEnd = frontmatterMatch.index + frontmatterMatch[0].length; - newContent = content.slice(0, frontmatterEnd) + '\n\n' + importLine + content.slice(frontmatterEnd); - } else { - newContent = importLine + '\n\n' + content; - } - } - } - } - - // 替换定义部分 - const newDefinitionSection = `### 定义\n\n${definitionComponent}\n\n`; - newContent = newContent.replace(definitionRegex, newDefinitionSection); - - // 写回文件 - fs.writeFileSync(filePath, newContent); - - return { - processed: true, - namespace, - assemblies: assemblies.map(a => a.name), - component: definitionComponent - }; -} - -// 主函数 -function main() { - const docsDir = path.join(__dirname, '..', 'docs'); - const versionedDocsDir = path.join(__dirname, '..', 'versioned_docs'); - - const allFiles = [ - ...findMdxFiles(docsDir), - ...findMdxFiles(versionedDocsDir) - ]; - - console.log(`找到 ${allFiles.length} 个 mdx 文件`); - - let processed = 0; - let skipped = 0; - const results = []; - - for (const file of allFiles) { - const relativePath = path.relative(path.join(__dirname, '..'), file); - const result = processFile(file); - - if (result.processed) { - processed++; - console.log(`✓ ${relativePath}: ${result.component}`); - results.push({ file: relativePath, ...result }); - } else { - skipped++; - console.log(`- ${relativePath}: ${result.reason}`); - } - } - - console.log(`\n处理完成:`); - console.log(`处理了 ${processed} 个文件`); - console.log(`跳过了 ${skipped} 个文件`); - - // 输出统计信息 - const typeStats = {}; - results.forEach(result => { - const key = result.assemblies.join(','); - if (!typeStats[key]) { - typeStats[key] = 0; - } - typeStats[key]++; - }); - - console.log('\n程序集统计:'); - Object.entries(typeStats).forEach(([assemblies, count]) => { - console.log(` ${assemblies}: ${count} 个文件`); - }); -} - -if (require.main === module) { - main(); -} - -module.exports = { processFile, parseDefinition, generateDefinitionComponent }; diff --git a/handbook/scripts/replace-definitions.js b/handbook/scripts/replace-definitions.js deleted file mode 100644 index c44bb80b6..000000000 --- a/handbook/scripts/replace-definitions.js +++ /dev/null @@ -1,100 +0,0 @@ -// 批量替换定义部分的脚本 -// 使用说明:这个脚本可以帮助您批量替换文档中的定义部分 - -const fs = require('fs'); -const path = require('path'); - -// 定义不同程序集的映射 -const assemblyMapping = { - 'TouchSocket.Core': { - namespace: 'TouchSocket.Core', - assembly: 'TouchSocket.Core.dll', - nugetUrl: 'https://www.nuget.org/packages/TouchSocket.Core' - }, - 'TouchSocket.Dmtp': { - namespace: 'TouchSocket.Dmtp', - assembly: 'TouchSocket.Dmtp.dll', - nugetUrl: 'https://www.nuget.org/packages/TouchSocket.Dmtp' - }, - 'TouchSocket.Http': { - namespace: 'TouchSocket.Http', - assembly: 'TouchSocket.Http.dll', - nugetUrl: 'https://www.nuget.org/packages/TouchSocket.Http' - }, - 'TouchSocket.Rpc': { - namespace: 'TouchSocket.Rpc', - assembly: 'TouchSocket.Rpc.dll', - nugetUrl: 'https://www.nuget.org/packages/TouchSocket.Rpc' - }, - 'TouchSocket.Mqtt': { - namespace: 'TouchSocket.Mqtt', - assembly: 'TouchSocket.Mqtt.dll', - nugetUrl: 'https://www.nuget.org/packages/TouchSocket.Mqtt' - }, - 'TouchSocket.Modbus': { - namespace: 'TouchSocket.Modbus', - assembly: 'TouchSocket.Modbus.dll', - nugetUrl: 'https://www.nuget.org/packages/TouchSocket.Modbus' - } -}; - -// 函数:替换文件中的定义部分 -function replaceDefinitionInFile(filePath, assemblyKey = 'TouchSocket.Core') { - const config = assemblyMapping[assemblyKey]; - if (!config) { - console.log(`Unknown assembly: ${assemblyKey}`); - return; - } - - try { - let content = fs.readFileSync(filePath, 'utf8'); - - // 匹配模式:### 定义\n\n命名空间:xxx
\n程序集:[xxx](xxx) - const definitionPattern = /### 定义\s*\n\s*命名空间:[^\n<]+\s*\n\s*程序集:\[[^\]]+\]\([^)]+\)/g; - - // 检查是否已经导入了Definition组件 - const hasDefinitionImport = content.includes("import Definition from '@site/src/components/Definition.js';"); - - if (definitionPattern.test(content)) { - // 添加导入语句(如果还没有) - if (!hasDefinitionImport) { - // 查找现有的import语句 - const importLines = content.match(/^import .+;$/gm) || []; - if (importLines.length > 0) { - // 在最后一个import语句后添加Definition import - const lastImport = importLines[importLines.length - 1]; - content = content.replace(lastImport, `${lastImport}\nimport Definition from '@site/src/components/Definition.js';`); - } else { - // 如果没有import语句,在frontmatter后添加 - content = content.replace(/---\s*\n/, `---\n\nimport Definition from '@site/src/components/Definition.js';\n`); - } - } - - // 替换定义部分 - const definitionComponent = ``; - - content = content.replace(definitionPattern, definitionComponent); - - // 写回文件 - fs.writeFileSync(filePath, content, 'utf8'); - console.log(`✅ Updated: ${filePath}`); - } else { - console.log(`ℹ️ No definition section found in: ${filePath}`); - } - } catch (error) { - console.error(`❌ Error processing ${filePath}:`, error.message); - } -} - -// 使用示例: -// replaceDefinitionInFile('./docs/example.mdx', 'TouchSocket.Core'); - -console.log('Definition replacement script ready!'); -console.log('Available assemblies:', Object.keys(assemblyMapping)); -console.log('Usage example: replaceDefinitionInFile("./docs/filename.mdx", "TouchSocket.Core");'); - -module.exports = { replaceDefinitionInFile, assemblyMapping }; diff --git a/handbook/scripts/test-precise-detailed.js b/handbook/scripts/test-precise-detailed.js deleted file mode 100644 index 2f57f0ee4..000000000 --- a/handbook/scripts/test-precise-detailed.js +++ /dev/null @@ -1,62 +0,0 @@ -const { parseDefinition, generateDefinitionComponent } = require('./replace-definitions-precise.js'); - -// 测试不同的程序集组合 -const testCases = [ - { - name: 'TouchSocket.Dmtp + TouchSocketPro.Dmtp', - text: ` -命名空间:TouchSocket.Dmtp -程序集:[TouchSocket.Dmtp.dll](https://www.nuget.org/packages/TouchSocket.Dmtp) -程序集:[TouchSocketPro.Dmtp.dll](https://www.nuget.org/packages/TouchSocketPro.Dmtp) -` - }, - { - name: '单个 TouchSocket.Dmtp', - text: ` -命名空间:TouchSocket.Dmtp -程序集:[TouchSocket.Dmtp.dll](https://www.nuget.org/packages/TouchSocket.Dmtp) -` - }, - { - name: '单个 TouchSocketPro.Dmtp', - text: ` -命名空间:TouchSocket.Dmtp -程序集:[TouchSocketPro.Dmtp.dll](https://www.nuget.org/packages/TouchSocketPro.Dmtp) -` - } -]; - -testCases.forEach(testCase => { - console.log(`\n=== ${testCase.name} ===`); - const result = parseDefinition(testCase.text); - console.log('解析结果:', result); - - const component = generateDefinitionComponent(result.namespace, result.assemblies); - console.log('生成的组件:', component); - - // 检查程序集类型 - const assemblyTypes = result.assemblies.map(a => a.name.replace('.dll', '')); - console.log('程序集类型:', assemblyTypes); - - // 检查是否有对应的预定义组件 - const fs = require('fs'); - const path = require('path'); - const definitionJsPath = path.join(__dirname, '..', 'src', 'components', 'Definition.js'); - const definitionJsContent = fs.readFileSync(definitionJsPath, 'utf8'); - - const assemblyToTypeMap = {}; - const typeDefRegex = /export const (\w+)Definition = \([^)]*\) =>\s* { - if (assemblyToTypeMap[type]) { - console.log(` ${type} -> ${assemblyToTypeMap[type]}`); - } else { - console.log(` ${type} -> 无预定义组件`); - } - }); -}); diff --git a/handbook/scripts/test-precise.js b/handbook/scripts/test-precise.js deleted file mode 100644 index 4b54b1cfb..000000000 --- a/handbook/scripts/test-precise.js +++ /dev/null @@ -1,16 +0,0 @@ -const { parseDefinition, generateDefinitionComponent } = require('./replace-definitions-precise.js'); - -const definitionText = ` -命名空间:TouchSocket.Dmtp -程序集:[TouchSocket.Dmtp.dll](https://www.nuget.org/packages/TouchSocket.Dmtp) -程序集:[TouchSocketPro.Dmtp.dll](https://www.nuget.org/packages/TouchSocketPro.Dmtp) -`; - -console.log('原始定义文本:'); -console.log(definitionText); - -const result = parseDefinition(definitionText); -console.log('\n解析结果:', result); - -const component = generateDefinitionComponent(result.namespace, result.assemblies); -console.log('\n生成的组件:', component); diff --git a/handbook/scripts/test-single-file.js b/handbook/scripts/test-single-file.js deleted file mode 100644 index 6392ad579..000000000 --- a/handbook/scripts/test-single-file.js +++ /dev/null @@ -1,6 +0,0 @@ -const { processFile } = require('./replace-definitions-precise.js'); - -const filePath = 'd:/CodeOpen/TouchSocket/handbook/docs/dmtpclient.mdx'; -const result = processFile(filePath); - -console.log('处理结果:', result); diff --git a/handbook/scripts/validate-imports.js b/handbook/scripts/validate-imports.js deleted file mode 100644 index 695b750f7..000000000 --- a/handbook/scripts/validate-imports.js +++ /dev/null @@ -1,131 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -// 递归查找所有 mdx 文件 -function findMdxFiles(dir) { - const files = []; - const items = fs.readdirSync(dir); - - for (const item of items) { - const fullPath = path.join(dir, item); - const stat = fs.statSync(fullPath); - - if (stat.isDirectory()) { - files.push(...findMdxFiles(fullPath)); - } else if (item.endsWith('.mdx')) { - files.push(fullPath); - } - } - - return files; -} - -// 验证单个文件的导入 -function validateFile(filePath) { - const content = fs.readFileSync(filePath, 'utf8'); - - // 查找文件中使用的 Definition 组件 - const usedComponents = new Set(); - const componentRegex = /<(\w+Definition)\s*\/>/g; - let componentMatch; - - while ((componentMatch = componentRegex.exec(content)) !== null) { - usedComponents.add(componentMatch[1]); - } - - if (usedComponents.size === 0) { - return { valid: true, reason: '没有使用Definition组件' }; - } - - // 检查导入语句 - const importRegex = /import\s+{\s*([^}]+)\s*}\s+from\s+["']@site\/src\/components\/Definition\.js["'];?/; - const importMatch = content.match(importRegex); - - if (!importMatch) { - return { - valid: false, - reason: '缺少Definition导入语句', - usedComponents: Array.from(usedComponents) - }; - } - - // 解析导入的组件 - const importedComponents = importMatch[1].split(',').map(c => c.trim()); - const missingComponents = Array.from(usedComponents).filter(c => !importedComponents.includes(c)); - const unusedComponents = importedComponents.filter(c => !usedComponents.has(c)); - - if (missingComponents.length > 0 || unusedComponents.length > 0) { - return { - valid: false, - reason: '导入和使用不匹配', - missingComponents, - unusedComponents, - usedComponents: Array.from(usedComponents), - importedComponents - }; - } - - return { valid: true, components: Array.from(usedComponents) }; -} - -// 主函数 -function main() { - const docsDir = path.join(__dirname, '..', 'docs'); - const versionedDocsDir = path.join(__dirname, '..', 'versioned_docs'); - - const allFiles = [ - ...findMdxFiles(docsDir), - ...findMdxFiles(versionedDocsDir) - ]; - - console.log(`验证 ${allFiles.length} 个 mdx 文件`); - - let validFiles = 0; - let invalidFiles = 0; - const errors = []; - - for (const file of allFiles) { - const relativePath = path.relative(path.join(__dirname, '..'), file); - const result = validateFile(file); - - if (result.valid) { - validFiles++; - if (result.components) { - console.log(`✓ ${relativePath}: ${result.components.join(', ')}`); - } - } else { - invalidFiles++; - console.log(`✗ ${relativePath}: ${result.reason}`); - if (result.missingComponents?.length > 0) { - console.log(` 缺少导入: ${result.missingComponents.join(', ')}`); - } - if (result.unusedComponents?.length > 0) { - console.log(` 多余导入: ${result.unusedComponents.join(', ')}`); - } - if (result.usedComponents?.length > 0) { - console.log(` 使用的组件: ${result.usedComponents.join(', ')}`); - } - if (result.importedComponents?.length > 0) { - console.log(` 导入的组件: ${result.importedComponents.join(', ')}`); - } - errors.push({ file: relativePath, ...result }); - } - } - - console.log(`\n验证完成:`); - console.log(`✓ 有效文件:${validFiles}`); - console.log(`✗ 无效文件:${invalidFiles}`); - - if (errors.length > 0) { - console.log('\n错误详情:'); - errors.forEach(error => { - console.log(`- ${error.file}: ${error.reason}`); - }); - } else { - console.log('\n🎉 所有文件的导入都正确!'); - } -} - -if (require.main === module) { - main(); -} diff --git a/handbook/scripts/verify-definition-imports.js b/handbook/scripts/verify-definition-imports.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/handbook/sidebars.ts b/handbook/sidebars.ts index 19d33512b..f13163bb5 100644 --- a/handbook/sidebars.ts +++ b/handbook/sidebars.ts @@ -19,44 +19,29 @@ module.exports = "id": "description", "label": "01、说明(使用前必要阅读)" }, - { - "type": "doc", - "id": "upgrade", - "label": "02、历史更新" - }, { "type": "category", - "label": "03、支持作者及商业运营", + "label": "02、支持作者及商业运营", "items": [ { "type": "doc", "id": "donate", - "label": "3.1 支持作者" + "label": "2.1 支持作者" }, { "type": "doc", "id": "enterprise", - "label": "3.2 Pro相关" + "label": "2.2 Pro相关" }, {}, { "type": "category", - "label": "3.3 使用者项目", + "label": "2.3 使用者项目", "items": [ - { - "type": "doc", - "id": "fpsgame", - "label": "a.FPS实时游戏" - }, - { - "type": "doc", - "id": "engineertoolbox", - "label": "b.工程师软件工具箱" - }, { "type": "doc", "id": "thingsgateway", - "label": "c.ThingsGateway" + "label": "a.ThingsGateway" } ] } @@ -65,281 +50,189 @@ module.exports = { "type": "doc", "id": "startguide", - "label": "04、入门指南" + "label": "03、入门指南" }, { "type": "category", - "label": "05、疑难解答", + "label": "04、疑难解答", "items": [ { "type": "doc", "id": "troubleshootsourcecode", - "label": "5.1 源码相关" + "label": "4.1 源码相关" }, { "type": "doc", "id": "troubleshootunity3d", - "label": "5.2 Unity3D相关" + "label": "4.2 Unity3D相关" }, { "type": "doc", "id": "troubleshootissue", - "label": "5.3 Issue解答" + "label": "4.3 Issue解答" } ] }, { "type": "category", - "label": "06、Core", + "label": "05、Core", "items": [ { "type": "doc", "id": "bytepool", - "label": "6.1 内存池" + "label": "5.1 内存池" }, { "type": "doc", "id": "consoleaction", - "label": "6.2 控制台行为" + "label": "5.2 控制台行为" }, { "type": "doc", "id": "touchsocketbitconverter", - "label": "6.3 大小端转换器" - }, - { - "type": "doc", - "id": "datasecurity", - "label": "6.4 数据加密" + "label": "5.3 大小端转换器" }, { "type": "doc", "id": "ilog", - "label": "6.5 日志记录器" + "label": "5.4 日志记录器" }, { "type": "doc", "id": "appmessenger", - "label": "6.6 应用信使" + "label": "5.5 应用信使" }, { "type": "doc", "id": "fastbinaryformatter", - "label": "6.7 高性能二进制序列化" - }, - { - "type": "doc", - "id": "jsonserialize", - "label": "6.8 Json序列化" + "label": "5.6 高性能二进制序列化" }, { "type": "doc", "id": "ioc", - "label": "6.9 依赖注入容器(IOC)" + "label": "5.7 依赖注入容器(IOC)" }, { "type": "doc", "id": "dependencyproperty", - "label": "6.10 依赖属性" + "label": "5.8 依赖属性" }, { "type": "doc", "id": "filepool", - "label": "6.11 文件流池" + "label": "5.9 文件流池" }, { "type": "doc", "id": "pluginsmanager", - "label": "6.12 插件系统" + "label": "5.10 插件系统" }, { "type": "doc", "id": "ipackage", - "label": "6.13 包序列化模式" + "label": "5.11 包序列化模式" }, { "type": "doc", "id": "dynamicmethod", - "label": "6.14 动态方法调用" + "label": "5.12 动态方法调用" }, { "type": "doc", "id": "othercore", - "label": "6.15 其他相关功能类" + "label": "5.13 其他相关功能类" } ] }, { "type": "category", - "label": "07、Tcp组件", + "label": "06、Tcp组件", "items": [ { "type": "doc", "id": "tcpintroduction", - "label": "7.1 Tcp入门基础" + "label": "6.1 Tcp入门基础" }, { "type": "doc", "id": "tcpservice", - "label": "7.2 创建TcpService" + "label": "6.2 创建TcpService" }, { "type": "doc", "id": "tcpclient", - "label": "7.3 创建TcpClient" + "label": "6.3 创建TcpClient" }, { "type": "doc", "id": "natservice", - "label": "7.4 Tcp端口转发" - }, - { - "type": "doc", - "id": "resetid", - "label": "7.5 服务器重置Id" + "label": "6.4 Tcp端口转发" }, { "type": "doc", "id": "tcpcommandlineplugin", - "label": "7.6 命令行执行插件" + "label": "6.5 命令行执行插件" }, { "type": "doc", "id": "tcpcommonplugins", - "label": "7.7 其他常用插件" - }, - { - "type": "doc", - "id": "tcpaot", - "label": "7.8 AOT模式" + "label": "6.6 其他常用插件" } ] }, { "type": "category", - "label": "08、NamedPipe组件", + "label": "07、NamedPipe组件", "items": [ { "type": "doc", "id": "namedpipedescription", - "label": "8.1 命名管道描述" + "label": "7.1 命名管道描述" }, { "type": "doc", "id": "namedpipeservice", - "label": "8.2 创建NamedPipeService" + "label": "7.2 创建NamedPipeService" }, { "type": "doc", "id": "namedpipeclient", - "label": "8.2 创建NamedPipeClient" + "label": "7.2 创建NamedPipeClient" } ] }, { - "type": "category", - "label": "09、Udp组件", - "items": [ - { - "type": "doc", - "id": "udpsession", - "label": "9.1 创建UdpSession" - }, - { - "type": "doc", - "id": "udpwaitingclient", - "label": "9.2 同步请求数据" - }, - { - "type": "doc", - "id": "udptransmitbigdata", - "label": "9.3 传输大于64K的数据" - }, - { - "type": "doc", - "id": "udpbroadcast", - "label": "9.4 组播、广播" - } - ] + "type": "doc", + "id": "udpsession", + "label": "08、UdpSession组件" }, { "type": "doc", "id": "serialportclient", - "label": "10、串口组件" + "label": "09、串口组件" }, { "type": "doc", "id": "waitingclient", - "label": "11、等待响应组件" + "label": "10、等待响应组件" }, { "type": "category", - "label": "12、数据处理适配器", + "label": "11、数据处理适配器", "items": [ { "type": "doc", "id": "adapterdescription", - "label": "12.1 介绍及使用" + "label": "11.1 介绍及使用" + }, + { + "type": "doc", + "id": "singlethreadstreamadapter", + "label": "11.2 单线程流式适配器" }, { "type": "category", - "label": "12.2 单线程流式适配器", - "items": [ - { - "type": "doc", - "id": "datahandleadapter", - "label": "a.原始适配器" - }, - { - "type": "doc", - "id": "packageadapter", - "label": "b.内置包适配器" - }, - { - "type": "doc", - "id": "customdatahandlingadapter", - "label": "c.用户自定义适配器" - }, - { - "type": "doc", - "id": "customfixedheaderdatahandlingadapter", - "label": "d.模板解析固定包头适配器" - }, - { - "type": "doc", - "id": "bigfixedheadercustomdatahandlingadapter", - "label": "e.模板解析大数据固定包头适配器" - }, - { - "type": "doc", - "id": "customunfixedheaderdatahandlingadapter", - "label": "f.模板解析非固定包头适配器" - }, - { - "type": "doc", - "id": "custombigunfixedheaderdatahandlingadapter", - "label": "g.模板解析大数据非固定包头适配器" - }, - { - "type": "doc", - "id": "custombetweenanddatahandlingadapter", - "label": "h.模板解析区间数据适配器" - }, - { - "type": "doc", - "id": "customjsondatahandlingadapter", - "label": "i.模板解析“Json”数据适配器" - }, - { - "type": "doc", - "id": "customcountspliterdatahandlingadapter", - "label": "j.模板解析“固定数量分隔符”数据适配器" - } - ] - }, - { - "type": "category", - "label": "12.3 多线程非流式适配器", + "label": "11.3 多线程非流式适配器", "items": [ { "type": "doc", @@ -350,7 +243,7 @@ module.exports = }, { "type": "category", - "label": "12.4 适配器案例赏析", + "label": "11.4 适配器案例赏析", "items": [ { "type": "doc", @@ -372,160 +265,155 @@ module.exports = { "type": "doc", "id": "independentusedatahandlingadapter", - "label": "12.5 独立使用适配器" + "label": "11.5 独立使用适配器" }, { "type": "doc", "id": "adaptererrorcorrection", - "label": "12.6 适配器纠错" + "label": "11.6 适配器纠错" }, { "type": "doc", "id": "dataadaptertester", - "label": "12.7 适配器完整性、性能测试" + "label": "11.7 适配器完整性、性能测试" }, { "type": "doc", "id": "adapterbuilder", - "label": "12.8 适配器消息构建器" + "label": "11.8 适配器消息构建器" } ] }, { "type": "category", - "label": "13、Http组件", + "label": "12、Http组件", "items": [ { "type": "doc", "id": "httpservice", - "label": "13.1 创建HttpService" + "label": "12.1 创建HttpService" }, { "type": "doc", "id": "httpclient", - "label": "13.2 创建HttpClient" + "label": "12.2 创建HttpClient" }, { "type": "doc", "id": "httpstaticpageplugin", - "label": "13.3 静态页面插件" + "label": "12.3 静态页面插件" }, { "type": "doc", "id": "cors", - "label": "13.4 Cors跨域" + "label": "12.4 Cors跨域" } ] }, { "type": "category", - "label": "14、WebSocket组件", + "label": "13、WebSocket组件", "items": [ { "type": "doc", "id": "websocketdescription", - "label": "14.1 产品及架构介绍" + "label": "13.1 产品及架构介绍" }, { "type": "doc", "id": "websocketservice", - "label": "14.2 创建WebSocket服务器" + "label": "13.2 创建WebSocket服务器" }, { "type": "doc", "id": "websocketclient", - "label": "14.3 创建WebSocket客户端" - }, - { - "type": "doc", - "id": "websocketheartbeat", - "label": "14.4 心跳设置" + "label": "13.3 创建WebSocket客户端" }, { "type": "doc", "id": "wscommandlineplugin", - "label": "14.5 快捷事务命令行" + "label": "13.4 快捷事务命令行" } ] }, { "type": "category", - "label": "15、Rpc组件", + "label": "14、Rpc组件", "items": [ { "type": "doc", "id": "rpcdescription", - "label": "15.1 Rpc描述" + "label": "14.1 Rpc描述" }, { "type": "doc", "id": "rpcregister", - "label": "15.2 注册服务" + "label": "14.2 注册服务" }, { "type": "doc", "id": "rpcgenerateproxy", - "label": "15.3 生成调用代理" + "label": "14.3 生成调用代理" }, { "type": "doc", "id": "generateproxysourcegeneratordemo", - "label": "15.4 源生成代理推荐写法" + "label": "14.4 源生成代理推荐写法" }, { "type": "doc", "id": "rpcactionfilter", - "label": "15.5 Rpc服务AOP" + "label": "14.5 Rpc服务AOP" }, { "type": "doc", "id": "rpcallcontext", - "label": "15.6 调用上下文" + "label": "14.6 调用上下文" }, { "type": "doc", "id": "rpcratelimiting", - "label": "15.7 Rpc访问速率限制" + "label": "14.7 Rpc访问速率限制" }, { "type": "doc", "id": "rpcdispatcher", - "label": "15.8 Rpc执行调度器" + "label": "14.8 Rpc执行调度器" }, { "type": "doc", "id": "rpcauthorization", - "label": "15.9 Rpc鉴权授权策略" + "label": "14.9 Rpc鉴权授权策略" } ] }, { "type": "category", - "label": "16、Dmtp组件", + "label": "15、Dmtp组件", "items": [ { "type": "doc", "id": "dmtpdescription", - "label": "16.1 产品及架构介绍" + "label": "15.1 产品及架构介绍" }, { "type": "doc", "id": "dmtpservice", - "label": "16.2 创建Dmtp服务器" + "label": "15.2 创建Dmtp服务器" }, { "type": "doc", "id": "dmtplient", - "label": "16.3 创建Dmtp客户端" + "label": "15.3 创建Dmtp客户端" }, { "type": "doc", - "label": "16.4 基础功能", + "label": "15.4 基础功能", "id": "dmtpbase" }, { "type": "category", - "label": "16.5 进阶功能", + "label": "15.5 进阶功能", "items": [ { "type": "doc", @@ -536,116 +424,161 @@ module.exports = }, { "type": "doc", - "label": "16.6 Rpc功能", + "label": "15.6 Rpc功能", "id": "dmtprpc" }, { "type": "doc", - "label": "16.7 文件传输", + "label": "15.7 文件传输", "id": "dmtptransferfile" }, { "type": "doc", "id": "dmtpremoteaccess", - "label": "16.8 远程文件系统" + "label": "15.8 远程文件系统" }, { "type": "doc", "id": "dmtpremotestream", - "label": "16.9 远程流映射" + "label": "15.9 远程流映射" }, { "type": "doc", "id": "dmtprouterpackage", - "label": "16.10 路由包传输" + "label": "15.10 路由包传输" }, { "type": "doc", "id": "dmtpredis", - "label": "16.11 Redis缓存" + "label": "15.11 Redis缓存" } ] }, { "type": "category", - "label": "17、WebApi组件", + "label": "16、WebApi组件", "items": [ { "type": "doc", - "label": "17.1 WebApi", + "label": "16.1 WebApi概述", "id": "webapi" }, { "type": "doc", - "label": "17.2 Swagger页面", - "id": "swagger" + "label": "16.2 路由系统", + "id": "webapi-route" + }, + { + "type": "doc", + "label": "16.3 参数绑定", + "id": "webapi-parameter" + }, + { + "type": "doc", + "label": "16.4 调用上下文", + "id": "webapi-context" + }, + { + "type": "doc", + "label": "16.5 客户端调用", + "id": "webapi-client" + }, + { + "type": "doc", + "label": "16.6 数据序列化", + "id": "webapi-serialization" + }, + { + "type": "doc", + "label": "16.7 鉴权与授权", + "id": "webapi-auth" + }, + { + "type": "doc", + "label": "16.8 跨域配置", + "id": "webapi-cors" + }, + { + "type": "doc", + "label": "16.9 AOT支持", + "id": "webapi-aot" + }, + { + "type": "doc", + "label": "16.10 Swagger文档", + "id": "webapi-swagger" } ] }, { "type": "doc", - "label": "18、JsonRpc组件", + "label": "17、JsonRpc组件", "id": "jsonrpc" }, { "type": "doc", - "label": "19、XmlRpc组件", + "label": "18、XmlRpc组件", "id": "xmlrpc" }, { "type": "category", - "label": "20、Modbus组件", + "label": "19、Modbus组件", "items": [ { "type": "doc", - "label": "20.1 Modbus主站", + "label": "19.1 Modbus协议介绍", + "id": "modbusdescription" + }, + { + "type": "doc", + "label": "19.2 Modbus主站", "id": "modbusmaster" }, { "type": "doc", - "label": "20.2 Modbus从站", + "label": "19.3 Modbus从站", "id": "modbusslave" } ] }, { "type": "doc", - "label": "21、通用主机(Hosting)", + "label": "20、通用主机(Hosting)", "id": "generichost" }, { "type": "category", - "label": "22、Mqtt组件", + "label": "21、Mqtt组件", "items": [ { "type": "doc", - "label": "22.1 Mqtt服务端", + "label": "21.1 Mqtt服务端", "id": "mqttservice" }, { "type": "doc", - "label": "22.2 Mqtt客户端", + "label": "21.2 Mqtt客户端", "id": "mqttclient" } ] }, { "type": "category", - "label": "23、PlcBridge组件", + "label": "22、PlcBridge组件", "items": [ { "type": "doc", - "label": "23.1 PlcBridge说明", + "label": "22.1 PlcBridge说明", "id": "plcbridgedescription" }, { "type": "doc", - "label": "23.2 PlcBridge服务", + "label": "22.2 PlcBridge服务", "id": "plcbridgeservice" }, { "type": "doc", - "label": "23.3 PlcBridgeModbus", + "label": "22.3 PlcBridgeModbus", "id": "plcbridgemodbus" } ] diff --git a/handbook/src/components/CardLink.js b/handbook/src/components/CardLink.js index ef780dd5b..365217f27 100644 --- a/handbook/src/components/CardLink.js +++ b/handbook/src/components/CardLink.js @@ -2,33 +2,88 @@ import React from 'react'; import '../css/CardLink.css'; import CodeDemo from '../pages/codedemo.svg'; +import GiteeIcon from './icons/GiteeIcon'; +import GitHubIcon from './icons/GitHubIcon'; // 辅助函数:从URL中提取最后一个路径段 -function getLastPathSegment(url) { - const urlParts = new URL(url).pathname.split('/').filter(Boolean); - if (urlParts.length > 0) { - return urlParts[urlParts.length - 1]; - } else { - return url; // 如果没有路径段,返回整个URL +function getLastPathSegment(path) +{ + if (!path) return path; + + // 如果是完整URL,提取路径 + if (path.startsWith('http')) + { + const urlParts = new URL(path).pathname.split('/').filter(Boolean); + return urlParts.length > 0 ? urlParts[urlParts.length - 1] : path; } + + // 如果是相对路径,提取最后一段 + const pathParts = path.split('/').filter(Boolean); + return pathParts.length > 0 ? pathParts[pathParts.length - 1] : path; } -const CardLink = ({ title, link, isPro }) => { +// 辅助函数:构建完整的仓库URL +function buildRepoUrls(repoPath) +{ + const giteeBase = 'https://gitee.com/RRQM_Home/TouchSocket/tree/master/'; + const githubBase = 'https://github.com/RRQM/TouchSocket/tree/master/'; + + // 确保路径不以斜杠开头 + const cleanPath = repoPath.startsWith('/') ? repoPath.slice(1) : repoPath; + + return { + gitee: `${giteeBase}${cleanPath}`, + github: `${githubBase}${cleanPath}` + }; +} + +const CardLink = ({ title, link, isPro }) => +{ const resolvedTitle = title || getLastPathSegment(link); + const urls = buildRepoUrls(link); + + const handleLinkClick = (e, url) => + { + e.preventDefault(); + e.stopPropagation(); + window.open(url, '_blank'); + }; return (
- - ); }; diff --git a/handbook/src/components/Definition.css b/handbook/src/components/Definition.css index 89c0c4226..15eb48621 100644 --- a/handbook/src/components/Definition.css +++ b/handbook/src/components/Definition.css @@ -9,28 +9,14 @@ font-size: 1.3em; font-weight: 600; color: #2d5aa0; - background: linear-gradient(135deg, #2d5aa0 0%, #4f7cb8 100%); - -webkit-background-clip: text; - background-clip: text; - -webkit-text-fill-color: transparent; - filter: drop-shadow(0 1px 2px rgba(45, 90, 160, 0.1)); + color: #2d5aa0; } .definition-content { - background: linear-gradient(135deg, - rgba(248, 253, 255, 0.8) 0%, - rgba(240, 248, 255, 0.8) 50%, - rgba(232, 243, 255, 0.8) 100% - ); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); + background: rgba(240, 248, 255, 0.8); border: 1px solid rgba(79, 179, 255, 0.15); border-radius: 16px; padding: 20px 24px; - box-shadow: - 0 4px 16px rgba(79, 179, 255, 0.06), - 0 2px 4px rgba(79, 179, 255, 0.04); - transition: all 0.3s ease; position: relative; overflow: hidden; } @@ -42,21 +28,13 @@ left: 0; width: 4px; height: 100%; - background: linear-gradient(180deg, #4fb3ff 0%, #7fa8d3 100%); + background: #4fb3ff; border-radius: 2px 0 0 2px; } .definition-content:hover { - background: linear-gradient(135deg, - rgba(240, 248, 255, 0.9) 0%, - rgba(232, 243, 255, 0.9) 50%, - rgba(217, 235, 255, 0.9) 100% - ); + background: rgba(232, 243, 255, 0.9); border-color: rgba(79, 179, 255, 0.25); - box-shadow: - 0 6px 20px rgba(79, 179, 255, 0.08), - 0 3px 8px rgba(79, 179, 255, 0.06); - transform: translateY(-1px); } .definition-item { @@ -107,21 +85,14 @@ align-items: center; gap: 8px; flex: 1; - background: linear-gradient(135deg, - rgba(45, 90, 160, 0.05) 0%, - rgba(79, 124, 184, 0.05) 100% - ); + background: rgba(45, 90, 160, 0.05); border: 1px solid rgba(45, 90, 160, 0.15); border-radius: 8px; padding: 6px 8px; - transition: all 0.3s ease; } .install-command-container:hover { - background: linear-gradient(135deg, - rgba(45, 90, 160, 0.08) 0%, - rgba(79, 124, 184, 0.08) 100% - ); + background: rgba(45, 90, 160, 0.08); border-color: rgba(45, 90, 160, 0.25); } @@ -145,30 +116,23 @@ height: 28px; border: none; border-radius: 6px; - background: linear-gradient(135deg, #4f7cb8 0%, #7fa8d3 100%); + background: #4f7cb8; color: white; cursor: pointer; - transition: all 0.3s ease; padding: 0; flex-shrink: 0; } .copy-button:hover { - background: linear-gradient(135deg, #2d5aa0 0%, #4f7cb8 100%); - transform: translateY(-1px); - box-shadow: 0 4px 8px rgba(79, 124, 184, 0.3); -} - -.copy-button:active { - transform: translateY(0); + background: #2d5aa0; } .copy-button.copied { - background: linear-gradient(135deg, #28a745 0%, #34ce57 100%); + background: #28a745; } .copy-button.copied:hover { - background: linear-gradient(135deg, #28a745 0%, #34ce57 100%); + background: #28a745; } .definition-value { @@ -177,96 +141,45 @@ font-weight: 500; border-radius: 8px; padding: 4px 8px; - transition: all 0.3s ease; } .namespace { - background: linear-gradient(135deg, - rgba(79, 179, 255, 0.1) 0%, - rgba(79, 179, 255, 0.05) 100% - ); + background: rgba(79, 179, 255, 0.1); color: #2d5aa0; border: 1px solid rgba(79, 179, 255, 0.2); } .assembly-link { - background: linear-gradient(135deg, - rgba(45, 90, 160, 0.08) 0%, - rgba(79, 124, 184, 0.08) 100% - ); + background: rgba(45, 90, 160, 0.08); color: #2d5aa0; text-decoration: none; border: 1px solid rgba(45, 90, 160, 0.15); - position: relative; - overflow: hidden; -} - -.assembly-link::before { - content: ''; - position: absolute; - top: 0; - left: -100%; - width: 100%; - height: 100%; - background: linear-gradient(90deg, - transparent 0%, - rgba(79, 179, 255, 0.1) 50%, - transparent 100% - ); - transition: left 0.6s ease; -} - -.assembly-link:hover::before { - left: 100%; } .assembly-link:hover { - background: linear-gradient(135deg, - rgba(45, 90, 160, 0.12) 0%, - rgba(79, 124, 184, 0.12) 100% - ); + background: rgba(45, 90, 160, 0.12); border-color: rgba(45, 90, 160, 0.25); color: #1e4080; text-decoration: none; - transform: translateY(-1px); - box-shadow: 0 2px 8px rgba(45, 90, 160, 0.15); } /* 暗色主题适配 */ [data-theme='dark'] .definition-title { - background: linear-gradient(135deg, #a5c6e0 0%, #c7dded 100%); - -webkit-background-clip: text; - background-clip: text; - -webkit-text-fill-color: transparent; - filter: drop-shadow(0 1px 2px rgba(165, 198, 224, 0.2)); + color: #a5c6e0; } [data-theme='dark'] .definition-content { - background: linear-gradient(135deg, - rgba(28, 36, 52, 0.8) 0%, - rgba(35, 45, 62, 0.8) 50%, - rgba(42, 54, 72, 0.8) 100% - ); + background: rgba(35, 45, 62, 0.8); border: 1px solid rgba(96, 146, 182, 0.2); - box-shadow: - 0 4px 16px rgba(15, 23, 35, 0.2), - 0 2px 4px rgba(15, 23, 35, 0.15); } [data-theme='dark'] .definition-content::before { - background: linear-gradient(180deg, #8bb4d9 0%, #a5c6e0 100%); + background: #8bb4d9; } [data-theme='dark'] .definition-content:hover { - background: linear-gradient(135deg, - rgba(42, 54, 72, 0.9) 0%, - rgba(52, 68, 88, 0.9) 50%, - rgba(62, 82, 108, 0.9) 100% - ); + background: rgba(52, 68, 88, 0.9); border-color: rgba(96, 146, 182, 0.3); - box-shadow: - 0 6px 20px rgba(15, 23, 35, 0.3), - 0 3px 8px rgba(15, 23, 35, 0.2); } [data-theme='dark'] .definition-label { @@ -274,18 +187,12 @@ } [data-theme='dark'] .install-command-container { - background: linear-gradient(135deg, - rgba(139, 180, 217, 0.08) 0%, - rgba(165, 198, 224, 0.08) 100% - ); + background: rgba(139, 180, 217, 0.08); border: 1px solid rgba(139, 180, 217, 0.2); } [data-theme='dark'] .install-command-container:hover { - background: linear-gradient(135deg, - rgba(139, 180, 217, 0.12) 0%, - rgba(165, 198, 224, 0.12) 100% - ); + background: rgba(139, 180, 217, 0.12); border-color: rgba(139, 180, 217, 0.3); } @@ -294,56 +201,37 @@ } [data-theme='dark'] .copy-button { - background: linear-gradient(135deg, #6092b6 0%, #8bb4d9 100%); + background: #6092b6; } [data-theme='dark'] .copy-button:hover { - background: linear-gradient(135deg, #4f7698 0%, #6092b6 100%); - box-shadow: 0 4px 8px rgba(15, 23, 35, 0.4); + background: #4f7698; } [data-theme='dark'] .copy-button.copied { - background: linear-gradient(135deg, #28a745 0%, #34ce57 100%); + background: #28a745; } [data-theme='dark'] .copy-button.copied:hover { - background: linear-gradient(135deg, #28a745 0%, #34ce57 100%); + background: #28a745; } [data-theme='dark'] .namespace { - background: linear-gradient(135deg, - rgba(139, 180, 217, 0.15) 0%, - rgba(139, 180, 217, 0.08) 100% - ); + background: rgba(139, 180, 217, 0.15); color: #a5c6e0; border: 1px solid rgba(139, 180, 217, 0.25); } [data-theme='dark'] .assembly-link { - background: linear-gradient(135deg, - rgba(165, 198, 224, 0.12) 0%, - rgba(199, 221, 237, 0.12) 100% - ); + background: rgba(165, 198, 224, 0.12); color: #a5c6e0; border: 1px solid rgba(165, 198, 224, 0.2); } -[data-theme='dark'] .assembly-link::before { - background: linear-gradient(90deg, - transparent 0%, - rgba(139, 180, 217, 0.15) 50%, - transparent 100% - ); -} - [data-theme='dark'] .assembly-link:hover { - background: linear-gradient(135deg, - rgba(165, 198, 224, 0.18) 0%, - rgba(199, 221, 237, 0.18) 100% - ); + background: rgba(165, 198, 224, 0.18); border-color: rgba(165, 198, 224, 0.35); color: #c7dded; - box-shadow: 0 2px 8px rgba(15, 23, 35, 0.3); } /* 响应式设计 */ @@ -351,47 +239,47 @@ .definition-content { padding: 16px 20px; } - + .definition-item { flex-direction: column; align-items: flex-start; margin-bottom: 16px; } - + .definition-item.description-item { margin-bottom: 16px; } - + .definition-item.install-item { margin-bottom: 0; } - + .definition-label { margin-bottom: 6px; margin-right: 0; min-width: auto; } - + .definition-value { width: 100%; word-break: break-all; } - + .definition-values { width: 100%; flex-direction: column; align-items: flex-start; gap: 6px; } - + .definition-description { width: 100%; } - + .install-command-container { width: 100%; } - + .install-command { font-size: 0.8em; word-break: break-all; @@ -402,39 +290,39 @@ .definition-container { margin: 16px 0 24px 0; } - + .definition-title { font-size: 1.2em; margin-bottom: 12px; } - + .definition-content { padding: 14px 16px; } - + .definition-label { font-size: 0.9em; } - + .definition-value { font-size: 0.85em; } - + .definition-description { font-size: 0.85em; } - + .install-command { font-size: 0.75em; } - + .copy-button { width: 24px; height: 24px; } - + .copy-button svg { width: 14px; height: 14px; } -} +} \ No newline at end of file diff --git a/handbook/src/components/Definition.js b/handbook/src/components/Definition.js index 2f941ccd5..6c96a761f 100644 --- a/handbook/src/components/Definition.js +++ b/handbook/src/components/Definition.js @@ -26,7 +26,7 @@ const TOUCHSOCKET_PACKAGES = { }, TouchSocketHttp: { namespace: ['TouchSocket.Http', 'TouchSocket.Http.WebSockets'], - assembly: ['TouchSocket.Http.dll', 'TouchSocket.Http.WebSockets.dll'], + assembly: ['TouchSocket.Http.dll'], packageName: 'TouchSocket.Http', nugetUrl: ['https://www.nuget.org/packages/TouchSocket.Http', 'https://www.nuget.org/packages/TouchSocket.Http'] }, @@ -45,10 +45,10 @@ const TOUCHSOCKET_PACKAGES = { // RPC包 TouchSocketRpc: { - namespace: ['TouchSocket.Rpc', 'TouchSocket.Rpc.JsonRpc'], - assembly: ['TouchSocket.Rpc.dll', 'TouchSocket.Rpc.JsonRpc.dll'], + namespace: ['TouchSocket.Rpc'], + assembly: ['TouchSocket.Rpc.dll'], packageName: 'TouchSocket.Rpc', - nugetUrl: ['https://www.nuget.org/packages/TouchSocket.Rpc', 'https://www.nuget.org/packages/TouchSocket.Rpc'] + nugetUrl: ['https://www.nuget.org/packages/TouchSocket.Rpc'] }, TouchSocketJsonRpc: { namespace: 'TouchSocket.JsonRpc', @@ -153,9 +153,9 @@ const TOUCHSOCKET_PACKAGES = { nugetUrl: 'https://www.nuget.org/packages/TouchSocketPro.Modbus' }, TouchSocketProPlcBridges: { - namespace: ['TouchSocketPro.PlcBridges', 'TouchSocketPro.Modbus'], - assembly: ['TouchSocketPro.PlcBridges.dll', 'TouchSocketPro.Modbus.dll'], - packageName: ['TouchSocketPro.PlcBridges', 'TouchSocketPro.Modbus'], + namespace: ['TouchSocketPro.PlcBridges'], + assembly: ['TouchSocketPro.PlcBridges.dll'], + packageName: ['TouchSocketPro.PlcBridges'], nugetUrl: ['https://www.nuget.org/packages/TouchSocketPro.PlcBridges', 'https://www.nuget.org/packages/TouchSocketPro.Modbus'] }, diff --git a/handbook/src/components/Tag.module.css b/handbook/src/components/Tag.module.css index 9d28326f7..1c3c20e2c 100644 --- a/handbook/src/components/Tag.module.css +++ b/handbook/src/components/Tag.module.css @@ -2,7 +2,7 @@ display: inline-flex; align-items: center; color: #fff; - padding: 6px 12px; + padding: 4px 12px; font-size: 12px; color: #fff; border-radius: 16px; @@ -12,37 +12,9 @@ margin-right: 8px; font-weight: 600; letter-spacing: 0.5px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); - transition: all 0.3s ease; - position: relative; - overflow: hidden; border: 1px solid rgba(255, 255, 255, 0.2); } -.label::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: linear-gradient(135deg, - rgba(255, 255, 255, 0.15) 0%, - rgba(255, 255, 255, 0.05) 100% - ); - border-radius: 16px; - pointer-events: none; -} - -.label:hover { - transform: translateY(-1px) scale(1.05); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25); - filter: brightness(1.1); -} - .icon { margin-right: 4px; - position: relative; - z-index: 1; - filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.2)); } \ No newline at end of file diff --git a/handbook/src/components/VotingModal.module.css b/handbook/src/components/VotingModal.module.css new file mode 100644 index 000000000..50cd0cc38 --- /dev/null +++ b/handbook/src/components/VotingModal.module.css @@ -0,0 +1,206 @@ +/* 遮罩层 */ +.overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + z-index: 9998; + backdrop-filter: blur(2px); +} + +/* 弹窗容器 */ +.modal { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: white; + border-radius: 16px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); + z-index: 9999; + min-width: 320px; + max-width: 480px; + width: 90%; + animation: modalSlideIn 0.3s ease-out; +} + +@keyframes modalSlideIn { + from { + opacity: 0; + transform: translate(-50%, -60%); + } + to { + opacity: 1; + transform: translate(-50%, -50%); + } +} + +/* 头部 */ +.header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px 24px 0; + border-bottom: none; +} + +.title { + margin: 0; + font-size: 18px; + font-weight: 600; + color: #1a1a1a; + line-height: 1.4; +} + +.closeButton { + background: none; + border: none; + font-size: 24px; + color: #999; + cursor: pointer; + padding: 4px; + border-radius: 4px; + transition: all 0.2s ease; +} + +.closeButton:hover { + background: #f5f5f5; + color: #666; +} + +/* 内容区域 */ +.content { + padding: 16px 24px; + text-align: center; +} + +.icon { + font-size: 48px; + margin-bottom: 16px; + line-height: 1; +} + +.message { + font-size: 16px; + line-height: 1.6; + color: #333; + margin: 0 0 12px; +} + +.description { + font-size: 14px; + line-height: 1.5; + color: #666; + margin: 0; +} + +/* 按钮区域 */ +.actions { + display: flex; + gap: 12px; + padding: 0 24px 24px; +} + +.voteButton { + flex: 1; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border: none; + padding: 12px 24px; + border-radius: 8px; + font-size: 15px; + font-weight: 500; + cursor: pointer; + transition: all 0.3s ease; + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3); +} + +.voteButton:hover { + transform: translateY(-1px); + box-shadow: 0 6px 16px rgba(102, 126, 234, 0.4); +} + +.voteButton:active { + transform: translateY(0); +} + +.laterButton { + flex: 0.8; + background: #f8f9fa; + color: #666; + border: 1px solid #e9ecef; + padding: 12px 20px; + border-radius: 8px; + font-size: 14px; + cursor: pointer; + transition: all 0.2s ease; +} + +.laterButton:hover { + background: #e9ecef; + color: #495057; +} + +/* 暗色主题适配 */ +[data-theme='dark'] .modal { + background: #2a2a2a; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6); +} + +[data-theme='dark'] .title { + color: #ffffff; +} + +[data-theme='dark'] .message { + color: #e0e0e0; +} + +[data-theme='dark'] .description { + color: #b0b0b0; +} + +[data-theme='dark'] .closeButton { + color: #b0b0b0; +} + +[data-theme='dark'] .closeButton:hover { + background: #404040; + color: #e0e0e0; +} + +[data-theme='dark'] .laterButton { + background: #404040; + color: #b0b0b0; + border-color: #555; +} + +[data-theme='dark'] .laterButton:hover { + background: #4a4a4a; + color: #e0e0e0; +} + +/* 移动端适配 */ +@media (max-width: 480px) { + .modal { + margin: 20px; + width: calc(100% - 40px); + } + + .title { + font-size: 16px; + } + + .message { + font-size: 15px; + } + + .actions { + flex-direction: column; + } + + .laterButton { + flex: 1; + } +} \ No newline at end of file diff --git a/handbook/src/components/VotingModal.tsx b/handbook/src/components/VotingModal.tsx new file mode 100644 index 000000000..51dd8f2b5 --- /dev/null +++ b/handbook/src/components/VotingModal.tsx @@ -0,0 +1,73 @@ +import React, { useState, useEffect } from 'react'; +import styles from './VotingModal.module.css'; + +const VotingModal: React.FC = () => { + const [isVisible, setIsVisible] = useState(false); + + useEffect(() => { + // 检查当前会话是否已经显示过弹窗(使用 sessionStorage 记录) + const hasShownModal = sessionStorage.getItem('gitee-voting-modal-shown-2025'); + + if (!hasShownModal) { + // 延迟显示弹窗,让页面先加载完成 + const timer = setTimeout(() => { + setIsVisible(true); + }, 1000); + + return () => clearTimeout(timer); + } + }, []); + + const handleClose = () => { + setIsVisible(false); + // 记录当前会话已显示过弹窗,刷新页面后会重新显示 + sessionStorage.setItem('gitee-voting-modal-shown-2025', 'true'); + }; + + const handleVote = () => { + // 在新窗口打开投票链接 + window.open('https://gitee.com/activity/2025opensource?ident=IYFARH', '_blank'); + handleClose(); + }; + + if (!isVisible) return null; + + return ( + <> + {/* 遮罩层 */} +
+ + {/* 弹窗内容 */} +
+
+

🎉 支持 TouchSocket 参与 Gitee 2025 投票!

+ +
+ +
+
🏆
+

+ 我正在参加 Gitee 2025 最受欢迎的开源软件投票活动, + 快来给 TouchSocket 投票吧! +

+

+ 您的每一票都是对开源社区的支持,感谢您的参与! +

+
+ +
+ + +
+
+ + ); +}; + +export default VotingModal; \ No newline at end of file diff --git a/handbook/src/components/icons/GitHubIcon.js b/handbook/src/components/icons/GitHubIcon.js new file mode 100644 index 000000000..d3f70b35a --- /dev/null +++ b/handbook/src/components/icons/GitHubIcon.js @@ -0,0 +1,15 @@ +import React from 'react'; + +const GitHubIcon = ({ width = 16, height = 16, className = "" }) => ( + + + +); + +export default GitHubIcon; diff --git a/handbook/src/components/icons/GiteeIcon.js b/handbook/src/components/icons/GiteeIcon.js new file mode 100644 index 000000000..99c3bf8aa --- /dev/null +++ b/handbook/src/components/icons/GiteeIcon.js @@ -0,0 +1,9 @@ +import React from 'react'; + +const GiteeIcon = ({ width = 16, height = 16, className = "" }) => ( + + +); + +export default GiteeIcon; diff --git a/handbook/src/css/BilibiliCard.css b/handbook/src/css/BilibiliCard.css index 21dbf5b5e..be2aa5503 100644 --- a/handbook/src/css/BilibiliCard.css +++ b/handbook/src/css/BilibiliCard.css @@ -12,54 +12,18 @@ color: inherit; border-radius: 20px; overflow: hidden; - background: linear-gradient(135deg, - rgba(248, 253, 255, 0.95) 0%, - rgba(240, 248, 255, 0.95) 50%, - rgba(232, 243, 255, 0.95) 100% - ); - backdrop-filter: blur(12px); - -webkit-backdrop-filter: blur(12px); + background: rgba(240, 248, 255, 0.95); border: 1px solid rgba(79, 179, 255, 0.15); - box-shadow: - 0 4px 20px rgba(79, 179, 255, 0.08), - 0 2px 6px rgba(79, 179, 255, 0.05); - transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1); cursor: pointer; position: relative; overflow: hidden; } -.card-link::before { - content: ''; - position: absolute; - top: 0; - left: -100%; - width: 100%; - height: 100%; - background: linear-gradient(90deg, - transparent 0%, - rgba(79, 179, 255, 0.05) 50%, - transparent 100% - ); - transition: left 0.8s ease; - z-index: 1; -} -.card-link:hover::before { - left: 100%; -} .card-link:hover { - background: linear-gradient(135deg, - rgba(240, 248, 255, 0.98) 0%, - rgba(232, 243, 255, 0.98) 50%, - rgba(217, 235, 255, 0.98) 100% - ); + background: rgba(232, 243, 255, 0.98); border-color: rgba(79, 179, 255, 0.25); - box-shadow: - 0 8px 32px rgba(79, 179, 255, 0.12), - 0 4px 12px rgba(79, 179, 255, 0.08); - transform: translateY(-3px); color: inherit; text-decoration: none; } @@ -81,33 +45,21 @@ width: 42px; height: 42px; border-radius: 12px; - background: linear-gradient(135deg, - rgba(79, 179, 255, 0.1) 0%, - rgba(79, 179, 255, 0.05) 100% - ); - transition: all 0.3s ease; + background: rgba(79, 179, 255, 0.1); } .bilibili-icon { color: #4fb3ff; width: 36px; height: 36px; - transition: all 0.3s ease; - filter: drop-shadow(0 2px 4px rgba(79, 179, 255, 0.2)); } .card-link:hover .icon-container { - background: linear-gradient(135deg, - rgba(79, 179, 255, 0.15) 0%, - rgba(79, 179, 255, 0.08) 100% - ); - transform: scale(1.05); + background: rgba(79, 179, 255, 0.15); } .card-link:hover .bilibili-icon { color: #2d8ce6; - transform: scale(1.1); - filter: drop-shadow(0 4px 8px rgba(79, 179, 255, 0.3)); } .card-text { @@ -120,21 +72,11 @@ font-weight: 600; color: #2d5aa0; line-height: 1.3; - background: linear-gradient(135deg, #2d5aa0 0%, #4f7cb8 100%); - -webkit-background-clip: text; - background-clip: text; - -webkit-text-fill-color: transparent; - text-shadow: none; - filter: drop-shadow(0 1px 2px rgba(45, 90, 160, 0.1)); - transition: all 0.3s ease; + color: #2d5aa0; } .card-link:hover .card-content h3 { - background: linear-gradient(135deg, #1e4080 0%, #2d5aa0 100%); - -webkit-background-clip: text; - background-clip: text; - -webkit-text-fill-color: transparent; - transform: translateX(2px); + color: #1e4080; } .card-description { @@ -142,7 +84,6 @@ color: #6092b6; font-size: 0.8em; opacity: 0.8; - transition: all 0.3s ease; } .card-link:hover .card-description { @@ -166,22 +107,12 @@ padding: 6px 12px; border: none; border-radius: 14px; - background: linear-gradient(135deg, - #4f7cb8 0%, - #7fa8d3 50%, - #a5c6e0 100% - ); + background: #4f7cb8; color: white; text-decoration: none; cursor: pointer; letter-spacing: 0.3px; - box-shadow: - 0 3px 10px rgba(79, 124, 184, 0.25), - 0 1px 3px rgba(79, 124, 184, 0.15); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.2); - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); position: relative; overflow: hidden; } @@ -191,36 +122,10 @@ z-index: 1; } -.pro-badge a::before { - content: ''; - position: absolute; - top: 0; - left: -100%; - width: 100%; - height: 100%; - background: linear-gradient(90deg, - transparent 0%, - rgba(255, 255, 255, 0.2) 50%, - transparent 100% - ); - transition: left 0.6s ease; - z-index: 0; -} -.pro-badge a:hover::before { - left: 100%; -} .pro-badge a:hover { - background: linear-gradient(135deg, - #2d5aa0 0%, - #4f7cb8 50%, - #7fa8d3 100% - ); - transform: translateY(-2px) scale(1.05); - box-shadow: - 0 6px 20px rgba(79, 124, 184, 0.35), - 0 3px 8px rgba(79, 124, 184, 0.25); + background: #2d5aa0; color: white; text-decoration: none; } @@ -231,42 +136,19 @@ } [data-theme='dark'] .card-link { - background: linear-gradient(135deg, - rgba(28, 36, 52, 0.95) 0%, - rgba(35, 45, 62, 0.95) 50%, - rgba(42, 54, 72, 0.95) 100% - ); + background: rgba(35, 45, 62, 0.95); border: 1px solid rgba(96, 146, 182, 0.2); - box-shadow: - 0 4px 20px rgba(15, 23, 35, 0.3), - 0 2px 6px rgba(15, 23, 35, 0.2); } -[data-theme='dark'] .card-link::before { - background: linear-gradient(90deg, - transparent 0%, - rgba(96, 146, 182, 0.08) 50%, - transparent 100% - ); -} + [data-theme='dark'] .card-link:hover { - background: linear-gradient(135deg, - rgba(42, 54, 72, 0.98) 0%, - rgba(52, 68, 88, 0.98) 50%, - rgba(62, 82, 108, 0.98) 100% - ); + background: rgba(52, 68, 88, 0.98); border-color: rgba(96, 146, 182, 0.35); - box-shadow: - 0 8px 32px rgba(15, 23, 35, 0.4), - 0 4px 12px rgba(15, 23, 35, 0.25); } [data-theme='dark'] .icon-container { - background: linear-gradient(135deg, - rgba(96, 146, 182, 0.15) 0%, - rgba(96, 146, 182, 0.08) 100% - ); + background: rgba(96, 146, 182, 0.15); } [data-theme='dark'] .bilibili-icon { @@ -274,10 +156,7 @@ } [data-theme='dark'] .card-link:hover .icon-container { - background: linear-gradient(135deg, - rgba(96, 146, 182, 0.22) 0%, - rgba(96, 146, 182, 0.12) 100% - ); + background: rgba(96, 146, 182, 0.22); } [data-theme='dark'] .card-link:hover .bilibili-icon { @@ -285,18 +164,11 @@ } [data-theme='dark'] .card-content h3 { - background: linear-gradient(135deg, #a5c6e0 0%, #c7dded 100%); - -webkit-background-clip: text; - background-clip: text; - -webkit-text-fill-color: transparent; - filter: drop-shadow(0 1px 2px rgba(165, 198, 224, 0.2)); + color: #a5c6e0; } [data-theme='dark'] .card-link:hover .card-content h3 { - background: linear-gradient(135deg, #c7dded 0%, #e6f3ff 100%); - -webkit-background-clip: text; - background-clip: text; - -webkit-text-fill-color: transparent; + color: #c7dded; } [data-theme='dark'] .card-description { @@ -308,26 +180,12 @@ } [data-theme='dark'] .pro-badge a { - background: linear-gradient(135deg, - #6092b6 0%, - #8bb4d9 50%, - #a5c6e0 100% - ); + background: #6092b6; border: 1px solid rgba(255, 255, 255, 0.15); - box-shadow: - 0 3px 10px rgba(15, 23, 35, 0.4), - 0 1px 3px rgba(15, 23, 35, 0.25); } [data-theme='dark'] .pro-badge a:hover { - background: linear-gradient(135deg, - #4f7698 0%, - #6092b6 50%, - #8bb4d9 100% - ); - box-shadow: - 0 4px 16px rgba(15, 23, 35, 0.5), - 0 2px 6px rgba(15, 23, 35, 0.35); + background: #4f7698; } /* 响应式设计 */ @@ -335,35 +193,35 @@ .bilibili-card { margin: 12px 0; } - + .card-content { padding: 16px 20px; } - + .icon-container { width: 38px; height: 38px; margin-right: 14px; } - + .bilibili-icon { width: 32px; height: 32px; } - + .card-content h3 { font-size: 1em; } - + .card-description { font-size: 0.75em; } - + .pro-badge { top: 12px; right: 12px; } - + .pro-badge a { font-size: 9px; padding: 5px 10px; @@ -374,71 +232,38 @@ .bilibili-card { margin: 10px 0; } - + .card-content { padding: 14px 18px; } - + .icon-container { width: 36px; height: 36px; margin-right: 12px; } - + .bilibili-icon { width: 28px; height: 28px; } - + .card-content h3 { font-size: 0.95em; line-height: 1.2; } - + .card-description { font-size: 0.7em; } - + .pro-badge { top: 10px; right: 10px; } - + .pro-badge a { font-size: 8px; padding: 4px 8px; } -} - -/* 添加一些微动画增强体验 */ -@keyframes pulse { - 0% { - transform: scale(1); - opacity: 1; - } - 50% { - transform: scale(1.05); - opacity: 0.9; - } - 100% { - transform: scale(1); - opacity: 1; - } -} - -.card-link:active .icon-container { - animation: pulse 0.3s ease; -} - -.card-link:active .bilibili-icon { - animation: pulse 0.3s ease; -} - -/* 改善点击体验 */ -.card-link:active { - transform: translateY(-1px) scale(0.98); -} - -.pro-badge a:active { - transform: translateY(-1px) scale(0.95); } \ No newline at end of file diff --git a/handbook/src/css/CardLink.css b/handbook/src/css/CardLink.css index 304c89a90..939d23c22 100644 --- a/handbook/src/css/CardLink.css +++ b/handbook/src/css/CardLink.css @@ -12,7 +12,6 @@ border-radius: 5px; text-decoration: none; color: #333; - transition: background-color 0.3s ease; } .card-link:hover { @@ -45,11 +44,59 @@ /* 让描述占据剩余空间 */ } +/* 新增:双链接按钮样式 */ +.card-links { + display: flex; + gap: 10px; + margin-top: 15px; +} + +.link-button { + display: flex; + align-items: center; + gap: 5px; + padding: 8px 15px; + border: 1px solid #ddd; + border-radius: 6px; + text-decoration: none; + color: #333; + background-color: #fff; + font-size: 14px; +} + +.link-button:hover { + background-color: #f5f5f5; + border-color: #bbb; +} + +.link-button.gitee { + border-color: #c71d23; +} + +.link-button.gitee:hover { + background-color: #c71d23; + color: white; +} + +.link-button.github { + border-color: #333; +} + +.link-button.github:hover { + background-color: #333; + color: white; +} + +.link-icon { + width: 16px; + height: 16px; +} + .pro-badge { position: absolute; top: 5px; right: 5px; - z-index: 1; + z-index: 2; } .pro-badge a { @@ -59,6 +106,7 @@ background-color: #007bff; color: white; cursor: pointer; + text-decoration: none; } .pro-badge a:hover { diff --git a/handbook/src/css/Paypal.css b/handbook/src/css/Paypal.css index 6e84641ec..7bfae0c88 100644 --- a/handbook/src/css/Paypal.css +++ b/handbook/src/css/Paypal.css @@ -6,7 +6,6 @@ color: white; border: none; border-radius: 5px; - transition: background-color 0.3s; } .button:hover { diff --git a/handbook/src/css/custom.css b/handbook/src/css/custom.css index e49518874..72594ea78 100644 --- a/handbook/src/css/custom.css +++ b/handbook/src/css/custom.css @@ -7,49 +7,104 @@ /* You can override the default Infima variables here. */ :root { - --ifm-color-primary: #25c2a0; - --ifm-color-primary-dark: rgb(33, 175, 144); - --ifm-color-primary-darker: rgb(31, 165, 136); - --ifm-color-primary-darkest: rgb(26, 136, 112); - --ifm-color-primary-light: rgb(70, 203, 174); - --ifm-color-primary-lighter: rgb(102, 212, 189); - --ifm-color-primary-lightest: rgb(146, 224, 208); - --ifm-code-font-size: 95%; + --ifm-color-primary: #25c2a0; + --ifm-color-primary-dark: rgb(33, 175, 144); + --ifm-color-primary-darker: rgb(31, 165, 136); + --ifm-color-primary-darkest: rgb(26, 136, 112); + --ifm-color-primary-light: rgb(70, 203, 174); + --ifm-color-primary-lighter: rgb(102, 212, 189); + --ifm-color-primary-lightest: rgb(146, 224, 208); + --ifm-code-font-size: 95%; } .docusaurus-highlight-code-line { - background-color: rgb(72, 77, 91); - display: block; - margin: 0 calc(-1 * var(--ifm-pre-padding)); - padding: 0 var(--ifm-pre-padding); + background-color: rgb(72, 77, 91); + display: block; + margin: 0 calc(-1 * var(--ifm-pre-padding)); + padding: 0 var(--ifm-pre-padding); } .like-button { - background-color: #f1f1f1; - border: none; - color: #333; - padding: 10px 20px; - text-align: center; - text-decoration: none; - display: inline-block; - font-size: 16px; - margin: 4px 2px; - cursor: pointer; - border-radius: 20px; /* 设置左右两侧的半径为20px,这通常等于按钮的内边距 */ - transition: background-color 0.3s ease; + background-color: #f1f1f1; + border: none; + color: #333; + padding: 10px 20px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 16px; + margin: 4px 2px; + cursor: pointer; + border-radius: 20px; + /* 设置左右两侧的半径为20px,这通常等于按钮的内边距 */ } .like-button:hover { - background-color: #ddd; + background-color: #ddd; } .like-button:active { - background-color: #bbb; + background-color: #bbb; } -.like-button:disabled { /* 当按钮被禁用时 */ - background-color: #ccc; /* 变为灰色背景 */ - color: #999; /* 文字颜色也变为较浅的灰色 */ - cursor: not-allowed; /* 改变鼠标样式为禁止样式 */ +.like-button:disabled { + /* 当按钮被禁用时 */ + background-color: #ccc; + /* 变为灰色背景 */ + color: #999; + /* 文字颜色也变为较浅的灰色 */ + cursor: not-allowed; + /* 改变鼠标样式为禁止样式 */ +} + + +/* 修复移动端导航栏菜单项高度不一致问题 - 温和修复 */ +@media (max-width: 996px) { + + /* 只针对直接的菜单链接进行高度统一,不影响子菜单结构 */ + .navbar-sidebar .menu__list-item>.menu__link, + .navbar-sidebar .menu__list-item>.menu__list-item-collapsible>.menu__link { + min-height: 48px; + line-height: 48px; + padding-top: 0; + padding-bottom: 0; + display: flex; + align-items: center; + } + + /* 确保有子菜单的容器不破坏布局 */ + .navbar-sidebar .menu__list-item>.menu__list-item-collapsible { + min-height: 48px; + display: flex; + align-items: center; + } +} + +/* 小屏幕适配 */ +@media (max-width: 768px) { + + .navbar-sidebar .menu__list-item>.menu__link, + .navbar-sidebar .menu__list-item>.menu__list-item-collapsible>.menu__link { + min-height: 44px; + line-height: 44px; + } + + .navbar-sidebar .menu__list-item>.menu__list-item-collapsible { + min-height: 44px; + } +} + +/* 超小屏幕适配 */ +@media (max-width: 480px) { + + .navbar-sidebar .menu__list-item>.menu__link, + .navbar-sidebar .menu__list-item>.menu__list-item-collapsible>.menu__link { + min-height: 42px; + line-height: 42px; + } + + .navbar-sidebar .menu__list-item>.menu__list-item-collapsible { + min-height: 42px; + } } \ No newline at end of file diff --git a/handbook/src/pages/index.css b/handbook/src/pages/index.css index b2d04d57e..a3737dda9 100644 --- a/handbook/src/pages/index.css +++ b/handbook/src/pages/index.css @@ -3,46 +3,15 @@ align-items: center; position: relative; /* 白百合蓝色渐变背景 */ - background: linear-gradient(135deg, - #f0f8ff 0%, /* Alice Blue */ - #e6f3ff 25%, /* Light Alice Blue */ - #b3d9ff 50%, /* Soft Blue */ - #7fc8ff 75%, /* Light Blue */ - #4fb3ff 100% /* Vibrant Blue */ - ); - /* 毛玻璃效果 */ - backdrop-filter: blur(20px); - -webkit-backdrop-filter: blur(20px); - box-shadow: 0 8px 32px rgba(79, 179, 255, 0.1); + background: #e6f3ff; border: 1px solid rgba(255, 255, 255, 0.2); overflow: hidden; } -.TouchSocket-banner::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: - radial-gradient(circle at 20% 80%, rgba(79, 179, 255, 0.1) 0%, transparent 50%), - radial-gradient(circle at 80% 20%, rgba(127, 200, 255, 0.1) 0%, transparent 50%), - radial-gradient(circle at 40% 40%, rgba(240, 248, 255, 0.1) 0%, transparent 50%); - pointer-events: none; -} + .TouchSocket-banner.dark { - background: linear-gradient(135deg, - #1a2332 0%, /* Dark Blue */ - #2d3e5c 25%, /* Medium Dark Blue */ - #3e5a7a 50%, /* Blue Grey */ - #4f7698 75%, /* Light Blue Grey */ - #6092b6 100% /* Soft Blue */ - ); - backdrop-filter: blur(20px); - -webkit-backdrop-filter: blur(20px); - box-shadow: 0 8px 32px rgba(26, 35, 50, 0.3); + background: #2d3e5c; border: 1px solid rgba(96, 146, 182, 0.2); } @@ -78,11 +47,7 @@ .TouchSocket-banner-project { font-size: 1.5em; font-weight: 700; - background: linear-gradient(135deg, #2d5aa0 0%, #4f7cb8 100%); - -webkit-background-clip: text; - background-clip: text; - -webkit-text-fill-color: transparent; - filter: drop-shadow(0 2px 4px rgba(45, 90, 160, 0.2)); + color: #2d5aa0; margin-bottom: 0.5em; } @@ -91,18 +56,7 @@ font-size: 2.2em; font-weight: 700; line-height: 1.25; - /* 白百合蓝色渐变文字 */ - background: linear-gradient(135deg, - #2d5aa0 0%, /* Deep Alice Blue */ - #4f7cb8 25%, /* Medium Blue */ - #7fa8d3 50%, /* Light Blue */ - #a5c6e0 75%, /* Soft Blue */ - #c7dded 100% /* Very Light Blue */ - ); - -webkit-background-clip: text; - background-clip: text; - -webkit-text-fill-color: transparent; - filter: drop-shadow(0 2px 4px rgba(45, 90, 160, 0.1)); + color: #4a69bd; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -114,17 +68,7 @@ font-size: 2.2em; font-weight: 700; line-height: 1.25; - background: linear-gradient(135deg, - #c7dded 0%, /* Very Light Blue */ - #a5c6e0 25%, /* Soft Blue */ - #7fa8d3 50%, /* Light Blue */ - #6092b6 75%, /* Medium Blue */ - #8bb4d9 100% /* Light Alice Blue */ - ); - -webkit-background-clip: text; - background-clip: text; - -webkit-text-fill-color: transparent; - filter: drop-shadow(0 2px 4px rgba(199, 221, 237, 0.2)); + color: #74b9ff; } .TouchSocket-banner-spec { @@ -134,7 +78,8 @@ font-size: 1em; font-weight: 500; line-height: 1.6; - color: #2d5aa0; /* Deep Alice Blue for better readability */ + color: #2d5aa0; + /* Deep Alice Blue for better readability */ } .TouchSocket-banner-spec li { @@ -151,9 +96,8 @@ left: 0; width: 6px; height: 6px; - background: linear-gradient(135deg, #4f7cb8, #7fa8d3); + background: #4f7cb8; border-radius: 50%; - box-shadow: 0 2px 4px rgba(79, 124, 184, 0.3); } .TouchSocket-support-platform { @@ -175,13 +119,6 @@ .TouchSocket-support-icons span { margin-right: 24px; - transition: transform 0.3s ease, filter 0.3s ease; - filter: drop-shadow(0 2px 8px rgba(79, 124, 184, 0.2)); -} - -.TouchSocket-support-icons span:hover { - transform: translateY(-2px) scale(1.05); - filter: drop-shadow(0 4px 12px rgba(79, 124, 184, 0.4)); } .TouchSocket-get-start, @@ -190,7 +127,7 @@ border-radius: 2em; min-width: 160px; color: #ffffff; - background: linear-gradient(135deg, #4f7cb8 0%, #7fa8d3 100%); + background: #4f7cb8; position: relative; line-height: 1.6; text-align: center; @@ -200,46 +137,24 @@ white-space: nowrap; font-weight: 600; letter-spacing: 0.5px; - box-shadow: 0 4px 16px rgba(79, 124, 184, 0.3); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.3); - transition: all 0.3s ease; - overflow: hidden; } -.TouchSocket-get-start::before { - content: ''; - position: absolute; - top: 0; - left: -100%; - width: 100%; - height: 100%; - background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); - transition: left 0.5s ease; -} -.TouchSocket-get-start:hover::before { - left: 100%; -} .TouchSocket-try-demo { - background: linear-gradient(135deg, #2d5aa0 0%, #4f7cb8 100%); + background: #2d5aa0; margin-left: 24px; } .TouchSocket-get-start:hover { - background: linear-gradient(135deg, #4169a3 0%, #6b96c7 100%); - transform: translateY(-2px); - box-shadow: 0 8px 24px rgba(79, 124, 184, 0.4); + background: #4169a3; color: #ffffff; text-decoration: none; } .TouchSocket-try-demo:hover { - background: linear-gradient(135deg, #254a8a 0%, #4169a3 100%); - transform: translateY(-2px); - box-shadow: 0 8px 24px rgba(45, 90, 160, 0.4); + background: #254a8a; color: #ffffff; text-decoration: none; } @@ -257,15 +172,9 @@ } .system-top-bar { - background: linear-gradient(135deg, - rgba(79, 179, 255, 0.2) 0%, - rgba(127, 200, 255, 0.15) 50%, - rgba(165, 198, 224, 0.1) 100% - ); + background: rgba(79, 179, 255, 0.2); padding: 0.5em 1em; border-bottom: 1px solid rgba(79, 179, 255, 0.2); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); } .system-top-bar-circle { @@ -274,8 +183,6 @@ height: 0.6em; margin-left: 0.3em; border-radius: 50%; - filter: brightness(120%); - box-shadow: 0 0 4px rgba(255, 255, 255, 0.3); } .system-window { @@ -284,11 +191,12 @@ padding: 0; border-radius: 16px; overflow: hidden; - background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); - backdrop-filter: blur(20px); - -webkit-backdrop-filter: blur(20px); - border: 1px solid rgba(255, 255, 255, 0.1); + background: linear-gradient(135deg, + rgba(108, 61, 228, 0.85) 0%, + rgba(241, 54, 223, 0.8) 100%); + border: 1px solid rgba(79, 124, 184, 0.2); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); } .system-window iframe { @@ -319,7 +227,7 @@ padding: 1rem !important; font-size: 13px !important; } - + .system-window code { font-size: 13px !important; } @@ -331,7 +239,7 @@ font-size: 12px !important; line-height: 1.5 !important; } - + .system-window code { font-size: 12px !important; line-height: 1.5 !important; @@ -343,7 +251,7 @@ padding: 0.5rem !important; font-size: 11px !important; } - + .system-window code { font-size: 11px !important; } @@ -355,7 +263,7 @@ font-size: 15px !important; padding: 2rem !important; } - + .system-window code { font-size: 15px !important; } @@ -366,7 +274,7 @@ font-size: 16px !important; padding: 2.5rem !important; } - + .system-window code { font-size: 16px !important; } @@ -379,7 +287,6 @@ } .preview-border { - box-shadow: 0 8px 32px rgba(79, 124, 184, 0.15); border: solid 1px #4f7cb8; } @@ -388,73 +295,42 @@ margin-bottom: 4em; text-align: center; padding: 2rem; - background: linear-gradient(135deg, - rgba(240, 248, 255, 0.3) 0%, - rgba(255, 255, 255, 0.1) 100% - ); + background: rgba(240, 248, 255, 0.3); border-radius: 24px; margin-left: 2rem; margin-right: 2rem; - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); border: 1px solid rgba(79, 179, 255, 0.1); } .TouchSocket-small-title { - color: #2d5aa0; /* 白百合蓝色系 - 深蓝 */ + color: #2d5aa0; + /* 白百合蓝色系 - 深蓝 */ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 1em; font-weight: 600; letter-spacing: 1px; opacity: 0.8; text-transform: uppercase; - background: linear-gradient(135deg, #2d5aa0 0%, #4f7cb8 100%); - -webkit-background-clip: text; - background-clip: text; - -webkit-text-fill-color: transparent; - filter: drop-shadow(0 2px 4px rgba(45, 90, 160, 0.1)); } .TouchSocket-small-title.dark { - color: #a5c6e0; /* 白百合蓝色系 - 浅蓝 */ - background: linear-gradient(135deg, #a5c6e0 0%, #7fa8d3 100%); - -webkit-background-clip: text; - background-clip: text; - -webkit-text-fill-color: transparent; - filter: drop-shadow(0 2px 4px rgba(165, 198, 224, 0.2)); + color: #a5c6e0; + /* 白百合蓝色系 - 浅蓝 */ } .TouchSocket-big-title { - color: #2d5aa0; /* 白百合蓝色系 - 深蓝 */ + color: #2d5aa0; + /* 白百合蓝色系 - 深蓝 */ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 2.2em; font-weight: 700; line-height: 1.31; margin-bottom: 2em; - background: linear-gradient(135deg, - #2d5aa0 0%, /* Deep Alice Blue */ - #4f7cb8 25%, /* Medium Blue */ - #7fa8d3 50%, /* Light Blue */ - #a5c6e0 100% /* Soft Blue */ - ); - -webkit-background-clip: text; - background-clip: text; - -webkit-text-fill-color: transparent; - filter: drop-shadow(0 2px 8px rgba(45, 90, 160, 0.1)); } .TouchSocket-big-title.dark { - color: #a5c6e0; /* 白百合蓝色系 - 浅蓝 */ - background: linear-gradient(135deg, - #c7dded 0%, /* Very Light Blue */ - #a5c6e0 25%, /* Soft Blue */ - #7fa8d3 50%, /* Light Blue */ - #6092b6 100% /* Medium Blue */ - ); - -webkit-background-clip: text; - background-clip: text; - -webkit-text-fill-color: transparent; - filter: drop-shadow(0 2px 8px rgba(165, 198, 224, 0.2)); + color: #a5c6e0; + /* 白百合蓝色系 - 浅蓝 */ } .TouchSocket-gitee-log { @@ -471,29 +347,14 @@ position: relative; border-radius: 16px; overflow: hidden; - background: linear-gradient(135deg, - rgba(240, 248, 255, 0.9) 0%, - rgba(230, 243, 255, 0.9) 100% - ); - backdrop-filter: blur(20px); - -webkit-backdrop-filter: blur(20px); - box-shadow: 0 8px 32px rgba(79, 179, 255, 0.15); + background: rgba(240, 248, 255, 0.9); border: 1px solid rgba(255, 255, 255, 0.3); - transition: all 0.3s ease; -} - -.TouchSocket-log-item:hover { - transform: translateY(-4px); - box-shadow: 0 12px 40px rgba(79, 179, 255, 0.25); } .TouchSocket-log-jiao { width: 120px; height: 120px; - background: linear-gradient(135deg, - rgba(255, 255, 255, 0.8) 0%, - rgba(255, 255, 255, 0.4) 100% - ); + background: rgba(255, 255, 255, 0.8); position: absolute; top: -8px; right: -8px; @@ -503,10 +364,7 @@ } .TouchSocket-log-jiao.dark { - background: linear-gradient(135deg, - rgba(26, 35, 50, 0.8) 0%, - rgba(46, 62, 92, 0.6) 100% - ); + background: rgba(26, 35, 50, 0.8); border-top: 2px solid rgba(96, 146, 182, 0.4); border-right: 2px solid rgba(96, 146, 182, 0.4); } @@ -524,14 +382,11 @@ justify-content: center; align-items: center; flex-direction: column; - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); } .TouchSocket-log-number div { font-size: 3.2em; font-weight: 700; - text-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); margin-bottom: 0.2em; } @@ -541,7 +396,8 @@ font-style: normal; letter-spacing: 1px; line-height: normal; - color: #2d5aa0; /* 白百合蓝色系 - 深蓝 */ + color: #2d5aa0; + /* 白百合蓝色系 - 深蓝 */ font-weight: 600; font-size: 1.1em; text-transform: uppercase; @@ -549,7 +405,8 @@ } .TouchSocket-log-number span.dark { - color: #a5c6e0; /* 白百合蓝色系 - 浅蓝 */ + color: #a5c6e0; + /* 白百合蓝色系 - 浅蓝 */ } .TouchSocket-remark { @@ -695,7 +552,6 @@ overflow: hidden; border-radius: 4px; height: 170px; - box-shadow: 4px 3px 16px -3px #0009; box-sizing: border-box; position: relative; } @@ -754,52 +610,7 @@ font-size: 16px; } -@keyframes fadeInUp { - from { - opacity: 0; - transform: translateY(30px); - } - to { - opacity: 1; - transform: translateY(0); - } -} -.TouchSocket-banner-item { - animation: fadeInUp 0.8s ease-out; -} - -.TouchSocket-banner-item:nth-child(2) { - animation-delay: 0.2s; -} - -.TouchSocket-log-item { - animation: fadeInUp 0.6s ease-out; -} - -.TouchSocket-log-item:nth-child(2) { - animation-delay: 0.1s; -} - -.TouchSocket-log-item:nth-child(3) { - animation-delay: 0.2s; -} - -.TouchSocket-banner-spec li { - animation: fadeInUp 0.5s ease-out; -} - -.TouchSocket-banner-spec li:nth-child(2) { - animation-delay: 0.1s; -} - -.TouchSocket-banner-spec li:nth-child(3) { - animation-delay: 0.2s; -} - -.TouchSocket-banner-spec li:nth-child(4) { - animation-delay: 0.3s; -} @media screen and (max-width: 1024px) { .TouchSocket-banner { @@ -957,17 +768,17 @@ width: 48rem; max-width: 55vw; } - + .TouchSocket-banner-container { max-width: 1600px; gap: 3rem; } - + .TouchSocket-banner-item:first-child { max-width: 620px; min-width: 580px; } - + .TouchSocket-banner-description { font-size: 2.4em; } @@ -978,17 +789,17 @@ width: 56rem; max-width: 60vw; } - + .TouchSocket-banner-container { max-width: 1800px; gap: 4rem; } - + .TouchSocket-banner-item:first-child { max-width: 680px; min-width: 620px; } - + .TouchSocket-banner-description { font-size: 2.6em; } @@ -1000,12 +811,12 @@ width: 40rem; max-width: 48vw; } - + .TouchSocket-banner-item:first-child { max-width: 540px; min-width: 480px; } - + .TouchSocket-banner-description { font-size: 2.0em; } @@ -1035,50 +846,26 @@ /* 代码区域的语法高亮优化 */ .system-window .language-cs { - background: linear-gradient(135deg, #0d1117 0%, #161b22 100%) !important; - color: #e6edf3 !important; + background: linear-gradient(135deg, + #362a36) !important; + color: #3b5c8a !important; } -.system-window .token.keyword { - color: #4f7cb8 !important; /* 白百合蓝色 - 关键字 */ +/* 暗色模式下的代码区域样式 */ +[data-theme='dark'] .system-window .language-cs { + background: linear-gradient(135deg, + rgba(42, 54, 72, 0.95) 0%, + rgba(52, 68, 88, 0.95) 50%, + rgba(62, 82, 108, 0.95) 100%) !important; + color: #2d7bb2 !important; } -.system-window .token.string { - color: #7fa8d3 !important; /* 白百合蓝色 - 字符串 */ -} - -.system-window .token.comment { - color: #6092b6 !important; /* 白百合蓝色 - 注释 */ - font-style: italic; -} - -.system-window .token.class-name { - color: #2d5aa0 !important; /* 白百合蓝色 - 类名 */ -} - -.system-window .token.function { - color: #4f7cb8 !important; /* 白百合蓝色 - 函数 */ -} - -.system-window .token.operator { - color: #a5c6e0 !important; /* 白百合蓝色 - 操作符 */ -} - -.system-window .token.punctuation { - color: #8bb4d9 !important; /* 白百合蓝色 - 标点 */ -} - -.system-window .token.number { - color: #c7dded !important; /* 白百合蓝色 - 数字 */ -} - -/* 代码高亮行 */ -.system-window .docusaurus-highlight-code-line { - background-color: rgba(79, 124, 184, 0.1) !important; - display: block; - margin: 0 -1em; - padding: 0 1em; - border-left: 3px solid #4f7cb8; +/* 暗色模式下的系统窗口容器 */ +[data-theme='dark'] .system-window { + background: linear-gradient(135deg, + rgba(42, 54, 72, 0.9) 0%, + rgba(52, 68, 88, 0.9) 100%); + border: 1px solid rgba(165, 198, 224, 0.2); } /* 代码块滚动条样式 */ @@ -1093,12 +880,12 @@ } .system-window pre::-webkit-scrollbar-thumb { - background: linear-gradient(135deg, #4f7cb8 0%, #7fa8d3 100%); + background: #4f7cb8; border-radius: 4px; } .system-window pre::-webkit-scrollbar-thumb:hover { - background: linear-gradient(135deg, #2d5aa0 0%, #4f7cb8 100%); + background: #2d5aa0; } /* 小屏幕优化 (手机屏幕) */ @@ -1127,7 +914,7 @@ } .TouchSocket-support-icons span { - transform: scale(0.9); + margin: 0.25rem; } .TouchSocket-get-start { @@ -1172,7 +959,6 @@ } .TouchSocket-support-icons span { - transform: scale(0.8); margin: 0.25rem; } diff --git a/handbook/src/pages/index.own.css b/handbook/src/pages/index.own.css index 8981a8e97..dea130a97 100644 --- a/handbook/src/pages/index.own.css +++ b/handbook/src/pages/index.own.css @@ -1,37 +1,33 @@ .navbar { - background: linear-gradient(135deg, - #f0f8ff 0%, /* Alice Blue */ - #e6f3ff 25%, /* Light Alice Blue */ - #d9ecff 50%, /* Soft Blue */ - #cce5ff 100% /* Light Blue */ - ); + background: #e6f3ff; } .navbar__brand { - color: #2d5aa0; /* 深蓝色 */ + color: #2d5aa0; + /* 深蓝色 */ font-weight: 700; } .navbar__link { - color: #2d5aa0; /* 深蓝色 */ + color: #2d5aa0; + /* 深蓝色 */ font-weight: 500; padding: 0.5rem 1rem; border-radius: 6px; - transition: all 0.3s ease; position: relative; margin: 0 0.25rem; } .navbar__link:hover { - color: #4f7cb8; /* 中蓝色 */ + color: #4f7cb8; + /* 中蓝色 */ background: rgba(79, 124, 184, 0.1); - transform: translateY(-1px); } .navbar__link--active { - color: #4f7cb8; /* 中蓝色 */ + color: #4f7cb8; + /* 中蓝色 */ background: rgba(79, 124, 184, 0.15); - box-shadow: 0 2px 8px rgba(79, 124, 184, 0.2); } .navbar__link--active::after { @@ -42,34 +38,32 @@ transform: translateX(-50%); width: 60%; height: 2px; - background: linear-gradient(90deg, #4f7cb8, #7fa8d3); + background: #4f7cb8; border-radius: 1px; } .navbar__items { - color: #2d5aa0; /* 深蓝色 */ + color: #2d5aa0; + /* 深蓝色 */ } .menu__list-item .navbar__link--active, .menu__list-item .navbar__link:hover { - color: #4f7cb8; /* 中蓝色 */ + color: #4f7cb8; + /* 中蓝色 */ } /* 下拉菜单样式 */ .dropdown__menu { background: rgba(240, 248, 255, 0.95); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); border: 1px solid rgba(79, 124, 184, 0.2); border-radius: 8px; - box-shadow: 0 4px 16px rgba(79, 124, 184, 0.15); padding: 0.5rem 0; } .dropdown__link { color: #2d5aa0; padding: 0.5rem 1rem; - transition: all 0.2s ease; } .dropdown__link:hover { @@ -85,50 +79,49 @@ /* 暗色主题下的导航栏样式 */ [data-theme='dark'] .navbar { - background: linear-gradient(135deg, - #1a2332 0%, /* Dark Blue */ - #2d3e5c 25%, /* Medium Dark Blue */ - #3e5a7a 50%, /* Blue Grey */ - #4f7698 100% /* Light Blue Grey */ - ); + background: #2d3e5c; } [data-theme='dark'] .navbar__brand { - color: #a5c6e0; /* 浅蓝色 */ + color: #a5c6e0; + /* 浅蓝色 */ } [data-theme='dark'] .navbar__link { - color: #a5c6e0; /* 浅蓝色 */ + color: #a5c6e0; + /* 浅蓝色 */ } [data-theme='dark'] .navbar__link:hover { - color: #c7dded; /* 更浅的蓝色 */ + color: #c7dded; + /* 更浅的蓝色 */ background: rgba(165, 198, 224, 0.15); } [data-theme='dark'] .navbar__link--active { - color: #c7dded; /* 更浅的蓝色 */ + color: #c7dded; + /* 更浅的蓝色 */ background: rgba(165, 198, 224, 0.2); - box-shadow: 0 2px 8px rgba(165, 198, 224, 0.2); } [data-theme='dark'] .navbar__link--active::after { - background: linear-gradient(90deg, #a5c6e0, #c7dded); + background: #a5c6e0; } [data-theme='dark'] .navbar__items { - color: #a5c6e0; /* 浅蓝色 */ + color: #a5c6e0; + /* 浅蓝色 */ } [data-theme='dark'] .menu__list-item .navbar__link--active, [data-theme='dark'] .menu__list-item .navbar__link:hover { - color: #c7dded; /* 更浅的蓝色 */ + color: #c7dded; + /* 更浅的蓝色 */ } [data-theme='dark'] .dropdown__menu { background: rgba(26, 35, 50, 0.95); border: 1px solid rgba(96, 146, 182, 0.3); - box-shadow: 0 4px 16px rgba(26, 35, 50, 0.3); } [data-theme='dark'] .dropdown__link { @@ -150,24 +143,22 @@ color: #2d5aa0; padding: 0.75rem 1rem; border-radius: 6px; - transition: all 0.2s ease; margin: 0.125rem 0.5rem; display: block; position: relative; } - + .menu__link:hover { background: rgba(79, 124, 184, 0.1); color: #4f7cb8; - transform: translateX(4px); } - + .menu__link--active { background: rgba(79, 124, 184, 0.15); color: #4f7cb8; font-weight: 600; } - + .menu__link--active::before { content: ''; position: absolute; @@ -176,39 +167,35 @@ transform: translateY(-50%); width: 3px; height: 60%; - background: linear-gradient(180deg, #4f7cb8, #7fa8d3); + background: #4f7cb8; border-radius: 0 2px 2px 0; } - + .menu__list-item { margin: 0.25rem 0; } - - .menu__caret { - transition: transform 0.2s ease; - } - + .menu__list-item--collapsed .menu__caret { transform: rotate(-90deg); } - + /* 暗色主题移动端菜单 */ [data-theme='dark'] .menu__link { color: #a5c6e0; } - + [data-theme='dark'] .menu__link:hover { background: rgba(96, 146, 182, 0.15); color: #c7dded; } - + [data-theme='dark'] .menu__link--active { background: rgba(96, 146, 182, 0.2); color: #c7dded; } - + [data-theme='dark'] .menu__link--active::before { - background: linear-gradient(180deg, #a5c6e0, #c7dded); + background: #a5c6e0; } } @@ -218,7 +205,6 @@ text-decoration: none; padding: 0.25rem 0.5rem; border-radius: 4px; - transition: all 0.2s ease; } .breadcrumbs__link:hover { diff --git a/handbook/src/pages/index.tsx b/handbook/src/pages/index.tsx index 2c3e1986a..2ee987c58 100644 --- a/handbook/src/pages/index.tsx +++ b/handbook/src/pages/index.tsx @@ -14,7 +14,8 @@ import LinuxIcon from "./linux.svg"; import MacOSIcon from "./macos.svg"; import WindowIcon from "./windows.svg"; -function Home() { +function Home() +{ const context = useDocusaurusContext(); const { siteConfig = {} } = context; @@ -24,11 +25,15 @@ function Home() { + + + ); } -function Banner() { +function Banner() +{ const { colorMode, setLightTheme, setDarkTheme } = useColorMode(); const isDarkTheme = colorMode === "dark"; @@ -41,18 +46,20 @@ function Banner() { TouchSocket
- 一款简单易用的基础网络通讯组件库。 + 知行合一,从理论到实践的C#网络通讯组件库。
-
- 三十功名尘与土,八千里路云和月。 +
+ 纸上得来终觉浅,绝知此事要躬行。
  • Apache-2.0 宽松开源协议,商业免费授权
  • -
  • - 支持 .NET Framework 4.5及以上,.NET Standard2.0及以上 -
  • -
  • 极少依赖
  • -
  • 极速上手,极简使用
  • +
  • 支持 .NET Framework 462及以上,.NET Standard2.0及.NET6.0以上
  • +
  • 轻量级设计,开箱即用,几行代码即可构建网络应用
  • +
  • 高性能异步通讯,支撑海量并发连接
  • +
  • 丰富的协议支持:TCP、UDP、HTTP、WebSocket、MQTT等
  • +
  • 内置断线重连、心跳检测、流量控制等企业级特性
  • +
  • 完善的插件体系,灵活扩展业务逻辑
  • +
  • 详细的中文文档,丰富的示例代码
受支持平台:
@@ -88,20 +95,18 @@ function Banner() { language="cs" // section="schema" source={` -// highlight-next-line var service = new TcpService(); -service.Connecting = (client, e) => { return EasyTask.CompletedTask; };//有客户端正在连接 service.Connected = (client, e) => { return EasyTask.CompletedTask; };//有客户端成功连接 service.Disconnected = (client, e) => { return EasyTask.CompletedTask; };//有客户端断开连接 service.Received = (client, e) => { //从客户端收到信息 - string mes = Encoding.UTF8.GetString(e.ByteBlock.Buffer, 0, e.ByteBlock.Len); + string mes = Encoding.UTF8.GetString(e.Memory.Span); client.Logger.Info($"已从{client.Id}接收到信息:{mes}"); return EasyTask.CompletedTask; }; -service.Setup(new TouchSocketConfig()//载入配置 +await service.SetupAsync(new TouchSocketConfig()//载入配置 .SetListenIPHosts("tcp://127.0.0.1:7788", 7789)//同时监听两个地址 .ConfigureContainer(a => { @@ -111,7 +116,7 @@ service.Setup(new TouchSocketConfig()//载入配置 { //a.Add();//此处可以添加插件 })); -service.Start();//启动 +await service.StartAsync();//启动 `} /> @@ -121,7 +126,8 @@ service.Start();//启动 ); } -function Gitee() { +function Gitee() +{ const { colorMode, setLightTheme, setDarkTheme } = useColorMode(); const isDarkTheme = colorMode === "dark"; @@ -142,7 +148,7 @@ function Gitee() { className={"TouchSocket-log-jiao" + (isDarkTheme ? " dark" : "")} >
-
2000 +
+
4000 +
Stars
@@ -154,7 +160,7 @@ function Gitee() { className={"TouchSocket-log-jiao" + (isDarkTheme ? " dark" : "")} >
-
600 +
+
1000 +
Forks
@@ -166,7 +172,7 @@ function Gitee() { className={"TouchSocket-log-jiao" + (isDarkTheme ? " dark" : "")} >
-
202,125
+
524,288
Downloads
@@ -175,19 +181,23 @@ function Gitee() { ); } -function CodeSection(props) { +function CodeSection(props) +{ let { language, replace, section, source } = props; source = source.replace(/\/\/ <.*?\n/g, ""); - if (replace) { - for (const [pattern, value] of Object.entries(replace)) { + if (replace) + { + for (const [pattern, value] of Object.entries(replace)) + { source = source.replace(new RegExp(pattern, "gs"), value); } } source = source.trim(); - if (!source.includes("\n")) { + if (!source.includes("\n")) + { source += "\n"; } @@ -204,7 +214,8 @@ function CodeSection(props) { ); } -function SystemWindow(systemWindowProps) { +function SystemWindow(systemWindowProps) +{ const { children, className, ...props } = systemWindowProps; return (
+
+

+ 🚀 核心特性 +

+

+ 为现代C#应用程序提供全面的网络通讯解决方案 +

+
+
+ + + + + + +
+
+ ); +} + +function FeatureCard({ title, description, isDark }) +{ + return ( +
+

+ {title} +

+

+ {description} +

+
+ ); +} + +function UseCases() +{ + const { colorMode } = useColorMode(); + const isDarkTheme = colorMode === "dark"; + + return ( +
+
+

+ 💼 应用场景 +

+

+ TouchSocket广泛应用于各种行业和场景 +

+
+
+ + + + + + +
+
+ ); +} + +function UseCaseCard({ icon, title, description, isDark }) +{ + return ( +
+
{icon}
+

+ {title} +

+

+ {description} +

+
+ ); +} + +function QuickStart() +{ + const { colorMode } = useColorMode(); + const isDarkTheme = colorMode === "dark"; + + return ( +
+
+

+ ⚡ 快速开始 +

+

+ 三步即可开始你的网络编程之旅 +

+
+ +
+
+ + + +
+ +
+ + 开始使用 TouchSocket → + +
+
+
+ ); +} + +function StepCard({ step, title, description, code, isDark }) +{ + return ( +
+
+ {step} +
+

+ {title} +

+

+ {description} +

+ + {code} + +
+ ); +} + +export default Home; \ No newline at end of file diff --git a/handbook/src/pages/upgrade/index.mdx b/handbook/src/pages/upgrade/index.mdx new file mode 100644 index 000000000..c0d8d7a4d --- /dev/null +++ b/handbook/src/pages/upgrade/index.mdx @@ -0,0 +1,240 @@ +--- +id: upgrade +title: 历史更新 +--- + +import useBaseUrl from "@docusaurus/useBaseUrl"; +import Tag from "@site/src/components/Tag.js"; +import Highlight from '@site/src/components/Highlight.js'; + + +## v4.0 + +**更新日期:** + +暂未发布正式版,Nuget请勾选预发布版本可以尝鲜更新。 + +目前4.0版本已进入RC(发布候选)阶段,一般API基本不会再变动。正式版计划在2025年12月左右,随`.NET10`发布正式版。 + +**更新描述:** + +-  更新 此次更新可能会引入**破坏性变更**,升级前请务必做好代码备份。代码升级详情请看[4.0升级指南](/upgrade/upgrade-to-4-0) +-  调整 **Fast序列化**、**Dmtp组件**在运行时无法向下兼容v3.x版本,请保证客户端和服务器同时升级 +-  调整 全系不再支持`.NET Framework45`,最低支持`.NET Framework462`及以上版本,直至`.NET 10.0`。 +-  调整 全系不再异步方法同步化,即:不再提供异步方法的同步版本,所有异步方法均需使用`await`调用。 +-  重构 核心字节流处理系统重构和性能优化 +-  重构 全面重构数据适配器架构,提升数据处理能力 +-  新增 传输层新增多种传输模式支持 +-  新增 `WebSocket`新增异步读取、分包传输、自定义帧等高级功能 +-  新增 `DMTP`协议支持多种传输协议的服务器和客户端工厂模式 +-  升级 最高至`.NET 10.0`框架,享受最新性能改进 + +**更新详情:** + +#### TouchSocket.Core + +-  新增 `IBytesReader` 和 `IByteBlockReader` 接口,提供统一的字节读取抽象 +-  新增 `IBytesWriter` 和 `IByteBlockWriter` 接口,提供高性能的字节写入能力 +-  新增 `ByteBlockStream`、`ReadOnlyStream`、`WriteOnlyStream` 流包装类,增强流操作能力 +-  新增 `BytesWriter`、`PipeBytesWriter`、`SegmentedBytesWriter` 多种字节写入器实现 +-  新增 `WriterAnchor` 写入锚点机制,支持位置回溯和数据重写 +-  新增 `AsyncExchange` 异步数据交换器,提供线程安全的单槽异步交接 +-  新增 `ReadLease` 读取租约机制,优化资源管理和生命周期控制 +-  新增 `IPackageConverter` 包转换器接口,统一数据包序列化和反序列化 +-  新增 `CustomDataHandlingAdapterGroup` 适配器组,支持多适配器组合使用 +-  新增 `MultithreadingDataAdapterTester` 多线程数据适配器测试器 +-  新增 `TcpDataAdapterTester` 和 `UdpDataAdapterTester` 专用测试器 +-  新增 `UdpDataHandlingAdapter` UDP数据处理适配器基类 +-  新增 `UdpFrame` 和 `UdpPackage` UDP帧和包处理机制 +-  新增 `UdpPackageAdapter` UDP包适配器,支持大数据分包传输 +-  新增 `FastBinaryPrimitiveHelper` 快速二进制原生类型处理器 +-  新增 `TimeoutTokenSource` 类,提供超时令牌管理和异常区分机制 +-  新增 `TaskCompletionSourceWithoutInlining` 防止延续内联,提升性能 +-  新增 `AsyncWaitData` 高性能异步等待容器,基于 `ValueTask` 实现 +-  新增 `InternalUtilities` 内部工具类,优化队列中间元素移除等操作 +-  新增 `EmptyStruct` 轻量级空结构体 +-  新增 `NullableHelpers` 提供安全的空值转换 +-  优化 `WaitPool` 内部优化使用新的 `AsyncWaitData` 结构,支持高并发场景 +-  优化 异步操作的取消令牌处理逻辑和内存分配优化 + +#### TouchSocket.Http & WebSocket + +-  新增 `ContentCompletionStatus` 枚举,精确跟踪内容读取状态 +-  新增 `HttpReadOnlyMemoryBlockResult` 结构,支持零拷贝内存块读取 +-  新增 `ProxyAuthenticationException` 代理认证异常 +-  新增 `ProxyConnectionException` 代理连接异常 +-  新增 `AuthenticationPlugin` 基础认证插件,支持Basic Authentication认证机制 +-  新增 `WebSocketFeatureOptions` 统一配置类,支持URL路径匹配、自动Pong响应等 +-  重构 `ClientHttpResponse` 类,支持流式读取和分块传输(Chunked Transfer) +-  优化 `ServerHttpRequest` 和 `ServerHttpResponse` 内容处理机制 +-  新增 复杂代理场景的错误处理和状态管理 +-  新增 可配置用户名、密码和认证域的认证系统 +-  新增 WebSocket分包数据传输(Continuation frames) +-  新增 自定义WebSocket帧(RSV位设置) +-  新增 多种关闭状态码设置 +-  调整 WebSocket插件接口统一化:`IWebSocketHandshakingPlugin` → `IWebSocketConnectingPlugin` +-  调整 WebSocket插件接口统一化:`IWebSocketHandshakedPlugin` → `IWebSocketConnectedPlugin` +-  优化 Ping/Pong心跳机制的可靠性 +-  新增 插件扩展生成器自动化支持 + +#### TouchSocket.Sockets + +-  新增 `ITransport` 核心传输接口,统一读写器和生命周期管理 +-  新增 `ITransportReader` 和 `ITransportWriter` 分离读写职责 +-  新增 `BaseTransport` 抽象基类,提供管道化传输基础设施 +-  新增 `TcpTransport` TCP传输层实现,支持SSL/TLS加密 +-  新增 `StreamTransport` 通用流传输层,适配各种流类型 +-  新增 `TransportStream` 基于管道的流实现 +-  新增 `PipeTcpClient` 管道化TCP客户端 +-  新增 `IPipeTcpClient` 接口支持直接管道操作 +-  新增 `TcpOperationResult` TCP操作结果封装 +-  新增 `UdpOperationResult` UDP操作结果封装 +-  新增 `TransportOption` 传输配置类 +-  新增 `CheckClearOption` 连接检查和清理配置 +-  新增 `ReconnectionOptions` 重连机制配置增强 +-  新增 `SslOption` 基础SSL配置 +-  新增 `ClientSslOption` 客户端SSL配置 +-  新增 `ServiceSslOption` 服务端SSL配置 +-  新增 `BytesReaderEventArgs` 字节读取器事件参数 +-  新增 `MemoryEventArgs` 内存数据事件参数 +-  新增 `ConnectionCheckResult` 连接检查结果枚举 +-  新增 `TcpListenOption` TCP监听选项 +-  新增 零拷贝数据传输和内存优化 +-  新增 管道选项、缓冲区大小等精细化配置 +-  新增 证书验证回调、协议版本选择等高级功能 +-  调整 Socket错误处理和状态报告机制 +-  优化 插件事件传递机制和性能 + +#### TouchSocket.XmlRpc + +-  新增 `XmlRpcOption` 统一配置类,支持URL匹配、自定义验证委托等高级配置 +-  新增 `AsyncToSyncWarning` 特性标记,提供异步转同步警告 +-  调整 代理生成器参数类型规范化:`IInvokeOption` → `InvokeOption` +-  调整 异步方法命名规范 +-  优化 代码生成器的类型安全性 + +#### TouchSocket.Dmtp + +-  新增 `ChannelDataType` 通道数据类型枚举,支持多种通道数据类型 +-  新增 `TouchSocketDmtpSourceGenerationContext` JSON序列化上下文 +-  新增 `SealedDmtpActor` 密封演员模式实现 +-  新增 `DmtpAdapterSlim` 轻量级适配器(注释版本,预留扩展) +-  新增 `DmtpFeatureOption` 基础特性配置 +-  新增 `DmtpOption` 核心DMTP配置选项 +-  新增 `DmtpRpcOption` RPC配置选项类 +-  新增 `DmtpRpcActorExtension` 扩展方法集合 +-  新增 `DmtpFileTransferOption` 文件传输配置 +-  新增 `DmtpFileTransferPluginManagerExtension` 扩展方法 +-  新增 `DmtpRedisOption` Redis配置选项 +-  新增 `RedisPluginManagerExtension` 插件管理扩展 +-  新增 `IDmtpConnectingPlugin` 连接中插件接口 +-  新增 `IDmtpConnectedPlugin` 已连接插件接口 +-  新增 `TcpDmtpService` TCP协议DMTP服务 +-  新增 `HttpDmtpService` HTTP协议DMTP服务 +-  新增 `NamedPipeDmtpService` 命名管道DMTP服务 +-  新增 `UdpDmtp` UDP协议DMTP支持 +-  新增 `WebSocketDmtpService` WebSocket协议DMTP服务 +-  新增 `TcpDmtpClientFactory` TCP客户端连接池 +-  新增 `HttpDmtpClientFactory` HTTP客户端连接池 +-  新增 多种调度器模式:即时、队列、并发、全局队列 +-  新增 序列化选择器配置 +-  新增 文件资源控制器、根路径配置 +-  新增 小文件优化传输策略 +-  新增 字节序列化转换器配置 +-  新增 内存缓存和自定义缓存后端 +-  新增 动态方法生成和插件扩展 +-  新增 元数据、验证等核心类型的AOT编译优化 +-  新增 WebSocket协议的DMTP服务器 +-  新增 HTTP中间件的DMTP服务器 +-  新增 统一容器配置 +-  新增 连接数控制和超时管理 +-  调整 特性插件注册和生命周期管理 +-  调整 协议号自定义分配 +-  优化 DMTP消息处理性能和内存使用 +-  优化 序列化性能和包大小 + +#### TouchSocket.JsonRpc + +-  新增 `JsonRpcOption` 基础配置类,支持序列化器管理 +-  新增 `HttpJsonRpcOption` HTTP协议专用配置 +-  新增 `TcpJsonRpcOption` TCP协议专用配置 +-  新增 `WebSocketJsonRpcOption` WebSocket协议专用配置 +-  新增 `TouchSocketSerializerConverter` 序列化转换器基础设施 +-  新增 Newtonsoft.Json、System.Text.Json等多种序列化器 +-  新增 自定义序列化格式化器注册和管理 +-  新增 HTTP JsonRpc URL路径匹配和自定义验证 +-  新增 TCP JsonRpc连接验证委托 +-  新增 WebSocket JsonRpc连接上下文验证 +-  调整 不同传输协议的JsonRpc配置接口 +-  调整 AllowJsonRpc委托配置模式 + +#### TouchSocket.NamedPipe & TouchSocket.SerialPorts + +-  新增 `NamedPipeTransport` 基于流传输的命名管道实现 +-  新增 `SerialPortTransport` 专用串口传输实现 +-  新增 `SerialCore` 串口核心操作封装 +-  调整 `StreamTransport` 统一传输层接口 +-  调整 `BaseTransport` 提供统一的管道化接口 +-  新增 双向通信和异步操作 +-  新增 串口参数配置和异步读写操作 + +#### TouchSocket.WebApi & TouchSocket.Mqtt + +-  新增 `WebApiOption` 统一配置类 +-  新增 `WebApiSerializerConverter` 序列化转换器 +-  新增 `SwaggerOption` Swagger配置选项 +-  新增 MQTT协议插件扩展自动生成 +-  新增 JSON序列化器配置和自定义转换器 +-  新增 浏览器自动启动、访问前缀配置 +-  新增 连接、接收、关闭等事件插件 +-  优化 API文档生成和展示功能 +-  调整 插件注册和生命周期管理 + +#### TouchSocket.SourceGenerator + +-  新增 `UtilsCore` 源生成器工具类 +-  新增 `EndianType` 字节序类型枚举 +-  新增 大端、小端及交换字节序模式 +-  新增 代码生成器基础设施支持 + +#### TouchSocketPro.AspNetCore + +-  新增 `HttpStreamTransport` HTTP流传输实现 + +#### 重要技术架构改进 + +-  升级 统一传输层架构,引入 `ITransport` 接口统一所有传输协议(TCP、UDP、HTTP、WebSocket、命名管道、串口等) +-  升级 基于 `System.IO.Pipelines` 的高性能管道化数据处理 +-  升级 支持零拷贝数据传输,显著降低内存分配和GC压力 +-  升级 统一的SSL/TLS加密支持,可在任意传输层启用加密 +-  升级 全面采用 `ValueTask` 减少异步操作的内存分配 +-  升级 新增超时控制和取消令牌的统一处理机制 +-  升级 改进异步等待池系统,提升高并发场景性能 +-  升级 支持异步延续防内联,避免线程池饥饿 +-  升级 统一插件接口命名规范和生命周期管理 +-  升级 支持插件扩展代码自动生成,减少样板代码 +-  升级 改进插件事件传递机制,支持更复杂的事件链 +-  升级 新增插件配置选项模式,提供更灵活的插件配置 +-  升级 引入选项模式(Options Pattern),统一各组件配置接口 +-  升级 支持链式配置方法,提升开发体验 +-  升级 新增配置验证和默认值机制 +-  升级 支持运行时配置动态修改 + +#### 重要变更说明 + +-  调整 WebSocket插件接口重命名:`IWebSocketHandshakingPlugin` → `IWebSocketConnectingPlugin` +-  调整 WebSocket插件接口重命名:`IWebSocketHandshakedPlugin` → `IWebSocketConnectedPlugin` +-  调整 WebSocket插件方法重命名:`OnWebSocketHandshaking` → `OnWebSocketConnecting` +-  调整 WebSocket插件方法重命名:`OnWebSocketHandshaked` → `OnWebSocketConnected` +-  调整 WebSocket配置方式调整:从链式调用改为选项配置模式 +-  调整 XmlRpc配置方式调整:使用 `SetAllowXmlRpc` 替代 `SetXmlRpcUrl` +-  调整 重连插件调整:使用泛型版本 `UseReconnection()` 替代特定方法 + +*** + +- [v3.1 系列更新](/upgrade/v3-1) +- [v3.0 系列更新](/upgrade/v3-0) +- [v2.1 系列更新](/upgrade/v2-1) +- [v2.0 系列更新](/upgrade/v2-0) +- [v1 系列更新](/upgrade/v1) +- [v0 系列与早期版本](/upgrade/v0) \ No newline at end of file diff --git a/handbook/src/pages/upgrade/upgrade-to-4-0.mdx b/handbook/src/pages/upgrade/upgrade-to-4-0.mdx new file mode 100644 index 000000000..1a7b34294 --- /dev/null +++ b/handbook/src/pages/upgrade/upgrade-to-4-0.mdx @@ -0,0 +1,905 @@ +--- +id: upgrade-to-40 +title: TouchSocket 4.0 升级指南 +--- + +本文档将帮助您从TouchSocket 3.x版本升级到4.0版本。以下是主要的语法差异和升级方法。 + +## 1. WebSocket插件接口重命名 + +**旧语法 (3.x):** +```csharp +// 握手插件接口 +IWebSocketHandshakingPlugin +IWebSocketHandshakedPlugin + +// 插件方法 +OnWebSocketHandshaking() +OnWebSocketHandshaked() +``` + +**新语法 (4.0):** +```csharp +// 连接插件接口 +IWebSocketConnectingPlugin +IWebSocketConnectedPlugin + +// 插件方法 +OnWebSocketConnecting() +OnWebSocketConnected() +``` + +**如何更新:** +- 将所有 `IWebSocketHandshakingPlugin` 替换为 `IWebSocketConnectingPlugin` +- 将所有 `IWebSocketHandshakedPlugin` 替换为 `IWebSocketConnectedPlugin` +- 将所有 `OnWebSocketHandshaking` 替换为 `OnWebSocketConnecting` +- 将所有 `OnWebSocketHandshaked` 替换为 `OnWebSocketConnected` + +## 2. WebSocket配置方式更改 + +**旧语法 (3.x):** +```csharp +.ConfigurePlugins(a => +{ + a.UseWebSocket() + .SetWSUrl("/ws") + .UseAutoPong(); +}) +``` + +**新语法 (4.0):** +```csharp +.ConfigurePlugins(a => +{ + a.UseWebSocket(options => + { + options.SetUrl("/ws"); + options.SetAutoPong(true); + }); +}) +``` + +**如何更新:** +- 使用 `UseWebSocket(options => {})` 替代链式调用 +- `SetWSUrl()` 改为 `options.SetUrl()` +- `UseAutoPong()` 改为 `options.SetAutoPong(true)` + +## 3. WebSocket断线重连插件更改 + +**旧语法 (3.x):** +```csharp +.ConfigurePlugins(a => +{ + a.UseWebSocketReconnection(); +}) +``` + +**新语法 (4.0):** +```csharp +.ConfigurePlugins(a => +{ + a.UseReconnection(); +}) +``` + +**如何更新:** +- 将 `UseWebSocketReconnection()` 替换为 `UseReconnection()` + +## 4. WebSocket简化连接方式 + +**旧语法 (3.x):** +```csharp +var client = new WebSocketClient(); +await client.SetupAsync(new TouchSocketConfig() + .SetRemoteIPHost("ws://127.0.0.1:7789/ws")); +await client.ConnectAsync(); +``` + +**新语法 (4.0):** +```csharp +var client = new WebSocketClient(); +await client.ConnectAsync("ws://127.0.0.1:7789/ws"); +``` + +**如何更新:** +- 对于简单连接,可以直接使用 `ConnectAsync(url)` 方法 +- 复杂配置仍需要使用 `SetupAsync()` 方法 + +## 5. WebSocket数据接收委托 + +**新增功能 (4.0):** +```csharp +client.Received = (c, e) => +{ + Console.WriteLine(e.DataFrame.ToText()); + return EasyTask.CompletedTask; +}; +``` + +**如何更新:** +- 可以使用 `Received` 委托来简化数据接收处理 +- 替代复杂的插件配置方式 + +## 6. WebSocket ReadAsync 接收超时 + +**新增功能 (4.0):** +```csharp +// 设置接收超时 +using var cts = new CancellationTokenSource(1000 * 60); +using (var receiveResult = await client.ReadAsync(cts.Token)) +{ + if (receiveResult.IsCompleted) + { + // 连接已关闭 + break; + } +} +``` + +**如何更新:** +- 使用 `CancellationTokenSource` 设置接收超时 +- 通过 `receiveResult.IsCompleted` 判断连接状态 + +## 7. XmlRpc配置方式更改 + +**旧语法 (3.x):** +```csharp +.ConfigurePlugins(a => +{ + a.UseXmlRpc() + .SetXmlRpcUrl("/xmlRpc"); +}) +``` + +**新语法 (4.0):** +```csharp +.ConfigurePlugins(a => +{ + a.UseXmlRpc(options => + { + options.SetAllowXmlRpc("/xmlRpc"); + }); +}) +``` + +**如何更新:** +- 使用 `UseXmlRpc(options => {})` 替代链式调用 +- `SetXmlRpcUrl()` 改为 `options.SetAllowXmlRpc()` + +## 8. RPC代理调用方法名称更改 + +**旧语法 (3.x):** +```csharp +var result = client.Sum(10, 20); // 同步调用 +``` + +**新语法 (4.0):** +```csharp +var result = await client.SumAsync(10, 20); // 异步调用 +``` + +**如何更新:** +- 所有RPC代理方法都需要使用异步版本 +- 在方法名后添加 `Async` 后缀并使用 `await` + +## 9. 包版本管理统一化 + +**旧方式:** +```xml + +``` + +**新方式:** +```xml + + +``` + +**如何更新:** +- 移除所有PackageReference中的Version属性 +- 在项目根目录创建 `Directory.Packages.props` 文件统一管理版本 +- 设置 `true` + +## 10. 插件接口参数类型更改 + +**旧语法 (3.x):** +```csharp +public async Task OnWebSocketHandshaking(IWebSocket client, HttpContextEventArgs e) +``` + +**新语法 (4.0):** +```csharp +public async Task OnWebSocketConnecting(IWebSocket client, HttpContextEventArgs e) +``` + +**如何更新:** +- 检查所有插件接口的方法签名 +- 更新方法名称和参数类型以匹配新接口 + +## 11. WebSocket连接验证配置 + +**新增功能 (4.0):** +```csharp +a.UseWebSocket(options => +{ + options.SetVerifyConnection(async (client, context) => + { + // 自定义验证逻辑 + return context.Request.UrlEquals("/ws"); + }); +}); +``` + +**如何更新:** +- 使用 `SetVerifyConnection` 方法自定义连接验证逻辑 +- 替代之前的URL匹配方式 + +## 12. 适配器扩展方法更新 + +**旧语法 (3.x):** +```csharp +writer.WriteByte(value); +reader.ReadByte(); +``` + +**新语法 (4.0):** +```csharp +WriterExtension.WriteValue(ref writer, (byte)value); +ReaderExtension.ReadValue(ref reader); +``` + +**如何更新:** +- 使用新的扩展方法替代旧的直接调用方式 +- 注意参数类型的显式转换 + +## 13. TCP数据接收事件参数变化 + +**旧语法 (3.x):** +```csharp +service.Received = (client, e) => +{ + // 使用ByteBlock获取数据 + var data = e.ByteBlock.ToArray(); + var text = Encoding.UTF8.GetString(data); + return Task.CompletedTask; +}; +``` + +**新语法 (4.0):** +```csharp +service.Received = (client, e) => +{ + // 使用Memory获取数据 + var memory = e.Memory; + var text = memory.Span.ToString(Encoding.UTF8); + return Task.CompletedTask; +}; +``` + +**如何更新:** +- 将 `e.ByteBlock` 替换为 `e.Memory` +- 使用 `e.Memory.Span` 获取数据的Span表示 +- 使用 `memory.Span.ToString(Encoding.UTF8)` 进行字符串转换 +- 如需要数组,使用 `e.Memory.ToArray()` + +## 14. TCP插件中数据接收参数变化 + +**旧语法 (3.x):** +```csharp +public class MyPlugin : PluginBase, ITcpReceivedPlugin +{ + public async Task OnTcpReceived(ITcpSession client, ReceivedDataEventArgs e) + { + // 通过ByteBlock获取数据 + var byteBlock = e.ByteBlock; + var length = byteBlock.Len; + var data = byteBlock.ToArray(); + + await e.InvokeNext(); + } +} +``` + +**新语法 (4.0):** +```csharp +public class MyPlugin : PluginBase, ITcpReceivedPlugin +{ + public async Task OnTcpReceived(ITcpSession client, ReceivedDataEventArgs e) + { + // 通过Memory获取数据 + var memory = e.Memory; + var length = memory.Length; + var span = memory.Span; + + await e.InvokeNext(); + } +} +``` + +**如何更新:** +- 将 `e.ByteBlock` 替换为 `e.Memory` +- 将 `byteBlock.Len` 替换为 `memory.Length` +- 使用 `memory.Span` 获取ReadOnlySpan<byte> +- 使用 `memory.ToArray()` 获取byte数组(如确实需要) + +## 15. 适配器中数据处理方式变化 + +**旧语法 (3.x):** +```csharp +protected override FilterResult Filter(ref ByteBlock byteBlock, bool beCached, ref MyRequestInfo request) +{ + if (byteBlock.Len < 4) + { + return FilterResult.Cache; + } + + var header = byteBlock.ReadInt32(); + var data = byteBlock.ToArray(); + + return FilterResult.Success; +} +``` + +**新语法 (4.0):** +```csharp +protected override FilterResult Filter(ref TReader reader, bool beCached, ref MyRequestInfo request) +{ + if (reader.BytesRemaining < 4) + { + return FilterResult.Cache; + } + + var headerSpan = reader.GetSpan(4); + var header = headerSpan.ReadValue(); + reader.Advance(4); + + return FilterResult.Success; +} +``` + +**如何更新:** +- 将 `ByteBlock byteBlock` 参数替换为泛型 `TReader reader` +- 将 `byteBlock.Len` 替换为 `reader.BytesRemaining` +- 使用 `reader.GetSpan(length)` 获取指定长度的数据 +- 使用 `reader.Advance(length)` 推进读取位置 +- 使用 `span.ReadValue()` 读取基础类型数据 + +## 16. UDP数据接收参数变化 + +**旧语法 (3.x):** +```csharp +udpSession.Received = (client, e) => +{ + var byteBlock = e.ByteBlock; + var endPoint = e.EndPoint; + var data = byteBlock.ToArray(); + return Task.CompletedTask; +}; +``` + +**新语法 (4.0):** +```csharp +udpSession.Received = (client, e) => +{ + var memory = e.Memory; + var endPoint = e.EndPoint; + var span = memory.Span; + return Task.CompletedTask; +}; +``` + +**如何更新:** +- 将 `e.ByteBlock` 替换为 `e.Memory` +- 使用 `memory.Span` 获取数据视图 +- EndPoint属性保持不变 + +## 17. 串口数据接收参数变化 + +**旧语法 (3.x):** +```csharp +serialPort.Received = (client, e) => +{ + var byteBlock = e.ByteBlock; + var receivedData = byteBlock.ToArray(); + Console.WriteLine($"接收到数据:{Convert.ToHexString(receivedData)}"); + return Task.CompletedTask; +}; +``` + +**新语法 (4.0):** +```csharp +serialPort.Received = (client, e) => +{ + var memory = e.Memory; + var span = memory.Span; + Console.WriteLine($"接收到数据:{Convert.ToHexString(span)}"); + return Task.CompletedTask; +}; +``` + +**如何更新:** +- 将 `e.ByteBlock` 替换为 `e.Memory` +- 直接使用 `memory.Span` 进行十六进制转换 +- 避免不必要的 `ToArray()` 调用以提高性能 + +## 18. 命名管道数据接收参数变化 + +**旧语法 (3.x):** +```csharp +namedPipe.Received = (client, e) => +{ + var byteBlock = e.ByteBlock; + var message = Encoding.UTF8.GetString(byteBlock.ToArray()); + Console.WriteLine($"收到消息:{message}"); + return Task.CompletedTask; +}; +``` + +**新语法 (4.0):** +```csharp +namedPipe.Received = (client, e) => +{ + var memory = e.Memory; + var message = memory.Span.ToString(Encoding.UTF8); + Console.WriteLine($"收到消息:{message}"); + return Task.CompletedTask; +}; +``` + +**如何更新:** +- 将 `e.ByteBlock` 替换为 `e.Memory` +- 使用 `memory.Span.ToString(Encoding.UTF8)` 直接转换字符串 +- 避免创建临时数组,提高内存效率 + +## 19. 自定义数据处理适配器泛型化 + +**旧语法 (3.x):** +```csharp +public class MyAdapter : CustomDataHandlingAdapter +{ + protected override FilterResult Filter(ref ByteBlock byteBlock, bool beCached, ref MyRequestInfo request) + { + // 处理ByteBlock数据 + var data = byteBlock.ToArray(); + return FilterResult.Success; + } +} +``` + +**新语法 (4.0):** +```csharp +public class MyAdapter : CustomDataHandlingAdapter +{ + protected override FilterResult Filter(ref TReader reader, bool beCached, ref MyRequestInfo request) + { + // 使用泛型Reader处理数据 + var span = reader.GetSpan(reader.BytesRemaining); + reader.Advance(reader.BytesRemaining); + return FilterResult.Success; + } +} +``` + +**如何更新:** +- 方法签名改为泛型 `Filter(ref TReader reader, ...)` +- 使用 `reader.GetSpan()` 获取数据视图 +- 使用 `reader.Advance()` 推进读取位置 +- 使用 `reader.BytesRemaining` 获取剩余字节数 + +## 20. WaitingClient响应数据获取方式变化 + +**旧语法 (3.x):** +```csharp +var waitingClient = client.CreateWaitingClient(new WaitingOptions()); +using var responsedData = await waitingClient.SendThenResponseAsync("Hello"); +var data = responsedData.Data; // byte[] +var text = Encoding.UTF8.GetString(data); +``` + +**新语法 (4.0):** +```csharp +var waitingClient = client.CreateWaitingClient(new WaitingOptions()); +using var responsedData = await waitingClient.SendThenResponseAsync("Hello"); +var memory = responsedData.Memory; // ReadOnlyMemory +var text = memory.Span.ToString(Encoding.UTF8); +``` + +**如何更新:** +- 将 `responsedData.Data` 替换为 `responsedData.Memory` +- 使用 `memory.Span.ToString()` 进行字符串转换 +- 如需要数组,使用 `memory.ToArray()` + +## 21. 二进制数据读写方式优化 + +**旧语法 (3.x):** +```csharp +// 写入数据 +byteBlock.WriteByte(1); +byteBlock.WriteInt32(1000); +byteBlock.WriteString("Hello"); + +// 读取数据 +var b = byteBlock.ReadByte(); +var i = byteBlock.ReadInt32(); +var s = byteBlock.ReadString(); +``` + +**新语法 (4.0):** +```csharp +// 写入数据(使用Writer) +WriterExtension.WriteValue(ref writer, (byte)1); +WriterExtension.WriteValue(ref writer, (int)1000); +WriterExtension.WriteString(ref writer, "Hello", Encoding.UTF8); + +// 读取数据(使用Reader) +var b = ReaderExtension.ReadValue(ref reader); +var i = ReaderExtension.ReadValue(ref reader); +var s = ReaderExtension.ReadString(ref reader, Encoding.UTF8); +``` + +**如何更新:** +- 使用 `WriterExtension.WriteValue()` 替代直接写入方法 +- 使用 `ReaderExtension.ReadValue()` 替代直接读取方法 +- 需要显式指定数据类型和编码格式 +- 使用ref参数传递reader和writer + +## 22. HTTP请求体数据获取方式变化 + +**旧语法 (3.x):** +```csharp +public async Task OnHttpRequest(IHttpSessionClient client, HttpContextEventArgs e) +{ + var request = e.Context.Request; + if (request.ContentLength > 0) + { + var byteBlock = request.GetBody(); + var content = Encoding.UTF8.GetString(byteBlock.ToArray()); + } +} +``` + +**新语法 (4.0):** +```csharp +public async Task OnHttpRequest(IHttpSessionClient client, HttpContextEventArgs e) +{ + var request = e.Context.Request; + if (request.ContentLength > 0) + { + var memory = await request.GetContentAsync(); + var content = memory.Span.ToString(Encoding.UTF8); + } +} +``` + +**如何更新:** +- 将 `request.GetBody()` 替换为 `await request.GetContentAsync()` +- 使用 `memory.Span.ToString()` 获取字符串内容 +- 注意新方法是异步的,需要使用await + +## 23. 数据发送方式的内存优化 + +**旧语法 (3.x):** +```csharp +// 发送字节数组 +var data = Encoding.UTF8.GetBytes("Hello World"); +await client.SendAsync(data); + +// 发送ByteBlock +using var byteBlock = new ByteBlock(); +byteBlock.WriteString("Hello World"); +await client.SendAsync(byteBlock); +``` + +**新语法 (4.0):** +```csharp +// 直接发送字符串(内部优化) +await client.SendAsync("Hello World"); + +// 发送ReadOnlyMemory +var memory = Encoding.UTF8.GetBytes("Hello World").AsMemory(); +await client.SendAsync(memory); + +// 使用Span发送(避免分配) +var span = stackalloc byte[256]; +var length = Encoding.UTF8.GetBytes("Hello World", span); +await client.SendAsync(span.Slice(0, length)); +``` + +**如何更新:** +- 优先使用字符串直接发送,框架内部已优化 +- 使用 `ReadOnlyMemory` 替代byte数组 +- 考虑使用stackalloc和Span来避免堆分配 +- 利用Slice方法处理部分数据 + +## 24. 数据适配器中的内存池使用 + +**旧语法 (3.x):** +```csharp +public class MyAdapter : CustomDataHandlingAdapter +{ + protected override FilterResult Filter(ref ByteBlock byteBlock, bool beCached, ref MyRequestInfo request) + { + var tempBuffer = new byte[1024]; // 创建临时缓冲区 + byteBlock.Read(tempBuffer, 0, Math.Min(1024, byteBlock.Len)); + return FilterResult.Success; + } +} +``` + +**新语法 (4.0):** +```csharp +public class MyAdapter : CustomDataHandlingAdapter +{ + protected override FilterResult Filter(ref TReader reader, bool beCached, ref MyRequestInfo request) + { + var span = reader.GetSpan(Math.Min(1024, reader.BytesRemaining)); + // 直接使用span,无需额外分配 + ProcessData(span); + reader.Advance(span.Length); + return FilterResult.Success; + } + + private void ProcessData(ReadOnlySpan data) + { + // 处理数据,无内存分配 + } +} +``` + +**如何更新:** +- 使用 `reader.GetSpan()` 直接获取内存视图 +- 避免创建临时byte数组,直接操作Span +- 使用 `reader.Advance()` 正确推进读取位置 +- 利用Span的零拷贝特性提高性能 + +## 25. 数据传输事件中的内存管理 + +**旧语法 (3.x):** +```csharp +client.Sending = (c, e) => +{ + // 修改发送的数据 + var oldData = e.Data; // byte[] + var newData = ProcessData(oldData); + e.Data = newData; + return Task.CompletedTask; +}; + +client.Sent = (c, e) => +{ + var sentData = e.Data; // byte[] + Console.WriteLine($"已发送 {sentData.Length} 字节"); + return Task.CompletedTask; +}; +``` + +**新语法 (4.0):** +```csharp +client.Sending = (c, e) => +{ + // 修改发送的数据 + var oldMemory = e.Memory; // ReadOnlyMemory + var newData = ProcessData(oldMemory.Span); + e.Memory = newData.AsMemory(); + return Task.CompletedTask; +}; + +client.Sent = (c, e) => +{ + var sentMemory = e.Memory; // ReadOnlyMemory + Console.WriteLine($"已发送 {sentMemory.Length} 字节"); + return Task.CompletedTask; +}; +``` + +**如何更新:** +- 将事件参数中的 `e.Data` 替换为 `e.Memory` +- 使用 `memory.Span` 进行数据处理 +- 使用 `AsMemory()` 将处理结果转换回Memory类型 +- 利用Memory的引用语义减少数据复制 + +## 26. 文件传输中的内存优化 + +**旧语法 (3.x):** +```csharp +// 读取文件内容 +var fileData = File.ReadAllBytes(filePath); +await client.SendAsync(fileData); + +// 处理接收的文件数据 +client.Received = (c, e) => +{ + var receivedData = e.ByteBlock.ToArray(); + File.WriteAllBytes(targetPath, receivedData); + return Task.CompletedTask; +}; +``` + +**新语法 (4.0):** +```csharp +// 使用流式读取和发送 +using var fileStream = File.OpenRead(filePath); +var buffer = new byte[8192]; +int bytesRead; +while ((bytesRead = await fileStream.ReadAsync(buffer)) > 0) +{ + await client.SendAsync(buffer.AsMemory(0, bytesRead)); +} + +// 处理接收的文件数据 +client.Received = (c, e) => +{ + var receivedMemory = e.Memory; + using var targetStream = File.OpenWrite(targetPath); + targetStream.Write(receivedMemory.Span); + return Task.CompletedTask; +}; +``` + +**如何更新:** +- 使用流式处理替代一次性读取整个文件 +- 使用 `memory.Span` 直接写入流 +- 使用 `AsMemory(start, length)` 创建部分内存视图 +- 避免大文件的完整内存加载 + +## 27. 插件链中的数据传递优化 + +**旧语法 (3.x):** +```csharp +public class DataProcessPlugin : PluginBase, ITcpReceivedPlugin +{ + public async Task OnTcpReceived(ITcpSession client, ReceivedDataEventArgs e) + { + // 处理数据并传递给下一个插件 + var originalData = e.ByteBlock.ToArray(); + var processedData = ProcessData(originalData); + + // 需要重新包装数据 + using var newByteBlock = new ByteBlock(); + newByteBlock.Write(processedData); + e.ByteBlock = newByteBlock; + + await e.InvokeNext(); + } +} +``` + +**新语法 (4.0):** +```csharp +public class DataProcessPlugin : PluginBase, ITcpReceivedPlugin +{ + public async Task OnTcpReceived(ITcpSession client, ReceivedDataEventArgs e) + { + // 处理数据并传递给下一个插件 + var originalMemory = e.Memory; + var processedData = ProcessData(originalMemory.Span); + + // 直接设置新的内存引用 + e.Memory = processedData.AsMemory(); + + await e.InvokeNext(); + } + + private byte[] ProcessData(ReadOnlySpan input) + { + // 使用Span进行高效的数据处理 + Span output = stackalloc byte[input.Length]; + // 处理逻辑... + return output.ToArray(); + } +} +``` + +**如何更新:** +- 直接设置 `e.Memory` 而不需要重新包装 +- 使用Span进行数据处理,减少分配 +- 利用stackalloc在栈上分配临时缓冲区 +- 避免不必要的ByteBlock创建和释放 + +## 28. 数据编码和解码的性能优化 + +**旧语法 (3.x):** +```csharp +// 编码字符串 +var text = "Hello, 世界!"; +var bytes = Encoding.UTF8.GetBytes(text); +var byteBlock = new ByteBlock(); +byteBlock.Write(bytes); + +// 解码字符串 +var receivedBytes = e.ByteBlock.ToArray(); +var decodedText = Encoding.UTF8.GetString(receivedBytes); +``` + +**新语法 (4.0):** +```csharp +// 高效编码字符串 +var text = "Hello, 世界!"; +var maxByteCount = Encoding.UTF8.GetMaxByteCount(text.Length); +Span buffer = stackalloc byte[maxByteCount]; +var actualByteCount = Encoding.UTF8.GetBytes(text, buffer); +var encodedData = buffer.Slice(0, actualByteCount); + +// 高效解码字符串 +var receivedMemory = e.Memory; +var decodedText = receivedMemory.Span.ToString(Encoding.UTF8); +``` + +**如何更新:** +- 使用 `Encoding.UTF8.GetBytes(string, Span)` 避免数组分配 +- 使用 `span.ToString(Encoding)` 直接解码 +- 利用stackalloc创建栈上缓冲区 +- 使用 `Span.Slice()` 创建精确大小的视图 + +## 升级注意事项 + +### 内存管理优化 +1. **Memory vs ByteBlock**: 4.0版本大量使用了Memory<byte>和Span<byte>替代ByteBlock,这带来了显著的性能提升 +2. **零拷贝操作**: 尽量使用Span操作避免不必要的内存分配和拷贝 +3. **栈分配**: 对于小数据量,考虑使用stackalloc在栈上分配内存 + +### 性能优化建议 +1. **字符串处理**: 使用 `span.ToString(Encoding)` 替代 `Encoding.GetString(array)` +2. **数据发送**: 优先使用字符串直接发送,框架内部已优化 +3. **流式处理**: 对于大文件或大数据量,使用流式处理替代一次性加载 + +### 兼容性考虑 +1. **测试覆盖**: 升级后务必进行全面测试,特别是数据收发和适配器相关功能 +2. **依赖检查**: 确保所有依赖的第三方库与TouchSocket 4.0兼容 +3. **渐进升级**: 建议在测试环境先完成升级和验证 +4. **日志检查**: 关注升级后的日志输出,确保没有新的警告或错误 + +### 代码审查重点 +1. **数据接收**: 检查所有使用 `e.ByteBlock` 的地方,替换为 `e.Memory` +2. **适配器**: 重点检查自定义适配器的Filter方法实现 +3. **插件**: 验证所有插件接口的方法签名和参数使用 +4. **RPC调用**: 确保所有RPC调用使用异步版本 + +## 常见问题 + +**Q: 升级后编译出现接口找不到的错误?** +A: 检查是否正确更新了插件接口名称,特别是WebSocket相关接口。 + +**Q: WebSocket连接失败?** +A: 检查配置方式是否使用了新的options配置模式。 + +**Q: RPC调用出现异常?** +A: 确保使用了异步版本的代理方法,并正确使用await。 + +**Q: 数据接收时出现 'ByteBlock' 不存在的编译错误?** +A: 将所有 `e.ByteBlock` 替换为 `e.Memory`,这是4.0版本的重大变化。 + +**Q: 适配器中的Filter方法签名错误?** +A: 更新为泛型版本:`Filter(ref TReader reader, ...)`,并使用reader的相关方法。 + +**Q: 性能比3.x版本慢?** +A: 检查是否还在使用 `ToArray()` 等方法创建不必要的数组拷贝,应该直接使用Memory和Span操作。 + +**Q: 字符串编码解码出现异常?** +A: 使用 `memory.Span.ToString(Encoding.UTF8)` 替代 `Encoding.UTF8.GetString(array)`。 + +**Q: 数据发送后接收方无法正确解析?** +A: 检查发送时是否正确使用了新的Memory-based API,确保数据完整性。 + +**Q: 升级后内存使用量增加?** +A: 4.0版本实际上应该减少内存分配,检查是否还在使用旧的ByteBlock模式或不必要的ToArray()调用。 + +**Q: 插件中的数据处理逻辑失效?** +A: 确保插件中正确使用了Memory参数,并且在处理后正确设置了 `e.Memory`。 + +## 升级检查清单 + +在完成升级后,请按以下清单进行检查: + +- [ ] 所有WebSocket相关接口已更新(Handshaking → Connecting, Handshaked → Connected) +- [ ] 所有配置方式已改为options模式(UseWebSocket, UseXmlRpc等) +- [ ] 所有数据接收处理已从ByteBlock改为Memory +- [ ] 所有适配器已更新为泛型Reader版本 +- [ ] 所有RPC调用已改为异步版本 +- [ ] 包引用已移除Version属性,使用中央包管理 +- [ ] 所有插件接口方法名称已更新 +- [ ] 数据发送接收测试通过 +- [ ] 性能测试显示预期提升 +- [ ] 内存使用量检查正常 + +通过以上步骤和检查清单,您应该能够成功将TouchSocket从3.x版本升级到4.0版本。如果遇到其他问题,请参考官方文档或社区支持。 \ No newline at end of file diff --git a/handbook/versioned_docs/version-1.3.9/upgrade.mdx b/handbook/src/pages/upgrade/v0.mdx similarity index 53% rename from handbook/versioned_docs/version-1.3.9/upgrade.mdx rename to handbook/src/pages/upgrade/v0.mdx index a70a91d78..c42a1743d 100644 --- a/handbook/versioned_docs/version-1.3.9/upgrade.mdx +++ b/handbook/src/pages/upgrade/v0.mdx @@ -1,118 +1,12 @@ --- -id: upgrade -title: 历史更新 +id: upgrade-v0 +title: v0 系列与早期版本 --- import useBaseUrl from "@docusaurus/useBaseUrl"; import Tag from "@site/src/components/Tag.js"; +import Highlight from '@site/src/components/Highlight.js'; -:::tip `TouchSocket` 框架升级/发版规则 - -**升级前重点关注可能造成【破坏性】的标签类型**:修复调整移除升级 - -版本号规则:`主版本号.次版本号.修订版本号` - -- 只要【确认】为框架 `bug`,则当天修复,当天发版,修订版本号 `加 1`。 -- 如果 `.csproj` 文件有变更,则当天发版,修订版本号 `加 1`。 -- 其余情况,每年发布一个 `主版本`。 - -::: - -## v1.3 - -更新日期:2023.3.1 - -更新描述:兼容性更新。 - - -  优化 整体增加异步方法。 - -  优化 Rpc源代码生成策略,支持接口实例并存。 - -  修复 TcpClient在UseReconnect插件时,Disconnect事件不触发bug。 - -  新增 ws协议的TouchRpc服务端,增加HttpContext上下文获取。 ---- - -## v1.2 - -更新日期:2023.2.15 - -更新描述:兼容性更新。 - - -  优化 TouchRpc支持命名元组。 - -  优化 Rpc源代码生成策略。 - -  修复 TouchRpc在Websocket协议下,启动,连接异常bug。 - -  修复 TouchRpc在调用WaitSend下失败的bug。 - -  修复 TouchRpc在Handshaked时,调用Rpc超时bug。 - -  修复 序列化、反射在unity中使用il2cpp编译的bug。 - -  修复 反序列化在初次加载时会失败的bug。 - -  修复 BytePool没有公共构造函数的bug。 - -  新增 ws协议的TouchRpc服务端,增加HttpContext上下文获取。 - -  新增 ByteBlock对于int,long等数据,写入和读取的时候支持大小端指定。 - -  新增 IServicePlugin插件,用于显示通知服务器的启动状态。 - -  新增 Rpc支持接口特性标记。 - -  调整 将BytePool由静态调整为实例,且由其Default实例作为默认。 ---- - -## v1.1 - -更新日期:2023.1.13 - -更新描述:小版本升级,可能会有不兼容。请按下列提示修改。 - - -  优化 TouchRpc系文件传输时,文件夹不存在的提示。 - -  优化 WaitingClient,当客户端断开连接时,可选是否抛出异常。 - -  优化 Fast序列化时。可选序列化只读属性。 - -  修复 多个不稳定Bug。 - -  新增 Tcp客户端新增Disconnecting事件。在主动Close时生效。 - -  调整 多个事件类名称修改,请按照提示修改即可。 - -  移除 多个无用方法参数。 ---- - -## v1.0.0 - -更新日期:2023.1.1 - -更新描述:大版本升级,请详细阅读下列更新日志。 - - -  升级 将最高版本升级为NET7。 - -  优化 Tcp系异步发送效率。 - -  优化 TouchRpc系Channel的稳健性。 - -  修复 多个不稳定Bug。 - -  新增 ValueByteBlock,在简单代码块里面能有效减少创建的类。 - -  新增 MemoryCache类,其功能类似微软官方。但是支持全部泛型。 - -  新增 [IPackage系](https://www.yuque.com/rrqm/touchsocket/ag9tyar9mmhsme0m)。该系列能以超高效率的进行二进制序列化。 - -  新增 SingleTimer类,不可重入的Timer。 - -  新增 Jsonrpc支持自定义适配器解析(EE) - -  新增 严重TouchRpc系OnRouting通知,所有的客户端之间的通信,都必须经过OnRouting的筛查。 - -  新增 TouchRpc系小文件传输,在文件小于1Mb时,其传输效率是常规传输的10倍以上。 - -  新增 TouchRpc系超大文件多链路传输,支持多个客户端协同传输同一个文件,这在互联网环境中,效率比常规传输提高类3-5倍。 - -  新增 TouchRpc系Redis组件,能实现双端共同存储。 - -  调整 严重精简所有命名空间,删除所有三级命名空间。例如:TouchSocket.Core.ByteManager精简为TouchSocket.Core。 - -  调整 严重删除Newtonsoft.Json的源代码嵌入。全局的Json会根据环境动态调整,详情见[Json工具](https://www.yuque.com/rrqm/touchsocket/emqy43#PfVh1) - -  调整 严重框架默认日志由ConsoleLogger,替换为EmptyLogger(不输出任何东西)。 - -  调整 严重Tcp全系,在连接时,ID的初始值使用long类型从0递增。 - -  调整 严重Tcp服务器,将定时清理无数据交互的选项替换为UseCheckClear插件。并且默认没有启用,需要手动加入。 - -  调整 Tcp系适配器,取消部分参数。 - -  调整 DataLock改名为DataSecurity。 - -  调整 EasyAction改名EasyTask。 - -  调整 IMessage改名IMessageObject。 - -  调整 TokenInstance改名MessageInstance。 - -  调整 TouchRpc系,精简常规文件传输操作。 - -  调整 严重TouchRpc系,所有插件通知参数,默认都设为不允许操作,需要手动设置e.IsPermitOperation=true。 - -  移除 Newtonsoft.Json的源代码嵌入。全局的Json会根据环境动态调整,详情见[Json工具](https://www.yuque.com/rrqm/touchsocket/emqy43#PfVh1)。 - -*** 更新示例指南 *** - -(1)适配器参数报错:直接删除isAsync参数,以及isAsync为**True**的所有逻辑。 -![image.png](@site/static/img/docs/upgrade-1.png) -(2)依赖属性的声明报错:增加泛型约束即可,详情查看[依赖属性](https://www.yuque.com/rrqm/touchsocket/ubk57o#jyzSl) -![image.png](@site/static/img/docs/upgrade-2.png) -(3)服务端定时清理警告:在配置插件中使用UseCheckClear,并且进行相关配置。 -![image.png](@site/static/img/docs/upgrade-3.png) -![image.png](@site/static/img/docs/upgrade-4.png) - ---- - - -## 版本号: 0.7.0 更新日期:2022.9.21 更新描述:兼容性更新,增强型更新。**RPC内容需要客户端与服务器同步更新**。 更新详情: @@ -200,7 +94,7 @@ import Tag from "@site/src/components/Tag.js"; 更新示例 TouchRpc的相关事件均已使用插件代替。所以请使用插件实现操作。如果需要事件等功能的话,可以用TouchRpcActionPlugin的插件实现。例如: -```csharp +```csharp showLineNumbers .UsePlugin() .ConfigurePlugins(a=> { @@ -265,9 +159,9 @@ TouchRpc的相关事件均已使用插件代替。所以请使用插件实现操 1. 适配器可以设定发送IRequestInfo对象。 2. 插件新增UseWebSocket的快捷方式。 3. ReconnectionPlugin插件可以获得重连次数的重载设置。 -4. 【Pro】TcpService的服务注入。 -5. 【Pro】HttpService的服务注入。 -6. 【Pro】IOC容器的共享使用。 +4. ProTcpService的服务注入。 +5. ProHttpService的服务注入。 +6. ProIOC容器的共享使用。 修改 1. 各类发送逻辑,以最小化发送方法为基础,其余方法改为扩展方法。 @@ -301,8 +195,8 @@ TouchRpc的相关事件均已使用插件代替。所以请使用插件实现操 新增 1. Mapper类,支持简单类型映射 2. Tcp服务器、客户端、udp等增加端口复用配置。 -3. 【Pro】轮询式断线重连。 -4. 【Pro】NATService转发客户端重连。 +3. Pro轮询式断线重连。 +4. ProNATService转发客户端重连。 修改 1. RRQM二进制序列化,改名为Fast。 @@ -333,7 +227,7 @@ TouchRpc的相关事件均已使用插件代替。所以请使用插件实现操 | MsgEventArgs | MsgEventArgs | | RRQMEventAgrs | TouchSocketEventArgs | | IServerProvider | IRpcServer | -| ServerProvider | RpcServer | +| ServerProvider | SingletonRpcServer | | RRQMOverlengthException | OverlengthException | ### 3.使用逻辑修改 @@ -345,3 +239,4 @@ RpcStore使用变更 如果是仅有一个Rpc解析器,那么可以直接删除RpcStore的声明,从而使用对应的**解析器实例**,直接注册服务。然后可以通过其属性RpcStore,获取到具体的RpcStore实例。 如果是有多个解析器,那么,首先可以使用任意一个解析器的RpcStore属性实例,作为主RpcStore,然后添加其他解析器。当然也可以直接new RpcStore,然后统一管理解析器。其中构造函数中的Container容器,可以直接new Container(),但是更建议使用和解析器相同的容器,这样注入的服务会变得全局可用。 + diff --git a/handbook/src/pages/upgrade/v1.mdx b/handbook/src/pages/upgrade/v1.mdx new file mode 100644 index 000000000..672e6858b --- /dev/null +++ b/handbook/src/pages/upgrade/v1.mdx @@ -0,0 +1,100 @@ +--- +id: upgrade-v1 +title: v1 系列更新 +--- + +import useBaseUrl from "@docusaurus/useBaseUrl"; +import Tag from "@site/src/components/Tag.js"; +import Highlight from '@site/src/components/Highlight.js'; + + +## v1.3 + +更新日期:2023.3.1 + +更新描述:兼容性更新。 + + -  优化 整体增加异步方法。 + -  优化 Rpc源代码生成策略,支持接口实例并存。 + -  修复 TcpClient在UseReconnect插件时,Disconnect事件不触发bug。 + -  新增 ws协议的TouchRpc服务端,增加HttpContext上下文获取。 +--- + + +## v1.2 + +更新日期:2023.2.15 + +更新描述:兼容性更新。 + + -  优化 TouchRpc支持命名元组。 + -  优化 Rpc源代码生成策略。 + -  修复 TouchRpc在Websocket协议下,启动,连接异常bug。 + -  修复 TouchRpc在调用WaitSend下失败的bug。 + -  修复 TouchRpc在Handshaked时,调用Rpc超时bug。 + -  修复 序列化、反射在unity中使用il2cpp编译的bug。 + -  修复 反序列化在初次加载时会失败的bug。 + -  修复 BytePool没有公共构造函数的bug。 + -  新增 ws协议的TouchRpc服务端,增加HttpContext上下文获取。 + -  新增 ByteBlock对于int,long等数据,写入和读取的时候支持大小端指定。 + -  新增 IServicePlugin插件,用于显示通知服务器的启动状态。 + -  新增 Rpc支持接口特性标记。 + -  调整 将BytePool由静态调整为实例,且由其Default实例作为默认。 + + +## v1.1 + +更新日期:2023.1.13 + +更新描述:小版本升级,可能会有不兼容。请按下列提示修改。 + + -  优化 TouchRpc系文件传输时,文件夹不存在的提示。 + -  优化 WaitingClient,当客户端断开连接时,可选是否抛出异常。 + -  优化 Fast序列化时。可选序列化只读属性。 + -  修复 多个不稳定Bug。 + -  新增 Tcp客户端新增Disconnecting事件。在主动Close时生效。 + -  调整 多个事件类名称修改,请按照提示修改即可。 + -  移除 多个无用方法参数。 + +## v1.0.0 + +更新日期:2023.1.1 + +更新描述:大版本升级,请详细阅读下列更新日志。 + + -  升级 将最高版本升级为NET7。 + -  优化 Tcp系异步发送效率。 + -  优化 TouchRpc系Channel的稳健性。 + -  修复 多个不稳定Bug。 + -  新增 ValueByteBlock,在简单代码块里面能有效减少创建的类。 + -  新增 MemoryCache类,其功能类似微软官方。但是支持全部泛型。 + -  新增 [IPackage系](https://www.yuque.com/rrqm/touchsocket/ag9tyar9mmhsme0m)。该系列能以超高效率的进行二进制序列化。 + -  新增 SingleTimer类,不可重入的Timer。 + -  新增 Jsonrpc支持自定义适配器解析(EE) + -  新增 严重TouchRpc系OnRouting通知,所有的客户端之间的通信,都必须经过OnRouting的筛查。 + -  新增 TouchRpc系小文件传输,在文件小于1Mb时,其传输效率是常规传输的10倍以上。 + -  新增 TouchRpc系超大文件多链路传输,支持多个客户端协同传输同一个文件,这在互联网环境中,效率比常规传输提高类3-5倍。 + -  新增 TouchRpc系Redis组件,能实现双端共同存储。 + -  调整 严重精简所有命名空间,删除所有三级命名空间。例如:TouchSocket.Core.ByteManager精简为TouchSocket.Core。 + -  调整 严重删除Newtonsoft.Json的源代码嵌入。全局的Json会根据环境动态调整,详情见[Json工具](https://www.yuque.com/rrqm/touchsocket/emqy43#PfVh1) + -  调整 严重框架默认日志由ConsoleLogger,替换为EmptyLogger(不输出任何东西)。 + -  调整 严重Tcp全系,在连接时,ID的初始值使用long类型从0递增。 + -  调整 严重Tcp服务器,将定时清理无数据交互的选项替换为UseCheckClear插件。并且默认没有启用,需要手动加入。 + -  调整 Tcp系适配器,取消部分参数。 + -  调整 DataLock改名为DataSecurity。 + -  调整 EasyAction改名EasyTask。 + -  调整 IMessage改名IMessageObject。 + -  调整 TokenInstance改名MessageInstance。 + -  调整 TouchRpc系,精简常规文件传输操作。 + -  调整 严重TouchRpc系,所有插件通知参数,默认都设为不允许操作,需要手动设置e.IsPermitOperation=true。 + -  移除 Newtonsoft.Json的源代码嵌入。全局的Json会根据环境动态调整,详情见[Json工具](https://www.yuque.com/rrqm/touchsocket/emqy43#PfVh1)。 + +*** 更新示例指南 *** + +(1)适配器参数报错:直接删除isAsync参数,以及isAsync为**True**的所有逻辑。 +![image.png](@site/static/img/docs/upgrade-1.png) +(2)依赖属性的声明报错:增加泛型约束即可,详情查看[依赖属性](https://www.yuque.com/rrqm/touchsocket/ubk57o#jyzSl) +![image.png](@site/static/img/docs/upgrade-2.png) +(3)服务端定时清理警告:在配置插件中使用UseCheckClear,并且进行相关配置。 +![image.png](@site/static/img/docs/upgrade-3.png) +![image.png](@site/static/img/docs/upgrade-4.png) \ No newline at end of file diff --git a/handbook/src/pages/upgrade/v2-0.mdx b/handbook/src/pages/upgrade/v2-0.mdx new file mode 100644 index 000000000..2f909e4ce --- /dev/null +++ b/handbook/src/pages/upgrade/v2-0.mdx @@ -0,0 +1,338 @@ +--- +id: upgrade-v2-0 +title: v2.0 系列更新 +--- + +import useBaseUrl from "@docusaurus/useBaseUrl"; +import Tag from "@site/src/components/Tag.js"; +import Highlight from '@site/src/components/Highlight.js'; + + + +## v2.0.18 + +**更新日期:** 2024.9.10 + +**更新描述:** + +兼容性修复升级。 + +**更新详情:** + +-  修复 当Cancel延迟到Reset之后时,新获取的waitData会出现Status为Cancel异常bug [#IAQ2AI](https://gitee.com/RRQM_Home/TouchSocket/issues/IAQ2AI)。 + + +*** + + +## v2.0.17 + +**更新日期:** 2024.8.30 + +**更新描述:** + +兼容性修复升级。 + +**更新详情:** + +-  修复 使用AspNetCore容器时,默认没有注册ILog的bug。 + +*** + + +## v2.0.16 + +**更新日期:** 2024.8.19 + +**更新描述:** + +兼容性修复升级。 + +**更新详情:** + +-  修复 tcp在winform中会使用主线程接收的bug,导致各种同步接收异常。 + + +*** + + +## v2.0.15 + +**更新日期:** 2024.8.9 + +**更新描述:** + +兼容性修复升级。 + +**更新详情:** + +-  修复 当客户端断开连接时,http响应会出现object is null的bug提示。 + + +## v2.0.13(14) + +**更新日期:** 2024.7.24 + +**更新描述:** + +兼容性修复升级。 + +**更新详情:** + +-  修复 重连插件在长时间运行时,会失效的bug。 +-  修复 ssl加密下,连接响应时间过长bug[#IAET6V](https://gitee.com/RRQM_Home/TouchSocket/issues/IAET6V) 。 + +## v2.0.12 + +**更新日期:** 2024.7.17 + +**更新描述:** + +兼容性修复升级。 + +**更新详情:** + +-  修复 socket在接收连接时,异常无法拦截的bug[#IADIGX](https://gitee.com/RRQM_Home/TouchSocket/issues/IADIGX)。 +-  调整 日志记录在执行时,先判断日志组件的可用性。 + +## v2.0.11 + +**更新日期:** 2024.7.12 + +**更新描述:** + +兼容性修复升级。 + +**更新详情:** + +-  修复 Metadata在Add时不会覆盖原key的bug。 + + + +## v2.0.10 + +**更新日期:** 2024.5.31 + +**更新描述:** + +兼容性修复升级。 + +**更新详情:** + +-  修复 WebSocket快捷命令行bug。 [#I9TG3V](https://gitee.com/RRQM_Home/TouchSocket/issues/I9TG3V)。 + + +## v2.0.9 + +**更新日期:** 2024.5.29 + +**更新描述:** + +兼容性修复升级。 + +**更新详情:** + +-  修复 UdpPackage适配器在工作时调时导致的bug。 [#I9SYTR](https://gitee.com/RRQM_Home/TouchSocket/issues/I9SYTR)。 +-  修复 Http在GetBoundary时bug。 [#I9PXWT](https://gitee.com/RRQM_Home/TouchSocket/issues/I9PXWT)。 + + + +## v2.0.(7)8 + +**更新日期:** 2024.5.17 + +**更新描述:** + +兼容性修复升级。 + +**更新详情:** + +-  修复 udp首次建立连接无法接收数据的bug。 [#I9PV7C](https://gitee.com/RRQM_Home/TouchSocket/issues/I9PV7C)。 + +## v2.0.6 + +**更新日期:** 2024.5.12 + +**更新描述:** + +兼容性修复升级。 + +**更新详情:** + +-  修复 FlowGate的waitTime小于0时bug。 + + +## v2.0.5 + +**更新日期:** 2024.5.2 + +**更新描述:** + +兼容性修复升级。 + +**更新详情:** + +-  修复 wsclient Received事件与插件触发bug。[#I9L9WI](https://gitee.com/RRQM_Home/TouchSocket/issues/I9L9WI)。 +-  修复 ws命令行执行bug。 + +## v2.0.4 + +**更新日期:** 2024.4.30 + +**更新描述:** + +兼容性修复升级。 + +**更新详情:** + +-  修复 linux平台下,Socket吞吐量大幅降低。[#I9KURV](https://gitee.com/RRQM_Home/TouchSocket/issues/I9KURV)。 +-  修复 WebSocket在进行连接时,Host的Header写法错误,没有包含端口。[#I9KUVI](https://gitee.com/RRQM_Home/TouchSocket/issues/I9KUVI)。 + + +## v2.0.3 + +**更新日期:** 2024.4.14 + +**更新描述:** + +兼容性修复升级。 + +**更新详情:** + +-  修复 Tcp在接收时,内存池释放存在延迟,可能导致内存池快速扩张,浪费内存。[#I9FVAA](https://gitee.com/RRQM_Home/TouchSocket/issues/I9FVAA)。 +-  修复 在使用Host模型时,注入瞬态的TcpClient,在第一次获取实例是正常的,第二次就失败。[#I9G3SV](https://gitee.com/RRQM_Home/TouchSocket/issues/I9G3SV)。 +-  修复 WebSocketClient连接其他服务器时显示 “操作已被取消”。[#I9GG05](https://gitee.com/RRQM_Home/TouchSocket/issues/I9GG05)。 +-  新增 ConcurrentList新增实现IReadOnlyList接口。 + + +## v2.0.2 + +**更新日期:** 2024.4.1 + +**更新描述:** + +兼容性修复升级。 + +**更新详情:** + +-  修复 TouchSocketBitConverter中的ToBooleans方法存在Bug[#I9C1UY](https://gitee.com/RRQM_Home/TouchSocket/issues/I9C1UY)。 +-  修复 SystemExtensions中的GetBit和SetBit方法[#I9C1WM](https://gitee.com/RRQM_Home/TouchSocket/issues/I9C1WM)。 +-  修复 HttpService多次响应下载文件时,不会响应的bug。 + + + +## v2.0.1 + +**更新日期:** 2024.3.16 + +**更新描述:** + +兼容性修复升级。 + +**更新详情:** + +-  修复 HttpClient,无法通过HttpResponse.GetBody()获取响应内容[#I989SI](https://gitee.com/RRQM_Home/TouchSocket/issues/I989SI)。 +-  修复 SetNoDelay 异常[#I979B0](https://gitee.com/RRQM_Home/TouchSocket/issues/I979B0)。 +-  修复 调用Dmtp服务的大数据传输时,如果循环调用会出现收到的数据和发送的数据不一致。实际上该问题是由`ByteBlock`写入扩容bug而导致的[#I96FNF](https://gitee.com/RRQM_Home/TouchSocket/issues/I96FNF)。 + + +## v2.0.0 + +**更新日期:** 2024.3.9 + +**更新描述:** + +此版本是大版本更新。可能会产生很多不兼容部分,所以升级之前请做好备份,并且请详细阅读下列更新内容。 + +**升级指南:** + +- 升级时请先升级至`2.0.0-beta.190`,再升级至`2.0.0-rc.2`版本,因为正式版对于[Obsolete]特性的成员直接删除了,所以为友好升级,请先升级至此版本。 +- 由2.0.0-beta.200至2.0.0-beta.220 [#I8DE1D](https://gitee.com/RRQM_Home/TouchSocket/issues/I8DE1D) +- 由2.0.0-beta.220至2.0.0-beta.230 [#I8LAX4](https://gitee.com/RRQM_Home/TouchSocket/issues/I8LAX4) + +**更新详情:** + +#### TouchSokcet.Core + +-  优化 FileLogger支持指定不同目录。 +-  调整 所有自定义插件必须在自身内,主动调用e.InvokeNext()时,才会调用下一个插件。不然会中断插件传递。同时e.Handled功能依然有效。 +-  调整 Log项。LogType调整为LogLevel,并且不需要位运算。直接按日志等级输出。 +-  调整 修改IPluginsManager名称为IPluginsManager。 +-  移除 DependencyProperty中,移除对类型的定义。 +-  移除 所有组件的基础插件,强制用户插件必须继承PluginBase,然后实现需要的接口。 +-  移除 BytePool在创建ByteBlock时,移除EqualSize的设定,因为这会影响内存池的效率。 +-  调整 修改所有委托为异步Task。 +-  调整 修改所有Setup返回值为void。 +-  修复 Metadata在0个成员长度时,会被反序列化成null的bug。 +-  修复 PluginsManager在注册具有继承的插件时,会无法识别的bug。 + +#### TouchSokcet.Sokcets + +-  优化 IPHost支持从int、string直接隐式转换。 +-  调整 TouchSocket所有“ID”属性,改名为“Id”。 +-  调整 TouchSocket所有插件的执行顺序,移动至内部重写方法之后。 +-  调整 TouchSocket所有`ResetID`改名为`ResetId`。 +-  调整 UseCheckClear项,SetDuration调整名称为SetTick。 +-  调整 UseCheckClear项,不仅可以适用服务器,客户端也适用。 +-  调整 Config配置中,SetDataHandlingAdapter调整为SetTcpDataHandlingAdapter。 +-  调整 适配器项,CustomDataHandlingAdapter中的Filter方法中,byteBlock参数使用in修饰。 +-  调整 适配器项,DataHandlingAdapter改名为TcpDataHandlingAdapter。 +-  调整 适配器项,DataAdapterTester改名为TcpDataAdapterTester。 +-  调整 Config项,所有适配器的相关配置,使用SetAdapterOption配置。 +-  移除 UsePlugin的显式配置,当调用ConfigurePlugins时,会自动启用。 + +#### TouchSokcet.Http + +-  调整 WSCommandLinePlugin改名为WebSocketCommandLinePlugin。 +-  新增 WebSocket添加[同步非阻塞Read]。 +-  新增 WebSocket的WSDataFrame新增IsPing、IsPong、IsText、IsBinary、IsClose等属性。 +-  新增 静态网页插件新增NavigateAction与ResponseAction等委托,可以在静态页面请求之前重定向,或者请求返回时设置header等。 + +#### TouchSokcet.Rpc + +-  移除 整体功能迁移至TouchSokcet(Pro).Dmtp。 +-  调整 [RpcActionFilter]执行策略和顺序 +-  调整 修改ConfigureRpcStore为AddRpcStore。 +-  新增 [RealityProxy]透明代理方式。 +-  新增 [DispatchProxy]添加OnBefore和OnAfter的AOP调用。 + +#### TouchSokcet(Pro).Dmtp + +-  调整 原TouchRpc全系改名为Dmtp。例如:原TcpTouchRpcClient改名为TcpDmtpClient。 +-  调整 原TouchRpc中InvokeOption,改名为DmtpInvokeOption。InvokeOption依然有效,但是在调用DmtpRpc时,则无法指定序列化方式。所以可能需要使用DmtpInvokeOption。 +-  调整 原TouchRpc中Invoke直接调用的方式,改为InvokeT。 +-  调整 Dmtp相关配置,使用SetDmtpOption配置。 +-  移除 **暂时**移除EventBus功能,后续可能考虑添加。 +-  新增 ws协议的TouchRpc服务端,增加HttpContext上下文获取。 +-  新增 文件传输项,开放增加SetMaxSpeed功能。 +-  新增 DmtpRpc。 +-  新增 DmtpRpc组件在调用时,可以通过DmtpInvokeOption传入Metadata元数据。 +-  修复 DmtpRpc在调用无ref,out的函数时,参数会为null的bug。 + +#### TouchSokcet.JsonRpc + +-  修复 JsonRpc使用内联数组调用[#I79OFZ](https://gitee.com/RRQM_Home/TouchSocket/issues/I79OFZ)。 +-  调整 Rpc服务使用调用上下文时不需要再添加`IncludeCallContext`。 + +#### TouchSokcet.WebApi + +-  新增 WebApi新增[Swagger页面]。 +-  调整 Rpc服务使用调用上下文时不需要再添加`IncludeCallContext`。 + +#### TouchSokcet.XmlRpc + +-  调整 Rpc服务使用调用上下文时不需要再添加`IncludeCallContext`。 + +#### TouchSokcet(Pro).Hosting + +-  新增 新发布Hosting的包,用于构建更加强壮的运行程序。 + +#### TouchSokcet.SerialPorts + +-  新增 新发布串口的包。 + +#### TouchSokcet(Pro).Modbus + +-  新增 新发布Modbus的包,支持Tcp、Udp、Rtu、RtuOverTcp、RtuOverUdp协议的主站(Poll)和从站(Slave)。 +--- + diff --git a/handbook/src/pages/upgrade/v2-1.mdx b/handbook/src/pages/upgrade/v2-1.mdx new file mode 100644 index 000000000..ae3463b5e --- /dev/null +++ b/handbook/src/pages/upgrade/v2-1.mdx @@ -0,0 +1,206 @@ +--- +id: upgrade-v2-1 +title: v2.1 系列更新 +--- + +import useBaseUrl from "@docusaurus/useBaseUrl"; +import Tag from "@site/src/components/Tag.js"; +import Highlight from '@site/src/components/Highlight.js'; + + +## v2.1.10 + +**更新日期:** 2024.10.25 + +**更新描述:** + +兼容性修复升级。 + +**更新详情:** + +-  优化 `CheckClearPlugin`插件频繁输出poll日志的不合理设计。 + +*** + +## v2.1.9 + +**更新日期:** 2024.10.14 + +**更新描述:** + +兼容性修复升级。 + +**更新详情:** + +-  修复 CheckClearPlugin长时间工作时可能失效的bug。 + + +*** + +## v2.1.8 + +**更新日期:** 2024.10.11 + +**更新描述:** + +兼容性修复升级。 + +**更新详情:** + +-  修复 TcpService在Stop的时候,有个内部异常打了log。 [#IAWD4N](https://gitee.com/RRQM_Home/TouchSocket/issues/IAWD4N) + + +*** + +## v2.1.7 + +**更新日期:** 2024.10.5 + +**更新描述:** + +兼容性修复升级。 + +**更新详情:** + +-  修复 TcpService在启动时如果异常,则无法再重新启动的bug。 + +*** + +## v2.1.6 + +**更新日期:** 2024.10.1 + +**更新描述:** + +兼容性修复升级。 + +**更新详情:** + +-  新增 HttpContent机制,能上传超大数据。 +-  新增 StreamHttpContent,能上传流数据,例如:文件流。 +-  修复 Task内部异常时没有及时try,导致全局捕获时有无用捕获。 +-  修复 TcpServiceBase在调用StopAsync时,IServerStopedPlugin插件无法触发的bug。 +-  移除 TriggerQueue无用类。 +-  优化 HttpRequest,使其能上传超大数据。 + +*** + +## v2.1.4(5) + +**更新日期:** 2024.9.23 + +**更新描述:** + +兼容性修复升级。 + +**更新详情:** + +-  新增 DmtpRpc恢复性新增Xml序列化。 +-  修复 使用源生成(IPackage)打包时报错。[#IASTWJ](https://gitee.com/RRQM_Home/TouchSocket/issues/IASTWJ) +-  修复 Result泛型类中隐式转换错写成显示转换的bug。 +-  修复 DmtpRpc序列化选择器没有预留Json序列化配置的bug。 + + +*** + + +## v2.1.3 + +**更新日期:** 2024.9.22 + +**更新描述:** + +兼容性修复升级。 + +**更新详情:** + +-  优化 部分类,方法注释。 +-  优化 多数英文字符串资源。 +-  修复 Tcp、NamedPipe、SerialPort等组件在主动调用Close时,ClosedEventArgs参数属性Manual为false的bug,导致重连插件偶发性再次连接。[#IASH1A](https://gitee.com/RRQM_Home/TouchSocket/issues/IASH1A) +-  修复 `ByteBlock`类部分bug。 +-  移除 `DecimalConver`类,该类功能已完全由`TouchSocketBitConverter`代替,属于无用类。 +-  调整 受保护方法`ProtectedResetId`方法,名称更改为`ProtectedResetIdAsync`。 + +*** + + +## v2.1.2 + +**更新日期:** 2024.9.19 + +**更新描述:** + +兼容性修复升级。 + +**更新详情:** + +-  优化 部分类,方法注释。 +-  修复 TcpClient在释放时,重连插件会无限连接的bug,该bug会导致CPU占用过高。[#IAS9NG](https://gitee.com/RRQM_Home/TouchSocket/issues/IAS9NG) + +*** + +## v2.1.1 + +**更新日期:** 2024.9.18 + +**更新描述:** + +兼容性修复升级。 + +**更新详情:** + +-  新增 在`MemoryCache`中实现新的`TryGetValue`方法。 +-  优化 密封`CacheEntry`类并优化缓存管理逻辑。 +-  优化 扩展`PackageExtensions`类,增加泛型方法以提高可读性和可重用性。 +-  优化 优化`HttpStaticPagePlugin`构造函数和`StaticPageOptions`类以简化静态页面配置。 +-  修复 在`FastBinaryFormatter`中改进序列化逻辑,特别是对于**多维数组**的处理。 [#IARKE1](https://gitee.com/RRQM_Home/TouchSocket/issues/IARKE1) +-  修复 在`IPackage`中改进打包、解包逻辑,特别是对于**多维数组**的处理。 + +*** + +## v2.1.0 + +**更新日期:** 2024.9.15 + +**更新描述:** + +大版本升级,有部分不兼容性升级。所以请在升级前做好备份,同时在升级之后,请务必阅读[v2.1升级指南](https://gitee.com/RRQM_Home/TouchSocket/issues/I9QSDK)。 + +**更新亮点:** + +本次更新,主要有以下亮点: + +1. 全系支持Span、ValueTask、Memory、Unsafe等依赖。大幅提升并发性能与低GC能力。 +2. 全系组件,尽量多的提供了异步Api,大幅度提升并发能力。 +3. 重构fast序列化,Package包模式、Http、WebSocket等组件,使之更加易用。 +4. 资源国际化。本次更新会在内部使用中英双语信息提示,这在日志记录,堆栈跟踪等场景更加符合区域化。 +5. 增加完整注释。基本上能达到95%的代码注释率。 + +**更新详情:** + +#### TouchSokcet.Core + +-  新增 TouchSocketBitConverter新增To、UnsafeTo、WriteBytes、UnsafeWriteBytes等可以直接操作Span。 +-  新增 Crc类新增Span相关转换。 +-  新增 CustomDataHandlingAdapter新增`bool TryParseRequest(ref TByteBlock byteBlock, out TRequest request)`方法,可以同步完成适配器数据解析。 +-  新增 SingleStreamDataAdapterTester泛型测试器,可以对TryParseRequest进行完整性测试。 +-  新增 IPackage源生成器新增自定义PackageMember特性,用来定义打包的顺序和自定义转换器。 +-  新增 SetupConfigObject的SetupAsync。 +-  优化 CustomDataHandlingAdapter支持结构体作为泛型类型。 +-  优化 Fast序列化支持自定义FastSerializerContext,这可以极大的利用源生成来决定序列化和反序列化。 +-  调整 分离MemoryCache的同步和异步接口。 +-  调整 ByteBlock取消Stream的继承,如果需要使用Stream,可以使用ByteBlock.AsStream()。 +-  调整 Gzip类调整ByteBlock参数为Stream。 +-  调整 CustomDataHandlingAdapter解析的数据,均会以ReadonlySpan的形式投递。 +-  调整 IPackage接口,使之既可以在ByteBlock工作,也可以在ValueByteBlock工作。 +-  调整 PluginManager使用接口作为唯一键,规定一个接口中有且仅有一个方法。 +-  调整 ByteBlock、ValueByteBlock均继承IByteBlock接口规范。 +-  移除 ByteBlock、ValueByteBlock移除Buffer属性,如果想获取有效数据,可以通过Memory、Span等获取,如果想获取容量可以使用TotalMemory属性。 + +#### TouchSokcet.Sockets + +-  调整 Tcp、Udp组件,默认情况下适配器将为null,并且可以正常工作。 +-  调整 SocketClient、ISocketClient等服务器辅助类,改名为TcpSessionClient和ITcpSessionClient。 + + +*** diff --git a/handbook/src/pages/upgrade/v3-0.mdx b/handbook/src/pages/upgrade/v3-0.mdx new file mode 100644 index 000000000..7db388ec1 --- /dev/null +++ b/handbook/src/pages/upgrade/v3-0.mdx @@ -0,0 +1,659 @@ +--- +id: upgrade-v3-0 +title: v3.0 系列更新 +--- + +import useBaseUrl from "@docusaurus/useBaseUrl"; +import Tag from "@site/src/components/Tag.js"; +import Highlight from '@site/src/components/Highlight.js'; + + +## v3.0.26 + +**更新日期:** 2025.4.20 + +**更新描述:** + +兼容性修复升级。 + + +**更新详情:** + +#### TouchSocket.Rpc + +-  修复 `RpcActionFilterAttribute`在`RpcImplementation`程序集中使用时无效bug。 [#IC0IB0](https://gitee.com/RRQM_Home/TouchSocket/issues/IC0IB0) + +#### TouchSocket.Http + +-  修复 `WebSocket`服务端`WebSocket`使用`AsyncClose()`主动关闭连接时引发`NullReferenceException`异常。 [#IC1FZ8](https://gitee.com/RRQM_Home/TouchSocket/issues/IC1FZ8) + +*** + + +## v3.0.25 + +**更新日期:** 2025.4.12 + +**更新描述:** + +兼容性修复升级。 + + +**更新详情:** + +#### TouchSocket.SerialPort + +-  修复 接收数据时,如果业务出现延迟,则会导致接收失败的异常bug。 [#IBZHN2](https://gitee.com/RRQM_Home/TouchSocket/issues/IBZHN2) + +*** + +## v3.0.21(3)(4) + +**更新日期:** 2025.4.6 + +**更新描述:** + +兼容性修复升级。http组件性能优化。 + + +**更新详情:** + +#### TouchSocket.Core + +-  新增 `ReadOnlySpan`的`Trim`方法。 +-  新增 `Method`新增支持类型的直接构造函数方法。 +-  新增 `AsyncResetEvent`新增已释放判断。 +-  修复 `DynamicMethod`在ref、out等参数时,无法使用源生成调用的bug。 +-  调整 `FlowOperator`的在构造函数使用最大值初始化`MaxSpeed`。 + +#### TouchSocket.Sockets + +-  修复 `TcpCore`在特点情况下不释放资源的bug。[PR](https://gitee.com/RRQM_Home/TouchSocket/pulls/65) +-  调整 `ShutdownAsync`调整返回值,由`Task`改为`Task`。 + +#### TouchSocket.Http + +-  新增 `HttpContent`新增`TryComputeLength`的抽象方法。 +-  新增 `HttpResponse`的`FromFileAsync`扩展新增传输限速、进度功能。[#IBYGC7](https://gitee.com/RRQM_Home/TouchSocket/issues/IBYGC7) +-  优化 整个`Http`组件性能优化。 +-  调整 `HttpHeaders`由枚举改为静态类。(此操作不影响现有代码) + + +*** + + +## v3.0.20 + +**更新日期:** 2025.3.30 + +**更新描述:** + +兼容性修复升级。部分方法名大小写调整。 + + +**更新详情:** + +#### TouchSocket.Core + +-  调整 `TouchSocketCoreUtility`类中的所有静态字段大小写调整。 +-  调整 `StringExtension`类中的多个方法名大小写调整(此处调用时可能是扩展方法调用的,所以需要注意)。 + +#### TouchSocket.Http + +-  修复 `HttpClient`在请求`application/x-www-form-urlencoded`时,内部未进行编码的bug。[#IBVPAD](https://gitee.com/RRQM_Home/TouchSocket/issues/IBVPAD) +-  新增 `HttpResponse`新增`FromHtml`扩展方法,用于直接返回`Html`页面。 + +#### TouchSocket.Rpc + +-  新增 `IRpcCallContextAccessor`服务,可以在异步调用流中,通过服务直接获取到执行`Rpc`的`CallContext`。 + +#### TouchSocket.WebApi.Swagger + +-  更新 `Swagger`页面版本为`v5.20.2`,以支持一键复制url等功能。 [#IBX933](https://gitee.com/RRQM_Home/TouchSocket/issues/IBX933) + +#### TouchSocket.Modbus + +-  优化 `Modbus rtu`在响应数据时的严谨性,基本排除了站号,功能码不一致时仍然返回的错误情况。 [#github-issue 54](https://github.com/RRQM/TouchSocket/issues/54) + +*** + + +## v3.0.19 + +**更新日期:** 2025.3.23 + +**更新描述:** + +兼容性修复升级。 + + +**更新详情:** + +#### TouchSocket.Http + +-  修复 Http静态内容插件在客户端不支持gzip时仍然会使用gzip的bug。 +-  修复 Http静态内容插件在以文件响应时,限速为0的bug。[#IBVCPJ](https://gitee.com/RRQM_Home/TouchSocket/issues/IBVCPJ) + +*** + +## v3.0.18 + +**更新日期:** 2025.3.16 + +**更新描述:** + +兼容性修复升级。少量代码调整,详见 调整 + + +**更新详情:** + +#### TouchSocket.Sockets + +-  修复 `TryShutdown`的关闭机制没有考虑异步发送队列的情况。[#IBTKMP](https://gitee.com/RRQM_Home/TouchSocket/issues/IBTKMP) +-  调整 `TryShutdown`方法改为`ShutdownAsync`。 + +#### TouchSocket.Http + +-  修复 `HttpExtensions`中`GetBoundary`实现存在问题。[#IBT3RO](https://gitee.com/RRQM_Home/TouchSocket/issues/IBT3RO) +-  修复 静态页面插件在重新载入时prefix参数丢失问题。[#IBTP38](https://gitee.com/RRQM_Home/TouchSocket/issues/IBTP38) + +*** + + +## v3.0.17 + +**更新日期:** 2025.3.12 + +**更新描述:** + +兼容性修复升级。 + + +**更新详情:** + +#### TouchSocket.Sockets + +-  修复 UdpSessionBase的OnUdpReceiving方法添加EndPoint参数。[#IBSQVN](https://gitee.com/RRQM_Home/TouchSocket/issues/IBSQVN) + +#### TouchSocket.SerialPorts + +-  修复 SerialPortClient在接收数据时LastReceivedTime一直不会更新。[#IBSXDK](https://gitee.com/RRQM_Home/TouchSocket/issues/IBSXDK) + +*** + + +## v3.0.16 + +**更新日期:** 2025.3.9 + +**更新描述:** + +兼容性修复升级。 + + +**更新详情:** + +#### TouchSocket.Core + +-  修复 自定义适配器在接收数据时,不验证`MaxPackageSize`的`bug`。 +-  修复 `MakeIdentifier`在获取方法名称时,不显示中文等其他字符的`bug`[#IBQQHY](https://gitee.com/RRQM_Home/TouchSocket/issues/IBQQHY)。 + +#### TouchSocket.Sockets + +-  修复 当配置`NoDelay`时,`TcpCore`发送时仍然会把数据放入发送队列,可能会产生细微延迟 [#IBR1I2](https://gitee.com/RRQM_Home/TouchSocket/issues/IBR1I2)。 +-  调整 `IWaitingClient`实现了无效的`Dispose`方法,目前已取消。 + +*** + + +## v3.0.15 + +**更新日期:** 2025.3.2 + +**更新描述:** + +兼容性修复升级。 + + +**更新详情:** + +#### All + +-  优化 代码规范和相关注释。 + +#### TouchSocket.Core + +-  新增 `SystemExtension`新增`ReadAllToByteArray`方法。 + +*** + + +## v3.0.14 + +**更新日期:** 2025.2.15 + +**更新描述:** + +修复升级。区间字符适配器会有不兼容部分,请参阅[区间字符]。 + + +**更新详情:** + +#### All + +-  新增 所有的`Plugin`接口。均新增快捷扩展方法,简化使用。 +-  调整 使用自定义的`lock`锁对象。以简化使用场景。 + +#### TouchSocket.Core + +-  新增 `ILog`接口新增`DateTimeFormat`属性。 [#IBLQBX](https://gitee.com/RRQM_Home/TouchSocket/issues/IBLQBX) +-  修复 自定义区间适配器在未找到开始字符的情况下,也会缓存数据的bug。 [#IBKPXU](https://gitee.com/RRQM_Home/TouchSocket/issues/IBKPXU) +-  调整 自定义区间适配器的运行逻辑,简化使用方式。 [#IBKPXU](https://gitee.com/RRQM_Home/TouchSocket/issues/IBKPXU) + +#### TouchSocket.Rpc + +-  修复 `IRpcActionFilter.ExecutedAsync`在执行异常时,`Exception`参数一直为空的bug。[#IBK579](https://gitee.com/RRQM_Home/TouchSocket/issues/IBK579) + +#### TouchSocket.Dmtp + +-  新增 `TokenVerifyException`异常信息中新增Metadata属性。 +-  修复 `TcpDmtpService`中使用`e.IsPermitOperation = false;`拒绝客户端无效的bug。[#IBKO6A](https://gitee.com/RRQM_Home/TouchSocket/issues/IBKO6A) + +*** + +## v3.0.13 + +**更新日期:** 2025.1.27 + +**更新描述:** + +兼容性修复升级。 + + +**更新详情:** + + +#### TouchSocket.Core + +-  修复 在`net framework`下,如果`Span`为空时,`ToString`会异常的`bug`。 [#IBIYRQ](https://gitee.com/RRQM_Home/TouchSocket/issues/IBIYRQ) + +*** + +## v3.0.12 + +**更新日期:** 2025.1.19 + +**更新描述:** + +兼容性修复升级。 + + +**更新详情:** + + +#### TouchSocket.Core + +-  优化 `FileLogger`的路径合并方式。 + +*** + + +## v3.0.11 + +**更新日期:** 2025.1.12 + +**更新描述:** + +兼容性修复升级。 + + +**更新详情:** + +#### All + +-  新增 全系新增`EasyTask.ContinueOnCapturedContext`的设定,以解决`Unity3d`在`webgl`平台下,会卡住的问题。 + +#### TouchSocket.Core + +-  新增 `IWaitHandlePool`的接口抽象。 + +#### TouchSocket.Http + +-  新增 `HttpResponse`新增`SetRedirect`重定向功能。[#IBG1QT](https://gitee.com/RRQM_Home/TouchSocket/issues/IBG1QT) +-  优化 `HttpRequest`在请求时的编码效率。 +-  修复 `HttpRequest`不会对中文等非`Ascii`编码的字符进行`url encode`的`bug`。[#IBGATN](https://gitee.com/RRQM_Home/TouchSocket/issues/IBGATN) +-  修复 在客户端,执行`IWebApiRequestPlugin`插件时,`HttpRequest`无法通过`GetContent`或者`GetBody`获取数据。[#IBGARB](https://gitee.com/RRQM_Home/TouchSocket/issues/IBGARB) + +*** + +## v3.0.10 + +**更新日期:** 2025.1.5 + +**更新描述:** + +兼容性修复升级。 + + +**更新详情:** + +#### TouchSocket.Core + +-  新增 `AsyncBoundedQueue`新增`Capacity`、`Count`等属性。 +-  新增 `CustomJsonDataHandlingAdapter`自定义适配器,方便自定义继承实现。 +-  新增 `CustomCountSpliterDataHandlingAdapter`固定数量分隔符适配器。[#IBF0Z7](https://gitee.com/RRQM_Home/TouchSocket/issues/IBF0Z7) +-  新增 `IByteBlock`新增`ReadT`和`WriteT`方法。 + +#### TouchSocket.Sockets + +-  修复 `CheckClearPlugin`在客户端断开后,仍然会触发断开的bug。[#IBECPA](https://gitee.com/RRQM_Home/TouchSocket/issues/IBECPA) + +*** + +## v3.0.9 + +**更新日期:** 2024.12.22 + +**更新描述:** + +兼容性修复升级。 + + +**更新详情:** + +#### TouchSocket.Core + +-  新增 `SystemTextJsonStringToClassSerializerFormatter`转换器。 +-  新增 `IPackage`源生成时,默认支持`Guid`。[#IBC1FH](https://gitee.com/RRQM_Home/TouchSocket/issues/IBC1FH) + +#### TouchSocket.Sockets + +-  修复 `TcpService`在`ResetId`后,偶发性客户端集合丢失客户端的问题。[#IBCUHW](https://gitee.com/RRQM_Home/TouchSocket/issues/IBCUHW) +-  修复 `WaitingClient`使用`ResponsedData`时第二次接受报错。[#IBC8D2](https://gitee.com/RRQM_Home/TouchSocket/issues/IBC8D2) + +#### TouchSocket.SerialPort + +-  修复 `SerialPortClient`串口组件`ProtectedMainSerialPort`属性`null`错误。[#IBBQ4A](https://gitee.com/RRQM_Home/TouchSocket/issues/IBBQ4A) + + +#### TouchSocket.AspNetCore + +-  修复 `IWebSocketDmtpService`接口中缺少`Clients`属性。[#IBBQS5](https://gitee.com/RRQM_Home/TouchSocket/issues/IBBQS5) + +#### TouchSocket.Modbus + +-  优化 `ModbusRtu`在`crc`校验失败时,会抛出`ResponseMemoryVerificationError = 99`的错误码。[#IBC1J2](https://gitee.com/RRQM_Home/TouchSocket/issues/IBC1J2) + +#### TouchSocket.WebApi + +-  新增` WebApi`支持`Put`、`Delete`、`Options`请求方式。 +-  修复 `WebApi`当参数解析异常时,`RpcActionFilterAttribute`无法捕获。[#IBCI94](https://gitee.com/RRQM_Home/TouchSocket/issues/IBCI94) +-  调整 `WebApiParserPlugin`使用`Mapping`代替`GetRouteMap`和`PostRouteMap`。 + +#### TouchSocket.JsonRpc + +-  优化 `JsonRpc`支持`Aot`。 + +*** + +## v3.0.8 + +**更新日期:** 2024.12.15 + +**更新描述:** + +兼容性修复升级。 + + +**更新详情:** + +#### TouchSocket.Sockets + +-  修复 `IPHost`类,在`Mono`运行时会异常的bug。 [#IBAG51](https://gitee.com/RRQM_Home/TouchSocket/issues/IBAG51) +-  调整 `UdpSessionBase`类调整`ReceivingData`为`OnUdpReceiving`。 +-  新增 `Udp`组件,新增`IUdpReceivingPlugin`插件。[#IBB1F6](https://gitee.com/RRQM_Home/TouchSocket/issues/IBB1F6) + +#### TouchSocket.AspNetCore + +-  修复 当使用基于`WebSocket`协议,搭建`DmtpServer`服务时 使用`app.UseOutputCache()`缓存中间件导致连接失败后,重连无响应。[#IBAPTQ](https://gitee.com/RRQM_Home/TouchSocket/issues/IBAPTQ) + +#### TouchSocket.Dmtp + +-  修复 `DmtpActor`类,在连接成功时,`Handshaking`事件参数`e.Message`无法传回到请求端。 + +#### TouchSocket.SerialPorts + +-  修复 `SerialPortClient`类,在关闭或者释放时,资源无法释放的bug。 [#IBB8FD](https://gitee.com/RRQM_Home/TouchSocket/issues/IBB8FD) + +*** + +## v3.0.7 + +**更新日期:** 2024.12.8 + +**更新描述:** + +兼容性修复升级。在.Net9.0中,启用Lock锁代替object锁,提高锁效率。 + + +**更新详情:** + +#### TouchSocket.Core + +-  修复 `ValueByteBlock`类,在`WritePackage`时数据无效的bug。 +-  优化 开放`PackageFastBinaryConverter`类,以支持二进制数据序列化源生成。 + +#### TouchSocket.Dmtp + +-  新增 `DmtpRpc`新增`RpcDispatcher`调度器,支持多线程并发,或者单线程调度。 +-  优化 `DefaultSerializationSelector`优化支持`System.Text.Json`源生成模式的序列化。 +-  调整 触发`OnFileTransferred`事件的时机调到`SendAsync`前面,目的是保证调用方在收到回复时,响应方已经完成事件处理。此操作理论上不会对现有运行逻辑造成影响。 +-  修复 `DmtpRpc`在发送`Rpc`请求时,如果请求模式使用`OnlySend`、或者`WaitSend`,则会抛出异常的bug。[#IB9F8P](https://gitee.com/RRQM_Home/TouchSocket/issues/IB9F8P) + + +#### TouchSocket.Modbus + +-  新增 在`IModbusResponse`返回响应时,会同时携带返回响应的当前请求`IModbusRequest`。 + +#### TouchSocket.WebApi + +-  优化 `WebApiSerializerConverter`类,以支持更好的`System.Text.Json`源生成模式的序列化。 + +#### TouchSocket.Rpc + +-  新增 在`ReenterableAttribute`特性,可以强制设置`Rpc`函数是否为重入模式。 +-  新增 `ConcurrencyRpcDispatcher`并发调度器,支持多线程并发。 +-  新增 `ImmediateRpcDispatcher`当前调度器,直接在当前线程执行。 +-  新增 `QueueRpcDispatcher`队列调度器,把所有请求,放在一个队列中,等待处理。 + +*** + + +## v3.0.5(6) + +**更新日期:** 2024.12.1 + +**更新描述:** + +兼容性修复升级。全面支持`PluginManager`容器化模块和`Scoped`区域划分。 + +>此修改不影响现有运行逻辑。但是如果是Asp.Net Core项目,则可能会影响`Scoped`服务运行结果。 + +**更新详情:** + +#### TouchSocket.Core + +-  新增 `FlowOperator`类。以实现流量速度限制。 +-  新增 `PluginManager`类。新增`FromIoc`的设定,支持插件容器化。 +-  优化 `Result`类。使用`record`修饰,简化使用逻辑。 +-  调整 `IResolver`的`Resolve`行为。当服务未注册时,会返回`null`。不会再触发异常。 +-  调整 `SetupConfigObject`在Build时,会创建一个新的Scoped生命周期的容器。这个在现有架构下,不会影响运行结果。 +-  修复 `Metadata`在添加相同键值时,会异常的bug,正确操作应该是覆盖旧值。 +-  移除 `IResolver`移除对`IRegistered`接口的实现,所以无法再使用`IResolver`判断某个服务是否已注册。 +-  移除 `IResolver`移除对`TryResolve`扩展方法实现,请使用`Resolve`直接代替。 + +#### TouchSocket.Sockets + +-  修复 `IPHost`在`.NET Framework`下,设定任意端口无效的bug。 [#IB7PM1](https://gitee.com/RRQM_Home/TouchSocket/issues/IB7PM1),[#IA8NQ2](https://gitee.com/RRQM_Home/TouchSocket/issues/IA8NQ2) + +#### TouchSocket.Http + +-  新增 `HttpBase`新增`ReadCopyToAsync(Stream stream, HttpFlowOperator flowOperator)`方法。支持传输进度、速度显示。[#IB7GIC](https://gitee.com/RRQM_Home/TouchSocket/issues/IB7GIC) +-  新增 `HttpFlowOperator`类,支持Http上传、下载文件(流)时,可以方便的实现限速、传输进度、速度显示等。 +-  新增 `StreamHttpContent`在传输流数据时,支持`HttpFlowOperator`相关操作。 + + +#### TouchSocket.Rpc +-  修复 `Rpc`代码生成器在生成通用泛型类型时,会失败的bug。[#IB7IB2](https://gitee.com/RRQM_Home/TouchSocket/issues/IB7IB2) + + +*** + + +## v3.0.4 + +**更新日期:** 2024.11.24 + +**更新描述:** + +修复升级。**WebSocket有少量代码差异**,下面会详细介绍。 + +**更新详情:** + +#### TouchSocket.Core +-  新增 `JsonPackageAdapter`适配器,专门解决纯Json数据格式的粘分包。详情参见:[JsonPackageAdapter](/docs/packageadapter)。 +-  新增 `IByteBlock`新增`WriteNormalString`方法,用于写入普通字符串。 +-  新增 `Container`容器新增Scoped生命周期,但是本身容器并未实现功能,如果需使用,请使用[TouchSocket.Core.DependencyInjection](https://www.nuget.org/packages/TouchSocket.Core.DependencyInjection)。 +-  新增 `Tcp、NamedPipe、SerialPort、DmtpRpc、JsonRpc、WebApi`等所有组件,支持Scoped容器。(需配合[TouchSocket.Core.DependencyInjection](https://www.nuget.org/packages/TouchSocket.Core.DependencyInjection)容器)。 + +#### TouchSocket.Http +-  新增 `HttpStaticPagePlugin`新增`SetContentTypeProvider(Action provider)`方法。 +-  新增 `WebSocket`新增`CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription)`方法。 +-  修复 `WebSocket`在`Close`时,不符合规范的bug。[#IAAF3U](https://gitee.com/RRQM_Home/TouchSocket/issues/IAAF3U),[#IB5IH0](https://gitee.com/RRQM_Home/TouchSocket/issues/IB5IH0) +-  调整 `IWebSocketClosingPlugin`的事件参数,由`ClosedEventArgs`调整为`ClosingEventArgs`。 重新实现接口解决 + + +#### TouchSocket.Sockets + +-  修复 `TcpClient`在重连之后,适配器失效的bug。 [#IB6KZJ](https://gitee.com/RRQM_Home/TouchSocket/issues/IB6KZJ) + +#### TouchSocket.Rpc +-  新增 `Rpc`服务新增`IScopedRpcServer`。 + + +#### TouchSocket.SerialPort +-  修复 `SerialPortClient`在重连之后,适配器失效的bug。 + +#### TouchSocket.NamedPipe + +-  新增 `INamedPipeSession`新增`DataHandlingAdapter`属性。 +-  修复 `NamedPipeClient`在重连之后,适配器失效的bug。 + +#### TouchSocket.Modbus + +-  优化 `Modbus`在写入时,会携带`IModbusResponse`的返回值。 [#IATPWB](https://gitee.com/RRQM_Home/TouchSocket/issues/IATPWB) + + +*** + +## v3.0.3 + +**更新日期:** 2024.11.17 + +**更新描述:** + +兼容性修复升级。 + +**更新详情:** + +-  优化 `WaitingClient`在收到`ResponsedData`数据时,优先建议使用`ByteBlock`,在高效场景中代替原`Data`属性。 + +*** + + +## v3.0.2 + +**更新日期:** 2024.11.15 + +**更新描述:** + +兼容性修复升级。 + +**更新详情:** + +-  修复 串口的发送与接收无法通过插件获取到原始数据。[#IB4NF4](https://gitee.com/RRQM_Home/TouchSocket/issues/IB4NF4) + +*** + +## v3.0.0(1) + +**更新日期:** 2024.11.13 + +**更新描述:** + +大版本升级,有部分不兼容性升级。所以请在升级前做好备份,同时在升级之后,请务必阅读[v3.0升级指南](https://gitee.com/RRQM_Home/TouchSocket/issues/IB45VJ)。 + +**本次改动在运行时完全兼容`v2.1`,所以客户端和服务器可以差异版本更新。** + +**更新详情:** + +#### SDK + +-  新增 新增`net9.0`支持。 +-  移除 移除`net7.0`支持,但是不影响`net7.0`使用,因为最低到`net6.0`的支持。 + + +#### TouchSocket.Core + +-  优化 `Plugin`组件默认支持`AOT`,不再借助委托实现。 +-  优化 `AppMessenger`组件默认支持`AOT`。 +-  优化 `DependencyObject`类使用“懒汉式”加载内部成员,减少内存使用。 +-  新增 `PluginManager`支持在运行时移除插件。 +-  新增 `Method`类,在方法、或类添加`[DynamicMethod]`特性时,默认支持`AOT`,为动态调用提供极大方便。 + +#### TouchSocket.Core.DependencyInjection + +-  修复 `AspNetCoreContainer`不支持`KeysService`的bug。 + +#### TouchSocket.Sockets + +-  修复 `IReceiver`在启用缓存模式时,如果已经完成接收,则会抛出异常的bug。 [#IB44LL](https://gitee.com/RRQM_Home/TouchSocket/issues/IB44LL) + +#### TouchSocket.Http + +-  调整 `HttpRequest`在请求时,不用传参,直接使用默认构造函数即可。 +-  移除 `GetMultifileCollection`相关扩展方法,使用`GetFormCollectionAsync`代替。 + +#### TouchSocket.Rpc + +-  新增 `ICallContext`继承`IDependencyObject`,支持**扩展属性**读写,可以更方便的开发。 +-  调整 `Rpc`特性取消构造函数入参,使用属性设置。受影响的有:`DmtpRpc`、`JsonRpc`、`XmlRpc`、`WebApi`。 + + +#### TouchSocket.Dmtp + +-  修复 `DmtpRpc`在`Avalonia-Web`工作时,连接时间超长的bug。 +-  修复 `IWebSocketDmtpClient`不实现`IDmtpClient`的bug。 +-  修复 `DmtpHeartbeatPlugin`在长时间运行时,可能会失效的bug。 +-  调整 `[DmtpRpc]`特性不再允许继承,所有设置也是通过属性设置。 + +#### TouchSocket.JsonRpc + +-  调整 `[JsonRpc]`特性不再允许继承,所有设置也是通过属性设置。 + +#### TouchSocket.WebApi + +-  新增 `[FromBody]`特性,支持指定参数来源自Http的请求Body。 +-  新增 `[FromForm]`特性,支持指定参数来源自Http的请求Form表单。 +-  新增 `[FromHeader]`特性,支持指定参数来源自Http的请求Header。 +-  新增 `[FromQuery]`特性,支持指定参数来源自Http的请求Query。 +-  调整 `[WebApi]`特性不再允许继承,所有设置也是通过属性设置。 +-  调整 WebApi的请求方式,目前全部使用`WebApiRequest`类来表示全部入参,一般如果使用代理,或者源生成的话,只需要重新生成即可。 +-  移除 `HttpMethodType.GET`枚举,使用`HttpMethodType.Get`代替。 +-  移除 `HttpMethodType.POST`枚举,使用`HttpMethodType.Post`代替。 + +#### TouchSocket.XmlRpc + +-  调整 `[XmlRpc]`特性不再允许继承,所有设置也是通过属性设置。 + +*** diff --git a/handbook/src/pages/upgrade/v3-1.mdx b/handbook/src/pages/upgrade/v3-1.mdx new file mode 100644 index 000000000..9789c8329 --- /dev/null +++ b/handbook/src/pages/upgrade/v3-1.mdx @@ -0,0 +1,494 @@ +--- +id: upgrade-v3-1 +title: v3.1 系列更新 +--- + +import useBaseUrl from "@docusaurus/useBaseUrl"; +import Tag from "@site/src/components/Tag.js"; +import Highlight from '@site/src/components/Highlight.js'; + + +## v3.1.19 + +**更新日期:** 2025.10.25 + +**更新描述:** + +- 兼容性修复升级。 + +**更新详情:** + +#### TouchSocket.Sockets + +-  修复 `TcpClient`在连接失败时,没有正确释放Socket资源的bug。 + +*** + +## v3.1.18 + +**更新日期:** 2025.9.27 + +**更新描述:** + +- 版本号升级至 v3.1.18。 +- 修复 TCP 发送数据分片问题,确保大数据包完整发送。([#85](https://github.com/RRQM/TouchSocket/pull/85)) +- RPC 全局筛选器注册与解析机制调整,支持按类型注册,由容器解析实例,接口命名更清晰。([#83](https://github.com/RRQM/TouchSocket/pull/83)) +- WebSocket 关闭帧处理完善,正确解析 CloseStatus 与关闭原因,增强标准兼容性。 +- Dmtp RPC 调用上下文清理逻辑优化,降低潜在内存占用风险。 + +**更新详情:** + +#### TouchSocketVersion + +-  升级 基础版本号由 `3.1.17` 升级为 `3.1.18`(`TouchSocketVersion.props`)。 + +#### TouchSocket.Sockets + +-  修复 `TcpCore` 发送循环逻辑,改为使用 `sentLength` 进行切片发送与累计,避免出现“发送数据未切片导致大包未完整发送”的问题;当 `BytesTransferred == 0` 且仍有数据时抛出不完整传输异常。([#85](https://github.com/RRQM/TouchSocket/pull/85)) + +#### TouchSocket.Rpc + +-  调整 全局筛选器注册 API 更名:`Filter()`/`Filter(Type)` 调整为 `AddFilter()`/`AddFilter(Type)`,语义更清晰。 +-  新增 暴露 `RpcStore.Filters` 只读属性,便于获取已注册的全局筛选器类型列表。 +-  优化 `RpcMethod.GetFilters` 签名与实现调整,接收筛选器类型列表与 `IResolver`,按需从容器解析筛选器实例,减少共享实例的副作用;`InternalRpcServerProvider` 同步适配新的调用方式。([#83](https://github.com/RRQM/TouchSocket/pull/83)) + +#### TouchSocket.Http + +-  新增 `WebSocketClientBase` 在接收关闭帧时,解析前 2 字节(大端)为 `CloseStatus` 并赋值给连接对象,剩余负载按 UTF-8 作为关闭原因消息;触发关闭流程更符合规范。 + +#### TouchSocket.Dmtp + +-  修复 `DmtpRpcActor` 在处理响应时优先移除调用上下文项,避免因上下文残留导致的资源占用问题。 + +*** + +## v3.1.17 + +**更新日期:** 2025.9.9 + +**更新描述:** + +- 兼容性修复更新。 + +**更新详情:** + +#### TouchSocket.SerialPorts + +-  修复 `SerialPortClient` 在某些情况下,会导致程序奔溃的bug。 + +*** + + +## v3.1.16 + +**更新日期:** 2025.8.21 + +**更新描述:** + +- SSL配置重构和安全性改进。 +- 代码格式优化和规范化。 +- 版本号更新至3.1.16。 + +**更新详情:** + +#### TouchSocket.Core + +-  优化 `SystemExtension.WriteAsync` 方法的代码格式,将多行的 `ConfigureAwait` 调用合并为一行,提升代码可读性。 + +#### TouchSocket + +-  调整 `ClientSslOption` 构造函数重构,移除自动读取系统根证书存储的复杂逻辑,改为初始化空的 `X509Certificate2Collection` 集合,提升安全性。 +-  调整 `SslOption.SslProtocols` 属性添加默认值 `SslProtocols.None`,简化SSL配置。 +-  调整 `TouchSocketConfigExtension` 中SSL配置逻辑简化: + - 将 `TargetHost` 从 `value.Authority` 改为 `value.Host` + - 移除复杂的条件编译SSL协议配置,统一使用 `SslProtocols.None` + + +*** + +## v3.1.15 + +**更新日期:** 2025.8.3 + +**更新描述:** + +- HTTP协议头部解析性能优化。 +- RPC代码生成器命名空间改进。 + +**更新详情:** + +#### TouchSocket.Http + +-  优化 `HttpBase.ParsingHeader` 方法中的字节块解析逻辑,优化内存使用和变量命名,提升HTTP头部解析性能。 +-  修复 修复头部长度计算错误的问题。 + +#### TouchSocket.SourceGenerator + +-  调整 `RpcClientCodeBuilder.GetNamespace` 方法,改进默认命名空间生成策略,支持根据RpcAttributeName自定义命名空间格式。 +-  优化 代码生成器的命名空间逻辑,提升生成代码的组织结构。 + +*** + +## v3.1.14 + +**更新日期:** 2025.7.20 + +**更新描述:** + +- HTTP模块性能优化和内存管理改进。 + +**更新详情:** + +#### TouchSocket.Http + +-  优化 `HttpResponse.GetContentAsync` 方法中的内存流初始化,使用精确的内容长度预分配内存,提升性能。 +-  优化 内存分配策略,减少不必要的内存占用。 +-  优化 变量命名和局部变量使用,提升代码可读性。 + +*** + +## v3.1.13 + +**更新日期:** 2025.7.14 + +**更新描述:** + +- 修复和改进升级,提升稳定性和性能。 +- MQTT协议处理优化和错误修复。 + +**更新详情:** + +#### TouchSocket.Mqtt + +-  修复 MQTT 协议名称统一为 "MQTT"(大写),修复协议标准一致性问题。 +-  修复 `MqttAdapter` 中消息解析位置计算错误,修复数据包边界计算问题。 +-  修复 `VariableByteIntegerRecorder` 中长度设置错误,修复变长整数记录器的边界处理。 + +*** + +## v3.1.12 + +**更新日期:** 2025.7.8 + +**更新描述:** + +- 兼容性修复升级。 +- 代码质量和文档规范化改进,以及功能增强。 + +**更新详情:** + +#### TouchSocket.Core + +-  优化 代码注释规范化,统一将 "null" 改为 `` 的XML文档格式。 +-  新增 `EasyMemoryMarshal` 类增加了更详细的文档和泛型约束。 +-  优化 多个核心类的内存管理和线程安全性。 +-  优化 `TouchSocketBitConverter` 中的异常文档注释。 + +#### TouchSocket.Mqtt + +-  优化 MQTT协议名称从 "MQTT" 统一为 "Mqtt"。 +-  重构 `MqttSessionActor` 消息分发机制,提升性能和稳定性。 +-  新增 `ThreadSafeTopicSubscriptions` 类,提供线程安全的主题订阅管理。 +-  优化 `MqttBroker` 的消息转发逻辑,改进并发处理能力。 +-  修复 `MqttReceivedEventArgs` 中属性命名一致性问题。 + +#### TouchSocket.SerialPorts + +-  新增 `ISerialPortClient` 和 `SerialPortClient` 实现 `IConnectableClient` 接口。 +-  新增 `ConnectAsync` 方法支持异步连接操作。 +-  调整 `ISerialPortSession` 接口继承关系,移除冗余接口。 + +#### TouchSocket.Modbus + +-  新增 `IModbusRtuMaster` 实现 `IConnectableClient` 接口。 +-  新增 `ModbusRtuMaster` 增加 `ConnectAsync` 方法支持。 + +#### TouchSocket.WebApi + +-  新增 `RegexRouterAttribute` 类增加详细的XML文档注释。 +-  优化 `WebApiAttribute` 中的路由处理逻辑和变量命名。 + +#### TouchSocket.Sockets + +-  优化 网络监听器和客户端基类的参数验证和错误处理。 +-  修复 `ReconnectionPlugin` 的 Dispose 方法错误,修复无限循环任务的正确释放机制。([#68](https://github.com/RRQM/TouchSocket/pull/68) by [@godchadigo](https://github.com/godchadigo)) + +*** + + +## v3.1.9(10)(11) + +**更新日期:** 2025.6.24 + +**更新描述:** + +兼容性修复升级。 + +**更新详情:** + +#### TouchSocket.Core + +-  优化 `Result`和`Result`在调试时的显示。 +-  优化 `IByteBlock`的部分方法传参。 + +#### TouchSocket.Mqtt + +-  修复 `IMqttTcpClient`没有继承`ISetupConfigObject`的bug。 + +#### TouchSocket.SerialPort + +-  修复 `StreamAsync`异步流模式工作异常的bug。 + +#### TouchSocketPro.Modbus + +-  新增 `ModbusCoilsDrive`支持线圈读写。 +-  新增 `ModbusDiscreteInputsDrive`支持离散输入读写。 +-  新增 `ModbusHoldingRegistersDrive`支持保持寄存器读写。 +-  新增 `ModbusInputRegistersDrive`支持输入寄存器读写。 + +#### TouchSocketPro.PlcBridges + +-  新增 `PlcObject`组件。 + +*** + +## v3.1.8 + +**更新日期:** 2025.6.15 + +**更新描述:** + +兼容性修复升级。 + +**更新详情:** + +#### TouchSocket.Core + +-  优化 `DependencyProperty`在调试时的显示。 +-  调整 `SafeDispose`方法的返回,由`void`改为`Result`。此修改不影响代码编译,但是需要重新编译依赖dll。 + +#### TouchSocket.Http + +-  修复 当`Http服务器`在响应后,`HttpResponse`不会清空`Content`的`bug`。 [#ICEVTN](https://gitee.com/RRQM_Home/TouchSocket/issues/ICEVTN) + +#### TouchSocket.Modbus + +-  新增 `IModbusResponse`类新增`IsSuccess`属性,可用于快捷获取响应结果。 +-  调整 `IModbusResponse`的`Data`属性由`Byte[]`改为`ReadOnlyMemory`。如果仍需使用数组,请使用`Data.Span.ToArray()`方法获取数据。 + +#### TouchSocket.SerialPort + +-  新增 `StreamAsync`性能异步流模式,可在`SerialPortOption`中直接启用。 [#PR72](https://gitee.com/RRQM_Home/TouchSocket/pulls/72) + +#### TouchSocketPro.PlcBridges + +-  新增 新组件发布。 + +*** + +## v3.1.7 + +**更新日期:** 2025.6.8 + +**更新描述:** + +兼容性修复升级。 + +**更新详情:** + +#### TouchSocket.Core + +-  修复 FileLogger在Windows服务发布时,保存路径错误的bug。 + +#### TouchSocket.Sockets + +-  修复 ClientFactory的MinCount失效的bug。 + +#### TouchSocket.Dmtp + +-  新增 FileResourceInfo类新增Create方法和Save的其他重载。更方便的支持断点续传方式。 +-  修复 FileSectionResult类释放逻辑,减少异常的发生。 + +*** + + +## v3.1.6 + +**更新日期:** 2025.6.2 + +**更新描述:** + +兼容性修复升级。 + +**更新详情:** + +#### TouchSocket.SerialPort + +-  修复 串口组件数据接收回调事件异常。[#ICB8IN](https://gitee.com/RRQM_Home/TouchSocket/issues/ICB8IN) + +*** + + +## v3.1.5 + +**更新日期:** 2025.5.24 + +**更新描述:** + +兼容性修复升级。 + +**更新详情:** + +#### TouchSocket.Core + +-  优化 源生成器提示。 + +*** + + +## v3.1.3(4) + +**更新日期:** 2025.5.18 + +**更新描述:** + +兼容性修复升级。 + +**更新详情:** + +#### TouchSocket.Core + +-  优化 `Method`相关操作,具体请看[新文档](/docs/dynamicmethod)。 + +#### TouchSocket.Dmtp + +-  调整 `Dmtp Channel`的`HoldOnAsync`、`CompleteAsync`、`CancelAsync`等方法返回值改为`Result`,**不再**抛出异常。 + +*** + +## v3.1.2 + +**更新日期:** 2025.5.11 + +**更新描述:** + +兼容性修复升级。 + +**更新详情:** + +#### All + +-  优化 全系语法分析器和源生成器,使用增量源生成器,减少编译时间。 + +#### TouchSocket.Core + +-  优化 `Logger`在添加到容器时,支持直接设置日志等级。 +-  修复 `ByteBlock`、`ValueByteBlock`在请求的`length`为0时,会抛出异常。 [#69](https://gitee.com/RRQM_Home/TouchSocket/pulls/69) +-  修复 `WaitHandlePool在waitData`为`null`无法`Destroy`释放问题。[#IC64WX](https://gitee.com/RRQM_Home/TouchSocket/issues/IC64WX) +-  移除 弃用`ManualContainer`。 + +#### TouchSocket.Sockets + +-  优化 发送字符串的效率,极大减少GC的产生。 +-  优化 `UseCheckClear`的使用体验,使用更为贴切的方法名称。 +-  修复 `CheckClearPlugin`在应用到`Client`,第2次检验会失效的`bug`。[#IC5J1I](https://gitee.com/RRQM_Home/TouchSocket/issues/IC5J1I) + +#### TouchSocket.Http + +-  新增 `HttpClientBase`请求时,新增直接添加`Host`的逻辑。[#IC6OU2](https://gitee.com/RRQM_Home/TouchSocket/issues/IC6OU2) +-  优化 `WebSocket`在断开时,会判断是否带有主动断开消息,如果带有,则投递主动断开的消息。 +-  优化 `SetStatus`的使用体验,在默认参数(成功)时,则使用`SetStatusWithSuccess`代替。 +-  调整 `WebSocket`的`Ping`与`Pong`使用`Result`进行返回。不再抛出异常。 +-  修复 `Http`在处理请求时,阻塞接收执行上下文的bug,这会导致如果收到`http`请求,且需要较长时间执行时,如果连接方已断开,则断开消息不会立即执行的bug。也会间接导致`WebApi`的`CancellationToken`失效。 + + +#### TouchSocket.NamedPipe + +-  新增 `UseNamedPipeSessionCheckClear`的插件扩展方法。 + +#### TouchSocket.Rpc + +-  新增 `ISingletonRpcServer`的接口。 + +#### TouchSocket.SerialPort + +-  新增 `UseSerialPortSessionCheckClear`的插件扩展方法。 + +#### TouchSocket.WebApi + +-  修复 `WebApi`调用上下文中`CancellationToken`不起作用的bug。 + +*** + +## v3.1.0(1) + +**更新日期:** 2025.4.29 + +**更新描述:** + +此次升级可能会有部分代码不兼容项,请在升级前做好备份,并且详细阅读[3.1升级指南](https://gitee.com/RRQM_Home/TouchSocket/issues/IC1K3I)。 + +此次升级所有组件在运行时**完全兼容3.0**,所以无论客户端还是服务端,都可以进行差异化更新。 + +**更新详情:** + +#### All + +-  调整 `DateTime`调整为`DateTimeOffset`。 +-  调整 `Task.Run`全部使用`EasyTask.Run`再次封装运行。 +-  优化 所有组件在日志记录时,传入可触发源,可更好的进行日志记录。 + +#### TouchSocket.Core + +-  调整 `AsyncBoundedQueue`类调整为`AsyncQueue`。 +-  移除 `BytePool`类不再使用,目前使用`ArrayPool`代替。 +-  调整 `ByteBlock`类不再提供默认参数构造函数,目前强制传入申请长度。 +-  调整 `ResultCode`枚举项`Fail`调整为`Failure`。 +-  调整 `FastSerializerContext`默认不再支持`DataTable`和`DataSet`。如有需要,请自行添加`DataTableFastBinaryConverter`和`DataSetFastBinaryConverter`。 +-  优化 `PeriodPackageAdapter`接收性能。 +-  优化 `BlockSegment`性能。 +-  优化 `WaitHandlePool`性能。 +-  修复 `PeriodPackageAdapter`接收不判断最大包设定的bug。 +-  新增 若干扩展方法。 + + +#### TouchSocket.Sockets + +-  调整 `StopAsync`、`CloseAsync`等方法,在调用时不再抛出异常,而是返回`Task`。 +-  调整 `IClient`的接口,不再继承自`IDependencyObject`。如有需求请使用`IDependencyClient`。 +-  调整 `IServerStopedPlugin`插件,改名为`IServerStoppedPlugin`。 +-  优化 `Tcp`、`Udp`等组件的释放逻辑 + +#### TouchSocket.Dmtp + +-  优化 添加`IActor`的方式。 + +#### TouchSocket.Hosting + +-  优化 日志记录信息。 + +#### TouchSocket.Http + +-  优化 性能大幅提高。 +-  新增 `HttpStatusCode`静态类,支持常用的状态码。 +-  调整 WebSocket的`SwitchProtocolToWebSocketAsync`方法返回值由`bool`改为`Result`。 +-  调整 `InitHeaders`、`SetStatus`等方法中设置`Header`的方法,由`Add`调整为`TryAdd`。 +-  调整 默认情况下,服务在响应Header中,设置`Server`时,不再显示具体版本。[#IC25RJ](https://gitee.com/RRQM_Home/TouchSocket/issues/IC25RJ) +-  修复 HttpClient在断开连接时,不触发`OnTcpClosed`的bug。 + +#### TouchSocket.WebApi.Swagger + +-  新增 在Swagger显示时,支持参数属性。[PR68](https://gitee.com/RRQM_Home/TouchSocket/pulls/68) + +#### TouchSocket.Rpc + +-  调整 `SingletonRpcServer`名称改为`SingletonRpcServer`。 +-  优化 `QueueRpcDispatcher`性能。 + + +#### TouchSocket.Mqtt + +-  新增 功能首发。 + +*** + diff --git a/handbook/src/theme/Footer/Logo/styles.module.css b/handbook/src/theme/Footer/Logo/styles.module.css index faf0e60f3..86897ee41 100644 --- a/handbook/src/theme/Footer/Logo/styles.module.css +++ b/handbook/src/theme/Footer/Logo/styles.module.css @@ -1,9 +1,7 @@ .footerLogoLink { opacity: 0.5; - transition: opacity var(--ifm-transition-fast) - var(--ifm-transition-timing-default); } .footerLogoLink:hover { opacity: 1; -} +} \ No newline at end of file diff --git a/handbook/src/theme/Footer/index.tsx b/handbook/src/theme/Footer/index.tsx index 66869bce7..fe7b40db0 100644 --- a/handbook/src/theme/Footer/index.tsx +++ b/handbook/src/theme/Footer/index.tsx @@ -24,7 +24,6 @@ function Footer(): JSX.Element | null copyright={copyright && } /> - ); diff --git a/handbook/src/theme/Root.tsx b/handbook/src/theme/Root.tsx new file mode 100644 index 000000000..0f83f41f0 --- /dev/null +++ b/handbook/src/theme/Root.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import VotingModal from '../components/VotingModal'; + +// 包装原始的 Root 组件并添加我们的弹窗 +export default function Root({ children }) { + return ( + <> + {children} + + + ); +} \ No newline at end of file diff --git a/handbook/tsconfig.json b/handbook/tsconfig.json index 314eab8a4..7ab7de448 100644 --- a/handbook/tsconfig.json +++ b/handbook/tsconfig.json @@ -2,6 +2,7 @@ // This file is not used in compilation. It is here just for a nice editor experience. "extends": "@docusaurus/tsconfig", "compilerOptions": { - "baseUrl": "." + "baseUrl": ".", + "ignoreDeprecations": "6.0" } -} +} \ No newline at end of file diff --git a/handbook/versioned_docs/version-1.3.9/createtouchrpcservice.mdx b/handbook/versioned_docs/version-1.3.9/createtouchrpcservice.mdx index 1ef25e9e4..602548a28 100644 --- a/handbook/versioned_docs/version-1.3.9/createtouchrpcservice.mdx +++ b/handbook/versioned_docs/version-1.3.9/createtouchrpcservice.mdx @@ -65,7 +65,7 @@ TouchRpc服务器的架构与其所属的基础协议架构一致,例如,在 ### 5.1 基于Tcp协议 -这是基于Tcp协议TouchRpc。在可配置TouchRpc的基础之上,还可以配置与[TcpService可配置项](./createtcpservice.mdx#可配置项)相关的配置。 +这是基于Tcp协议TouchRpc。在可配置TouchRpc的基础之上,还可以配置与[TcpService可配置项](./createtcpservice.mdx)相关的配置。 ```csharp var service = new TcpTouchRpcService(); diff --git a/handbook/versioned_docs/version-1.3.9/enterprise.mdx b/handbook/versioned_docs/version-1.3.9/enterprise.mdx index 1dac9b007..dd8d77f3f 100644 --- a/handbook/versioned_docs/version-1.3.9/enterprise.mdx +++ b/handbook/versioned_docs/version-1.3.9/enterprise.mdx @@ -13,12 +13,12 @@ TouchSocketPro是TouchSocket系的加强版本。其基础功能完全包含Touc ## 二、TouchSocket与TouchSocketPro ### 2.1 Tcp组件 -- [轮询式断线重连](./reconnection.mdx#三使用pollingkeepalive插件-Pro) Pro +- [轮询式断线重连](./reconnection.mdx) Pro - [TLV适配器](./tlvdatahandlingadapter.mdx) Pro - 其余功能 ### 2.2 NAT组件 -- [转发客户端重连](./natservice.mdx#四转发断线重连-Pro) Pro +- [转发客户端重连](./natservice.mdx) Pro - 其余功能 ### 2.3 UDP组件 @@ -41,7 +41,6 @@ TouchSocketPro是TouchSocket系的加强版本。其基础功能完全包含Touc - [多线程文件传输](./multithreadingfiletransfer.mdx) Pro - [小文件传输](./smallfiletransfer.mdx) - 文件传输限速 Pro -- [EventBus功能](https://www.yuque.com/rrqm/touchsocket/ipt4zr) Pro - Redis ### 2.8 Http组件 @@ -90,7 +89,7 @@ TouchSocketPro是TouchSocket系的加强版本。其基础功能完全包含Touc ### 4.1 个人独立授权 -授权归属于购买者个人所有,规定购买者可将所购产品只能应用于所属个人的任何软件(产品)上,可以以此盈利,但必须遵守[个人使用协议](./description.mdx#qLp3q)。 +授权归属于购买者个人所有,规定购买者可将所购产品只能应用于所属个人的任何软件(产品)上,可以以此盈利,但必须遵守[个人使用协议](./description.mdx)。 ### 4.2 个人企业授权 diff --git a/handbook/versioned_docs/version-2.0/enterprise.mdx b/handbook/versioned_docs/version-2.0/enterprise.mdx index 07183bad3..24602f2d3 100644 --- a/handbook/versioned_docs/version-2.0/enterprise.mdx +++ b/handbook/versioned_docs/version-2.0/enterprise.mdx @@ -29,13 +29,13 @@ import Tag from "@site/src/components/Tag.js"; ### 2.1 Tcp组件 -- [轮询式断线重连](./reconnection.mdx#三使用pollingkeepalive插件-Pro) Pro +- [轮询式断线重连](./reconnection.mdx) Pro - [TLV适配器](./tlvdatahandlingadapter.mdx) Pro - 其余功能 ### 2.2 NAT组件 -- [转发客户端重连](./natservice.mdx#四转发断线重连-Pro) Pro +- [转发客户端重连](./natservice.mdx) Pro - 其余功能 ### 2.3 UDP组件 @@ -114,7 +114,7 @@ import Tag from "@site/src/components/Tag.js"; ### 4.1 个人独立授权 -授权归属于购买者个人所有,规定购买者可将所购产品只能应用于所属个人的任何软件(产品)上,可以以此盈利,但必须遵守[个人使用协议](./description.mdx#qLp3q)。 +授权归属于购买者个人所有,规定购买者可将所购产品只能应用于所属个人的任何软件(产品)上,可以以此盈利,但必须遵守[个人使用协议](./description.mdx)。 ### 4.2 个人企业授权 diff --git a/handbook/versioned_docs/version-2.0/upgrade.mdx b/handbook/versioned_docs/version-2.0/upgrade.mdx deleted file mode 100644 index caf4a4bb5..000000000 --- a/handbook/versioned_docs/version-2.0/upgrade.mdx +++ /dev/null @@ -1,667 +0,0 @@ ---- -id: upgrade -title: 历史更新 ---- - -import useBaseUrl from "@docusaurus/useBaseUrl"; -import Tag from "@site/src/components/Tag.js"; -import Highlight from '@site/src/components/Highlight.js'; - -:::tip `TouchSocket` 框架升级/发版规则 - -**升级前重点关注可能造成【破坏性】的标签类型**:修复调整移除升级 - -版本号规则:`主版本号.次版本号.修订版本号` - -- 只要【确认】为框架 `bug`,则当天修复,当天发版,修订版本号 `加 1`。 -- 如果 `.csproj` 文件有变更,则当天发版,修订版本号 `加 1`。 -- 其余情况,每年发布一个 `主版本`。 - -::: - -## v2.0.18 - -**更新日期:** 2024.9.10 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 当Cancel延迟到Reset之后时,新获取的waitData会出现Status为Cancel异常bug [#IAQ2AI](https://gitee.com/RRQM_Home/TouchSocket/issues/IAQ2AI)。 - - -## v2.0.17 - -**更新日期:** 2024.8.30 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 使用AspNetCore容器时,默认没有注册ILog的bug。 - -## v2.0.16 - -**更新日期:** 2024.8.19 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 tcp在winform中会使用主线程接收的bug,导致各种同步接收异常。 - - -## v2.0.15 - -**更新日期:** 2024.8.9 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 当客户端断开连接时,http响应会出现object is null的bug提示。 - - -## v2.0.13(14) - -**更新日期:** 2024.7.24 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 重连插件在长时间运行时,会失效的bug。 --  修复 ssl加密下,连接响应时间过长bug[#IAET6V](https://gitee.com/RRQM_Home/TouchSocket/issues/IAET6V) 。 - -## v2.0.12 - -**更新日期:** 2024.7.17 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 socket在接收连接时,异常无法拦截的bug[#IADIGX](https://gitee.com/RRQM_Home/TouchSocket/issues/IADIGX)。 --  调整 日志记录在执行时,先判断日志组件的可用性。 - -## v2.0.11 - -**更新日期:** 2024.7.12 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 Metadata在Add时不会覆盖原key的bug。 - - - -## v2.0.10 - -**更新日期:** 2024.5.31 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 WebSocket快捷命令行bug。 [#I9TG3V](https://gitee.com/RRQM_Home/TouchSocket/issues/I9TG3V)。 - - -## v2.0.9 - -**更新日期:** 2024.5.29 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 UdpPackage适配器在工作时调时导致的bug。 [#I9SYTR](https://gitee.com/RRQM_Home/TouchSocket/issues/I9SYTR)。 --  修复 Http在GetBoundary时bug。 [#I9PXWT](https://gitee.com/RRQM_Home/TouchSocket/issues/I9PXWT)。 - - - -## v2.0.(7)8 - -**更新日期:** 2024.5.17 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 udp首次建立连接无法接收数据的bug。 [#I9PV7C](https://gitee.com/RRQM_Home/TouchSocket/issues/I9PV7C)。 - -## v2.0.6 - -**更新日期:** 2024.5.12 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 FlowGate的waitTime小于0时bug。 - - -## v2.0.5 - -**更新日期:** 2024.5.2 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 wsclient Received事件与插件触发bug。[#I9L9WI](https://gitee.com/RRQM_Home/TouchSocket/issues/I9L9WI)。 --  修复 ws命令行执行bug。 - -## v2.0.4 - -**更新日期:** 2024.4.30 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 linux平台下,Socket吞吐量大幅降低。[#I9KURV](https://gitee.com/RRQM_Home/TouchSocket/issues/I9KURV)。 --  修复 WebSocket在进行连接时,Host的Header写法错误,没有包含端口。[#I9KUVI](https://gitee.com/RRQM_Home/TouchSocket/issues/I9KUVI)。 - - -## v2.0.3 - -**更新日期:** 2024.4.14 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 Tcp在接收时,内存池释放存在延迟,可能导致内存池快速扩张,浪费内存。[#I9FVAA](https://gitee.com/RRQM_Home/TouchSocket/issues/I9FVAA)。 --  修复 在使用Host模型时,注入瞬态的TcpClient,在第一次获取实例是正常的,第二次就失败。[#I9G3SV](https://gitee.com/RRQM_Home/TouchSocket/issues/I9G3SV)。 --  修复 WebSocketClient连接其他服务器时显示 “操作已被取消”。[#I9GG05](https://gitee.com/RRQM_Home/TouchSocket/issues/I9GG05)。 --  新增 ConcurrentList新增实现IReadOnlyList接口。 - - -## v2.0.2 - -**更新日期:** 2024.4.1 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 TouchSocketBitConverter中的ToBooleans方法存在Bug[#I9C1UY](https://gitee.com/RRQM_Home/TouchSocket/issues/I9C1UY)。 --  修复 SystemExtensions中的GetBit和SetBit方法[#I9C1WM](https://gitee.com/RRQM_Home/TouchSocket/issues/I9C1WM)。 --  修复 HttpService多次响应下载文件时,不会响应的bug。 - - - -## v2.0.1 - -**更新日期:** 2024.3.16 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 HttpClient,无法通过HttpResponse.GetBody()获取响应内容[#I989SI](https://gitee.com/RRQM_Home/TouchSocket/issues/I989SI)。 --  修复 SetNoDelay 异常[#I979B0](https://gitee.com/RRQM_Home/TouchSocket/issues/I979B0)。 --  修复 调用Dmtp服务的大数据传输时,如果循环调用会出现收到的数据和发送的数据不一致。实际上该问题是由`ByteBlock`写入扩容bug而导致的[#I96FNF](https://gitee.com/RRQM_Home/TouchSocket/issues/I96FNF)。 - - -## v2.0.0 - -**更新日期:** 2024.3.9 - -**更新描述:** - -此版本是大版本更新。可能会产生很多不兼容部分,所以升级之前请做好备份,并且请详细阅读下列更新内容。 - -**升级指南:** - -- 升级时请先升级至`2.0.0-beta.190`,再升级至`2.0.0-rc.2`版本,因为正式版对于[Obsolete]特性的成员直接删除了,所以为友好升级,请先升级至此版本。 -- 由2.0.0-beta.200至2.0.0-beta.220 [#I8DE1D](https://gitee.com/RRQM_Home/TouchSocket/issues/I8DE1D) -- 由2.0.0-beta.220至2.0.0-beta.230 [#I8LAX4](https://gitee.com/RRQM_Home/TouchSocket/issues/I8LAX4) - -**更新详情:** - -#### 【TouchSokcet.Core】 - --  优化 FileLogger支持指定不同目录。 --  调整 所有自定义插件必须在自身内,主动调用e.InvokeNext()时,才会调用下一个插件。不然会中断插件传递。同时e.Handled功能依然有效。 --  调整 Log项。LogType调整为LogLevel,并且不需要位运算。直接按日志等级输出。 --  调整 修改IPluginsManager名称为IPluginsManager。 --  移除 DependencyProperty中,移除对类型的定义。 --  移除 所有组件的基础插件,强制用户插件必须继承PluginBase,然后实现需要的接口。 --  移除 BytePool在创建ByteBlock时,移除EqualSize的设定,因为这会影响内存池的效率。 --  调整 修改所有委托为异步Task。 --  调整 修改所有Setup返回值为void。 --  修复 Metadata在0个成员长度时,会被反序列化成null的bug。 --  修复 PluginsManager在注册具有继承的插件时,会无法识别的bug。 - -#### 【TouchSokcet.Sokcets】 - --  优化 IPHost支持从int、string直接隐式转换。 --  调整 TouchSocket所有“ID”属性,改名为“Id”。 --  调整 TouchSocket所有插件的执行顺序,移动至内部重写方法之后。 --  调整 TouchSocket所有`ResetID`改名为`ResetId`。 --  调整 UseCheckClear项,SetDuration调整名称为SetTick。 --  调整 UseCheckClear项,不仅可以适用服务器,客户端也适用。 --  调整 Config配置中,SetDataHandlingAdapter调整为SetTcpDataHandlingAdapter。 --  调整 适配器项,CustomDataHandlingAdapter中的Filter方法中,byteBlock参数使用in修饰。 --  调整 适配器项,DataHandlingAdapter改名为TcpDataHandlingAdapter。 --  调整 适配器项,DataAdapterTester改名为TcpDataAdapterTester。 --  调整 Config项,所有适配器的相关配置,使用SetAdapterOption配置。 --  移除 UsePlugin的显式配置,当调用ConfigurePlugins时,会自动启用。 - -#### 【TouchSokcet.Http】 - --  调整 WSCommandLinePlugin改名为WebSocketCommandLinePlugin。 --  新增 WebSocket添加[同步非阻塞Read](./websocketservice.mdx#52-websocket显式readasync)。 --  新增 WebSocket的WSDataFrame新增IsPing、IsPong、IsText、IsBinary、IsClose等属性。 --  新增 静态网页插件新增NavigateAction与ResponseAction等委托,可以在静态页面请求之前重定向,或者请求返回时设置header等。 - -#### 【TouchSokcet.Rpc】 - --  移除 整体功能迁移至TouchSokcet(Pro).Dmtp。 --  调整 [RpcActionFilter](./rpcactionfilter.mdx#33-规则)执行策略和顺序 --  调整 修改ConfigureRpcStore为AddRpcStore。 --  新增 [RealityProxy](./rpcgenerateproxy.mdx)透明代理方式。 --  新增 [DispatchProxy](./rpcgenerateproxy.mdx)添加OnBefore和OnAfter的AOP调用。 - -#### 【TouchSokcet(Pro).Dmtp】 - --  调整 原TouchRpc全系改名为Dmtp。例如:原TcpTouchRpcClient改名为TcpDmtpClient。 --  调整 原TouchRpc中InvokeOption,改名为DmtpInvokeOption。InvokeOption依然有效,但是在调用DmtpRpc时,则无法指定序列化方式。所以可能需要使用DmtpInvokeOption。 --  调整 原TouchRpc中Invoke直接调用的方式,改为InvokeT。 --  调整 Dmtp相关配置,使用SetDmtpOption配置。 --  移除 **暂时**移除EventBus功能,后续可能考虑添加。 --  新增 ws协议的TouchRpc服务端,增加HttpContext上下文获取。 --  新增 文件传输项,开放增加SetMaxSpeed功能。 --  新增 DmtpRpc。 --  新增 DmtpRpc组件在调用时,可以通过DmtpInvokeOption传入Metadata元数据。 --  修复 DmtpRpc在调用无ref,out的函数时,参数会为null的bug。 - -#### 【TouchSokcet.JsonRpc】 - --  修复 JsonRpc使用内联数组调用[#I79OFZ](https://gitee.com/RRQM_Home/TouchSocket/issues/I79OFZ)。 --  调整 Rpc服务使用调用上下文时不需要再添加`IncludeCallContext`。 - -#### 【TouchSokcet.WebApi】 - --  新增 WebApi新增[Swagger页面](./swagger.mdx)。 --  调整 Rpc服务使用调用上下文时不需要再添加`IncludeCallContext`。 - -#### 【TouchSokcet.XmlRpc】 - --  调整 Rpc服务使用调用上下文时不需要再添加`IncludeCallContext`。 - -#### 【TouchSokcet(Pro).Hosting】 - --  新增 新发布Hosting的包,用于构建更加强壮的运行程序。 - -#### 【TouchSokcet.SerialPorts】 - --  新增 新发布串口的包。 - -#### 【TouchSokcet(Pro).Modbus】 - --  新增 新发布Modbus的包,支持Tcp、Udp、Rtu、RtuOverTcp、RtuOverUdp协议的主站(Poll)和从站(Slave)。 ---- - - -## v1.3 - -更新日期:2023.3.1 - -更新描述:兼容性更新。 - - -  优化 整体增加异步方法。 - -  优化 Rpc源代码生成策略,支持接口实例并存。 - -  修复 TcpClient在UseReconnect插件时,Disconnect事件不触发bug。 - -  新增 ws协议的TouchRpc服务端,增加HttpContext上下文获取。 ---- - -## v1.2 - -更新日期:2023.2.15 - -更新描述:兼容性更新。 - - -  优化 TouchRpc支持命名元组。 - -  优化 Rpc源代码生成策略。 - -  修复 TouchRpc在Websocket协议下,启动,连接异常bug。 - -  修复 TouchRpc在调用WaitSend下失败的bug。 - -  修复 TouchRpc在Handshaked时,调用Rpc超时bug。 - -  修复 序列化、反射在unity中使用il2cpp编译的bug。 - -  修复 反序列化在初次加载时会失败的bug。 - -  修复 BytePool没有公共构造函数的bug。 - -  新增 ws协议的TouchRpc服务端,增加HttpContext上下文获取。 - -  新增 ByteBlock对于int,long等数据,写入和读取的时候支持大小端指定。 - -  新增 IServicePlugin插件,用于显示通知服务器的启动状态。 - -  新增 Rpc支持接口特性标记。 - -  调整 将BytePool由静态调整为实例,且由其Default实例作为默认。 ---- - -## v1.1 - -更新日期:2023.1.13 - -更新描述:小版本升级,可能会有不兼容。请按下列提示修改。 - - -  优化 TouchRpc系文件传输时,文件夹不存在的提示。 - -  优化 WaitingClient,当客户端断开连接时,可选是否抛出异常。 - -  优化 Fast序列化时。可选序列化只读属性。 - -  修复 多个不稳定Bug。 - -  新增 Tcp客户端新增Disconnecting事件。在主动Close时生效。 - -  调整 多个事件类名称修改,请按照提示修改即可。 - -  移除 多个无用方法参数。 ---- - -## v1.0.0 - -更新日期:2023.1.1 - -更新描述:大版本升级,请详细阅读下列更新日志。 - - -  升级 将最高版本升级为NET7。 - -  优化 Tcp系异步发送效率。 - -  优化 TouchRpc系Channel的稳健性。 - -  修复 多个不稳定Bug。 - -  新增 ValueByteBlock,在简单代码块里面能有效减少创建的类。 - -  新增 MemoryCache类,其功能类似微软官方。但是支持全部泛型。 - -  新增 [IPackage系](https://www.yuque.com/rrqm/touchsocket/ag9tyar9mmhsme0m)。该系列能以超高效率的进行二进制序列化。 - -  新增 SingleTimer类,不可重入的Timer。 - -  新增 Jsonrpc支持自定义适配器解析(EE) - -  新增 严重TouchRpc系OnRouting通知,所有的客户端之间的通信,都必须经过OnRouting的筛查。 - -  新增 TouchRpc系小文件传输,在文件小于1Mb时,其传输效率是常规传输的10倍以上。 - -  新增 TouchRpc系超大文件多链路传输,支持多个客户端协同传输同一个文件,这在互联网环境中,效率比常规传输提高类3-5倍。 - -  新增 TouchRpc系Redis组件,能实现双端共同存储。 - -  调整 严重精简所有命名空间,删除所有三级命名空间。例如:TouchSocket.Core.ByteManager精简为TouchSocket.Core。 - -  调整 严重删除Newtonsoft.Json的源代码嵌入。全局的Json会根据环境动态调整,详情见[Json工具](https://www.yuque.com/rrqm/touchsocket/emqy43#PfVh1) - -  调整 严重框架默认日志由ConsoleLogger,替换为EmptyLogger(不输出任何东西)。 - -  调整 严重Tcp全系,在连接时,ID的初始值使用long类型从0递增。 - -  调整 严重Tcp服务器,将定时清理无数据交互的选项替换为UseCheckClear插件。并且默认没有启用,需要手动加入。 - -  调整 Tcp系适配器,取消部分参数。 - -  调整 DataLock改名为DataSecurity。 - -  调整 EasyAction改名EasyTask。 - -  调整 IMessage改名IMessageObject。 - -  调整 TokenInstance改名MessageInstance。 - -  调整 TouchRpc系,精简常规文件传输操作。 - -  调整 严重TouchRpc系,所有插件通知参数,默认都设为不允许操作,需要手动设置e.IsPermitOperation=true。 - -  移除 Newtonsoft.Json的源代码嵌入。全局的Json会根据环境动态调整,详情见[Json工具](https://www.yuque.com/rrqm/touchsocket/emqy43#PfVh1)。 - -*** 更新示例指南 *** - -(1)适配器参数报错:直接删除isAsync参数,以及isAsync为**True**的所有逻辑。 -![image.png](@site/static/img/docs/upgrade-1.png) -(2)依赖属性的声明报错:增加泛型约束即可,详情查看[依赖属性](https://www.yuque.com/rrqm/touchsocket/ubk57o#jyzSl) -![image.png](@site/static/img/docs/upgrade-2.png) -(3)服务端定时清理警告:在配置插件中使用UseCheckClear,并且进行相关配置。 -![image.png](@site/static/img/docs/upgrade-3.png) -![image.png](@site/static/img/docs/upgrade-4.png) - ---- - - -## 版本号: 0.7.0 -更新日期:2022.9.21 -更新描述:兼容性更新,增强型更新。**RPC内容需要客户端与服务器同步更新**。 -更新详情: - -优化 -1. Fast二进制序列化,支持自定义序列化。 -2. TouchRpc全系,在文件传输等大型IO时,由于心跳失败而断开连接。 -3. 优化AspNetCore的IContainer。 -4. TcpCommandLinePlugin与WSCommandLinePlugin支持获取客户端参数。 - -新增 -1. 插件实例会以单例注入容器。 -2. 所有适配器支持[缓存超时](https://www.yuque.com/rrqm/touchsocket/83526e6320dfc85fef317d850aa51e92#Z0S0g)设定。 -3. 修改所有事件为委托。 -4. 开放[AspnetCore](https://www.yuque.com/rrqm/touchsocket/55e5bbf58745fa639dba511c7bcd54d1#WqOmh)创建Tcp,Http等服务器的配置。 -5. IClient增加发送、接收的最后时间记录。 -6. Http支持多文件上传(目前仅支持小文件,具体大小以实际运行内存为准,实测100Mb没问题)。 -7. Websocket插件默认会处理Close报文。且插件支持Close。 -8. Rpc支持模板代码重写。 -9. TouchRpc支持元组。 -10. JsonRpc支持Websocket协议。 - -修改 -1. IScopedContainer修改为IContainerProvider - -修复 -1. BytePool回收内存时不判断大小的bug。 - -删除 -1. 无。 - - ---- - -## 版本号: 0.6.0 -更新日期:2022.9.10 -更新描述:兼容性更新,增强型更新。**专为Unity 3D适配**。 -更新详情: - -优化 -1. Gzip的压缩效率。 -2. 发送效率。 - -新增 -1. IDataCompressor数据传输压缩接口。 -2. [RemoteStream](https://www.yuque.com/rrqm/touchsocket/ukq0mu)支持数据读写压缩。 -3. WaitResultPackageBase类,专属非序列化的数据格式化。 -4. DelaySender[延迟缓存发送](https://www.yuque.com/rrqm/touchsocket/1f21a56ee75f896a5b5b38b37b071881#RL0kx)。 - -修改 -1. 无 - -修复 -1. Rpc注册服务为单例时,实际上是瞬时服务的bug。 - -删除 -1. 独立线程发送。 - ---- - -## 版本号: 0.5.0 -更新日期:2022.9.1 -更新描述:兼容性更新,增强型更新。 -更新详情: - -优化 -1. 全局资源的获取逻辑。 - -新增 -1. Container增加卸载注册功能。 -2. FilePool新增FileStorageStream的获取。 -3. http客户端(及websocket)支持代理和验证代理。 -4. TouchRpc全系新增[远程文件操作](https://www.yuque.com/rrqm/touchsocket/pearz0) -5. TouchRpc(除udp)新增[远程流访问](https://www.yuque.com/rrqm/touchsocket/ukq0mu) - -修改 -1. 无 - -修复 -1. 修复Http客户端请求重复Header时的bug。 - -删除 -1. TouchRpc全系的事件操作,推荐直接插件的方式,或者使用TouchRpcActionPlugin然后添加委托。 - - -更新示例 -TouchRpc的相关事件均已使用插件代替。所以请使用插件实现操作。如果需要事件等功能的话,可以用TouchRpcActionPlugin的插件实现。例如: -```csharp showLineNumbers -.UsePlugin() -.ConfigurePlugins(a=> -{ - a.Add>()//此处的逻辑可用插件替代完成。 - .SetFileTransfering((client, e) => - { - //有可能是上传,也有可能是下载 - client.Logger.Info($"服务器请求传输文件,ID={client.ID},请求类型={e.TransferType},文件名={e.FileInfo.FileName}"); - }) - .SetFileTransfered((client, e) => - { - //传输结束,但是不一定成功,需要从e.Result判断状态。 - client.Logger.Info($"服务器传输文件结束,ID={client.ID},请求类型={e.TransferType},文件名={e.FileInfo.FileName},请求状态={e.Result}"); - }); -}) -``` - ---- - -## 版本号: 0.4.5 -更新日期:2022.8.25 -更新描述:兼容性更新,增强型更新。 -更新详情: - -优化 -1. FileLogger的写入逻辑,大大地提升了写入效率。 - -新增 -1. [Pipeline适配器](https://www.yuque.com/rrqm/touchsocket/ofnliu) -2. [TLV适配器](https://www.yuque.com/rrqm/touchsocket/wug4bv) -3. WaitingClient支持按条件等待返回。 -4. 日志系统可以筛选日志的输出类型 -5. Rpc系统,可以使用单例、瞬时生命周期的服务。 -6. Rpc系统,可定义持久化模型。 -7. Rpc在使用瞬时生命周期的服务时,可以直接获取调用上下文。 -8. XmlRpc增加调用上下文。 - -修改 -1. 日志系统。 -2. Rpc的调用上下文均采用接口,例如:JsonRpc改为IJsonRpcCallContext,WebApi为IWebApiCallContext。 -3. IRpcActionFilter的参数列表。 - -修复 -1. UdpSession资源不释放的Bug。 - -删除 -1. 冗余元素。 - - ---- - -## 版本号: 0.3.5 -更新日期:2022.8.12 -更新描述:兼容性更新,增强型更新。 -更新详情: - -优化 -1. 各类客户端发送逻辑。 -2. Method类的调用逻辑。 - -新增 -1. 适配器可以设定发送IRequestInfo对象。 -2. 插件新增UseWebSocket的快捷方式。 -3. ReconnectionPlugin插件可以获得重连次数的重载设置。 -4. 【Pro】TcpService的服务注入。 -5. 【Pro】HttpService的服务注入。 -6. 【Pro】IOC容器的共享使用。 - -修改 -1. 各类发送逻辑,以最小化发送方法为基础,其余方法改为扩展方法。 -2. 相关接口的实现。 -3. 由网友[修改GetInfo](https://gitee.com/dotnetchina/TouchSocket/pulls/11) - -修复 -1. Container获取泛型失败bug。 -2. BetweenAnd适配器适配器部分bug。 -3. Router标签无法路由的bug。 -4. 修复TouchRpc推送文件状态不正确bug -5. 修复独立线程在断线重连后发送bug。 - -删除 -1. 冗余的发送方法,不影响上版本任何使用。 - - ---- - - -## 版本号: 0.2.4 -更新日期:2022.7.28 -更新描述:兼容性更新。 -更新详情: - -优化 -1. 优化IOC容器。 -2. 优化Metadata的写入方式。 -3. FileLogger,当日志文件达到1Mb时,会再新增文件序号。 - -新增 -1. Mapper类,支持简单类型映射 -2. Tcp服务器、客户端、udp等增加端口复用配置。 -3. 【Pro】轮询式断线重连。 -4. 【Pro】NATService转发客户端重连。 - -修改 -1. RRQM二进制序列化,改名为Fast。 -2. TouchRpcClient连接时的Metadata,改为由Config配置注入。 -3. FilePool,取消延迟释放机制。 - -修复 -1. 修复WebSocket连接问题 - -删除 -1. 客户端直接调用的短线重连方式。仅保留在Config注入的功能。 - ---- - -## 版本号: 0.1.0 -更新日期:2022.7.16 -更新描述:初始化版本发布。由RRQMSocket迁移而来。 - -迁移指南: -### 1.所有类的命名空间修改,此处如果类型名未修改的话,可由vs智能提示解决。 -### 2.类型名称修改 -| 原类型名称 | 新类型名称 | -| --- | --- | -| RRQMBitConverter | TouchSocketBitConverter | -| RRQMConfig | TouchSocketConfig | -| RRQMConverter | TouchSocketConverter | -| RRQMDependencyObject | DependencyObject | -| MsgEventArgs | MsgEventArgs | -| RRQMEventAgrs | TouchSocketEventArgs | -| IServerProvider | IRpcServer | -| ServerProvider | RpcServer | -| RRQMOverlengthException | OverlengthException | - -### 3.使用逻辑修改 -1)原RRQMConfig设置Logger的方法,改为容器注入: -![image.png](@site/static/img/docs/upgrade-5.png) -断线重连逻辑 -![image.png](@site/static/img/docs/upgrade-6.png) -RpcStore使用变更 -如果是仅有一个Rpc解析器,那么可以直接删除RpcStore的声明,从而使用对应的**解析器实例**,直接注册服务。然后可以通过其属性RpcStore,获取到具体的RpcStore实例。 - -如果是有多个解析器,那么,首先可以使用任意一个解析器的RpcStore属性实例,作为主RpcStore,然后添加其他解析器。当然也可以直接new RpcStore,然后统一管理解析器。其中构造函数中的Container容器,可以直接new Container(),但是更建议使用和解析器相同的容器,这样注入的服务会变得全局可用。 diff --git a/handbook/versioned_docs/version-2.1/bytepool.mdx b/handbook/versioned_docs/version-2.1/bytepool.mdx index b36877b95..11335b983 100644 --- a/handbook/versioned_docs/version-2.1/bytepool.mdx +++ b/handbook/versioned_docs/version-2.1/bytepool.mdx @@ -241,4 +241,4 @@ ByteBlock在设置SetHolding(false)后,不需要再调用Dispose。 ## 六、本文示例Demo - \ No newline at end of file + \ No newline at end of file diff --git a/handbook/versioned_docs/version-2.1/enterprise.mdx b/handbook/versioned_docs/version-2.1/enterprise.mdx index 07183bad3..587fc14e2 100644 --- a/handbook/versioned_docs/version-2.1/enterprise.mdx +++ b/handbook/versioned_docs/version-2.1/enterprise.mdx @@ -29,13 +29,13 @@ import Tag from "@site/src/components/Tag.js"; ### 2.1 Tcp组件 -- [轮询式断线重连](./reconnection.mdx#三使用pollingkeepalive插件-Pro) Pro +- [轮询式断线重连](./reconnection.mdx) Pro - [TLV适配器](./tlvdatahandlingadapter.mdx) Pro - 其余功能 ### 2.2 NAT组件 -- [转发客户端重连](./natservice.mdx#四转发断线重连-Pro) Pro +- [转发客户端重连](./natservice.mdx) Pro - 其余功能 ### 2.3 UDP组件 @@ -63,7 +63,6 @@ import Tag from "@site/src/components/Tag.js"; - [多线程文件传输](./dmtptransferfile.mdx) Pro - [小文件传输](./dmtptransferfile.mdx) - 文件传输限速 Pro -- [EventBus功能](https://www.yuque.com/rrqm/touchsocket/ipt4zr) Pro - Redis ### 2.8 Http组件 @@ -114,7 +113,7 @@ import Tag from "@site/src/components/Tag.js"; ### 4.1 个人独立授权 -授权归属于购买者个人所有,规定购买者可将所购产品只能应用于所属个人的任何软件(产品)上,可以以此盈利,但必须遵守[个人使用协议](./description.mdx#qLp3q)。 +授权归属于购买者个人所有,规定购买者可将所购产品只能应用于所属个人的任何软件(产品)上,可以以此盈利,但必须遵守[个人使用协议](./description.mdx)。 ### 4.2 个人企业授权 diff --git a/handbook/versioned_docs/version-2.1/jsonrpc.mdx b/handbook/versioned_docs/version-2.1/jsonrpc.mdx index 154f6c07d..ee1d1a737 100644 --- a/handbook/versioned_docs/version-2.1/jsonrpc.mdx +++ b/handbook/versioned_docs/version-2.1/jsonrpc.mdx @@ -433,4 +433,4 @@ class MyPluginClass : PluginBase, IWebSocketHandshakedPlugin ## 八、本文示例Demo - + diff --git a/handbook/versioned_docs/version-2.1/upgrade.mdx b/handbook/versioned_docs/version-2.1/upgrade.mdx deleted file mode 100644 index 42da8ac84..000000000 --- a/handbook/versioned_docs/version-2.1/upgrade.mdx +++ /dev/null @@ -1,851 +0,0 @@ ---- -id: upgrade -title: 历史更新 ---- - -import useBaseUrl from "@docusaurus/useBaseUrl"; -import Tag from "@site/src/components/Tag.js"; -import Highlight from '@site/src/components/Highlight.js'; - -:::tip `TouchSocket` 框架升级/发版规则 - -**升级前重点关注可能造成【破坏性】的标签类型**:修复调整移除升级 - -版本号规则:`主版本号.次版本号.修订版本号` - -- 只要【确认】为框架 `bug`,则当天修复,当天发版,修订版本号 `加 1`。 -- 如果 `.csproj` 文件有变更,则当天发版,修订版本号 `加 1`。 -- 其余情况,每年发布一个 `主版本`。 - -::: - -## v2.1.10 - -**更新日期:** 2024.10.25 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  优化 `CheckClearPlugin`插件频繁输出poll日志的不合理设计。 - - -## v2.1.9 - -**更新日期:** 2024.10.14 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 CheckClearPlugin长时间工作时可能失效的bug。 - - - - -## v2.1.8 - -**更新日期:** 2024.10.11 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 TcpService在Stop的时候,有个内部异常打了log。 [#IAWD4N](https://gitee.com/RRQM_Home/TouchSocket/issues/IAWD4N) - - -## v2.1.7 - -**更新日期:** 2024.10.5 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 TcpService在启动时如果异常,则无法再重新启动的bug。 - - -## v2.1.6 - -**更新日期:** 2024.10.1 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  新增 HttpContent机制,能上传超大数据。 --  新增 StreamHttpContent,能上传流数据,例如:文件流。 --  修复 Task内部异常时没有及时try,导致全局捕获时有无用捕获。 --  修复 TcpServiceBase在调用StopAsync时,IServerStopedPlugin插件无法触发的bug。 --  移除 TriggerQueue无用类。 --  优化 HttpRequest,使其能上传超大数据。 - -## v2.1.4(5) - -**更新日期:** 2024.9.23 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  新增 DmtpRpc恢复性新增Xml序列化。 --  修复 使用源生成(IPackage)打包时报错。[#IASTWJ](https://gitee.com/RRQM_Home/TouchSocket/issues/IASTWJ) --  修复 Result泛型类中隐式转换错写成显示转换的bug。 --  修复 DmtpRpc序列化选择器没有预留Json序列化配置的bug。 - - -## v2.1.3 - -**更新日期:** 2024.9.22 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  优化 部分类,方法注释。 --  优化 多数英文字符串资源。 --  修复 Tcp、NamedPipe、SerialPort等组件在主动调用Close时,ClosedEventArgs参数属性Manual为false的bug,导致重连插件偶发性再次连接。[#IASH1A](https://gitee.com/RRQM_Home/TouchSocket/issues/IASH1A) --  修复 `ByteBlock`类部分bug。 --  移除 `DecimalConver`类,该类功能已完全由`TouchSocketBitConverter`代替,属于无用类。 --  调整 受保护方法`ProtectedResetId`方法,名称更改为`ProtectedResetIdAsync`。 - - -## v2.1.2 - -**更新日期:** 2024.9.19 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  优化 部分类,方法注释。 --  修复 TcpClient在释放时,重连插件会无限连接的bug,该bug会导致CPU占用过高。[#IAS9NG](https://gitee.com/RRQM_Home/TouchSocket/issues/IAS9NG) - - -## v2.1.1 - -**更新日期:** 2024.9.18 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  新增 在`MemoryCache`中实现新的`TryGetValue`方法。 --  优化 密封`CacheEntry`类并优化缓存管理逻辑。 --  优化 扩展`PackageExtensions`类,增加泛型方法以提高可读性和可重用性。 --  优化 优化`HttpStaticPagePlugin`构造函数和`StaticPageOptions`类以简化静态页面配置。 --  修复 在`FastBinaryFormatter`中改进序列化逻辑,特别是对于**多维数组**的处理。 [#IARKE1](https://gitee.com/RRQM_Home/TouchSocket/issues/IARKE1) --  修复 在`IPackage`中改进打包、解包逻辑,特别是对于**多维数组**的处理。 - - -## v2.1.0 - -**更新日期:** 2024.9.15 - -**更新描述:** - -大版本升级,有部分不兼容性升级。所以请在升级前做好备份,同时在升级之后,请务必阅读[v2.1升级指南](https://gitee.com/RRQM_Home/TouchSocket/issues/I9QSDK)。 - -**更新亮点:** - -本次更新,主要有以下亮点: - -1. 全系支持Span、ValueTask、Memory、Unsafe等依赖。大幅提升并发性能与低GC能力。 -2. 全系组件,尽量多的提供了异步Api,大幅度提升并发能力。 -3. 重构fast序列化,Package包模式、Http、WebSocket等组件,使之更加易用。 -4. 资源国际化。本次更新会在内部使用中英双语信息提示,这在日志记录,堆栈跟踪等场景更加符合区域化。 -5. 增加完整注释。基本上能达到95%的代码注释率。 - -**更新详情:** - -#### 【TouchSokcet.Core】 - --  新增 TouchSocketBitConverter新增To、UnsafeTo、WriteBytes、UnsafeWriteBytes等可以直接操作Span。 --  新增 Crc类新增Span相关转换。 --  新增 CustomDataHandlingAdapter新增`bool TryParseRequest(ref TByteBlock byteBlock, out TRequest request)`方法,可以同步完成适配器数据解析。 --  新增 SingleStreamDataAdapterTester泛型测试器,可以对TryParseRequest进行完整性测试。 --  新增 IPackage源生成器新增自定义PackageMember特性,用来定义打包的顺序和自定义转换器。 --  新增 SetupConfigObject的SetupAsync。 --  优化 CustomDataHandlingAdapter支持结构体作为泛型类型。 --  优化 Fast序列化支持自定义FastSerializerContext,这可以极大的利用源生成来决定序列化和反序列化。 --  调整 分离MemoryCache的同步和异步接口。 --  调整 ByteBlock取消Stream的继承,如果需要使用Stream,可以使用ByteBlock.AsStream()。 --  调整 Gzip类调整ByteBlock参数为Stream。 --  调整 CustomDataHandlingAdapter解析的数据,均会以ReadonlySpan的形式投递。 --  调整 IPackage接口,使之既可以在ByteBlock工作,也可以在ValueByteBlock工作。 --  调整 PluginManager使用接口作为唯一键,规定一个接口中有且仅有一个方法。 --  调整 ByteBlock、ValueByteBlock均继承IByteBlock接口规范。 --  移除 ByteBlock、ValueByteBlock移除Buffer属性,如果想获取有效数据,可以通过Memory、Span等获取,如果想获取容量可以使用TotalMemory属性。 - -#### 【TouchSokcet.Sockets】 - --  调整 Tcp、Udp组件,默认情况下适配器将为null,并且可以正常工作。 --  调整 SocketClient、ISocketClient等服务器辅助类,改名为TcpSessionClient和ITcpSessionClient。 - - -::: - -## v2.0.18 - -**更新日期:** 2024.9.10 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 当Cancel延迟到Reset之后时,新获取的waitData会出现Status为Cancel异常bug [#IAQ2AI](https://gitee.com/RRQM_Home/TouchSocket/issues/IAQ2AI)。 - - -## v2.0.17 - -**更新日期:** 2024.8.30 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 使用AspNetCore容器时,默认没有注册ILog的bug。 - -## v2.0.16 - -**更新日期:** 2024.8.19 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 tcp在winform中会使用主线程接收的bug,导致各种同步接收异常。 - - -## v2.0.15 - -**更新日期:** 2024.8.9 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 当客户端断开连接时,http响应会出现object is null的bug提示。 - - -## v2.0.13(14) - -**更新日期:** 2024.7.24 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 重连插件在长时间运行时,会失效的bug。 --  修复 ssl加密下,连接响应时间过长bug[#IAET6V](https://gitee.com/RRQM_Home/TouchSocket/issues/IAET6V) 。 - -## v2.0.12 - -**更新日期:** 2024.7.17 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 socket在接收连接时,异常无法拦截的bug[#IADIGX](https://gitee.com/RRQM_Home/TouchSocket/issues/IADIGX)。 --  调整 日志记录在执行时,先判断日志组件的可用性。 - -## v2.0.11 - -**更新日期:** 2024.7.12 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 Metadata在Add时不会覆盖原key的bug。 - - - -## v2.0.10 - -**更新日期:** 2024.5.31 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 WebSocket快捷命令行bug。 [#I9TG3V](https://gitee.com/RRQM_Home/TouchSocket/issues/I9TG3V)。 - - -## v2.0.9 - -**更新日期:** 2024.5.29 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 UdpPackage适配器在工作时调时导致的bug。 [#I9SYTR](https://gitee.com/RRQM_Home/TouchSocket/issues/I9SYTR)。 --  修复 Http在GetBoundary时bug。 [#I9PXWT](https://gitee.com/RRQM_Home/TouchSocket/issues/I9PXWT)。 - - - -## v2.0.(7)8 - -**更新日期:** 2024.5.17 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 udp首次建立连接无法接收数据的bug。 [#I9PV7C](https://gitee.com/RRQM_Home/TouchSocket/issues/I9PV7C)。 - -## v2.0.6 - -**更新日期:** 2024.5.12 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 FlowGate的waitTime小于0时bug。 - - -## v2.0.5 - -**更新日期:** 2024.5.2 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 wsclient Received事件与插件触发bug。[#I9L9WI](https://gitee.com/RRQM_Home/TouchSocket/issues/I9L9WI)。 --  修复 ws命令行执行bug。 - -## v2.0.4 - -**更新日期:** 2024.4.30 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 linux平台下,Socket吞吐量大幅降低。[#I9KURV](https://gitee.com/RRQM_Home/TouchSocket/issues/I9KURV)。 --  修复 WebSocket在进行连接时,Host的Header写法错误,没有包含端口。[#I9KUVI](https://gitee.com/RRQM_Home/TouchSocket/issues/I9KUVI)。 - - -## v2.0.3 - -**更新日期:** 2024.4.14 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 Tcp在接收时,内存池释放存在延迟,可能导致内存池快速扩张,浪费内存。[#I9FVAA](https://gitee.com/RRQM_Home/TouchSocket/issues/I9FVAA)。 --  修复 在使用Host模型时,注入瞬态的TcpClient,在第一次获取实例是正常的,第二次就失败。[#I9G3SV](https://gitee.com/RRQM_Home/TouchSocket/issues/I9G3SV)。 --  修复 WebSocketClient连接其他服务器时显示 “操作已被取消”。[#I9GG05](https://gitee.com/RRQM_Home/TouchSocket/issues/I9GG05)。 --  新增 ConcurrentList新增实现IReadOnlyList接口。 - - -## v2.0.2 - -**更新日期:** 2024.4.1 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 TouchSocketBitConverter中的ToBooleans方法存在Bug[#I9C1UY](https://gitee.com/RRQM_Home/TouchSocket/issues/I9C1UY)。 --  修复 SystemExtensions中的GetBit和SetBit方法[#I9C1WM](https://gitee.com/RRQM_Home/TouchSocket/issues/I9C1WM)。 --  修复 HttpService多次响应下载文件时,不会响应的bug。 - - - -## v2.0.1 - -**更新日期:** 2024.3.16 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 HttpClient,无法通过HttpResponse.GetBody()获取响应内容[#I989SI](https://gitee.com/RRQM_Home/TouchSocket/issues/I989SI)。 --  修复 SetNoDelay 异常[#I979B0](https://gitee.com/RRQM_Home/TouchSocket/issues/I979B0)。 --  修复 调用Dmtp服务的大数据传输时,如果循环调用会出现收到的数据和发送的数据不一致。实际上该问题是由`ByteBlock`写入扩容bug而导致的[#I96FNF](https://gitee.com/RRQM_Home/TouchSocket/issues/I96FNF)。 - - -## v2.0.0 - -**更新日期:** 2024.3.9 - -**更新描述:** - -此版本是大版本更新。可能会产生很多不兼容部分,所以升级之前请做好备份,并且请详细阅读下列更新内容。 - -**升级指南:** - -- 升级时请先升级至`2.0.0-beta.190`,再升级至`2.0.0-rc.2`版本,因为正式版对于[Obsolete]特性的成员直接删除了,所以为友好升级,请先升级至此版本。 -- 由2.0.0-beta.200至2.0.0-beta.220 [#I8DE1D](https://gitee.com/RRQM_Home/TouchSocket/issues/I8DE1D) -- 由2.0.0-beta.220至2.0.0-beta.230 [#I8LAX4](https://gitee.com/RRQM_Home/TouchSocket/issues/I8LAX4) - -**更新详情:** - -#### 【TouchSokcet.Core】 - --  优化 FileLogger支持指定不同目录。 --  调整 所有自定义插件必须在自身内,主动调用e.InvokeNext()时,才会调用下一个插件。不然会中断插件传递。同时e.Handled功能依然有效。 --  调整 Log项。LogType调整为LogLevel,并且不需要位运算。直接按日志等级输出。 --  调整 修改IPluginsManager名称为IPluginsManager。 --  移除 DependencyProperty中,移除对类型的定义。 --  移除 所有组件的基础插件,强制用户插件必须继承PluginBase,然后实现需要的接口。 --  移除 BytePool在创建ByteBlock时,移除EqualSize的设定,因为这会影响内存池的效率。 --  调整 修改所有委托为异步Task。 --  调整 修改所有Setup返回值为void。 --  修复 Metadata在0个成员长度时,会被反序列化成null的bug。 --  修复 PluginsManager在注册具有继承的插件时,会无法识别的bug。 - -#### 【TouchSokcet.Sokcets】 - --  优化 IPHost支持从int、string直接隐式转换。 --  调整 TouchSocket所有“ID”属性,改名为“Id”。 --  调整 TouchSocket所有插件的执行顺序,移动至内部重写方法之后。 --  调整 TouchSocket所有`ResetID`改名为`ResetId`。 --  调整 UseCheckClear项,SetDuration调整名称为SetTick。 --  调整 UseCheckClear项,不仅可以适用服务器,客户端也适用。 --  调整 Config配置中,SetDataHandlingAdapter调整为SetTcpDataHandlingAdapter。 --  调整 适配器项,CustomDataHandlingAdapter中的Filter方法中,byteBlock参数使用in修饰。 --  调整 适配器项,DataHandlingAdapter改名为TcpDataHandlingAdapter。 --  调整 适配器项,DataAdapterTester改名为TcpDataAdapterTester。 --  调整 Config项,所有适配器的相关配置,使用SetAdapterOption配置。 --  移除 UsePlugin的显式配置,当调用ConfigurePlugins时,会自动启用。 - -#### 【TouchSokcet.Http】 - --  调整 WSCommandLinePlugin改名为WebSocketCommandLinePlugin。 --  新增 WebSocket添加[同步非阻塞Read](./websocketservice.mdx#52-websocket显式readasync)。 --  新增 WebSocket的WSDataFrame新增IsPing、IsPong、IsText、IsBinary、IsClose等属性。 --  新增 静态网页插件新增NavigateAction与ResponseAction等委托,可以在静态页面请求之前重定向,或者请求返回时设置header等。 - -#### 【TouchSokcet.Rpc】 - --  移除 整体功能迁移至TouchSokcet(Pro).Dmtp。 --  调整 [RpcActionFilter](./rpcactionfilter.mdx#33-规则)执行策略和顺序 --  调整 修改ConfigureRpcStore为AddRpcStore。 --  新增 [RealityProxy](./rpcgenerateproxy.mdx)透明代理方式。 --  新增 [DispatchProxy](./rpcgenerateproxy.mdx)添加OnBefore和OnAfter的AOP调用。 - -#### 【TouchSokcet(Pro).Dmtp】 - --  调整 原TouchRpc全系改名为Dmtp。例如:原TcpTouchRpcClient改名为TcpDmtpClient。 --  调整 原TouchRpc中InvokeOption,改名为DmtpInvokeOption。InvokeOption依然有效,但是在调用DmtpRpc时,则无法指定序列化方式。所以可能需要使用DmtpInvokeOption。 --  调整 原TouchRpc中Invoke直接调用的方式,改为InvokeT。 --  调整 Dmtp相关配置,使用SetDmtpOption配置。 --  移除 **暂时**移除EventBus功能,后续可能考虑添加。 --  新增 ws协议的TouchRpc服务端,增加HttpContext上下文获取。 --  新增 文件传输项,开放增加SetMaxSpeed功能。 --  新增 DmtpRpc。 --  新增 DmtpRpc组件在调用时,可以通过DmtpInvokeOption传入Metadata元数据。 --  修复 DmtpRpc在调用无ref,out的函数时,参数会为null的bug。 - -#### 【TouchSokcet.JsonRpc】 - --  修复 JsonRpc使用内联数组调用[#I79OFZ](https://gitee.com/RRQM_Home/TouchSocket/issues/I79OFZ)。 --  调整 Rpc服务使用调用上下文时不需要再添加`IncludeCallContext`。 - -#### 【TouchSokcet.WebApi】 - --  新增 WebApi新增[Swagger页面](./swagger.mdx)。 --  调整 Rpc服务使用调用上下文时不需要再添加`IncludeCallContext`。 - -#### 【TouchSokcet.XmlRpc】 - --  调整 Rpc服务使用调用上下文时不需要再添加`IncludeCallContext`。 - -#### 【TouchSokcet(Pro).Hosting】 - --  新增 新发布Hosting的包,用于构建更加强壮的运行程序。 - -#### 【TouchSokcet.SerialPorts】 - --  新增 新发布串口的包。 - -#### 【TouchSokcet(Pro).Modbus】 - --  新增 新发布Modbus的包,支持Tcp、Udp、Rtu、RtuOverTcp、RtuOverUdp协议的主站(Poll)和从站(Slave)。 ---- - - -## v1.3 - -更新日期:2023.3.1 - -更新描述:兼容性更新。 - - -  优化 整体增加异步方法。 - -  优化 Rpc源代码生成策略,支持接口实例并存。 - -  修复 TcpClient在UseReconnect插件时,Disconnect事件不触发bug。 - -  新增 ws协议的TouchRpc服务端,增加HttpContext上下文获取。 ---- - -## v1.2 - -更新日期:2023.2.15 - -更新描述:兼容性更新。 - - -  优化 TouchRpc支持命名元组。 - -  优化 Rpc源代码生成策略。 - -  修复 TouchRpc在Websocket协议下,启动,连接异常bug。 - -  修复 TouchRpc在调用WaitSend下失败的bug。 - -  修复 TouchRpc在Handshaked时,调用Rpc超时bug。 - -  修复 序列化、反射在unity中使用il2cpp编译的bug。 - -  修复 反序列化在初次加载时会失败的bug。 - -  修复 BytePool没有公共构造函数的bug。 - -  新增 ws协议的TouchRpc服务端,增加HttpContext上下文获取。 - -  新增 ByteBlock对于int,long等数据,写入和读取的时候支持大小端指定。 - -  新增 IServicePlugin插件,用于显示通知服务器的启动状态。 - -  新增 Rpc支持接口特性标记。 - -  调整 将BytePool由静态调整为实例,且由其Default实例作为默认。 ---- - -## v1.1 - -更新日期:2023.1.13 - -更新描述:小版本升级,可能会有不兼容。请按下列提示修改。 - - -  优化 TouchRpc系文件传输时,文件夹不存在的提示。 - -  优化 WaitingClient,当客户端断开连接时,可选是否抛出异常。 - -  优化 Fast序列化时。可选序列化只读属性。 - -  修复 多个不稳定Bug。 - -  新增 Tcp客户端新增Disconnecting事件。在主动Close时生效。 - -  调整 多个事件类名称修改,请按照提示修改即可。 - -  移除 多个无用方法参数。 ---- - -## v1.0.0 - -更新日期:2023.1.1 - -更新描述:大版本升级,请详细阅读下列更新日志。 - - -  升级 将最高版本升级为NET7。 - -  优化 Tcp系异步发送效率。 - -  优化 TouchRpc系Channel的稳健性。 - -  修复 多个不稳定Bug。 - -  新增 ValueByteBlock,在简单代码块里面能有效减少创建的类。 - -  新增 MemoryCache类,其功能类似微软官方。但是支持全部泛型。 - -  新增 [IPackage系](https://www.yuque.com/rrqm/touchsocket/ag9tyar9mmhsme0m)。该系列能以超高效率的进行二进制序列化。 - -  新增 SingleTimer类,不可重入的Timer。 - -  新增 Jsonrpc支持自定义适配器解析(EE) - -  新增 严重TouchRpc系OnRouting通知,所有的客户端之间的通信,都必须经过OnRouting的筛查。 - -  新增 TouchRpc系小文件传输,在文件小于1Mb时,其传输效率是常规传输的10倍以上。 - -  新增 TouchRpc系超大文件多链路传输,支持多个客户端协同传输同一个文件,这在互联网环境中,效率比常规传输提高类3-5倍。 - -  新增 TouchRpc系Redis组件,能实现双端共同存储。 - -  调整 严重精简所有命名空间,删除所有三级命名空间。例如:TouchSocket.Core.ByteManager精简为TouchSocket.Core。 - -  调整 严重删除Newtonsoft.Json的源代码嵌入。全局的Json会根据环境动态调整,详情见[Json工具](https://www.yuque.com/rrqm/touchsocket/emqy43#PfVh1) - -  调整 严重框架默认日志由ConsoleLogger,替换为EmptyLogger(不输出任何东西)。 - -  调整 严重Tcp全系,在连接时,ID的初始值使用long类型从0递增。 - -  调整 严重Tcp服务器,将定时清理无数据交互的选项替换为UseCheckClear插件。并且默认没有启用,需要手动加入。 - -  调整 Tcp系适配器,取消部分参数。 - -  调整 DataLock改名为DataSecurity。 - -  调整 EasyAction改名EasyTask。 - -  调整 IMessage改名IMessageObject。 - -  调整 TokenInstance改名MessageInstance。 - -  调整 TouchRpc系,精简常规文件传输操作。 - -  调整 严重TouchRpc系,所有插件通知参数,默认都设为不允许操作,需要手动设置e.IsPermitOperation=true。 - -  移除 Newtonsoft.Json的源代码嵌入。全局的Json会根据环境动态调整,详情见[Json工具](https://www.yuque.com/rrqm/touchsocket/emqy43#PfVh1)。 - -*** 更新示例指南 *** - -(1)适配器参数报错:直接删除isAsync参数,以及isAsync为**True**的所有逻辑。 -![image.png](@site/static/img/docs/upgrade-1.png) -(2)依赖属性的声明报错:增加泛型约束即可,详情查看[依赖属性](https://www.yuque.com/rrqm/touchsocket/ubk57o#jyzSl) -![image.png](@site/static/img/docs/upgrade-2.png) -(3)服务端定时清理警告:在配置插件中使用UseCheckClear,并且进行相关配置。 -![image.png](@site/static/img/docs/upgrade-3.png) -![image.png](@site/static/img/docs/upgrade-4.png) - ---- - - -## 版本号: 0.7.0 -更新日期:2022.9.21 -更新描述:兼容性更新,增强型更新。**RPC内容需要客户端与服务器同步更新**。 -更新详情: - -优化 -1. Fast二进制序列化,支持自定义序列化。 -2. TouchRpc全系,在文件传输等大型IO时,由于心跳失败而断开连接。 -3. 优化AspNetCore的IContainer。 -4. TcpCommandLinePlugin与WSCommandLinePlugin支持获取客户端参数。 - -新增 -1. 插件实例会以单例注入容器。 -2. 所有适配器支持[缓存超时](https://www.yuque.com/rrqm/touchsocket/83526e6320dfc85fef317d850aa51e92#Z0S0g)设定。 -3. 修改所有事件为委托。 -4. 开放[AspnetCore](https://www.yuque.com/rrqm/touchsocket/55e5bbf58745fa639dba511c7bcd54d1#WqOmh)创建Tcp,Http等服务器的配置。 -5. IClient增加发送、接收的最后时间记录。 -6. Http支持多文件上传(目前仅支持小文件,具体大小以实际运行内存为准,实测100Mb没问题)。 -7. Websocket插件默认会处理Close报文。且插件支持Close。 -8. Rpc支持模板代码重写。 -9. TouchRpc支持元组。 -10. JsonRpc支持Websocket协议。 - -修改 -1. IScopedContainer修改为IContainerProvider - -修复 -1. BytePool回收内存时不判断大小的bug。 - -删除 -1. 无。 - - ---- - -## 版本号: 0.6.0 -更新日期:2022.9.10 -更新描述:兼容性更新,增强型更新。**专为Unity 3D适配**。 -更新详情: - -优化 -1. Gzip的压缩效率。 -2. 发送效率。 - -新增 -1. IDataCompressor数据传输压缩接口。 -2. [RemoteStream](https://www.yuque.com/rrqm/touchsocket/ukq0mu)支持数据读写压缩。 -3. WaitResultPackageBase类,专属非序列化的数据格式化。 -4. DelaySender[延迟缓存发送](https://www.yuque.com/rrqm/touchsocket/1f21a56ee75f896a5b5b38b37b071881#RL0kx)。 - -修改 -1. 无 - -修复 -1. Rpc注册服务为单例时,实际上是瞬时服务的bug。 - -删除 -1. 独立线程发送。 - ---- - -## 版本号: 0.5.0 -更新日期:2022.9.1 -更新描述:兼容性更新,增强型更新。 -更新详情: - -优化 -1. 全局资源的获取逻辑。 - -新增 -1. Container增加卸载注册功能。 -2. FilePool新增FileStorageStream的获取。 -3. http客户端(及websocket)支持代理和验证代理。 -4. TouchRpc全系新增[远程文件操作](https://www.yuque.com/rrqm/touchsocket/pearz0) -5. TouchRpc(除udp)新增[远程流访问](https://www.yuque.com/rrqm/touchsocket/ukq0mu) - -修改 -1. 无 - -修复 -1. 修复Http客户端请求重复Header时的bug。 - -删除 -1. TouchRpc全系的事件操作,推荐直接插件的方式,或者使用TouchRpcActionPlugin然后添加委托。 - - -更新示例 -TouchRpc的相关事件均已使用插件代替。所以请使用插件实现操作。如果需要事件等功能的话,可以用TouchRpcActionPlugin的插件实现。例如: -```csharp showLineNumbers -.UsePlugin() -.ConfigurePlugins(a=> -{ - a.Add>()//此处的逻辑可用插件替代完成。 - .SetFileTransfering((client, e) => - { - //有可能是上传,也有可能是下载 - client.Logger.Info($"服务器请求传输文件,ID={client.ID},请求类型={e.TransferType},文件名={e.FileInfo.FileName}"); - }) - .SetFileTransfered((client, e) => - { - //传输结束,但是不一定成功,需要从e.Result判断状态。 - client.Logger.Info($"服务器传输文件结束,ID={client.ID},请求类型={e.TransferType},文件名={e.FileInfo.FileName},请求状态={e.Result}"); - }); -}) -``` - ---- - -## 版本号: 0.4.5 -更新日期:2022.8.25 -更新描述:兼容性更新,增强型更新。 -更新详情: - -优化 -1. FileLogger的写入逻辑,大大地提升了写入效率。 - -新增 -1. [Pipeline适配器](https://www.yuque.com/rrqm/touchsocket/ofnliu) -2. [TLV适配器](https://www.yuque.com/rrqm/touchsocket/wug4bv) -3. WaitingClient支持按条件等待返回。 -4. 日志系统可以筛选日志的输出类型 -5. Rpc系统,可以使用单例、瞬时生命周期的服务。 -6. Rpc系统,可定义持久化模型。 -7. Rpc在使用瞬时生命周期的服务时,可以直接获取调用上下文。 -8. XmlRpc增加调用上下文。 - -修改 -1. 日志系统。 -2. Rpc的调用上下文均采用接口,例如:JsonRpc改为IJsonRpcCallContext,WebApi为IWebApiCallContext。 -3. IRpcActionFilter的参数列表。 - -修复 -1. UdpSession资源不释放的Bug。 - -删除 -1. 冗余元素。 - - ---- - -## 版本号: 0.3.5 -更新日期:2022.8.12 -更新描述:兼容性更新,增强型更新。 -更新详情: - -优化 -1. 各类客户端发送逻辑。 -2. Method类的调用逻辑。 - -新增 -1. 适配器可以设定发送IRequestInfo对象。 -2. 插件新增UseWebSocket的快捷方式。 -3. ReconnectionPlugin插件可以获得重连次数的重载设置。 -4. 【Pro】TcpService的服务注入。 -5. 【Pro】HttpService的服务注入。 -6. 【Pro】IOC容器的共享使用。 - -修改 -1. 各类发送逻辑,以最小化发送方法为基础,其余方法改为扩展方法。 -2. 相关接口的实现。 -3. 由网友[修改GetInfo](https://gitee.com/dotnetchina/TouchSocket/pulls/11) - -修复 -1. Container获取泛型失败bug。 -2. BetweenAnd适配器适配器部分bug。 -3. Router标签无法路由的bug。 -4. 修复TouchRpc推送文件状态不正确bug -5. 修复独立线程在断线重连后发送bug。 - -删除 -1. 冗余的发送方法,不影响上版本任何使用。 - - ---- - - -## 版本号: 0.2.4 -更新日期:2022.7.28 -更新描述:兼容性更新。 -更新详情: - -优化 -1. 优化IOC容器。 -2. 优化Metadata的写入方式。 -3. FileLogger,当日志文件达到1Mb时,会再新增文件序号。 - -新增 -1. Mapper类,支持简单类型映射 -2. Tcp服务器、客户端、udp等增加端口复用配置。 -3. 【Pro】轮询式断线重连。 -4. 【Pro】NATService转发客户端重连。 - -修改 -1. RRQM二进制序列化,改名为Fast。 -2. TouchRpcClient连接时的Metadata,改为由Config配置注入。 -3. FilePool,取消延迟释放机制。 - -修复 -1. 修复WebSocket连接问题 - -删除 -1. 客户端直接调用的短线重连方式。仅保留在Config注入的功能。 - ---- - -## 版本号: 0.1.0 -更新日期:2022.7.16 -更新描述:初始化版本发布。由RRQMSocket迁移而来。 - -迁移指南: -### 1.所有类的命名空间修改,此处如果类型名未修改的话,可由vs智能提示解决。 -### 2.类型名称修改 -| 原类型名称 | 新类型名称 | -| --- | --- | -| RRQMBitConverter | TouchSocketBitConverter | -| RRQMConfig | TouchSocketConfig | -| RRQMConverter | TouchSocketConverter | -| RRQMDependencyObject | DependencyObject | -| MsgEventArgs | MsgEventArgs | -| RRQMEventAgrs | TouchSocketEventArgs | -| IServerProvider | IRpcServer | -| ServerProvider | RpcServer | -| RRQMOverlengthException | OverlengthException | - -### 3.使用逻辑修改 -1)原RRQMConfig设置Logger的方法,改为容器注入: -![image.png](@site/static/img/docs/upgrade-5.png) -断线重连逻辑 -![image.png](@site/static/img/docs/upgrade-6.png) -RpcStore使用变更 -如果是仅有一个Rpc解析器,那么可以直接删除RpcStore的声明,从而使用对应的**解析器实例**,直接注册服务。然后可以通过其属性RpcStore,获取到具体的RpcStore实例。 - -如果是有多个解析器,那么,首先可以使用任意一个解析器的RpcStore属性实例,作为主RpcStore,然后添加其他解析器。当然也可以直接new RpcStore,然后统一管理解析器。其中构造函数中的Container容器,可以直接new Container(),但是更建议使用和解析器相同的容器,这样注入的服务会变得全局可用。 diff --git a/handbook/versioned_docs/version-3.0/bytepool.mdx b/handbook/versioned_docs/version-3.0/bytepool.mdx index 47dd00fe9..38e1b4f64 100644 --- a/handbook/versioned_docs/version-3.0/bytepool.mdx +++ b/handbook/versioned_docs/version-3.0/bytepool.mdx @@ -241,4 +241,4 @@ ByteBlock在设置SetHolding(false)后,不需要再调用Dispose。 ## 六、本文示例Demo - \ No newline at end of file + \ No newline at end of file diff --git a/handbook/versioned_docs/version-3.0/customcountspliterdatahandlingadapter.mdx b/handbook/versioned_docs/version-3.0/customcountspliterdatahandlingadapter.mdx index 1bf08a069..87847e4cd 100644 --- a/handbook/versioned_docs/version-3.0/customcountspliterdatahandlingadapter.mdx +++ b/handbook/versioned_docs/version-3.0/customcountspliterdatahandlingadapter.mdx @@ -132,4 +132,4 @@ private static async Task CreateService() ## 五、示例Demo - \ No newline at end of file + \ No newline at end of file diff --git a/handbook/versioned_docs/version-3.0/customjsondatahandlingadapter.mdx b/handbook/versioned_docs/version-3.0/customjsondatahandlingadapter.mdx index bb4e8b66c..c62fc6f9f 100644 --- a/handbook/versioned_docs/version-3.0/customjsondatahandlingadapter.mdx +++ b/handbook/versioned_docs/version-3.0/customjsondatahandlingadapter.mdx @@ -139,4 +139,4 @@ private static async Task CreateService() ## 五、示例Demo - \ No newline at end of file + \ No newline at end of file diff --git a/handbook/versioned_docs/version-3.0/dynamicmethod.mdx b/handbook/versioned_docs/version-3.0/dynamicmethod.mdx index 7e8bc25cb..a8741b332 100644 --- a/handbook/versioned_docs/version-3.0/dynamicmethod.mdx +++ b/handbook/versioned_docs/version-3.0/dynamicmethod.mdx @@ -191,4 +191,4 @@ var methods = typeof(CustomService) ## 七、示例项目 - + diff --git a/handbook/versioned_docs/version-3.0/enterprise.mdx b/handbook/versioned_docs/version-3.0/enterprise.mdx index 7e9f67c3f..c9abbfdf9 100644 --- a/handbook/versioned_docs/version-3.0/enterprise.mdx +++ b/handbook/versioned_docs/version-3.0/enterprise.mdx @@ -32,13 +32,13 @@ import Paypal from '@site/src/components/Paypal.js'; ### 2.1 Tcp组件 -- [轮询式断线重连](./reconnection.mdx#三使用pollingkeepalive插件-Pro) Pro +- [轮询式断线重连](./reconnection.mdx) Pro - [TLV适配器](./tlvdatahandlingadapter.mdx) Pro - 其余功能 ### 2.2 NAT组件 -- [转发客户端重连](./natservice.mdx#四转发断线重连-Pro) Pro +- [转发客户端重连](./natservice.mdx) Pro - 其余功能 ### 2.3 UDP组件 @@ -66,7 +66,6 @@ import Paypal from '@site/src/components/Paypal.js'; - [多线程文件传输](./dmtptransferfile.mdx) Pro - [小文件传输](./dmtptransferfile.mdx) - 文件传输限速 Pro -- [EventBus功能](https://www.yuque.com/rrqm/touchsocket/ipt4zr) Pro - Redis ### 2.8 Http组件 @@ -117,7 +116,7 @@ import Paypal from '@site/src/components/Paypal.js'; ### 4.1 个人独立授权 -授权归属于购买者个人所有,规定购买者可将所购产品只能应用于所属个人的任何软件(产品)上,可以以此盈利,但必须遵守[个人使用协议](./description.mdx#qLp3q)。 +授权归属于购买者个人所有,规定购买者可将所购产品只能应用于所属个人的任何软件(产品)上,可以以此盈利,但必须遵守[个人使用协议](./description.mdx)。 ### 4.2 个人企业授权 diff --git a/handbook/versioned_docs/version-3.0/httpclient.mdx b/handbook/versioned_docs/version-3.0/httpclient.mdx index dca7b0294..a4c0e3e3b 100644 --- a/handbook/versioned_docs/version-3.0/httpclient.mdx +++ b/handbook/versioned_docs/version-3.0/httpclient.mdx @@ -195,4 +195,4 @@ await client.UploadFileAsync("/upfile", new FileInfo("filePath")); ## 七、本文示例Demo - \ No newline at end of file + \ No newline at end of file diff --git a/handbook/versioned_docs/version-3.0/httpservice.mdx b/handbook/versioned_docs/version-3.0/httpservice.mdx index 94f112de3..3e6dd5e76 100644 --- a/handbook/versioned_docs/version-3.0/httpservice.mdx +++ b/handbook/versioned_docs/version-3.0/httpservice.mdx @@ -478,5 +478,5 @@ Https服务器,和http服务器几乎一样,只不过增加了一个Ssl的 ## 十、本文示例Demo - - \ No newline at end of file + + \ No newline at end of file diff --git a/handbook/versioned_docs/version-3.0/ipackage.mdx b/handbook/versioned_docs/version-3.0/ipackage.mdx index 6b4bab520..746f07216 100644 --- a/handbook/versioned_docs/version-3.0/ipackage.mdx +++ b/handbook/versioned_docs/version-3.0/ipackage.mdx @@ -535,4 +535,4 @@ internal partial class MyGeneratorConvertPackage : PackageBase ## 八、本文示例Demo - \ No newline at end of file + \ No newline at end of file diff --git a/handbook/versioned_docs/version-3.0/jsonrpc.mdx b/handbook/versioned_docs/version-3.0/jsonrpc.mdx index 154f6c07d..ee1d1a737 100644 --- a/handbook/versioned_docs/version-3.0/jsonrpc.mdx +++ b/handbook/versioned_docs/version-3.0/jsonrpc.mdx @@ -433,4 +433,4 @@ class MyPluginClass : PluginBase, IWebSocketHandshakedPlugin ## 八、本文示例Demo - + diff --git a/handbook/versioned_docs/version-3.0/packageadapter.mdx b/handbook/versioned_docs/version-3.0/packageadapter.mdx index 1f2235d79..fed1f7953 100644 --- a/handbook/versioned_docs/version-3.0/packageadapter.mdx +++ b/handbook/versioned_docs/version-3.0/packageadapter.mdx @@ -454,4 +454,4 @@ private static async Task CreateService() ## 六、本文示例Demo - + diff --git a/handbook/versioned_docs/version-3.0/udpsession.mdx b/handbook/versioned_docs/version-3.0/udpsession.mdx index d84246051..19c279417 100644 --- a/handbook/versioned_docs/version-3.0/udpsession.mdx +++ b/handbook/versioned_docs/version-3.0/udpsession.mdx @@ -183,4 +183,4 @@ udpService.Start(); ## 八、本文示例Demo - + diff --git a/handbook/versioned_docs/version-3.0/upgrade.mdx b/handbook/versioned_docs/version-3.0/upgrade.mdx deleted file mode 100644 index e0f98ffe2..000000000 --- a/handbook/versioned_docs/version-3.0/upgrade.mdx +++ /dev/null @@ -1,1580 +0,0 @@ ---- -id: upgrade -title: 历史更新 ---- - -import useBaseUrl from "@docusaurus/useBaseUrl"; -import Tag from "@site/src/components/Tag.js"; -import Highlight from '@site/src/components/Highlight.js'; - -:::tip `TouchSocket` 框架升级/发版规则 - -**升级前重点关注可能造成破坏性的标签类型**:修复调整移除升级 - -版本号规则:`主版本号.次版本号.修订版本号` - -- 只要确认为框架 `bug`,则当天修复,下个周日发版,修订版本号 `加 1`。 -- 其余情况,每年发布一个 `主版本`,发布时间跟随Dotnet的发布时间。 - -::: - - -## v3.0.26 - -**更新日期:** 2025.4.20 - -**更新描述:** - -兼容性修复升级。 - - -**更新详情:** - -#### TouchSocket.Rpc - --  修复 `RpcActionFilterAttribute`在`RpcImplementation`程序集中使用时无效bug。 [#IC0IB0](https://gitee.com/RRQM_Home/TouchSocket/issues/IC0IB0) - -#### TouchSocket.Http - --  修复 `WebSocket`服务端`WebSocket`使用`AsyncClose()`主动关闭连接时引发`NullReferenceException`异常。 [#IC1FZ8](https://gitee.com/RRQM_Home/TouchSocket/issues/IC1FZ8) - -*** - -
-v3.0 -
- -## v3.0.25 - -**更新日期:** 2025.4.12 - -**更新描述:** - -兼容性修复升级。 - - -**更新详情:** - -#### TouchSocket.SerialPort - --  修复 接收数据时,如果业务出现延迟,则会导致接收失败的异常bug。 [#IBZHN2](https://gitee.com/RRQM_Home/TouchSocket/issues/IBZHN2) - -*** - -## v3.0.21(3)(4) - -**更新日期:** 2025.4.6 - -**更新描述:** - -兼容性修复升级。http组件性能优化。 - - -**更新详情:** - -#### TouchSocket.Core - --  新增 `ReadOnlySpan`的`Trim`方法。 --  新增 `Method`新增支持类型的直接构造函数方法。 --  新增 `AsyncResetEvent`新增已释放判断。 --  修复 `DynamicMethod`在ref、out等参数时,无法使用源生成调用的bug。 --  调整 `FlowOperator`的在构造函数使用最大值初始化`MaxSpeed`。 - -#### TouchSocket.Sockets - --  修复 `TcpCore`在特点情况下不释放资源的bug。[PR](https://gitee.com/RRQM_Home/TouchSocket/pulls/65) --  调整 `ShutdownAsync`调整返回值,由`Task`改为`Task`。 - -#### TouchSocket.Http - --  新增 `HttpContent`新增`TryComputeLength`的抽象方法。 --  新增 `HttpResponse`的`FromFileAsync`扩展新增传输限速、进度功能。[#IBYGC7](https://gitee.com/RRQM_Home/TouchSocket/issues/IBYGC7) --  优化 整个`Http`组件性能优化。 --  调整 `HttpHeaders`由枚举改为静态类。(此操作不影响现有代码) - - -*** - - -## v3.0.20 - -**更新日期:** 2025.3.30 - -**更新描述:** - -兼容性修复升级。部分方法名大小写调整。 - - -**更新详情:** - -#### TouchSocket.Core - --  调整 `TouchSocketCoreUtility`类中的所有静态字段大小写调整。 --  调整 `StringExtension`类中的多个方法名大小写调整(此处调用时可能是扩展方法调用的,所以需要注意)。 - -#### TouchSocket.Http - --  修复 `HttpClient`在请求`application/x-www-form-urlencoded`时,内部未进行编码的bug。[#IBVPAD](https://gitee.com/RRQM_Home/TouchSocket/issues/IBVPAD) --  新增 `HttpResponse`新增`FromHtml`扩展方法,用于直接返回`Html`页面。 - -#### TouchSocket.Rpc - --  新增 `IRpcCallContextAccessor`服务,可以在异步调用流中,通过服务直接获取到执行`Rpc`的`CallContext`。 - -#### TouchSocket.WebApi.Swagger - --  更新 `Swagger`页面版本为`v5.20.2`,以支持一键复制url等功能。 [#IBX933](https://gitee.com/RRQM_Home/TouchSocket/issues/IBX933) - -#### TouchSocket.Modbus - --  优化 `Modbus rtu`在响应数据时的严谨性,基本排除了站号,功能码不一致时仍然返回的错误情况。 [#github-issue 54](https://github.com/RRQM/TouchSocket/issues/54) - -*** - - -## v3.0.19 - -**更新日期:** 2025.3.23 - -**更新描述:** - -兼容性修复升级。 - - -**更新详情:** - -#### TouchSocket.Http - --  修复 Http静态内容插件在客户端不支持gzip时仍然会使用gzip的bug。 --  修复 Http静态内容插件在以文件响应时,限速为0的bug。[#IBVCPJ](https://gitee.com/RRQM_Home/TouchSocket/issues/IBVCPJ) - -*** - -## v3.0.18 - -**更新日期:** 2025.3.16 - -**更新描述:** - -兼容性修复升级。少量代码调整,详见 调整 - - -**更新详情:** - -#### TouchSocket.Sockets - --  修复 `TryShutdown`的关闭机制没有考虑异步发送队列的情况。[#IBTKMP](https://gitee.com/RRQM_Home/TouchSocket/issues/IBTKMP) --  调整 `TryShutdown`方法改为`ShutdownAsync`。 - -#### TouchSocket.Http - --  修复 `HttpExtensions`中`GetBoundary`实现存在问题。[#IBT3RO](https://gitee.com/RRQM_Home/TouchSocket/issues/IBT3RO) --  修复 静态页面插件在重新载入时prefix参数丢失问题。[#IBTP38](https://gitee.com/RRQM_Home/TouchSocket/issues/IBTP38) - -*** - - -## v3.0.17 - -**更新日期:** 2025.3.12 - -**更新描述:** - -兼容性修复升级。 - - -**更新详情:** - -#### TouchSocket.Sockets - --  修复 UdpSessionBase的OnUdpReceiving方法添加EndPoint参数。[#IBSQVN](https://gitee.com/RRQM_Home/TouchSocket/issues/IBSQVN) - -#### TouchSocket.SerialPorts - --  修复 SerialPortClient在接收数据时LastReceivedTime一直不会更新。[#IBSXDK](https://gitee.com/RRQM_Home/TouchSocket/issues/IBSXDK) - -*** - - -## v3.0.16 - -**更新日期:** 2025.3.9 - -**更新描述:** - -兼容性修复升级。 - - -**更新详情:** - -#### TouchSocket.Core - --  修复 自定义适配器在接收数据时,不验证`MaxPackageSize`的`bug`。 --  修复 `MakeIdentifier`在获取方法名称时,不显示中文等其他字符的`bug`[#IBQQHY](https://gitee.com/RRQM_Home/TouchSocket/issues/IBQQHY)。 - -#### TouchSocket.Sockets - --  修复 当配置`NoDelay`时,`TcpCore`发送时仍然会把数据放入发送队列,可能会产生细微延迟 [#IBR1I2](https://gitee.com/RRQM_Home/TouchSocket/issues/IBR1I2)。 --  调整 `IWaitingClient`实现了无效的`Dispose`方法,目前已取消。 - -*** - - -## v3.0.15 - -**更新日期:** 2025.3.2 - -**更新描述:** - -兼容性修复升级。 - - -**更新详情:** - -#### All - --  优化 代码规范和相关注释。 - -#### TouchSocket.Core - --  新增 `SystemExtension`新增`ReadAllToByteArray`方法。 - -*** - - -## v3.0.14 - -**更新日期:** 2025.2.15 - -**更新描述:** - -修复升级。区间字符适配器会有不兼容部分,请参阅[区间字符](./custombetweenanddatahandlingadapter.mdx)。 - - -**更新详情:** - -#### All - --  新增 所有的`Plugin`接口。均新增快捷扩展方法,简化使用。 --  调整 使用自定义的`lock`锁对象。以简化使用场景。 - -#### TouchSocket.Core - --  新增 `ILog`接口新增`DateTimeFormat`属性。 [#IBLQBX](https://gitee.com/RRQM_Home/TouchSocket/issues/IBLQBX) --  修复 自定义区间适配器在未找到开始字符的情况下,也会缓存数据的bug。 [#IBKPXU](https://gitee.com/RRQM_Home/TouchSocket/issues/IBKPXU) --  调整 自定义区间适配器的运行逻辑,简化使用方式。 [#IBKPXU](https://gitee.com/RRQM_Home/TouchSocket/issues/IBKPXU) - -#### TouchSocket.Rpc - --  修复 `IRpcActionFilter.ExecutedAsync`在执行异常时,`Exception`参数一直为空的bug。[#IBK579](https://gitee.com/RRQM_Home/TouchSocket/issues/IBK579) - -#### TouchSocket.Dmtp - --  新增 `TokenVerifyException`异常信息中新增Metadata属性。 --  修复 `TcpDmtpService`中使用`e.IsPermitOperation = false;`拒绝客户端无效的bug。[#IBKO6A](https://gitee.com/RRQM_Home/TouchSocket/issues/IBKO6A) - -*** - -## v3.0.13 - -**更新日期:** 2025.1.27 - -**更新描述:** - -兼容性修复升级。 - - -**更新详情:** - - -#### TouchSocket.Core - --  修复 在`net framework`下,如果`Span`为空时,`ToString`会异常的`bug`。 [#IBIYRQ](https://gitee.com/RRQM_Home/TouchSocket/issues/IBIYRQ) - -*** - -## v3.0.12 - -**更新日期:** 2025.1.19 - -**更新描述:** - -兼容性修复升级。 - - -**更新详情:** - - -#### TouchSocket.Core - --  优化 `FileLogger`的路径合并方式。 - -*** - - -## v3.0.11 - -**更新日期:** 2025.1.12 - -**更新描述:** - -兼容性修复升级。 - - -**更新详情:** - -#### All - --  新增 全系新增`EasyTask.ContinueOnCapturedContext`的设定,以解决`Unity3d`在`webgl`平台下,会卡住的问题。 - -#### TouchSocket.Core - --  新增 `IWaitHandlePool`的接口抽象。 - -#### TouchSocket.Http - --  新增 `HttpResponse`新增`SetRedirect`重定向功能。[#IBG1QT](https://gitee.com/RRQM_Home/TouchSocket/issues/IBG1QT) --  优化 `HttpRequest`在请求时的编码效率。 --  修复 `HttpRequest`不会对中文等非`Ascii`编码的字符进行`url encode`的`bug`。[#IBGATN](https://gitee.com/RRQM_Home/TouchSocket/issues/IBGATN) --  修复 在客户端,执行`IWebApiRequestPlugin`插件时,`HttpRequest`无法通过`GetContent`或者`GetBody`获取数据。[#IBGARB](https://gitee.com/RRQM_Home/TouchSocket/issues/IBGARB) - -*** - -## v3.0.10 - -**更新日期:** 2025.1.5 - -**更新描述:** - -兼容性修复升级。 - - -**更新详情:** - -#### TouchSocket.Core - --  新增 `AsyncBoundedQueue`新增`Capacity`、`Count`等属性。 --  新增 `CustomJsonDataHandlingAdapter`自定义适配器,方便自定义继承实现。 --  新增 `CustomCountSpliterDataHandlingAdapter`固定数量分隔符适配器。[#IBF0Z7](https://gitee.com/RRQM_Home/TouchSocket/issues/IBF0Z7) --  新增 `IByteBlock`新增`ReadT`和`WriteT`方法。 - -#### TouchSocket.Sockets - --  修复 `CheckClearPlugin`在客户端断开后,仍然会触发断开的bug。[#IBECPA](https://gitee.com/RRQM_Home/TouchSocket/issues/IBECPA) - -*** - -## v3.0.9 - -**更新日期:** 2024.12.22 - -**更新描述:** - -兼容性修复升级。 - - -**更新详情:** - -#### TouchSocket.Core - --  新增 `SystemTextJsonStringToClassSerializerFormatter`转换器。 --  新增 `IPackage`源生成时,默认支持`Guid`。[#IBC1FH](https://gitee.com/RRQM_Home/TouchSocket/issues/IBC1FH) - -#### TouchSocket.Sockets - --  修复 `TcpService`在`ResetId`后,偶发性客户端集合丢失客户端的问题。[#IBCUHW](https://gitee.com/RRQM_Home/TouchSocket/issues/IBCUHW) --  修复 `WaitingClient`使用`ResponsedData`时第二次接受报错。[#IBC8D2](https://gitee.com/RRQM_Home/TouchSocket/issues/IBC8D2) - -#### TouchSocket.SerialPort - --  修复 `SerialPortClient`串口组件`ProtectedMainSerialPort`属性`null`错误。[#IBBQ4A](https://gitee.com/RRQM_Home/TouchSocket/issues/IBBQ4A) - - -#### TouchSocket.AspNetCore - --  修复 `IWebSocketDmtpService`接口中缺少`Clients`属性。[#IBBQS5](https://gitee.com/RRQM_Home/TouchSocket/issues/IBBQS5) - -#### TouchSocket.Modbus - --  优化 `ModbusRtu`在`crc`校验失败时,会抛出`ResponseMemoryVerificationError = 99`的错误码。[#IBC1J2](https://gitee.com/RRQM_Home/TouchSocket/issues/IBC1J2) - -#### TouchSocket.WebApi - --  新增` WebApi`支持`Put`、`Delete`、`Options`请求方式。 --  修复 `WebApi`当参数解析异常时,`RpcActionFilterAttribute`无法捕获。[#IBCI94](https://gitee.com/RRQM_Home/TouchSocket/issues/IBCI94) --  调整 `WebApiParserPlugin`使用`Mapping`代替`GetRouteMap`和`PostRouteMap`。 - -#### TouchSocket.JsonRpc - --  优化 `JsonRpc`支持`Aot`。 - -*** - -## v3.0.8 - -**更新日期:** 2024.12.15 - -**更新描述:** - -兼容性修复升级。 - - -**更新详情:** - -#### TouchSocket.Sockets - --  修复 `IPHost`类,在`Mono`运行时会异常的bug。 [#IBAG51](https://gitee.com/RRQM_Home/TouchSocket/issues/IBAG51) --  调整 `UdpSessionBase`类调整`ReceivingData`为`OnUdpReceiving`。 --  新增 `Udp`组件,新增`IUdpReceivingPlugin`插件。[#IBB1F6](https://gitee.com/RRQM_Home/TouchSocket/issues/IBB1F6) - -#### TouchSocket.AspNetCore - --  修复 当使用基于`WebSocket`协议,搭建`DmtpServer`服务时 使用`app.UseOutputCache()`缓存中间件导致连接失败后,重连无响应。[#IBAPTQ](https://gitee.com/RRQM_Home/TouchSocket/issues/IBAPTQ) - -#### TouchSocket.Dmtp - --  修复 `DmtpActor`类,在连接成功时,`Handshaking`事件参数`e.Message`无法传回到请求端。 - -#### TouchSocket.SerialPorts - --  修复 `SerialPortClient`类,在关闭或者释放时,资源无法释放的bug。 [#IBB8FD](https://gitee.com/RRQM_Home/TouchSocket/issues/IBB8FD) - -*** - -## v3.0.7 - -**更新日期:** 2024.12.8 - -**更新描述:** - -兼容性修复升级。在.Net9.0中,启用Lock锁代替object锁,提高锁效率。 - - -**更新详情:** - -#### TouchSocket.Core - --  修复 `ValueByteBlock`类,在`WritePackage`时数据无效的bug。 --  优化 开放`PackageFastBinaryConverter`类,以支持二进制数据序列化源生成。 - -#### TouchSocket.Dmtp - --  新增 `DmtpRpc`新增`RpcDispatcher`调度器,支持多线程并发,或者单线程调度。 --  优化 `DefaultSerializationSelector`优化支持`System.Text.Json`源生成模式的序列化。 --  调整 触发`OnFileTransferred`事件的时机调到`SendAsync`前面,目的是保证调用方在收到回复时,响应方已经完成事件处理。此操作理论上不会对现有运行逻辑造成影响。 --  修复 `DmtpRpc`在发送`Rpc`请求时,如果请求模式使用`OnlySend`、或者`WaitSend`,则会抛出异常的bug。[#IB9F8P](https://gitee.com/RRQM_Home/TouchSocket/issues/IB9F8P) - - -#### TouchSocket.Modbus - --  新增 在`IModbusResponse`返回响应时,会同时携带返回响应的当前请求`IModbusRequest`。 - -#### TouchSocket.WebApi - --  优化 `WebApiSerializerConverter`类,以支持更好的`System.Text.Json`源生成模式的序列化。 - -#### TouchSocket.Rpc - --  新增 在`ReenterableAttribute`特性,可以强制设置`Rpc`函数是否为重入模式。 --  新增 `ConcurrencyRpcDispatcher`并发调度器,支持多线程并发。 --  新增 `ImmediateRpcDispatcher`当前调度器,直接在当前线程执行。 --  新增 `QueueRpcDispatcher`队列调度器,把所有请求,放在一个队列中,等待处理。 - -*** - - -## v3.0.5(6) - -**更新日期:** 2024.12.1 - -**更新描述:** - -兼容性修复升级。全面支持`PluginManager`容器化模块和`Scoped`区域划分。 - ->此修改不影响现有运行逻辑。但是如果是Asp.Net Core项目,则可能会影响`Scoped`服务运行结果。 - -**更新详情:** - -#### TouchSocket.Core - --  新增 `FlowOperator`类。以实现流量速度限制。 --  新增 `PluginManager`类。新增`FromIoc`的设定,支持插件容器化。 --  优化 `Result`类。使用`record`修饰,简化使用逻辑。 --  调整 `IResolver`的`Resolve`行为。当服务未注册时,会返回`null`。不会再触发异常。 --  调整 `SetupConfigObject`在Build时,会创建一个新的Scoped生命周期的容器。这个在现有架构下,不会影响运行结果。 --  修复 `Metadata`在添加相同键值时,会异常的bug,正确操作应该是覆盖旧值。 --  移除 `IResolver`移除对`IRegistered`接口的实现,所以无法再使用`IResolver`判断某个服务是否已注册。 --  移除 `IResolver`移除对`TryResolve`扩展方法实现,请使用`Resolve`直接代替。 - -#### TouchSocket.Sockets - --  修复 `IPHost`在`.NET Framework`下,设定任意端口无效的bug。 [#IB7PM1](https://gitee.com/RRQM_Home/TouchSocket/issues/IB7PM1),[#IA8NQ2](https://gitee.com/RRQM_Home/TouchSocket/issues/IA8NQ2) - -#### TouchSocket.Http - --  新增 `HttpBase`新增`ReadCopyToAsync(Stream stream, HttpFlowOperator flowOperator)`方法。支持传输进度、速度显示。[#IB7GIC](https://gitee.com/RRQM_Home/TouchSocket/issues/IB7GIC) --  新增 `HttpFlowOperator`类,支持Http上传、下载文件(流)时,可以方便的实现限速、传输进度、速度显示等。 --  新增 `StreamHttpContent`在传输流数据时,支持`HttpFlowOperator`相关操作。 - - -#### TouchSocket.Rpc --  修复 `Rpc`代码生成器在生成通用泛型类型时,会失败的bug。[#IB7IB2](https://gitee.com/RRQM_Home/TouchSocket/issues/IB7IB2) - - -*** - - -## v3.0.4 - -**更新日期:** 2024.11.24 - -**更新描述:** - -修复升级。**WebSocket有少量代码差异**,下面会详细介绍。 - -**更新详情:** - -#### TouchSocket.Core --  新增 `JsonPackageAdapter`适配器,专门解决纯Json数据格式的粘分包。详情参见:[JsonPackageAdapter](./packageadapter.mdx)。 --  新增 `IByteBlock`新增`WriteNormalString`方法,用于写入普通字符串。 --  新增 `Container`容器新增Scoped生命周期,但是本身容器并未实现功能,如果需使用,请使用[TouchSocket.Core.DependencyInjection](https://www.nuget.org/packages/TouchSocket.Core.DependencyInjection)。 --  新增 `Tcp、NamedPipe、SerialPort、DmtpRpc、JsonRpc、WebApi`等所有组件,支持Scoped容器。(需配合[TouchSocket.Core.DependencyInjection](https://www.nuget.org/packages/TouchSocket.Core.DependencyInjection)容器)。 - -#### TouchSocket.Http --  新增 `HttpStaticPagePlugin`新增`SetContentTypeProvider(Action provider)`方法。 --  新增 `WebSocket`新增`CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription)`方法。 --  修复 `WebSocket`在`Close`时,不符合规范的bug。[#IAAF3U](https://gitee.com/RRQM_Home/TouchSocket/issues/IAAF3U),[#IB5IH0](https://gitee.com/RRQM_Home/TouchSocket/issues/IB5IH0) --  调整 `IWebSocketClosingPlugin`的事件参数,由`ClosedEventArgs`调整为`ClosingEventArgs`。 重新实现接口解决 - - -#### TouchSocket.Sockets - --  修复 `TcpClient`在重连之后,适配器失效的bug。 [#IB6KZJ](https://gitee.com/RRQM_Home/TouchSocket/issues/IB6KZJ) - -#### TouchSocket.Rpc --  新增 `Rpc`服务新增`IScopedRpcServer`。 - - -#### TouchSocket.SerialPort --  修复 `SerialPortClient`在重连之后,适配器失效的bug。 - -#### TouchSocket.NamedPipe - --  新增 `INamedPipeSession`新增`DataHandlingAdapter`属性。 --  修复 `NamedPipeClient`在重连之后,适配器失效的bug。 - -#### TouchSocket.Modbus - --  优化 `Modbus`在写入时,会携带`IModbusResponse`的返回值。 [#IATPWB](https://gitee.com/RRQM_Home/TouchSocket/issues/IATPWB) - - -*** - -## v3.0.3 - -**更新日期:** 2024.11.17 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  优化 `WaitingClient`在收到`ResponsedData`数据时,优先建议使用`ByteBlock`,在高效场景中代替原`Data`属性。 - -*** - - -## v3.0.2 - -**更新日期:** 2024.11.15 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 串口的发送与接收无法通过插件获取到原始数据。[#IB4NF4](https://gitee.com/RRQM_Home/TouchSocket/issues/IB4NF4) - -*** - -## v3.0.0(1) - -**更新日期:** 2024.11.13 - -**更新描述:** - -大版本升级,有部分不兼容性升级。所以请在升级前做好备份,同时在升级之后,请务必阅读[v3.0升级指南](https://gitee.com/RRQM_Home/TouchSocket/issues/IB45VJ)。 - -**本次改动在运行时完全兼容`v2.1`,所以客户端和服务器可以差异版本更新。** - -**更新详情:** - -#### SDK - --  新增 新增`net9.0`支持。 --  移除 移除`net7.0`支持,但是不影响`net7.0`使用,因为最低到`net6.0`的支持。 - - -#### TouchSocket.Core - --  优化 `Plugin`组件默认支持`AOT`,不再借助委托实现。 --  优化 `AppMessenger`组件默认支持`AOT`。 --  优化 `DependencyObject`类使用“懒汉式”加载内部成员,减少内存使用。 --  新增 `PluginManager`支持在运行时移除插件。 --  新增 `Method`类,在方法、或类添加`[DynamicMethod]`特性时,默认支持`AOT`,为动态调用提供极大方便。 - -#### TouchSocket.Core.DependencyInjection - --  修复 `AspNetCoreContainer`不支持`KeysService`的bug。 - -#### TouchSocket.Sockets - --  修复 `IReceiver`在启用缓存模式时,如果已经完成接收,则会抛出异常的bug。 [#IB44LL](https://gitee.com/RRQM_Home/TouchSocket/issues/IB44LL) - -#### TouchSocket.Http - --  调整 `HttpRequest`在请求时,不用传参,直接使用默认构造函数即可。 --  移除 `GetMultifileCollection`相关扩展方法,使用`GetFormCollectionAsync`代替。 - -#### TouchSocket.Rpc - --  新增 `ICallContext`继承`IDependencyObject`,支持**扩展属性**读写,可以更方便的开发。 --  调整 `Rpc`特性取消构造函数入参,使用属性设置。受影响的有:`DmtpRpc`、`JsonRpc`、`XmlRpc`、`WebApi`。 - - -#### TouchSocket.Dmtp - --  修复 `DmtpRpc`在`Avalonia-Web`工作时,连接时间超长的bug。 --  修复 `IWebSocketDmtpClient`不实现`IDmtpClient`的bug。 --  修复 `DmtpHeartbeatPlugin`在长时间运行时,可能会失效的bug。 --  调整 `[DmtpRpc]`特性不再允许继承,所有设置也是通过属性设置。 - -#### TouchSocket.JsonRpc - --  调整 `[JsonRpc]`特性不再允许继承,所有设置也是通过属性设置。 - -#### TouchSocket.WebApi - --  新增 `[FromBody]`特性,支持指定参数来源自Http的请求Body。 --  新增 `[FromForm]`特性,支持指定参数来源自Http的请求Form表单。 --  新增 `[FromHeader]`特性,支持指定参数来源自Http的请求Header。 --  新增 `[FromQuery]`特性,支持指定参数来源自Http的请求Query。 --  调整 `[WebApi]`特性不再允许继承,所有设置也是通过属性设置。 --  调整 WebApi的请求方式,目前全部使用`WebApiRequest`类来表示全部入参,一般如果使用代理,或者源生成的话,只需要重新生成即可。 --  移除 `HttpMethodType.GET`枚举,使用`HttpMethodType.Get`代替。 --  移除 `HttpMethodType.POST`枚举,使用`HttpMethodType.Post`代替。 - -#### TouchSocket.XmlRpc - --  调整 `[XmlRpc]`特性不再允许继承,所有设置也是通过属性设置。 - -*** - -
-
- -
-v2.1 -
- -## v2.1.10 - -**更新日期:** 2024.10.25 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  优化 `CheckClearPlugin`插件频繁输出poll日志的不合理设计。 - -*** - -## v2.1.9 - -**更新日期:** 2024.10.14 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 CheckClearPlugin长时间工作时可能失效的bug。 - - -*** - -## v2.1.8 - -**更新日期:** 2024.10.11 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 TcpService在Stop的时候,有个内部异常打了log。 [#IAWD4N](https://gitee.com/RRQM_Home/TouchSocket/issues/IAWD4N) - - -*** - -## v2.1.7 - -**更新日期:** 2024.10.5 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 TcpService在启动时如果异常,则无法再重新启动的bug。 - -*** - -## v2.1.6 - -**更新日期:** 2024.10.1 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  新增 HttpContent机制,能上传超大数据。 --  新增 StreamHttpContent,能上传流数据,例如:文件流。 --  修复 Task内部异常时没有及时try,导致全局捕获时有无用捕获。 --  修复 TcpServiceBase在调用StopAsync时,IServerStopedPlugin插件无法触发的bug。 --  移除 TriggerQueue无用类。 --  优化 HttpRequest,使其能上传超大数据。 - -*** - -## v2.1.4(5) - -**更新日期:** 2024.9.23 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  新增 DmtpRpc恢复性新增Xml序列化。 --  修复 使用源生成(IPackage)打包时报错。[#IASTWJ](https://gitee.com/RRQM_Home/TouchSocket/issues/IASTWJ) --  修复 Result泛型类中隐式转换错写成显示转换的bug。 --  修复 DmtpRpc序列化选择器没有预留Json序列化配置的bug。 - - -*** - - -## v2.1.3 - -**更新日期:** 2024.9.22 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  优化 部分类,方法注释。 --  优化 多数英文字符串资源。 --  修复 Tcp、NamedPipe、SerialPort等组件在主动调用Close时,ClosedEventArgs参数属性Manual为false的bug,导致重连插件偶发性再次连接。[#IASH1A](https://gitee.com/RRQM_Home/TouchSocket/issues/IASH1A) --  修复 `ByteBlock`类部分bug。 --  移除 `DecimalConver`类,该类功能已完全由`TouchSocketBitConverter`代替,属于无用类。 --  调整 受保护方法`ProtectedResetId`方法,名称更改为`ProtectedResetIdAsync`。 - -*** - - -## v2.1.2 - -**更新日期:** 2024.9.19 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  优化 部分类,方法注释。 --  修复 TcpClient在释放时,重连插件会无限连接的bug,该bug会导致CPU占用过高。[#IAS9NG](https://gitee.com/RRQM_Home/TouchSocket/issues/IAS9NG) - -*** - -## v2.1.1 - -**更新日期:** 2024.9.18 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  新增 在`MemoryCache`中实现新的`TryGetValue`方法。 --  优化 密封`CacheEntry`类并优化缓存管理逻辑。 --  优化 扩展`PackageExtensions`类,增加泛型方法以提高可读性和可重用性。 --  优化 优化`HttpStaticPagePlugin`构造函数和`StaticPageOptions`类以简化静态页面配置。 --  修复 在`FastBinaryFormatter`中改进序列化逻辑,特别是对于**多维数组**的处理。 [#IARKE1](https://gitee.com/RRQM_Home/TouchSocket/issues/IARKE1) --  修复 在`IPackage`中改进打包、解包逻辑,特别是对于**多维数组**的处理。 - -*** - -## v2.1.0 - -**更新日期:** 2024.9.15 - -**更新描述:** - -大版本升级,有部分不兼容性升级。所以请在升级前做好备份,同时在升级之后,请务必阅读[v2.1升级指南](https://gitee.com/RRQM_Home/TouchSocket/issues/I9QSDK)。 - -**更新亮点:** - -本次更新,主要有以下亮点: - -1. 全系支持Span、ValueTask、Memory、Unsafe等依赖。大幅提升并发性能与低GC能力。 -2. 全系组件,尽量多的提供了异步Api,大幅度提升并发能力。 -3. 重构fast序列化,Package包模式、Http、WebSocket等组件,使之更加易用。 -4. 资源国际化。本次更新会在内部使用中英双语信息提示,这在日志记录,堆栈跟踪等场景更加符合区域化。 -5. 增加完整注释。基本上能达到95%的代码注释率。 - -**更新详情:** - -#### TouchSokcet.Core - --  新增 TouchSocketBitConverter新增To、UnsafeTo、WriteBytes、UnsafeWriteBytes等可以直接操作Span。 --  新增 Crc类新增Span相关转换。 --  新增 CustomDataHandlingAdapter新增`bool TryParseRequest(ref TByteBlock byteBlock, out TRequest request)`方法,可以同步完成适配器数据解析。 --  新增 SingleStreamDataAdapterTester泛型测试器,可以对TryParseRequest进行完整性测试。 --  新增 IPackage源生成器新增自定义PackageMember特性,用来定义打包的顺序和自定义转换器。 --  新增 SetupConfigObject的SetupAsync。 --  优化 CustomDataHandlingAdapter支持结构体作为泛型类型。 --  优化 Fast序列化支持自定义FastSerializerContext,这可以极大的利用源生成来决定序列化和反序列化。 --  调整 分离MemoryCache的同步和异步接口。 --  调整 ByteBlock取消Stream的继承,如果需要使用Stream,可以使用ByteBlock.AsStream()。 --  调整 Gzip类调整ByteBlock参数为Stream。 --  调整 CustomDataHandlingAdapter解析的数据,均会以ReadonlySpan的形式投递。 --  调整 IPackage接口,使之既可以在ByteBlock工作,也可以在ValueByteBlock工作。 --  调整 PluginManager使用接口作为唯一键,规定一个接口中有且仅有一个方法。 --  调整 ByteBlock、ValueByteBlock均继承IByteBlock接口规范。 --  移除 ByteBlock、ValueByteBlock移除Buffer属性,如果想获取有效数据,可以通过Memory、Span等获取,如果想获取容量可以使用TotalMemory属性。 - -#### TouchSokcet.Sockets - --  调整 Tcp、Udp组件,默认情况下适配器将为null,并且可以正常工作。 --  调整 SocketClient、ISocketClient等服务器辅助类,改名为TcpSessionClient和ITcpSessionClient。 - - -*** - -
-
- - -
-v2.0 -
- -## v2.0.18 - -**更新日期:** 2024.9.10 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 当Cancel延迟到Reset之后时,新获取的waitData会出现Status为Cancel异常bug [#IAQ2AI](https://gitee.com/RRQM_Home/TouchSocket/issues/IAQ2AI)。 - - -*** - - -## v2.0.17 - -**更新日期:** 2024.8.30 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 使用AspNetCore容器时,默认没有注册ILog的bug。 - -*** - - -## v2.0.16 - -**更新日期:** 2024.8.19 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 tcp在winform中会使用主线程接收的bug,导致各种同步接收异常。 - - -*** - - -## v2.0.15 - -**更新日期:** 2024.8.9 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 当客户端断开连接时,http响应会出现object is null的bug提示。 - - -## v2.0.13(14) - -**更新日期:** 2024.7.24 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 重连插件在长时间运行时,会失效的bug。 --  修复 ssl加密下,连接响应时间过长bug[#IAET6V](https://gitee.com/RRQM_Home/TouchSocket/issues/IAET6V) 。 - -## v2.0.12 - -**更新日期:** 2024.7.17 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 socket在接收连接时,异常无法拦截的bug[#IADIGX](https://gitee.com/RRQM_Home/TouchSocket/issues/IADIGX)。 --  调整 日志记录在执行时,先判断日志组件的可用性。 - -## v2.0.11 - -**更新日期:** 2024.7.12 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 Metadata在Add时不会覆盖原key的bug。 - - - -## v2.0.10 - -**更新日期:** 2024.5.31 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 WebSocket快捷命令行bug。 [#I9TG3V](https://gitee.com/RRQM_Home/TouchSocket/issues/I9TG3V)。 - - -## v2.0.9 - -**更新日期:** 2024.5.29 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 UdpPackage适配器在工作时调时导致的bug。 [#I9SYTR](https://gitee.com/RRQM_Home/TouchSocket/issues/I9SYTR)。 --  修复 Http在GetBoundary时bug。 [#I9PXWT](https://gitee.com/RRQM_Home/TouchSocket/issues/I9PXWT)。 - - - -## v2.0.(7)8 - -**更新日期:** 2024.5.17 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 udp首次建立连接无法接收数据的bug。 [#I9PV7C](https://gitee.com/RRQM_Home/TouchSocket/issues/I9PV7C)。 - -## v2.0.6 - -**更新日期:** 2024.5.12 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 FlowGate的waitTime小于0时bug。 - - -## v2.0.5 - -**更新日期:** 2024.5.2 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 wsclient Received事件与插件触发bug。[#I9L9WI](https://gitee.com/RRQM_Home/TouchSocket/issues/I9L9WI)。 --  修复 ws命令行执行bug。 - -## v2.0.4 - -**更新日期:** 2024.4.30 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 linux平台下,Socket吞吐量大幅降低。[#I9KURV](https://gitee.com/RRQM_Home/TouchSocket/issues/I9KURV)。 --  修复 WebSocket在进行连接时,Host的Header写法错误,没有包含端口。[#I9KUVI](https://gitee.com/RRQM_Home/TouchSocket/issues/I9KUVI)。 - - -## v2.0.3 - -**更新日期:** 2024.4.14 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 Tcp在接收时,内存池释放存在延迟,可能导致内存池快速扩张,浪费内存。[#I9FVAA](https://gitee.com/RRQM_Home/TouchSocket/issues/I9FVAA)。 --  修复 在使用Host模型时,注入瞬态的TcpClient,在第一次获取实例是正常的,第二次就失败。[#I9G3SV](https://gitee.com/RRQM_Home/TouchSocket/issues/I9G3SV)。 --  修复 WebSocketClient连接其他服务器时显示 “操作已被取消”。[#I9GG05](https://gitee.com/RRQM_Home/TouchSocket/issues/I9GG05)。 --  新增 ConcurrentList新增实现IReadOnlyList接口。 - - -## v2.0.2 - -**更新日期:** 2024.4.1 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 TouchSocketBitConverter中的ToBooleans方法存在Bug[#I9C1UY](https://gitee.com/RRQM_Home/TouchSocket/issues/I9C1UY)。 --  修复 SystemExtensions中的GetBit和SetBit方法[#I9C1WM](https://gitee.com/RRQM_Home/TouchSocket/issues/I9C1WM)。 --  修复 HttpService多次响应下载文件时,不会响应的bug。 - - - -## v2.0.1 - -**更新日期:** 2024.3.16 - -**更新描述:** - -兼容性修复升级。 - -**更新详情:** - --  修复 HttpClient,无法通过HttpResponse.GetBody()获取响应内容[#I989SI](https://gitee.com/RRQM_Home/TouchSocket/issues/I989SI)。 --  修复 SetNoDelay 异常[#I979B0](https://gitee.com/RRQM_Home/TouchSocket/issues/I979B0)。 --  修复 调用Dmtp服务的大数据传输时,如果循环调用会出现收到的数据和发送的数据不一致。实际上该问题是由`ByteBlock`写入扩容bug而导致的[#I96FNF](https://gitee.com/RRQM_Home/TouchSocket/issues/I96FNF)。 - - -## v2.0.0 - -**更新日期:** 2024.3.9 - -**更新描述:** - -此版本是大版本更新。可能会产生很多不兼容部分,所以升级之前请做好备份,并且请详细阅读下列更新内容。 - -**升级指南:** - -- 升级时请先升级至`2.0.0-beta.190`,再升级至`2.0.0-rc.2`版本,因为正式版对于[Obsolete]特性的成员直接删除了,所以为友好升级,请先升级至此版本。 -- 由2.0.0-beta.200至2.0.0-beta.220 [#I8DE1D](https://gitee.com/RRQM_Home/TouchSocket/issues/I8DE1D) -- 由2.0.0-beta.220至2.0.0-beta.230 [#I8LAX4](https://gitee.com/RRQM_Home/TouchSocket/issues/I8LAX4) - -**更新详情:** - -#### TouchSokcet.Core - --  优化 FileLogger支持指定不同目录。 --  调整 所有自定义插件必须在自身内,主动调用e.InvokeNext()时,才会调用下一个插件。不然会中断插件传递。同时e.Handled功能依然有效。 --  调整 Log项。LogType调整为LogLevel,并且不需要位运算。直接按日志等级输出。 --  调整 修改IPluginsManager名称为IPluginsManager。 --  移除 DependencyProperty中,移除对类型的定义。 --  移除 所有组件的基础插件,强制用户插件必须继承PluginBase,然后实现需要的接口。 --  移除 BytePool在创建ByteBlock时,移除EqualSize的设定,因为这会影响内存池的效率。 --  调整 修改所有委托为异步Task。 --  调整 修改所有Setup返回值为void。 --  修复 Metadata在0个成员长度时,会被反序列化成null的bug。 --  修复 PluginsManager在注册具有继承的插件时,会无法识别的bug。 - -#### TouchSokcet.Sokcets - --  优化 IPHost支持从int、string直接隐式转换。 --  调整 TouchSocket所有“ID”属性,改名为“Id”。 --  调整 TouchSocket所有插件的执行顺序,移动至内部重写方法之后。 --  调整 TouchSocket所有`ResetID`改名为`ResetId`。 --  调整 UseCheckClear项,SetDuration调整名称为SetTick。 --  调整 UseCheckClear项,不仅可以适用服务器,客户端也适用。 --  调整 Config配置中,SetDataHandlingAdapter调整为SetTcpDataHandlingAdapter。 --  调整 适配器项,CustomDataHandlingAdapter中的Filter方法中,byteBlock参数使用in修饰。 --  调整 适配器项,DataHandlingAdapter改名为TcpDataHandlingAdapter。 --  调整 适配器项,DataAdapterTester改名为TcpDataAdapterTester。 --  调整 Config项,所有适配器的相关配置,使用SetAdapterOption配置。 --  移除 UsePlugin的显式配置,当调用ConfigurePlugins时,会自动启用。 - -#### TouchSokcet.Http - --  调整 WSCommandLinePlugin改名为WebSocketCommandLinePlugin。 --  新增 WebSocket添加[同步非阻塞Read](./websocketservice.mdx#52-websocket显式readasync)。 --  新增 WebSocket的WSDataFrame新增IsPing、IsPong、IsText、IsBinary、IsClose等属性。 --  新增 静态网页插件新增NavigateAction与ResponseAction等委托,可以在静态页面请求之前重定向,或者请求返回时设置header等。 - -#### TouchSokcet.Rpc - --  移除 整体功能迁移至TouchSokcet(Pro).Dmtp。 --  调整 [RpcActionFilter](./rpcactionfilter.mdx#33-规则)执行策略和顺序 --  调整 修改ConfigureRpcStore为AddRpcStore。 --  新增 [RealityProxy](./rpcgenerateproxy.mdx)透明代理方式。 --  新增 [DispatchProxy](./rpcgenerateproxy.mdx)添加OnBefore和OnAfter的AOP调用。 - -#### TouchSokcet(Pro).Dmtp - --  调整 原TouchRpc全系改名为Dmtp。例如:原TcpTouchRpcClient改名为TcpDmtpClient。 --  调整 原TouchRpc中InvokeOption,改名为DmtpInvokeOption。InvokeOption依然有效,但是在调用DmtpRpc时,则无法指定序列化方式。所以可能需要使用DmtpInvokeOption。 --  调整 原TouchRpc中Invoke直接调用的方式,改为InvokeT。 --  调整 Dmtp相关配置,使用SetDmtpOption配置。 --  移除 **暂时**移除EventBus功能,后续可能考虑添加。 --  新增 ws协议的TouchRpc服务端,增加HttpContext上下文获取。 --  新增 文件传输项,开放增加SetMaxSpeed功能。 --  新增 DmtpRpc。 --  新增 DmtpRpc组件在调用时,可以通过DmtpInvokeOption传入Metadata元数据。 --  修复 DmtpRpc在调用无ref,out的函数时,参数会为null的bug。 - -#### TouchSokcet.JsonRpc - --  修复 JsonRpc使用内联数组调用[#I79OFZ](https://gitee.com/RRQM_Home/TouchSocket/issues/I79OFZ)。 --  调整 Rpc服务使用调用上下文时不需要再添加`IncludeCallContext`。 - -#### TouchSokcet.WebApi - --  新增 WebApi新增[Swagger页面](./swagger.mdx)。 --  调整 Rpc服务使用调用上下文时不需要再添加`IncludeCallContext`。 - -#### TouchSokcet.XmlRpc - --  调整 Rpc服务使用调用上下文时不需要再添加`IncludeCallContext`。 - -#### TouchSokcet(Pro).Hosting - --  新增 新发布Hosting的包,用于构建更加强壮的运行程序。 - -#### TouchSokcet.SerialPorts - --  新增 新发布串口的包。 - -#### TouchSokcet(Pro).Modbus - --  新增 新发布Modbus的包,支持Tcp、Udp、Rtu、RtuOverTcp、RtuOverUdp协议的主站(Poll)和从站(Slave)。 ---- - - -
-
- -
-v1.3 -
- -## v1.3 - -更新日期:2023.3.1 - -更新描述:兼容性更新。 - - -  优化 整体增加异步方法。 - -  优化 Rpc源代码生成策略,支持接口实例并存。 - -  修复 TcpClient在UseReconnect插件时,Disconnect事件不触发bug。 - -  新增 ws协议的TouchRpc服务端,增加HttpContext上下文获取。 ---- - -
-
- -
-v1.2 -
- -## v1.2 - -更新日期:2023.2.15 - -更新描述:兼容性更新。 - - -  优化 TouchRpc支持命名元组。 - -  优化 Rpc源代码生成策略。 - -  修复 TouchRpc在Websocket协议下,启动,连接异常bug。 - -  修复 TouchRpc在调用WaitSend下失败的bug。 - -  修复 TouchRpc在Handshaked时,调用Rpc超时bug。 - -  修复 序列化、反射在unity中使用il2cpp编译的bug。 - -  修复 反序列化在初次加载时会失败的bug。 - -  修复 BytePool没有公共构造函数的bug。 - -  新增 ws协议的TouchRpc服务端,增加HttpContext上下文获取。 - -  新增 ByteBlock对于int,long等数据,写入和读取的时候支持大小端指定。 - -  新增 IServicePlugin插件,用于显示通知服务器的启动状态。 - -  新增 Rpc支持接口特性标记。 - -  调整 将BytePool由静态调整为实例,且由其Default实例作为默认。 ---- - -
-
- -
-v1.1 -
- -## v1.1 - -更新日期:2023.1.13 - -更新描述:小版本升级,可能会有不兼容。请按下列提示修改。 - - -  优化 TouchRpc系文件传输时,文件夹不存在的提示。 - -  优化 WaitingClient,当客户端断开连接时,可选是否抛出异常。 - -  优化 Fast序列化时。可选序列化只读属性。 - -  修复 多个不稳定Bug。 - -  新增 Tcp客户端新增Disconnecting事件。在主动Close时生效。 - -  调整 多个事件类名称修改,请按照提示修改即可。 - -  移除 多个无用方法参数。 ---- - -
-
- -
-v1.0 -
- -## v1.0.0 - -更新日期:2023.1.1 - -更新描述:大版本升级,请详细阅读下列更新日志。 - - -  升级 将最高版本升级为NET7。 - -  优化 Tcp系异步发送效率。 - -  优化 TouchRpc系Channel的稳健性。 - -  修复 多个不稳定Bug。 - -  新增 ValueByteBlock,在简单代码块里面能有效减少创建的类。 - -  新增 MemoryCache类,其功能类似微软官方。但是支持全部泛型。 - -  新增 [IPackage系](https://www.yuque.com/rrqm/touchsocket/ag9tyar9mmhsme0m)。该系列能以超高效率的进行二进制序列化。 - -  新增 SingleTimer类,不可重入的Timer。 - -  新增 Jsonrpc支持自定义适配器解析(EE) - -  新增 严重TouchRpc系OnRouting通知,所有的客户端之间的通信,都必须经过OnRouting的筛查。 - -  新增 TouchRpc系小文件传输,在文件小于1Mb时,其传输效率是常规传输的10倍以上。 - -  新增 TouchRpc系超大文件多链路传输,支持多个客户端协同传输同一个文件,这在互联网环境中,效率比常规传输提高类3-5倍。 - -  新增 TouchRpc系Redis组件,能实现双端共同存储。 - -  调整 严重精简所有命名空间,删除所有三级命名空间。例如:TouchSocket.Core.ByteManager精简为TouchSocket.Core。 - -  调整 严重删除Newtonsoft.Json的源代码嵌入。全局的Json会根据环境动态调整,详情见[Json工具](https://www.yuque.com/rrqm/touchsocket/emqy43#PfVh1) - -  调整 严重框架默认日志由ConsoleLogger,替换为EmptyLogger(不输出任何东西)。 - -  调整 严重Tcp全系,在连接时,ID的初始值使用long类型从0递增。 - -  调整 严重Tcp服务器,将定时清理无数据交互的选项替换为UseCheckClear插件。并且默认没有启用,需要手动加入。 - -  调整 Tcp系适配器,取消部分参数。 - -  调整 DataLock改名为DataSecurity。 - -  调整 EasyAction改名EasyTask。 - -  调整 IMessage改名IMessageObject。 - -  调整 TokenInstance改名MessageInstance。 - -  调整 TouchRpc系,精简常规文件传输操作。 - -  调整 严重TouchRpc系,所有插件通知参数,默认都设为不允许操作,需要手动设置e.IsPermitOperation=true。 - -  移除 Newtonsoft.Json的源代码嵌入。全局的Json会根据环境动态调整,详情见[Json工具](https://www.yuque.com/rrqm/touchsocket/emqy43#PfVh1)。 - -*** 更新示例指南 *** - -(1)适配器参数报错:直接删除isAsync参数,以及isAsync为**True**的所有逻辑。 -![image.png](@site/static/img/docs/upgrade-1.png) -(2)依赖属性的声明报错:增加泛型约束即可,详情查看[依赖属性](https://www.yuque.com/rrqm/touchsocket/ubk57o#jyzSl) -![image.png](@site/static/img/docs/upgrade-2.png) -(3)服务端定时清理警告:在配置插件中使用UseCheckClear,并且进行相关配置。 -![image.png](@site/static/img/docs/upgrade-3.png) -![image.png](@site/static/img/docs/upgrade-4.png) - ---- - -
-
- -
-v0 -
- -## 版本号: 0.7.0 -更新日期:2022.9.21 -更新描述:兼容性更新,增强型更新。**RPC内容需要客户端与服务器同步更新**。 -更新详情: - -优化 -1. Fast二进制序列化,支持自定义序列化。 -2. TouchRpc全系,在文件传输等大型IO时,由于心跳失败而断开连接。 -3. 优化AspNetCore的IContainer。 -4. TcpCommandLinePlugin与WSCommandLinePlugin支持获取客户端参数。 - -新增 -1. 插件实例会以单例注入容器。 -2. 所有适配器支持[缓存超时](https://www.yuque.com/rrqm/touchsocket/83526e6320dfc85fef317d850aa51e92#Z0S0g)设定。 -3. 修改所有事件为委托。 -4. 开放[AspnetCore](https://www.yuque.com/rrqm/touchsocket/55e5bbf58745fa639dba511c7bcd54d1#WqOmh)创建Tcp,Http等服务器的配置。 -5. IClient增加发送、接收的最后时间记录。 -6. Http支持多文件上传(目前仅支持小文件,具体大小以实际运行内存为准,实测100Mb没问题)。 -7. Websocket插件默认会处理Close报文。且插件支持Close。 -8. Rpc支持模板代码重写。 -9. TouchRpc支持元组。 -10. JsonRpc支持Websocket协议。 - -修改 -1. IScopedContainer修改为IContainerProvider - -修复 -1. BytePool回收内存时不判断大小的bug。 - -删除 -1. 无。 - - ---- - -## 版本号: 0.6.0 -更新日期:2022.9.10 -更新描述:兼容性更新,增强型更新。**专为Unity 3D适配**。 -更新详情: - -优化 -1. Gzip的压缩效率。 -2. 发送效率。 - -新增 -1. IDataCompressor数据传输压缩接口。 -2. [RemoteStream](https://www.yuque.com/rrqm/touchsocket/ukq0mu)支持数据读写压缩。 -3. WaitResultPackageBase类,专属非序列化的数据格式化。 -4. DelaySender[延迟缓存发送](https://www.yuque.com/rrqm/touchsocket/1f21a56ee75f896a5b5b38b37b071881#RL0kx)。 - -修改 -1. 无 - -修复 -1. Rpc注册服务为单例时,实际上是瞬时服务的bug。 - -删除 -1. 独立线程发送。 - ---- - -## 版本号: 0.5.0 -更新日期:2022.9.1 -更新描述:兼容性更新,增强型更新。 -更新详情: - -优化 -1. 全局资源的获取逻辑。 - -新增 -1. Container增加卸载注册功能。 -2. FilePool新增FileStorageStream的获取。 -3. http客户端(及websocket)支持代理和验证代理。 -4. TouchRpc全系新增[远程文件操作](https://www.yuque.com/rrqm/touchsocket/pearz0) -5. TouchRpc(除udp)新增[远程流访问](https://www.yuque.com/rrqm/touchsocket/ukq0mu) - -修改 -1. 无 - -修复 -1. 修复Http客户端请求重复Header时的bug。 - -删除 -1. TouchRpc全系的事件操作,推荐直接插件的方式,或者使用TouchRpcActionPlugin然后添加委托。 - - -更新示例 -TouchRpc的相关事件均已使用插件代替。所以请使用插件实现操作。如果需要事件等功能的话,可以用TouchRpcActionPlugin的插件实现。例如: -```csharp showLineNumbers -.UsePlugin() -.ConfigurePlugins(a=> -{ - a.Add>()//此处的逻辑可用插件替代完成。 - .SetFileTransfering((client, e) => - { - //有可能是上传,也有可能是下载 - client.Logger.Info($"服务器请求传输文件,ID={client.ID},请求类型={e.TransferType},文件名={e.FileInfo.FileName}"); - }) - .SetFileTransfered((client, e) => - { - //传输结束,但是不一定成功,需要从e.Result判断状态。 - client.Logger.Info($"服务器传输文件结束,ID={client.ID},请求类型={e.TransferType},文件名={e.FileInfo.FileName},请求状态={e.Result}"); - }); -}) -``` - ---- - -## 版本号: 0.4.5 -更新日期:2022.8.25 -更新描述:兼容性更新,增强型更新。 -更新详情: - -优化 -1. FileLogger的写入逻辑,大大地提升了写入效率。 - -新增 -1. [Pipeline适配器](https://www.yuque.com/rrqm/touchsocket/ofnliu) -2. [TLV适配器](https://www.yuque.com/rrqm/touchsocket/wug4bv) -3. WaitingClient支持按条件等待返回。 -4. 日志系统可以筛选日志的输出类型 -5. Rpc系统,可以使用单例、瞬时生命周期的服务。 -6. Rpc系统,可定义持久化模型。 -7. Rpc在使用瞬时生命周期的服务时,可以直接获取调用上下文。 -8. XmlRpc增加调用上下文。 - -修改 -1. 日志系统。 -2. Rpc的调用上下文均采用接口,例如:JsonRpc改为IJsonRpcCallContext,WebApi为IWebApiCallContext。 -3. IRpcActionFilter的参数列表。 - -修复 -1. UdpSession资源不释放的Bug。 - -删除 -1. 冗余元素。 - - ---- - -## 版本号: 0.3.5 -更新日期:2022.8.12 -更新描述:兼容性更新,增强型更新。 -更新详情: - -优化 -1. 各类客户端发送逻辑。 -2. Method类的调用逻辑。 - -新增 -1. 适配器可以设定发送IRequestInfo对象。 -2. 插件新增UseWebSocket的快捷方式。 -3. ReconnectionPlugin插件可以获得重连次数的重载设置。 -4. ProTcpService的服务注入。 -5. ProHttpService的服务注入。 -6. ProIOC容器的共享使用。 - -修改 -1. 各类发送逻辑,以最小化发送方法为基础,其余方法改为扩展方法。 -2. 相关接口的实现。 -3. 由网友[修改GetInfo](https://gitee.com/dotnetchina/TouchSocket/pulls/11) - -修复 -1. Container获取泛型失败bug。 -2. BetweenAnd适配器适配器部分bug。 -3. Router标签无法路由的bug。 -4. 修复TouchRpc推送文件状态不正确bug -5. 修复独立线程在断线重连后发送bug。 - -删除 -1. 冗余的发送方法,不影响上版本任何使用。 - - ---- - - -## 版本号: 0.2.4 -更新日期:2022.7.28 -更新描述:兼容性更新。 -更新详情: - -优化 -1. 优化IOC容器。 -2. 优化Metadata的写入方式。 -3. FileLogger,当日志文件达到1Mb时,会再新增文件序号。 - -新增 -1. Mapper类,支持简单类型映射 -2. Tcp服务器、客户端、udp等增加端口复用配置。 -3. Pro轮询式断线重连。 -4. ProNATService转发客户端重连。 - -修改 -1. RRQM二进制序列化,改名为Fast。 -2. TouchRpcClient连接时的Metadata,改为由Config配置注入。 -3. FilePool,取消延迟释放机制。 - -修复 -1. 修复WebSocket连接问题 - -删除 -1. 客户端直接调用的短线重连方式。仅保留在Config注入的功能。 - ---- - -## 版本号: 0.1.0 -更新日期:2022.7.16 -更新描述:初始化版本发布。由RRQMSocket迁移而来。 - -迁移指南: -### 1.所有类的命名空间修改,此处如果类型名未修改的话,可由vs智能提示解决。 -### 2.类型名称修改 -| 原类型名称 | 新类型名称 | -| --- | --- | -| RRQMBitConverter | TouchSocketBitConverter | -| RRQMConfig | TouchSocketConfig | -| RRQMConverter | TouchSocketConverter | -| RRQMDependencyObject | DependencyObject | -| MsgEventArgs | MsgEventArgs | -| RRQMEventAgrs | TouchSocketEventArgs | -| IServerProvider | IRpcServer | -| ServerProvider | RpcServer | -| RRQMOverlengthException | OverlengthException | - -### 3.使用逻辑修改 -1)原RRQMConfig设置Logger的方法,改为容器注入: -![image.png](@site/static/img/docs/upgrade-5.png) -断线重连逻辑 -![image.png](@site/static/img/docs/upgrade-6.png) -RpcStore使用变更 -如果是仅有一个Rpc解析器,那么可以直接删除RpcStore的声明,从而使用对应的**解析器实例**,直接注册服务。然后可以通过其属性RpcStore,获取到具体的RpcStore实例。 - -如果是有多个解析器,那么,首先可以使用任意一个解析器的RpcStore属性实例,作为主RpcStore,然后添加其他解析器。当然也可以直接new RpcStore,然后统一管理解析器。其中构造函数中的Container容器,可以直接new Container(),但是更建议使用和解析器相同的容器,这样注入的服务会变得全局可用。 - - -
-
- diff --git a/handbook/versioned_docs/version-3.0/waitingclient.mdx b/handbook/versioned_docs/version-3.0/waitingclient.mdx index b714454a5..cf29df3d9 100644 --- a/handbook/versioned_docs/version-3.0/waitingclient.mdx +++ b/handbook/versioned_docs/version-3.0/waitingclient.mdx @@ -275,4 +275,4 @@ this.m_tcpClient.Received =async (client,e) => ## 六、本文示例Demo - \ No newline at end of file + \ No newline at end of file diff --git a/handbook/versioned_docs/version-3.0/webapi.mdx b/handbook/versioned_docs/version-3.0/webapi.mdx index 019d2febe..32f180265 100644 --- a/handbook/versioned_docs/version-3.0/webapi.mdx +++ b/handbook/versioned_docs/version-3.0/webapi.mdx @@ -742,6 +742,6 @@ internal partial class AppJsonSerializerContext : JsonSerializerContext ## 十三、本文示例Demo - - + + diff --git a/handbook/versioned_docs/version-3.0/websocketclient.mdx b/handbook/versioned_docs/version-3.0/websocketclient.mdx index 68fe18afe..537b7fdf5 100644 --- a/handbook/versioned_docs/version-3.0/websocketclient.mdx +++ b/handbook/versioned_docs/version-3.0/websocketclient.mdx @@ -663,4 +663,4 @@ await webSocket.CloseAsync(WebSocketCloseStatus.EndpointUnavailable,"关闭");// ## 九、本文示例Demo - + diff --git a/handbook/versioned_docs/version-3.0/websocketservice.mdx b/handbook/versioned_docs/version-3.0/websocketservice.mdx index c9684ba80..8defbe7a6 100644 --- a/handbook/versioned_docs/version-3.0/websocketservice.mdx +++ b/handbook/versioned_docs/version-3.0/websocketservice.mdx @@ -910,4 +910,4 @@ await webSocket.CloseAsync(WebSocketCloseStatus.EndpointUnavailable,"关闭");// ## 八、本文示例Demo - \ No newline at end of file + \ No newline at end of file diff --git a/handbook/versioned_docs/version-3.1/adapterbuilder.mdx b/handbook/versioned_docs/version-3.1/adapterbuilder.mdx new file mode 100644 index 000000000..021bd56b2 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/adapterbuilder.mdx @@ -0,0 +1,212 @@ +--- +id: adapterbuilder +title: 适配器消息构建器 +--- + +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +适配器消息构建器,用于构建适配器消息。其目的就是简化上层接口的编写,让上层调用更简单。 + + + +## 二、使用消息构建器 + +消息构建,实际上就是对发送的数据进行再次封装,让他能够正确地被接收端解析。 + +在实际应用中,大家可能对[包模式适配器](./packageadapter.mdx)比较疑惑。为什么本来不具备粘分包的组件,在使用包模式适配器后,就具备了粘分包的能力? + +原因就是:包模式适配器,在发送数据之前,会重写发送,并对数据进行再次封装。然后对方在接收时,只要按照约定解包。这样就实现了粘分包的功能。 + +### 2.1 直接构建数组消息 + +以**固定包头包适配器**为例: + +下列摘抄部分[源码](https://gitee.com/RRQM_Home/TouchSocket/blob/master/src/TouchSocket.Core/DataAdapter/_Package/FixedHeaderPackageAdapter.cs#L22): + +`FixedHeaderPackageAdapter`适配器重写了`PreviewSend`与`PreviewSendAsync`的多个重载函数。其目的就是拦截发送数据,并对其进行再次封装。 + +最后,将封装的数据通过GoSend(异步是GoSendAsync)函数发送出去。 + +:::caution 注意 + +重写`PreviewSend`函数时,应该也重写`PreviewSendAsync`函数。不然异步调用会失败。 + +同时,应该注意,在`PreviewSend`函数中,应该调用`GoSend`函数,在`PreviewSendAsync`函数中,应该调用`GoSendAsync`函数。 + +::: + + + +```csharp showLineNumbers +/// +/// 固定包头数据包处理适配器,支持Byte、UShort、Int三种类型作为包头。使用大小端设置。 +/// +public class FixedHeaderPackageAdapter : SingleStreamDataHandlingAdapter +{ + /// + /// 当发送数据前处理数据 + /// + /// + /// + /// + protected override void PreviewSend(byte[] buffer, int offset, int length) + { + ... + } + + /// + /// + /// + /// + protected override void PreviewSend(IList> transferBytes) + { + ... + } + + /// + /// + /// + /// + protected override void PreviewSend(IRequestInfo requestInfo) + { + throw new NotImplementedException(); + } + + /// + protected override Task PreviewSendAsync(IRequestInfo requestInfo) + { + throw new NotImplementedException(); + } + + /// + protected override async Task PreviewSendAsync(byte[] buffer, int offset, int length) + { + ... + } + + /// + protected override async Task PreviewSendAsync(IList> transferBytes) + { + ... + } +} +``` + +### 2.2 从IRequestInfo构建消息 + +好奇的小伙伴应该发现了,`PreviewSend`和`PreviewSendAsync`方法都有一个参数为`IRequestInfo`的重载,那么这个`IRequestInfo`是什么呢? + +实际上这就是自定义适配器定义的一个消息实体,我们可以通过该实体,直接发送数据。 + +例如,我们定义一个TLV数据格式。 + +|Tag|Length|Value| + +其中,Tag为1字节,Length为1字节,Value为可变长度的数据。我们可以声明以下代码: + + +```csharp showLineNumbers +class MyAdapter : CustomFixedHeaderDataHandlingAdapter +{ + public override int HeaderLength => 2; + + protected override MyRequestInfo GetInstance() + { + throw new NotImplementedException(); + } +} + +//未实现接口的伪代码 +class MyRequestInfo:IFixedHeaderRequestInfo +{ + private byte[] m_value; + + public byte Tag { get; set; } + public byte Length => (byte)(this.Value==null ? 0 :this.Value.Length); + public byte[] Value + { + get => m_value; + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + if (value.Length>byte.MaxValue) + { + throw new Exception($"数组长度不能大于{byte.MaxValue}"); + } + m_value = value; + } + } + +} +``` + +假如我们想要发送一个`TLV`数据包,那么一般我们会自己拼接数据。 + +但是,自己拼接数据很明显不是很好用,我们在编程时还是比较喜欢直接属性操作。 + +例如:我们还是希望能直接发送`MyRequestInfo`实例,将会变得非常简单。 + +```csharp showLineNumbers +var myRequestInfo = new MyRequestInfo(); +myRequestInfo.Tag = 10; +myRequestInfo.Value = new byte[] { 1, 2, 3 }; +``` +那么,我们只需要将`MyRequestInfo`实现`IRequestInfoBuilder`接口,并实现其成员即可。 + +其中: + +- `MaxLength`成员,标识了该消息实体最大的长度,其作用是指示内存池申请大小,避免内存池扩张带来的性能消耗。 +- `Build`成员,则指示了如何将`MyRequestInfo`实例转换为字节数组。 + +```csharp showLineNumbers + class MyRequestInfo:IRequestInfoBuilder + { + ... + + int IRequestInfoBuilder.MaxLength => 1024; + + void IRequestInfoBuilder.Build(ByteBlock byteBlock) + { + byteBlock.Write((byte)Tag); + byteBlock.Write((byte)Length); + byteBlock.Write((byte[])Value); + } + } +``` + +经过上述代码,我们则可以直接向适配器发送`MyRequestInfo`实例。 + +不过,在发送之前,我们还需要设置适配器支持,即重写`CanSendRequestInfo`为`True`。 + +```csharp {5} showLineNumbers +//下列代码为伪代码 +class MyAdapter : CustomFixedHeaderDataHandlingAdapter +{ + ... + + public override bool CanSendRequestInfo => true; +} +``` + +**此时,只要发送的对象实现了`IRequestInfoBuilder`接口,我们就可以直接发送。** + +当然,自己也可以直接重写`PreviewSend`函数,实现自定义的发送逻辑。 + +:::tip 提示 + +在此示例中,我们只是发送了`MyRequestInfo`实例,但是,我们也可以发送其他类型的实例,只要它实现了`IRequestInfoBuilder`。 + +当然如果你并不想发送其他类型的实例,你也可以重写`PreviewSend(IRequestInfo)`函数,实现自定义的筛选发送逻辑。 + +::: diff --git a/handbook/versioned_docs/version-3.1/adapterdemodescription.mdx b/handbook/versioned_docs/version-3.1/adapterdemodescription.mdx new file mode 100644 index 000000000..25a42cf07 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/adapterdemodescription.mdx @@ -0,0 +1,15 @@ +--- +id: adapterdemodescription +title: 说明 +--- + +## 说明 + +此页面展示用户通过TouchSocket自行实现的适配器案例。更好的共享资源。 + +欢迎大家提交更多。邮箱:505554090@qq.com + +## 提交要求 + +1. 通过单元测试。 +2. 编码尽可能规范。 \ No newline at end of file diff --git a/handbook/versioned_docs/version-3.1/adapterdescription.mdx b/handbook/versioned_docs/version-3.1/adapterdescription.mdx new file mode 100644 index 000000000..87e3e477d --- /dev/null +++ b/handbook/versioned_docs/version-3.1/adapterdescription.mdx @@ -0,0 +1,165 @@ +--- +id: adapterdescription +title: 介绍及使用 +--- + +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +数据处理适配器,是TouchSocket的核心组件,其作用就是**解析数据**。 + +而解析可以分为两种,第一种是解决数据的黏、分包问题,第二种是将收到的数据解析为数据对象。 + + + +## 二、为什么需要适配器 + +适配器的作用就是解析数据,在TouchSocket系中,数据可分为**流式单线程**和**非流式多线程**两种。其中**流式单线程**以`Tcp`,`Namedpipe`、`SerialPort`等为代表。**非流式多线程**则以`Udp`为代表。接下来,我们会对其进行详细介绍。 + + + +### 2.1 流式单线程 + +顾名思义,流式单线程的数据,就是只有一个线程访问的流式数据,它像水流一样,在传输时滔滔不绝。其特点是: + +1. 数据包是有序的。 +2. 数据包是偶发性**粘连**的,即数据包之间没有分界,且**粘连**的因素不确定。 +3. 在读取时,可能会读到多个数据包,或者多个半包。 + +那么如何解决粘包、半包问题呢? + +实际上很好理解。既然数据是有序流式的,那么**标识**和**顺位**就是解决问题的关键。一般来说,通讯协议可分为`固定包头`、`不固定包头`、`固定长度`和`字符区间`四种。下面我们将以通俗易懂的方式来讲解。 + +假如,我们把传输的过程比作是一条行进中的队伍。把一个字节,比作队伍中的一个人。从宏观的角度来说,我们很难分辨队伍中有多少个小队,每个小队具体是做什么工作的。 + +但是我们可以在队伍中,每确定一个小队,就把小队第一个人任命为**队长**,并告诉他这支小队总共多少人。然后当队伍行进到终点时,只需要先问队长,小队有多少人,然后将这支小队归队。然后再问下一个队长,以此往复。鉴于队伍的连续性和有序性,只要队长不记错小队人数,那么我们必然能识别每个小队长,也能从他口中得知每个小队人数。 + +**那么,这就是`固定包头`的思路。不过很多时候,都不是一个字节标识长度,甚至固定的字节中还会有其他数据,但这并不影响固定包头的识别。** + +或者,我们并不知到队长在哪里,只能通过某种约定逐个询问,直到先找到队长,然后询问队长队伍中有多少人。 + +**那么,这就是`不固定包头`的思路。** + +或者,我们可以在队伍出发前,就和接收单位约定好,每个小队就是10个人,那么我们就可以在队伍到达终点时,每10个人归为一个小队。这样也能识别小队,也能知道小队人数。 + +**那么,这就是`固定长度`的思路。** + +或者,我们可以在队伍出发前,就做好标识,比如,遇到戴红帽子的,即视为队长,直到遇到下一个红帽子,这中间的所有人都归为一队。或者遇到蓝帽子的,即视为小队最后的那个人,即蓝帽子之前的人都归为一队。亦或者,一个小队,必须由红帽子开头,蓝帽子结尾。那么无论那种,我们很好的分辨出小队。 + +**那么,这就是`字符区间`的思路。** + +一般来说,99.5%的通讯协议,都是以上四种思路,或者其混合体。只要我们了解这些思路,那么就能很好的解决粘包、半包问题,**而适配器的时候,则会大大简化这个操作**。 + + + +### 2.2 非流式多线程 + +非流式多线程的数据,就是多个线程同时访问的数据。其特点是: + +1. 数据包是**独立**的。 +2. 数据包是**无序**的。 + +对于非流式多线程的数据,它一般不需要解决粘、分包问题。所以适配器的使用仅仅是将接收的数据包,按照`IRequestInfo`的格式进行封装投递。 + + +## 三、设计架构 + +### 3.1 工作逻辑 + + + +### 3.2 数据逻辑 + +TouchSocket的适配器,在初始阶段(原始Tcp),会收到一个ByteBlock数据,然后经过适配器处理以后,可选择两个参数(`ByteBlock`和`IRequestInfo`)的**任意组合**投递数据。 + +例如:**FixedHeaderPackageAdapter**,仅投递ByteBlock数据,届时IRequestInfo将为null。而如果是继承的**CustomDataHandlingAdapter**,则仅投递IRequestInfo,ByteBlock将为null。 + +### 3.3 设计解释 + +大家有时候可能会迷惑,为什么TouchSocket要设计两个参数投递,而不像其他的那样的,在会话里面,把适配器直接泛型规范了,直接抛出对应的类型。这是因为泛型约束太大,不够灵活。例如: + +- 第一,不能随时切换适配器,例如适配WebSocket,在握手阶段,要解析http数据,所以,此时应该选择http数据处理适配,而完成握手以后,就要解析ws数据格式了,所以此时应该切换适配器为ws数据处理适配器。 +- 第二,两个参数能提高性能。例如HTTP数据处理适配器,在高性能工作模式下,由`IRequestInfo`投递请求头,由`ByteBlock`投递Body,这样Body是从内存池获得,就不存在内存消耗了。 + +## 四、Tcp使用 + +在Tcp系中使用数据处理适配器是非常简单的一个过程。而且为了不同场景,TouchSocket支持多种方式的适配器使用。服务器和客户端使用一致 + +### 4.1 在Config配置中使用 + +在Config配置使用时,相当于初始化赋值。比较单一,但是很可靠。 +```csharp showLineNumbers +var config = new TouchSocketConfig(); +config.SetTcpDataHandlingAdapter(() => new NormalDataHandlingAdapter()); +``` + +### 4.2 直接设置适配器 + +直接设置适配器,可以在任意时刻进行。 + +```csharp showLineNumbers + client.SetDataHandlingAdapter(new NormalDataHandlingAdapter()); +``` + +## 五、Udp使用插件 + +Udp使用的插件,只能从Config配置。 + +【**UdpSession**】 +```csharp showLineNumbers +m_udpSession.Setup(new TouchSocketConfig() + .SetBindIPHost(new IPHost(this.textBox2.Text)) + .SetUdpDataHandlingAdapter(()=> + { + return new NormalUdpDataHandlingAdapter(); + }) + .ConfigureContainer(a => + { + + })); +m_udpSession.Start(); +``` + +:::caution 注意 + +同一个适配器实例,只可被赋值一次,不然会异常。 + +::: + +## 六、限制使用 + +限制使用的场景应用于自定义封装。例如:自己封装一个服务器,然后适配器仅**特定使用**,不允许外部随意赋值,那么可以如下实现: + +```csharp showLineNumbers +public class MySessionClient : SessionClient +{ + /// + /// + /// + public override sealed bool CanSetDataHandlingAdapter => false;//不允许随意赋值 + + private void InternalSetAdapter(DataHandlingAdapter adapter) + { + this.SetAdapter(adapter);//仅继承内部赋值 + } +} +``` + +## 七、缓存超时(仅Tcp适配器) + +当适配器在工作时,如果一个数据包在设置的周期内(默认1000ms)没有完成接收,则会清空所有缓存数据,然后重新接收。 +这种设计是为了应对,当接收数据时,如果发送方发送了异常数据(也有可能在移动网时,由运营商发送的无效包)而导致整个接收队列数据无效的问题。 +现在加入缓存超时设置,则如果发送上述情况,也会在一个周期后,快速恢复接收。 + +相关属性设置: + +- CacheTimeoutEnable:是否启用缓存超时。 +- CacheTimeout:缓存超时时间 +- UpdateCacheTimeWhenRev:是否每次接收就更新缓存时间。默认true,意味着只要有数据接收,则缓存永远不会过期。当为false时,则每个缓存包,必须在设置的周期内完成接收。 diff --git a/handbook/versioned_docs/version-3.1/adaptererrorcorrection.mdx b/handbook/versioned_docs/version-3.1/adaptererrorcorrection.mdx new file mode 100644 index 000000000..b17b447bc --- /dev/null +++ b/handbook/versioned_docs/version-3.1/adaptererrorcorrection.mdx @@ -0,0 +1,250 @@ +--- +id: adaptererrorcorrection +title: 适配器纠错 +--- + +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +适配器纠错功能,用于对接收的数据进行纠错。 + + + +## 二、纠错流式单线程数据 + +我们在[适配器介绍](./adapterdescription.mdx)中讲过,流式单线程数据的解析,**标识**和**顺位**就是解决问题的关键,这也就意味着一般来说,流式单线程数据是不允许有其他数据的。不然数据解析将会发生灾难性故障。 + +但是有时候,往往环境不是我们所控制的。在数据实际传输时,有极小的情况确实会发生其他数据包混入的情况。 + +此时,我们就可以使用适配器纠错功能,来解决这种问题。 + +## 2.1 纠错原理 + +对于纠错,我们的目的就是要重新定位到正确数据的起始位置,让数据能够重新解析。那么对于重新定位,我们有以下策略: + +1. 按照算法找到正确的起始位置。这要求数据格式本身就具有验证性。 +2. 即时重置缓存。 +3. 断开连接。 + +下列,我们将以Tcp数据为例,讲解如何使用适配器纠错功能。 + +## 2.2 纠错示例 + +我们假设一个常见的TLV数据,其格式为: + +|Tag|Length|Value| + +其中,Tag为4字节,Length为4字节,Value为可变长度的数据。 + +要解析该数据,我们非常容易的会想到使用[模版解析固定包头适配器](./customfixedheaderdatahandlingadapter.mdx)来解决问题。 + +所以代码大概如下: + +```csharp showLineNumbers +public class MyFixedHeaderCustomDataHandlingAdapter : CustomFixedHeaderDataHandlingAdapter +{ + /// + /// 接口实现,指示固定包头长度 + /// + public override int HeaderLength => 8; + + /// + /// 获取新实例 + /// + /// + protected override MyFixedHeaderRequestInfo GetInstance() + { + return new MyFixedHeaderRequestInfo(); + } +} +public class MyFixedHeaderRequestInfo : IFixedHeaderRequestInfo +{ + public int Tag { get; set; } + public int Length => this.m_bodyLength; + public byte[] Value { get;private set; } + private int m_bodyLength; + + int IFixedHeaderRequestInfo.BodyLength => this.m_bodyLength; + + bool IFixedHeaderRequestInfo.OnParsingBody(byte[] body) + { + if (body.Length!=this.m_bodyLength) + { + return false; + } + + this.Value = body; + return true; + } + + bool IFixedHeaderRequestInfo.OnParsingHeader(byte[] header) + { + if (header.Length!=8) + { + return false; + } + + this.Tag = TouchSocketBitConverter.BigEndian.ToInt32(header,0); + this.m_bodyLength = TouchSocketBitConverter.BigEndian.ToInt32(header,4); + return true; + } +} +``` + +此时,如果我们的数据是完全符合我们定义的协议的,那么我们的算法就是100%可靠的。 + +但是,如果我们的数据不符合我们的协议呢?例如:在Tag、Length、Value之间多出n个字节,那么我们的算法就无法解析了。 + +此时,我们需要对数据进行处理,进行纠错。 + +## 2.3 自我纠错 + +首先,我们来看能不能自己纠错。自己纠错,必须是数据格式自己能识别错误,并且能自己纠错。在此案例中,明显是不适用的,因为简单的TLV数据是不具备自我纠错能力的。 + +那么什么样的数据可以自我纠错呢? + +例如:|Begin|Tag|TagCrc|Length|LengthCrc|Value|ValueCrc|End| + +其中假设:Begin、End是固定的“\*\*和##”,Tag、Length均为4字节,TagCrc、LengthCrc、ValueCrc是对应数据的CRC校验。 + +那么对于此类数据,**可能**具有一定纠错能力。 + +原因是,当在解析数据时,如果我们发现该数据并不是以“\*\*”开头,则会认为该数据是错误的。可以在`OnParsingHeader`中返回`false`,表示数据错误。**然后适配器会向后递推1个字节,再次尝试解析**。Tag、Length、Value也可以使用crc校验来决定返回`true`或`false`。 + +聪明的小伙伴应该发现了,这种机制也只能解决部分问题。例如:当冗余数据出现在Tag、Length、Value数据间隔时,可能是有用的。一旦冗余数据在Tag、Length、Value之中时,我们仍然无法解析。所以,这种机制**可能**能解决部分问题。 + +但是,这仍然是有意义的,因为纠错的目的不仅仅是纠错还原本次数据,更主要的是能解析下次正确的数据。 + +## 2.4 即时重置缓存 + +即时重置缓存,是指当数据不完整时,立即重置缓存。这也就意味着,当数据不完整时,我们会立即放弃本次数据的解析。直接进行下一次数据的解析。 + +该操作对于[用户自定义解析](./customdatahandlingadapter.mdx)是容易的。您只需要调用`this.Reset()`即可。 + +但是对于模版解析,可能需要传递一些委托来完成重置工作。 + +例如上述代码,我们可以这样写: + +在`MyFixedHeaderRequestInfo`的构造函数中,将`MyFixedHeaderCustomDataHandlingAdapter`的`Reset`函数通过委托传递给`MyFixedHeaderRequestInfo`。 + +```csharp {14,19-22,35,47} showLineNumbers +public class MyFixedHeaderCustomDataHandlingAdapter : CustomFixedHeaderDataHandlingAdapter +{ + /// + /// 接口实现,指示固定包头长度 + /// + public override int HeaderLength => 8; + + /// + /// 获取新实例 + /// + /// + protected override MyFixedHeaderRequestInfo GetInstance() + { + return new MyFixedHeaderRequestInfo(this.Reset); + } +} +public class MyFixedHeaderRequestInfo : IFixedHeaderRequestInfo +{ + public MyFixedHeaderRequestInfo(Action action) + { + this.m_actionForReset = action; + } + public int Tag { get; set; } + public int Length => this.m_bodyLength; + public byte[] Value { get;private set; } + private int m_bodyLength; + private readonly Action m_actionForReset; + + int IFixedHeaderRequestInfo.BodyLength => this.m_bodyLength; + + bool IFixedHeaderRequestInfo.OnParsingBody(byte[] body) + { + if (body.Length!=this.m_bodyLength) + { + this.m_actionForReset.Invoke(); + return false; + } + + this.Value = body; + return true; + } + + bool IFixedHeaderRequestInfo.OnParsingHeader(byte[] header) + { + if (header.Length!=8) + { + this.m_actionForReset.Invoke(); + return false; + } + + this.Tag = TouchSocketBitConverter.BigEndian.ToInt32(header,0); + this.m_bodyLength = TouchSocketBitConverter.BigEndian.ToInt32(header,4); + return true; + } +} +``` + +亦或者,使用适配器自带的缓存超时来处理。 + +例如下列设置: + +启用了缓存超时,和超时更新。 + +这也就意味着,当我们收到一个错误数据时,只要等待1秒以后再重新发送,那么上个错误的缓存将不再有效,也就是此次数据会被正确解析。 + +```csharp {3-8} showLineNumbers +public class MyFixedHeaderCustomDataHandlingAdapter : CustomFixedHeaderDataHandlingAdapter +{ + public MyFixedHeaderCustomDataHandlingAdapter() + { + this.CacheTimeoutEnable = true; + this.CacheTimeout=TimeSpan.FromSeconds(1); + this.UpdateCacheTimeWhenRev = true; + } + /// + /// 接口实现,指示固定包头长度 + /// + public override int HeaderLength => 8; + + /// + /// 获取新实例 + /// + /// + protected override MyFixedHeaderRequestInfo GetInstance() + { + return new MyFixedHeaderRequestInfo(this.Reset); + } +} +``` + +:::caution 注意 + +使用此配置,要注意发送数据的频次,如果是数秒甚至更大时间发送一次数据,那么这将是有用的。 + +::: + +## 2.5 断开连接 + +当数据有异常时,直接断开连接,也不失为一种处理方式。同样,该操作对于[用户自定义解析](./customdatahandlingadapter.mdx)是容易的。您只需要调用`Owner`的相关操作即可。例如: + +可以声明一个方法。用于关闭连接。 + +```csharp showLineNumbers +private void Close() +{ + if (this.Owner is ICloseObject client) + { + client.Close("适配器关闭"); + } +} +``` + +同理,如果是模版解析,依然需要使用委托的方法,将`Close()`函数传递到`IRequestInfo`中。此处就不再赘述。 \ No newline at end of file diff --git a/handbook/docs/adaptermodbus.mdx b/handbook/versioned_docs/version-3.1/adaptermodbus.mdx similarity index 100% rename from handbook/docs/adaptermodbus.mdx rename to handbook/versioned_docs/version-3.1/adaptermodbus.mdx diff --git a/handbook/versioned_docs/version-3.1/adaptersiemenss7.mdx b/handbook/versioned_docs/version-3.1/adaptersiemenss7.mdx new file mode 100644 index 000000000..87c050370 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/adaptersiemenss7.mdx @@ -0,0 +1,79 @@ +--- +id: adaptersiemenss7 +title: 西门子S7协议 +--- + +## 说明 + +本代码仅适用以下协议。 + +西门子S7协议 + +:::tip 提示 + +封装较多,以下代码只摘录部分,如果需要全部代码,请查看[开源地址](https://gitee.com/diego2098/ThingsGateway) + +::: + +## 版权 + +该代码所有版权归Diego所有,使用时请务必注明。 + +## 代码 + +```csharp showLineNumbers + + +namespace ThingsGateway.Foundation.Adapter.Siemens; + +/// +/// SiemensS7PLCDataHandleAdapter +/// +public class SiemensS7PLCDataHandleAdapter : ReadWriteDevicesTcpDataHandleAdapter +{ + /// + public override byte[] PackCommand(byte[] command) + { + return command; + } + + /// + /// + /// + /// + protected override SiemensMessage GetInstance() + { + return new SiemensMessage(); + } + + /// + protected override FilterResult UnpackResponse(SiemensMessage request, byte[] send, byte[] body, byte[] response) + { + var result = new OperResult(); + if (response[2] * 256 + response[3] == 7) + { + result = new OperResult(response); + } + else + { + //以请求方为准,分开返回类型校验 + switch (send[17]) + { + case 0x04: + result = SiemensHelper.AnalysisReadByte(send, response); + break; + case 0x05: + result = SiemensHelper.AnalysisWrite(response); + break; + } + } + request.ResultCode = result.ResultCode; + request.Message = result.Message; + request.Content = result.Content; + return FilterResult.Success; + } +} + + + +``` diff --git a/handbook/versioned_docs/version-3.1/appmessenger.mdx b/handbook/versioned_docs/version-3.1/appmessenger.mdx new file mode 100644 index 000000000..db0104711 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/appmessenger.mdx @@ -0,0 +1,88 @@ +--- +id: appmessenger +title: 应用信使 +--- + +import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 +应用信使是在进程内的,行使注册和触发功能的组件。可**代替事件**,可**跨越程序集**,可**依赖倒置**。 + +## 二、注册 + +下列演示时,是使用`AppMessenger.Default`默认实例,实际上,用户可以自己新实例化的`AppMessenger`。 + +### 2.1 注册实例 + +首先让类实例实现`IMessageObject`接口,然后在实例类中声明**异步公共实例**方法,并使用`AppMessage`特性标记。 + +然后一般情况下,建议在构造函数中,注册消息。 + +```csharp {5} showLineNumbers +public class MessageObject : IMessageObject +{ + public MessageObject() + { + AppMessenger.Default.Register(this); + } + + [AppMessage] + public Task Add(int a, int b) + { + return Task.FromResult(a + b); + } + + [AppMessage] + public Task Sub(int a, int b) + { + return Task.FromResult(a - b); + } +} +``` + +:::info 信息 + +对于实例类,如果构造函数中,没有注册消息,那么在构造函数之后,也可以使用其**实例**注册消息。 + +```csharp {2} showLineNumbers +var messageObject = new MessageObject(); +AppMessenger.Default.Register(messageObject); +``` + +::: + +### 2.1 注册静态方法 + +注册静态方法,只需在类中直接声明**异步公共实例**方法,并使用`AppMessage`特性标记即可。 + +```csharp showLineNumbers +public static class MessageObject : IMessageObject +{ + [AppMessage] + public static Task StaticAdd(int a, int b) + { + return Task.FromResult(a + b); + } +} +``` + +使用`RegisterStatic`进行注册 + +```csharp showLineNumbers +AppMessenger.Default.RegisterStatic(); +``` + +## 三、触发 + +触发时,泛型类型,即时返回值类型。 + +```csharp showLineNumbers +int add = await appMessenger.SendAsync("Add", 20, 10); + +int sub =await appMessenger.SendAsync("Sub", 20, 10); +``` diff --git a/handbook/docs/bigfixedheadercustomdatahandlingadapter.mdx b/handbook/versioned_docs/version-3.1/bigfixedheadercustomdatahandlingadapter.mdx similarity index 100% rename from handbook/docs/bigfixedheadercustomdatahandlingadapter.mdx rename to handbook/versioned_docs/version-3.1/bigfixedheadercustomdatahandlingadapter.mdx diff --git a/handbook/docs/bilibili-card-test.mdx b/handbook/versioned_docs/version-3.1/bilibili-card-test.mdx similarity index 100% rename from handbook/docs/bilibili-card-test.mdx rename to handbook/versioned_docs/version-3.1/bilibili-card-test.mdx diff --git a/handbook/versioned_docs/version-3.1/blog.mdx b/handbook/versioned_docs/version-3.1/blog.mdx new file mode 100644 index 000000000..9eb9f2829 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/blog.mdx @@ -0,0 +1,12 @@ +--- +id: blog +title: 博客 +--- + +## Tcp组件 + +- [C# Tcp如何限制单个客户端的访问流量](https://blog.csdn.net/qq_40374647/article/details/125496769) +- [C# Tcp服务器如何限制同一个IP的连接数量?](https://blog.csdn.net/qq_40374647/article/details/125390655) +- [C# 实现为Tcp服务器设计访问黑名单、白名单](https://blog.csdn.net/qq_40374647/article/details/128640132) +- [C# Tcp服务器实现多端口、多协议解析](https://blog.csdn.net/qq_40374647/article/details/128641766) +- [C# 优雅的为Tcp客户端设置心跳数据包](https://blog.csdn.net/qq_40374647/article/details/125598921) \ No newline at end of file diff --git a/handbook/versioned_docs/version-3.1/bytepool.mdx b/handbook/versioned_docs/version-3.1/bytepool.mdx new file mode 100644 index 000000000..f20148a0a --- /dev/null +++ b/handbook/versioned_docs/version-3.1/bytepool.mdx @@ -0,0 +1,230 @@ +--- +id: bytepool +title: 内存池 +--- + +import CardLink from "@site/src/components/CardLink.js"; +import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +内存池是`TouchSocket`系列的最重要的组成部分,在`TouchSocket`产品中,`ArrayPool`贯穿始终。所以熟练使用`ArrayPool`,也是非常重要的。 + +## 二、功能 + +内存池(`ArrayPool`)是微软的`ArrayPool`。但是在用法上做了一些的优化。 + +内存池的最小实现单体是`内存块(ByteBlock)`和`值内存块(ValueByteBlock)`。它们均实现了`IBufferWriter`接口,可以更好的使用`GetMemory`、`GetSpan`与`Advance`等功能。 + +## 三、创建与回收 + +### 3.1 内存池 + +`ArrayPool`在默认情况提供了一个`ArrayPool.Shared`的默认静态实例。这是整个**进程**可以共享使用的。 + +当然您可以创建只属于自己的`ArrayPool`。 + +```csharp showLineNumbers +ArrayPool bytePool = new ArrayPool(maxArrayLength: 1024 * 1024, maxArraysPerBucket: 50); +``` + + +其中: + +- maxArrayLength,是内存池的最大字节数组尺寸。 +- maxArraysPerBucket是每个内存块桶的最大数组数量。 + +### 3.2 创建、释放内存块 + +内存块就是可以使用的字节数组。框架提供了`ByteBlock`和`ValueByteBlock`两种内存块。`ValueByteBlock`是值类型的,其余特点完全一致。 + +【创建ByteBlock】 + +```csharp showLineNumbers +var byteBlock = new ByteBlock(1024 * 64); +byteBlock.Dispose(); +``` + + +【创建ValueByteBlock】 + +```csharp showLineNumbers +var byteBlock = new ValueByteBlock(1024 * 64); +byteBlock.Dispose(); +``` + +以上的创建方式,都是从默认内存池创建。如果想要自定义内存池,可以在new的时候指定内存池。 + +```csharp showLineNumbers +var byteBlock = new ByteBlock(1024 * 64,ArrayPool.Default); +byteBlock.Dispose(); +``` + +释放过程也完全可以使用`using`。 + +```csharp showLineNumbers +using (var byteBlock = new ByteBlock(1024 * 64)) +{ + //使用ByteBlock +} +``` + +:::tip 提示 + +`byteSize`用于申请的最小字节尺寸。例如:当申请100长度时,可能会得到100,1000,甚至更大尺寸的内存,但绝不会小于100 + +::: + +:::caution 注意 + +创建的`ByteBlock`(`ValueByteBlock`)必须显示释放(`Dispose`),可用`using`,如果不释放,虽然不会内存泄露,但是会影响性能。 + +::: + +## 四、内存块使用 + +内存块就是可以使用的字节数组。只不过他具备*借用*与*归还*的功能。 + +我们可以使用`TotalMemory`,获取到整个内存块原始数据。 + +```csharp showLineNumbers +Memory memory = byteBlock.TotalMemory; +``` + +但一般情况下,我们不会直接使用内存块的`TotalMemory`,而是使用`Span`、`Memory`、甚至是`ToArray()`等**有效数据**。 + +:::info 信息 + +**有效数据** 是指在内存块中已经被有效写入,或者可以被有效读取的数据。因为当一个内存块被申请的时候,无论它的容量多大,它的有效数据长度总是为0(`Length`)。我们必须通过`Write`、`Advance`、`SetLength`等方法,来完成数据的有效化。 + +::: + +:::tip 趣味理解 + +`ByteBlock`的工作模式,就像是你向商店(`ArrayPool`)租借了一个杯子(`ByteBlock`),`TotalMemory`就是这个杯子本身。 + +于我们而言,只在乎杯中实际有多少水(`Span`、`Memory`),所以`byteBlock.Length`就是水位线。所以无论何时,我们都不可以喝超过水位线的水。因为超过水位线的水,可能是上个租客留下的口水。 + +同时,喝水的时候,还可以接力喝。比如先让张三(方法A)喝1口,那么我们可以把已喝的水做个标记(`Position`赋值为1),然后把杯子(`ByteBlock`)传递给李四(方法B),那么这时候,李四就只能按张三喝过的水继续喝,当然如果李四不嫌弃的话,也是可以从头再喝的。 + +最后就是杯子的归还(`Dispose`),以及杯中水的处理。在杯子未归还至前,杯子的所有属性(例如:`Length`、`Position`、`Span`、`Memory`)都代表的是当前的状态。而当杯子被归还时,这些属性将变得没有意义,尤其是`Memory`。因为`Memory`可以被其他对象引用,但是杯子归还后,`Memory`指向的内存块可能已经是新的申请人,这杯中的水,终究是变成了“前人的口水”。 + +但是如果我们确实需要保存杯中水的话,可以使用`ToArray()`方法。其返回值是一个数组,它可以以新引用的方式保存,且与内存池没有任何关系了,所有的生命周期归`GC`管理。 + +::: + + +### 4.1 读写数组 + +使用比较简单,支持Byte[],Span、Memory等数据的直接写入。 + +```csharp showLineNumbers +using (var byteBlock = new ByteBlock(1024*64)) +{ + byteBlock.Write(new byte[] { 0, 1, 2, 3 });//将字节数组写入 + + byteBlock.SeekToStart();//将游标重置 + + var buffer = new byte[byteBlock.Length];//定义一个数组容器 + var r = byteBlock.Read(buffer);//读取数据到容器,并返回读取的长度r +} +``` + +### 4.2 基础类型的写入和读取 + +```csharp showLineNumbers +using (var byteBlock = new ByteBlock(1024*64)) +{ + byteBlock.WriteByte(byte.MaxValue);//写入byte类型 + byteBlock.WriteInt32(int.MaxValue);//写入int类型 + byteBlock.WriteInt64(long.MaxValue);//写入long类型 + byteBlock.WriteString("RRQM");//写入字符串类型 + + byteBlock.SeekToStart();//读取时,先将游标移动到初始写入的位置,然后按写入顺序,依次读取 + + var byteValue = byteBlock.ReadByte(); + var intValue = byteBlock.ReadInt32(); + var longValue = byteBlock.ReadInt64(); + var stringValue = byteBlock.ReadString(); +} +``` + +### 4.3 按照BufferWriter方式写入 + +```csharp showLineNumbers +using (var byteBlock = new ByteBlock(1024*64)) +{ + var span = byteBlock.GetSpan(4); + span[0] = 0; + span[1] = 1; + span[2] = 2; + span[3] = 3; + byteBlock.Advance(4); + + var memory = byteBlock.GetMemory(4); + memory.Span[0] = 4; + memory.Span[1] = 5; + memory.Span[2] = 6; + memory.Span[3] = 7; + byteBlock.Advance(4); + + //byteBlock.Length 应该是8 +} +``` + +## 五、多线程同步协作(Hold) + +在多线程异步时,设计架构应当遵守谁(Thread)创建的ByteBlock,由谁释放,这样就能很好的避免未释放的情况发生。实际上TouchSocket中,就是秉承这样的设计,任何非用户创建的ByteBlock,都会由创建的线程最后释放。但是在使用中,经常出现异步多线程的操作。 + +以TouchSocket的TcpClient为例。如果直接在收到数据时,使用Task异步,则必定会发生关于ByteBlock的各种各样的异常。 + +**原因非常简单,byteBlock对象在到达HandleReceivedData时,触发Task异步,此时触发线程会立即返回,并释放byteBlock,而Task异步线程会滞后,然后试图从已释放的byteBlock中获取数据,所以,必定发生异常。** + +```csharp showLineNumbers +public class MyTClient : TcpClient +{ + protected override bool HandleReceivedData(ByteBlock byteBlock, IRequestInfo requestInfo) + { + Task.Run(()=> + { + string mes = byteBlock.Span.ToString(Encoding.UTF8); + Console.WriteLine($"已接收到信息:{mes}"); + }); + return true; + } +} +``` + +解决方法也非常简单,只需要在异步前锁定,然后使用完成后取消锁定,且不用再调用Dispose。 + +```csharp showLineNumbers +public class MyTClient : TcpClient +{ + protected override bool HandleReceivedData(ByteBlock byteBlock, IRequestInfo requestInfo) + { + byteBlock.SetHolding(true);//异步前锁定 + Task.Run(()=> + { + string mes = byteBlock.Span.ToString(Encoding.UTF8); + byteBlock.SetHolding(false);//使用完成后取消锁定,且不用再调用Dispose + Console.WriteLine($"已接收到信息:{mes}"); + }); + return true; + } +} +``` + +:::caution 注意 + +ByteBlock在设置SetHolding(false)后,不需要再调用Dispose。 + +::: + +## 六、本文示例Demo + + \ No newline at end of file diff --git a/handbook/versioned_docs/version-3.1/consoleaction.mdx b/handbook/versioned_docs/version-3.1/consoleaction.mdx new file mode 100644 index 000000000..c1e1173e1 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/consoleaction.mdx @@ -0,0 +1,35 @@ +--- +id: consoleaction +title: 控制台行为 +--- + +import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 +这是一个很简单的控制台命令器,重要作用就是很方便的实现控制台控制。 + +## 二、使用 +```csharp showLineNumbers +ConsoleAction consoleAction = new ConsoleAction("h|help|?");//设置帮助命令 +consoleAction.OnException += ConsoleAction_OnException;//订阅执行异常输出 + +//下列的ShareProxy,StopShareProxy,GetAll均为无参数的方法 +consoleAction.Add("sp|shareProxy", "分享代理", ShareProxy);//示例命令 +consoleAction.Add("ssp|stopShareProxy", "停止分享代理", StopShareProxy);//示例命令 +consoleAction.Add("ga|getAll", "获取所有客户端信息", GetAll);//示例命令 +consoleAction.ShowAll(); +while (true) +{ + if (!consoleAction.Run(Console.ReadLine())) + { + Console.WriteLine("命令不正确,请输入“h|help|?”获得帮助。"); + } +} +``` +## 三、效果图 +![](@site/static/img/docs/consoleaction-1.gif) diff --git a/handbook/docs/cooperation.mdx b/handbook/versioned_docs/version-3.1/cooperation.mdx similarity index 100% rename from handbook/docs/cooperation.mdx rename to handbook/versioned_docs/version-3.1/cooperation.mdx diff --git a/handbook/versioned_docs/version-3.1/cors.mdx b/handbook/versioned_docs/version-3.1/cors.mdx new file mode 100644 index 000000000..598b25194 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/cors.mdx @@ -0,0 +1,67 @@ +--- +id: cors +title: 跨域资源共享 +--- + +import { TouchSocketHttpDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +什么是跨域?简单来说,当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域。那为什么会出现跨域问题呢? + +出于浏览器的同源策略限制。同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说 Web 是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)。 + + +什么是CORS?跨源资源共享 (CORS) : + +- 是一种 W3C 标准,可让服务器放宽相同的源策略。 +- 不是一项安全功能,CORS 放宽 security。 API 不能通过允许 CORS 来更安全。 有关详细信息,请参阅 CORS 工作原理。 +- 允许服务器明确允许一些跨源请求,同时拒绝其他请求。 +- 比早期的技术(如 JSONP)更安全且更灵活。 + +## 二、使用方法 + +### 2.1 添加Cors服务 + +```csharp {4} showLineNumbers +.ConfigureContainer(a => +{ + //添加跨域服务 + a.AddCors(corsOption => + { + //添加跨域策略,后续使用policyName即可应用跨域策略。 + corsOption.Add("cors", corsBuilder => + { + corsBuilder.AllowAnyMethod() + .AllowAnyOrigin(); + }); + }); +}) +``` + +:::tip 提示 + +`corsBuilder`可以通过With方法,添加更多的跨域设置。例如设置特定的源。 + +::: + +### 2.2 应用跨域策略 + +```csharp {4} showLineNumbers +.ConfigurePlugins(a => +{ + //应用名称为cors的跨域策略。 + a.UseCors("cors"); +}) +``` + +:::tip 提示 + +`UseCors()`的设定,会应用到插件访问的,之前的所有的http请求上。所以如果需要,可以将`UseCors()`放置到最前项。 + +::: \ No newline at end of file diff --git a/handbook/docs/custombetweenanddatahandlingadapter.mdx b/handbook/versioned_docs/version-3.1/custombetweenanddatahandlingadapter.mdx similarity index 100% rename from handbook/docs/custombetweenanddatahandlingadapter.mdx rename to handbook/versioned_docs/version-3.1/custombetweenanddatahandlingadapter.mdx diff --git a/handbook/docs/custombigunfixedheaderdatahandlingadapter.mdx b/handbook/versioned_docs/version-3.1/custombigunfixedheaderdatahandlingadapter.mdx similarity index 100% rename from handbook/docs/custombigunfixedheaderdatahandlingadapter.mdx rename to handbook/versioned_docs/version-3.1/custombigunfixedheaderdatahandlingadapter.mdx diff --git a/handbook/docs/customcountspliterdatahandlingadapter.mdx b/handbook/versioned_docs/version-3.1/customcountspliterdatahandlingadapter.mdx similarity index 97% rename from handbook/docs/customcountspliterdatahandlingadapter.mdx rename to handbook/versioned_docs/version-3.1/customcountspliterdatahandlingadapter.mdx index 44636cfc1..c11b1093b 100644 --- a/handbook/docs/customcountspliterdatahandlingadapter.mdx +++ b/handbook/versioned_docs/version-3.1/customcountspliterdatahandlingadapter.mdx @@ -135,4 +135,4 @@ private static async Task CreateService() ## 五、示例Demo - \ No newline at end of file + \ No newline at end of file diff --git a/handbook/docs/customdatahandlingadapter.mdx b/handbook/versioned_docs/version-3.1/customdatahandlingadapter.mdx similarity index 100% rename from handbook/docs/customdatahandlingadapter.mdx rename to handbook/versioned_docs/version-3.1/customdatahandlingadapter.mdx diff --git a/handbook/docs/customfixedheaderdatahandlingadapter.mdx b/handbook/versioned_docs/version-3.1/customfixedheaderdatahandlingadapter.mdx similarity index 100% rename from handbook/docs/customfixedheaderdatahandlingadapter.mdx rename to handbook/versioned_docs/version-3.1/customfixedheaderdatahandlingadapter.mdx diff --git a/handbook/docs/customjsondatahandlingadapter.mdx b/handbook/versioned_docs/version-3.1/customjsondatahandlingadapter.mdx similarity index 97% rename from handbook/docs/customjsondatahandlingadapter.mdx rename to handbook/versioned_docs/version-3.1/customjsondatahandlingadapter.mdx index afc0eeeb5..e304095f3 100644 --- a/handbook/docs/customjsondatahandlingadapter.mdx +++ b/handbook/versioned_docs/version-3.1/customjsondatahandlingadapter.mdx @@ -141,4 +141,4 @@ private static async Task CreateService() ## 五、示例Demo - \ No newline at end of file + \ No newline at end of file diff --git a/handbook/docs/customunfixedheaderdatahandlingadapter.mdx b/handbook/versioned_docs/version-3.1/customunfixedheaderdatahandlingadapter.mdx similarity index 100% rename from handbook/docs/customunfixedheaderdatahandlingadapter.mdx rename to handbook/versioned_docs/version-3.1/customunfixedheaderdatahandlingadapter.mdx diff --git a/handbook/versioned_docs/version-3.1/dataadaptertester.mdx b/handbook/versioned_docs/version-3.1/dataadaptertester.mdx new file mode 100644 index 000000000..6456d5f10 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/dataadaptertester.mdx @@ -0,0 +1,76 @@ +--- +id: dataadaptertester +title: 适配器完整性、性能测试 +--- + +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +适配器测试是测试适配器在正常情况下,极端工作的一种测试方式。能够在前期,解决100%的算法问题。也能在极端配置下,模拟极端工作环境,能够简单,直观的展示出适配器的稳定性和工作性能。 + + + + +### 1.1 测试原理 + +假设发送数据为[0,1,2,3,4],连续发送10次。 +当bufferLength=1时,会先接收一个字节,然后适配器判断无法解析,然后缓存,然后再接收下一个字节,直到成功解析一个完整数据包。该模式解决的就是大家所说的分包,也就是能很好的模拟**网络很差**的环境。 +当bufferLength>5时,假如为8,则会先接收[0,1,2,3,4,0,1,2],然后适配器成功判断解析前五字节,然后缓存后三字节,然后再接收下一个续包,直到解析结束。 + + +### 1.2 测试事项 + +1. bufferLength应该多次设置,且最好不要整除于发送数据的长度,这样避免巧合发生,测不出极端问题。 +2. Run的次数应该多设,模拟高频情况。 + + +## 二、Tcp适配器 + +Tcp适配器的工作环境,只需考虑单线程即可。因为是客户端与适配器是一一对应的。 + +下列以 [固定包头数据处理适配器](./packageadapter.mdx) 为例 + +```csharp showLineNumbers +//Tcp适配器测试 +//bufferLength的作用是模拟tcp接收缓存区,例如: + +//发送数据为{0,1,2,3,4}时 +//当bufferLength=1时,会先接收一个字节,然后适配器判断无法解析,然后缓存,然后再接收下一个字节,直到成功解析。 +//该模式能很好的模拟网络很差的环境。 +//当bufferLength=8时,会先接收{0,1,2,3,4,0,1,2},然后适配器判断解析前五字节,然后缓存后三字节,然后再接收下一个续包,直到解析结束 + +for (int bufferLength = 1; bufferLength < 1024 * 10; bufferLength += 1024) +{ + bool isSuccess = true; + var data = new byte[] { 0, 1, 2, 3, 4 }; + SingleStreamDataAdapterTester tester = SingleStreamDataAdapterTester.CreateTester(new FixedHeaderPackageAdapter() + , bufferLength, (byteBlock, requestInfo) => + { + //此处就是接收,如果是自定义适配器,可以将requestInfo强制转换为实际对象,然后判断数据的确定性 + if (byteBlock.Length!=5||(!byteBlock.ToArray().SequenceEqual(data))) + { + isSuccess = false; + } + }); + + //data是发送的数据,因为此处使用的是固定包头适配器, + //发送前适配器会自动添加包头,所以,此处只发送数据即可。 + //如果测试的是自定义适配器,发送前没有封装的话,就需要自行构建发送数据。 + //随后的两个参数,10,10是测试次数,和期望次数,一般这两个值是相等的。 + //意为:本次数据将循环发送10次,且会接收10次。不然此处会一直阻塞。 + //最后一个参数是测试的最大超时时间。 + + var time = tester.Run(data, 10, 10, 1000 * 10); + Thread.Sleep(1000); + Console.WriteLine($"测试结束,状态:{isSuccess},用时:{time}"); +} +Console.WriteLine("测试结束"); + +``` diff --git a/handbook/docs/dataforwarding.mdx b/handbook/versioned_docs/version-3.1/dataforwarding.mdx similarity index 100% rename from handbook/docs/dataforwarding.mdx rename to handbook/versioned_docs/version-3.1/dataforwarding.mdx diff --git a/handbook/docs/datahandleadapter.mdx b/handbook/versioned_docs/version-3.1/datahandleadapter.mdx similarity index 100% rename from handbook/docs/datahandleadapter.mdx rename to handbook/versioned_docs/version-3.1/datahandleadapter.mdx diff --git a/handbook/docs/datasecurity.mdx b/handbook/versioned_docs/version-3.1/datasecurity.mdx similarity index 100% rename from handbook/docs/datasecurity.mdx rename to handbook/versioned_docs/version-3.1/datasecurity.mdx diff --git a/handbook/versioned_docs/version-3.1/dependencyproperty.mdx b/handbook/versioned_docs/version-3.1/dependencyproperty.mdx new file mode 100644 index 000000000..1a3157334 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/dependencyproperty.mdx @@ -0,0 +1,216 @@ +--- +id: dependencyproperty +title: 依赖属性 +--- + +import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 +用过WPF的小伙伴一定对依赖属性不陌生。所以TouchSocket模仿其结构,创建了适用于网络框架的依赖属性。 + + +## 二、什么是依赖属性? + +我们知道常规属性,就是拥有get,set访问器的字段,叫做属性。 +```csharp showLineNumbers +class MyClass +{ + public int MyProperty { get; set; } +} +``` +而依赖属性,则是具有注入特征的属性。它具有如下特性: + +1. 可以像普通属性一样,声明在类内部(示例1),对外界的公布和常规数据一模一样。 +2. 声明在静态类中,做成扩展方法,实现真正的注入型属性(示例2)。 +3. 可以声明初始值。 + +### 2.1 内部声明 + +1. 继承**DependencyObject** +2. 按如下格式生成属性项(propdp代码块可快速实现) +```csharp showLineNumbers +class MyDependencyObject: DependencyObject +{ + /// + /// 属性项 + /// + public int MyProperty1 + { + get { return GetValue(MyPropertyProperty1); } + set { SetValue(MyPropertyProperty1, value); } + } + + /// + /// 依赖项 + /// + public static readonly DependencyProperty MyPropertyProperty1 = + DependencyProperty.Register("MyProperty1", typeof(MyDependencyObject), 10); + +} +``` + +### 2.2 扩展声明 + +扩展声明,必须要提前声明扩展类。 + +下列示例声明一个MyProperty的属性扩展。 + +```csharp showLineNumbers +public static class DependencyExtensions +{ + /// + /// 依赖项 + /// + public static readonly DependencyProperty MyPropertyProperty2 = + DependencyProperty.Register("MyProperty2", typeof(DependencyExtensions), 10); + + /// + /// 设置MyProperty2 + /// + /// + /// + /// + /// + public static TClient SetMyProperty2(this TClient client, int value) where TClient : IDependencyObject + { + client.SetValue(MyPropertyProperty2, value); + return client; + } + + /// + /// 获取MyProperty2 + /// + /// + /// + /// + public static int GetMyProperty2(this TClient client) where TClient : IDependencyObject + { + return client.GetValue(MyPropertyProperty2); + } +} +``` + +那么这时候,**MyDependencyObject**对象即可赋值和获取**MyProperty2**的属性。 + +```csharp {2-3} +MyDependencyObject obj=new MyDependencyObject(); +obj.SetMyProperty2(2);//扩展属性必须通过扩展方法 +int value=obj.GetMyProperty2(); +``` + +:::tip 提示 + +扩展的**SetMyProperty2**和**GetMyProperty2**不是必须的。如果没有这两个方法,我们依然可以使用**GetValue**和**SetValue**方法访问。 + +```csharp {2-3} +MyDependencyObject obj=new MyDependencyObject(); +obj.SetValue(DependencyExtensions.MyPropertyProperty2,2); +int value=obj.GetValue(DependencyExtensions.MyPropertyProperty2); +``` + +::: + +## 三、场景 + +假设以下情况: +有一个Person类,已经被封装好了,甚至已经被编译成dll了。但是他只有一个Age属性,如果我们想在开发后期再添加属性,应该怎么办呢? + +常规做法就是继承,然后在子类添加属性。亦或者修改源码,重新编译。 + +无论哪一种都有很大的麻烦事。 + +继承,会让显式的Person类无法使用声明到子类的属性,到时候必须进行强制转换,而一旦继承分支多起来的话,将非常糟糕。 + +而重新编译,带来的问题就更大了,总不能把属性都声明在父类吧。何况还有dll版本依赖问题,同事推脱问题,巴拉巴拉。 + +```csharp showLineNumbers +public class Person +{ + /// + /// 年龄 + /// + public int Age { get; set; }//常规属性 +} +``` + +那我们如何解决呢? + +我们可以在一开始,就将Person类按如下声明。继承DependencyObject。 + +```csharp showLineNumbers +public class Person : DependencyObject +{ + /// + /// 年龄 + /// + public int Age { get; set; }//常规属性 +} +``` + +:::tip 提示 + +如果不方便继承,也可以实现**IDependencyObject**接口,但是可能你需要复制**DependencyObject**的实现源码到你的基类中。 + +::: + +然后,就Ok了。当后续你需要什么属性的时候,自己声明扩展即可。 + +这样,你就可以随意的往**Person**类中添加属性了。 + +```csharp showLineNumbers +public static class DependencyExtensions +{ + /// + /// 依赖项 + /// + public static readonly DependencyProperty MyPropertyProperty2 = + DependencyProperty.Register("MyProperty2", typeof(DependencyExtensions), 10); + + /// + /// 设置MyProperty2 + /// + /// + /// + /// + /// + public static TClient SetMyProperty2(this TClient client, int value) where TClient : IDependencyObject + { + client.SetValue(MyPropertyProperty2, value); + return client; + } + + /// + /// 获取MyProperty2 + /// + /// + /// + /// + public static int GetMyProperty2(this TClient client) where TClient : IDependencyObject + { + return client.GetValue(MyPropertyProperty2); + } +} +``` + +:::tip 提示 + +在声明扩展时,还可以对**where TClient : IDependencyObject**进行泛型约束,这样,就能管理属性的作用域了。 + +::: + +## 四、优缺点 + +**优点:** + +1. 可以不声明在类内部。这意味着可以从外部注入。 +2. 不需要初始赋值,也就意味着创建大量对象时,可以不需要占用太多内存。 + +**缺点:** + +1. 对性能有一定性能影响。准确说,对于值类型,有拆装箱、查字典两个损耗。对于引用类型,会多一个字典查询操作。但是这种性能损耗,经实测,在1亿次存取时,才有不到一秒的差距。 + diff --git a/handbook/versioned_docs/version-3.1/description.mdx b/handbook/versioned_docs/version-3.1/description.mdx new file mode 100644 index 000000000..4a7eabe17 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/description.mdx @@ -0,0 +1,139 @@ +--- +id: description +title: 说明 +slug: / +--- + +--- +:::tip 新课程公告!!! + +**C# TouchSocket 网络通信开发课程正式上线啦!** + +n多课时系统讲解TCP/IP协议原理、Socket编程核心技术。涵盖开发环境搭建、插件机制、12种消息适配器实战,含企业级聊天系统案例(更新中)。支持多端学习。适合零基础开发者及技术进阶人群。 + +立即报名:[课程详情](./video.mdx) + + +特此公告。 + +::: +--- + + +## 使用前必要阅读 + +TouchSocket 由作者若汝棋茗及其他贡献者开发,所有版权归作者若汝棋茗所有,程序集源代码在遵循 Apache License 2.0 的开源协议以及**附加协议**下,可**免费**供其他开发者二次开发或(商业)使用。 + +包含以下组件: + +- [![NuGet version (TouchSocket.Core)](https://img.shields.io/nuget/v/TouchSocket.Core.svg?label=TouchSocket.Core)](https://www.nuget.org/packages/TouchSocket.Core) +- [![NuGet version (TouchSocket.Core.DependencyInjection)](https://img.shields.io/nuget/v/TouchSocket.Core.DependencyInjection.svg?label=TouchSocket.Core.DependencyInjection)](https://www.nuget.org/packages/TouchSocket.Core.DependencyInjection) +- [![NuGet version (TouchSocket.Core.Autofac)](https://img.shields.io/nuget/v/TouchSocket.Core.Autofac.svg?label=TouchSocket.Core.Autofac)](https://www.nuget.org/packages/TouchSocket.Core.Autofac) +- [![NuGet version (TouchSocket)](https://img.shields.io/nuget/v/TouchSocket.svg?label=TouchSocket)](https://www.nuget.org/packages/TouchSocket) +- [![NuGet version (TouchSocket.Http)](https://img.shields.io/nuget/v/TouchSocket.Http.svg?label=TouchSocket.Http)](https://www.nuget.org/packages/TouchSocket.Http) +- [![NuGet version (TouchSocket.NamedPipe)](https://img.shields.io/nuget/v/TouchSocket.NamedPipe.svg?label=TouchSocket.NamedPipe)](https://www.nuget.org/packages/TouchSocket.NamedPipe) +- [![NuGet version (TouchSocket.Rpc)](https://img.shields.io/nuget/v/TouchSocket.Rpc.svg?label=TouchSocket.Rpc)](https://www.nuget.org/packages/TouchSocket.Rpc) +- [![NuGet version (TouchSocket.Dmtp)](https://img.shields.io/nuget/v/TouchSocket.Dmtp.svg?label=TouchSocket.Dmtp)](https://www.nuget.org/packages/TouchSocket.Dmtp) +- [![NuGet version (TouchSocket.JsonRpc)](https://img.shields.io/nuget/v/TouchSocket.JsonRpc.svg?label=TouchSocket.JsonRpc)](https://www.nuget.org/packages/TouchSocket.JsonRpc) +- [![NuGet version (TouchSocket.XmlRpc)](https://img.shields.io/nuget/v/TouchSocket.XmlRpc.svg?label=TouchSocket.XmlRpc)](https://www.nuget.org/packages/TouchSocket.XmlRpc) +- [![NuGet version (TouchSocket.WebApi)](https://img.shields.io/nuget/v/TouchSocket.WebApi.svg?label=TouchSocket.WebApi)](https://www.nuget.org/packages/TouchSocket.WebApi) +- [![NuGet version (TouchSocket.WebApi.Swagger)](https://img.shields.io/nuget/v/TouchSocket.WebApi.Swagger.svg?label=TouchSocket.WebApi.Swagger)](https://www.nuget.org/packages/TouchSocket.WebApi.Swagger) +- [![NuGet version (TouchSocket.Hosting)](https://img.shields.io/nuget/v/TouchSocket.Hosting.svg?label=TouchSocket.Hosting)](https://www.nuget.org/packages/TouchSocket.Hosting) +- [![NuGet version (TouchSocket.AspNetCore)](https://img.shields.io/nuget/v/TouchSocket.AspNetCore.svg?label=TouchSocket.AspNetCore)](https://www.nuget.org/packages/TouchSocket.AspNetCore) +- [![NuGet version (TouchSocket.Modbus)](https://img.shields.io/nuget/v/TouchSocket.Modbus.svg?label=TouchSocket.Modbus)](https://www.nuget.org/packages/TouchSocket.Modbus) + +:::tip 提示 + +即所有以“TouchSocket”开头的Nuget包,均已完全开源,个人、商用均可完全免费使用。 + +::: + +# Apache License 2.0 开源协议简述 + +- 永久权利 +- 一旦被授权,永久拥有。 +- 全球范围的权利 +- 在一个国家获得授权,适用于所有国家。假如你在美国,许可是从印度授权的,也没有问题。 +- 授权免费,且无版税 +- 前期,后期均无任何费用。 +- 授权无排他性 +- 任何人都可以获得授权 +- 授权不可撤消 +- 一旦获得授权,没有任何人可以取消。比如,你基于该产品代码开发了衍生产品,你不用担心会在某一天被禁止使用该代码。 + +# 附加协议 + + + +### 个人使用须知: + +- 不得将程序集用作违法犯罪活动。 +- 不得将程序集单独包装售卖,申请专利等。 +- 不得擦除程序集所有有关作者的信息。 + +:::caution 警告 + +以上内容必须全部符合,个人使用授权才成立。 + +::: + +### 二次开发须知: + +- 不得将程序集用作违法犯罪活动。 +- 不得将程序集单独包装售卖,申请专利等。 +- 不得擦除程序集所有有关作者的信息。 +- 二次开发完成后的作品必须附带源作品所有作者信息,包括但不限于作者名、Gitee、Github 地址等。 +- ~~**完成后**的作品(仅 TouchSocket 部分)必须将发布时最新源代码提交一份给本作者,QQ 邮箱:505554090@qq.com~~。 + +:::caution 警告 + +以上内容必须全部符合,二次开发授权才成立。 + +::: + +### 盈利性(商业)用途使用须知: + +- 不得将程序集用作违法犯罪活动。 +- 不得将程序集单独包装售卖,申请专利等。 +- **不得擦除程序集所有有关作者的信息。** + +:::caution 警告 + +以上内容必须全部符合,使用授权才成立。 + +::: + +:::tip 建议 + +TouchSocket核心部分都是开源免费的,所以我们希望当它帮到您的时候,您也能帮TouchSocket多多宣传。例如:可以在用户可见界面进行提名感谢等。 + +::: + +## TouchSocketPro 系商用许可 + +`TouchSocketPro`系是`TouchSocket`的Pro,其 99%功能与`TouchSocket`一致。所有版权归作者若汝棋茗所有。 + +包含以下组件: + +- [![NuGet version (TouchSocketPro)](https://img.shields.io/nuget/v/TouchSocketPro.svg?label=TouchSocketPro)](https://www.nuget.org/packages/TouchSocketPro) +- [![NuGet version (TouchSocketPro.Dmtp)](https://img.shields.io/nuget/v/TouchSocketPro.Dmtp.svg?label=TouchSocketPro.Dmtp)](https://www.nuget.org/packages/TouchSocketPro.Dmtp) +- [![NuGet version (TouchSocketPro.AspNetCore)](https://img.shields.io/nuget/v/TouchSocketPro.AspNetCore.svg?label=TouchSocketPro.AspNetCore)](https://www.nuget.org/packages/TouchSocketPro.AspNetCore) +- [![NuGet version (TouchSocketPro.Modbus)](https://img.shields.io/nuget/v/TouchSocketPro.Modbus.svg?label=TouchSocketPro.Modbus)](https://www.nuget.org/packages/TouchSocketPro.Modbus) +- [![NuGet version (TouchSocketPro.PlcBridges)](https://img.shields.io/nuget/v/TouchSocketPro.PlcBridges.svg?label=TouchSocketPro.PlcBridges)](https://www.nuget.org/packages/TouchSocketPro.PlcBridges) + +:::tip 提示 + +即所有以“TouchSocketPro”开头的Nuget包,这可能需要授权才可以使用。 + +::: + +### TouchSocketPro 功能部分遵循: + +- 限时(1h)免费测试,测试期间可参与商业使用。 +- 付费使用,购买后还须遵循相关使用协议,详情咨询若汝棋茗。 + +**`TouchSocketPro`程序集源代码不公开开源,需要付费购买。** + +# 免责申明 + +**在使用`TouchSocket`**或**`TouchSocketPro`之前请进行缜密的测试。在使用期间,由本程序集造成或间接造成的所有损失,均自己承担,与本程序集无关。** diff --git a/handbook/versioned_docs/version-3.1/dmtpbase.mdx b/handbook/versioned_docs/version-3.1/dmtpbase.mdx new file mode 100644 index 000000000..614a7ceef --- /dev/null +++ b/handbook/versioned_docs/version-3.1/dmtpbase.mdx @@ -0,0 +1,603 @@ +--- +id: dmtpbase +title: Dmtp基础功能 +--- + +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import CardLink from "@site/src/components/CardLink.js"; +import { TouchSocketDmtpDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、连接 + +连接验证可以初步保证连接客户端的安全性。框架内部默认使用一个`string`类型的`Token`作为验证凭证。当然也允许服务器进行其他验证。具体如下: + +### 1.1 Token验证 + +在服务器或客户端的配置上,设置`VerifyToken`,即可实现字符串`Token`验证。 + + + +```csharp {4} showLineNumbers +var config = new TouchSocketConfig() + .SetDmtpOption(new DmtpOption() + { + VerifyToken = "Dmtp" + }); +``` + +### 1.2 动态验证 + +使用插件,实现**IDmtpHandshakingPlugin**插件。然后可以自行判断一些信息,例如元数据等。 + +如果是特定协议的`Dmtp`,还可以判断一些特定信息,例如,在`TcpDmtp`中,可以判断`IP`地址等。 + +客户端,在连接时,可以设置元数据。 + +```csharp {4} showLineNumbers +var config = new TouchSocketConfig() + .SetDmtpOption(new DmtpOption() + { + Metadata=new Metadata().Add("a","a") + }); +``` + +服务端使用插件验证连接信息。 + +```csharp showLineNumbers +internal class MyVerifyPlugin : PluginBase, IDmtpHandshakingPlugin +{ + public async Task OnDmtpHandshaking(IDmtpActorObject client, DmtpVerifyEventArgs e) + { + if (e.Metadata["a"] != "a") + { + e.IsPermitOperation = false;//不允许连接 + e.Message = "元数据不对";//同时返回消息 + //表示该消息已在此处处理。 + return; + } + + if (client is ITcpDmtpSessionClient sessionClient) + { + //在特定协议的情况下,可以获取特定信息 + var ip = sessionClient.IP; + } + + if (e.Token == "Dmtp") + { + e.IsPermitOperation = true;//允许连接 + } + + await e.InvokeNext(); + } +} +``` + +### 1.4 跨语言 + +为使`Dmtp`支持跨语言,`Dmtp`在设计之初就预留了跨语言连接的便利性。诚如[Dmtp描述](./dmtpdescription.mdx)所示,其基础数据报文为`Head+Flags+Length+Data`。而框架内部的`Handshake`、`Ping`、`Pong`、`Close`等指令均是采用`Json`数据格式。但是即使如此,连接时的真正数据,还与其基础协议有关。具体如下: + +以连接、操作`TcpDmtpService`为例。其基础协议即为`tcp`,则使用常规的`tcp`客户端即可模拟链接。 + +```csharp showLineNumbers +using var tcpClient = new TcpClient();//创建一个普通的tcp客户端。 +tcpClient.Received = (client, e) => +{ + //此处接收服务器返回的消息 + + var head = e.ByteBlock.ToArray(0, 2); + e.ByteBlock.Seek(2, SeekOrigin.Begin); + var flags = e.ByteBlock.ReadUInt16(EndianType.Big); + var length = e.ByteBlock.ReadInt32(EndianType.Big); + + var json = e.ByteBlock.Span.ToString(Encoding.UTF8); + + ConsoleLogger.Default.Info($"收到响应:flags={flags},length={length},json={json.Replace("\r\n", string.Empty).Replace(" ", string.Empty)}"); + + + return Task.CompletedTask; +}; + +//开始链接服务器 +await tcpClient.ConnectAsync("127.0.0.1:7789"); + +//以json的数据方式。 +//其中Token、Metadata为连接的验证数据,分别为字符串、字符串字典类型。 +//Id则表示指定的默认id,字符串类型。 +//Sign为本次请求的序号,一般在连接时指定一个大于0的任意数字即可。 +var json = @"{""Token"":""Dmtp"",""Metadata"":{""a"":""a""},""Id"":null,""Sign"":1}"; + +//将json转为utf-8编码。 +var jsonBytes = Encoding.UTF8.GetBytes(json); + +using (var byteBlock = new ByteBlock(1024*64)) +{ + //按照Head+Flags+Length+Data的格式。 + byteBlock.Write(Encoding.ASCII.GetBytes("dm")); + byteBlock.Write(TouchSocketBitConverter.BigEndian.GetBytes((ushort)1)); + byteBlock.Write(TouchSocketBitConverter.BigEndian.GetBytes((int)jsonBytes.Length)); + byteBlock.Write(jsonBytes); + + await tcpClient.SendAsync(byteBlock.Memory); +} + +await Task.Delay(2000); +``` + +接收输出: + +收到的`Json`字符串,会返回服务器最终修改的`Token`、`Metadata`。同时还包括分配或指定的`Id`。`Sign`会与请求时一致,表示这是同一组请求。`Status`等于`1`即为连接成功。其他值则可能在`Message`表明连接失败的原因。 + +``` +收到响应:flags=2,length=124,json={"Token":"Dmtp","Metadata":{"a":"a"},"Id":"1","Message":null,"Sign":1,"Status":1} +``` + +其他`Json`请求,包括: + +【请求连接】 + +- Token:连接令牌,字符串类型。 +- Metadata:连接元数据,字典类型。 +- Id:初始连接Id,字符串类型。 +- Sign:请求序号,整数类型,应当每次请求递增。 +- Status(仅返回时携带):连接状态,整数类型,为1时成功,其他状态可以获取Message。 +- Message(仅返回时携带):连接消息。 + +```javascript +{"Token":"Dmtp","Metadata":{"a":"a"},"Id":null,"Sign":1} +``` + +【Ping】 + +- Sign:请求序号,整数类型,应当每次请求递增。 +- Status(仅返回时携带):连接状态,整数类型,为1时成功,其他状态可以获取Message。 +- Message(仅返回时携带):连接消息。 +- Route:是否路由,布尔类型,当TargetId不为空时应设为True。 +- SourceId:源Id,字符串类型,当需要Ping其他客户端时,应该将该值设为自身Id,以保证返回的信息能够顺利路由回来。 +- TargetId:目标Id,字符串类型,当需要Ping其他客户端时,应该将该值设为目标Id,以保证信息能够顺利送达。 + +```javascript +{"Sign":0,"Status":0,"Message":null,"Route":false,"SourceId":null,"TargetId":null} +``` + +【Close】 + +`Close`报文直接采用utf8编码的字符串。 +``` +"close" +``` + +:::note 备注 + +DMTP基础功能是比较容易跨语言的,但这并不意味着DMTP所有的功能都支持。其扩展功能,则需要实际的跨平台设计。不过依靠简单的Head+Flags+Length+Data的格式,也能自己实现很多的功能。 + +::: + +## 二、Id同步 + +在Dmtp中,存在于服务器的辅助客户端(SessionClient),与远程客户端(Client)是一一对应关系,其Id也**完全一致**。所以在任意一方修改Id(调用ResetId),都会同时修改远程Id。所以合理使用该操作,可以完成复用Id(重置Id)的需求。 + + + +## 三、发送数据 + +Dmtp提供协议发送数据,又叫协议扩展功能,就是对现有的Dmtp进行自定义的扩展协议。 + +使用起来是非常简单的,每个DmtpActor,都实现了Send方法接口。 + +第一个参数为`ushort`类型,使用者可以**约定任意大于20数值**。 + + + + + +```csharp showLineNumbers +client.SendAsync(1000,Encoding.UTF8.GetBytes("RRQM")); +``` + +:::caution 注意 + +`Flags`不要使用小于20的,因为框架内部在使用。并且小于100的也最好不要使用,因为可能其他组件也在使用。 + +::: + +在**接收方**订阅`IDmtpReceivedPlugin`,已经包含了协议参数,所以直接自行筛选即可。 + +```csharp showLineNumbers +internal class MyFlagsPlugin : PluginBase, IDmtpReceivedPlugin +{ + public async Task OnDmtpReceived(IDmtpActorObject client, DmtpMessageEventArgs e) + { + if (e.DmtpMessage.ProtocolFlags == 1000) + { + //判断完协议以后,从 e.DmtpMessage.BodyByteBlock可以拿到实际的数据 + string msg = e.DmtpMessage.BodyByteBlock.ToString(); + + return; + } + + //flags不满足,调用下一个插件 + await e.InvokeNext(); + } +} +``` + +## 四、使用Channel发送数据 + +`Channel`是`DmtpActor`的一个传输通道。一般来说,`DmtpActor`是一个逻辑连接(例如:一个`Tcp`、`Udp`、`NamedPipe`等)。而`Channel`则是更加细分`DmtpActor`的一个传输通道。 + +换言之,一个`DmtpActor`可以有**多个Channel**。多个`Channel`可以**同时进行传输**。并且数据互不干扰。 + + + + + +### 4.1 特点 + +1. 每个`Channel`都有独立的Id,所以多个`Channel`可以同时工作。 +2. 每个`Channel`在被创建后,都支持双向读写。 +3. 每个`Channel`都支持自定义限速和读写超时设定。 + +### 4.2 使用步骤 + +1. 由一方`DmtpActor`创建一个`Channel`,并记住Id。 +2. 另一方`DmtpActor`使用这个Id订阅`Channel`。默认情况下,`Channel`在被创建时,接收端会触发`IDmtpCreatedChannelPlugin`插件。此时可以直接订阅。 +3. 发送方使用`Channel`发送(`WriteAsync`)数据 +4. 接收方通过订阅的`Channel`,接收(Read)数据 + +下列以`TcpDmtpClient`发送、`TcpDmtpService`接收为例: + +【创建服务器】 + +```csharp showLineNumbers +var service = new TcpDmtpService(); + +var config = new TouchSocketConfig()//配置 + .SetListenIPHosts(7789) + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .ConfigurePlugins(a => + { + a.Add(); + }) + .SetDmtpOption(new DmtpOption() + { + VerifyToken = "Channel"//连接验证口令。 + }); + +await service.SetupAsync(config); +await service.StartAsync(); +service.Logger.Info("服务器成功启动"); +``` + +【创建客户端】 + +```csharp showLineNumbers +var client = await new TouchSocketConfig() + .SetRemoteIPHost("127.0.0.1:7789") + .SetDmtpOption(new DmtpOption() + { + VerifyToken = "Channel" + }) + .SetSendTimeout(0) + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .ConfigurePlugins(a => + { + }) + .BuildClientAsync();//相当于创建客户端并配置,连接 + +client.Logger.Info("连接成功"); +``` + +【MyPlugin插件】 + +```csharp showLineNumbers +internal class MyPlugin : PluginBase, IDmtpCreatedChannelPlugin +{ + private readonly ILog m_logger; + + public MyPlugin(ILog logger) + { + this.m_logger = logger; + } + + public async Task OnDmtpCreatedChannel(IDmtpActorObject client, CreateChannelEventArgs e) + { + if (client.TrySubscribeChannel(e.ChannelId, out var channel)) + { + //设定读取超时时间 + //channel.Timeout = TimeSpan.FromSeconds(30); + using (channel) + { + this.m_logger.Info("通道开始接收"); + + //此判断主要是探测是否有Hold操作 + while (channel.CanMoveNext) + { + long count = 0; + foreach (var byteBlock in channel) + { + //这里处理数据 + count += byteBlock.Length; + this.m_logger.Info($"通道已接收:{count}字节"); + } + + this.m_logger.Info($"通道接收结束,状态={channel.Status},短语={channel.LastOperationMes},共接收{count / (1048576.0):0.00}Mb字节"); + } + } + } + + await e.InvokeNext(); + } +} +``` + +【客户端创建Channel并写入数据】 + +`Channel`支持持续写入,且无长度限制。但是需要注意的是,在写入结束时,应该调用**终止语句**(下文详解),以结束写入并通知接收端。 + +```csharp {14,18} showLineNumbers +var count = 1024 * 1;//测试1Gb数据 + +//1.创建通道,同时支持通道路由和元数据传递 +using (var channel = client.CreateChannel()) +{ + //设置限速 + //channel.MaxSpeed = 1024 * 1024; + + ConsoleLogger.Default.Info($"通道创建成功,即将写入{count}Mb数据"); + var bytes = new byte[1024 * 1024]; + for (var i = 0; i < count; i++) + { + //2.持续写入数据 + await channel.WriteAsync(bytes); + } + + //3.在写入完成后调用终止指令。例如:Complete、Cancel、HoldOn、Dispose等 + await channel.CompleteAsync("我完成了"); + ConsoleLogger.Default.Info("通道写入结束"); +} +``` + + +【服务器使用插件订阅Channel】 + +插件的主要作用是处理`OnDmtpCreatedChannel`事件。 + +在插件中,我们需要**订阅Channel**,然后**接收Channel的数据**,最后执行相应的操作。 + +不过这并不是必须的,如果你有其他逻辑能处理**订阅**,那么也可以不使用插件。例如:[DmtpRpc大数据传输](./dmtprpc.mdx)示例中,就使用了`Rpc`将创建的`Channel`的`Id`传递到响应端,然后响应端再处理订阅和读写。 + +```csharp {3,15-20} showLineNumbers +public async Task OnDmtpCreatedChannel(IDmtpActorObject client, CreateChannelEventArgs e) +{ + if (client.TrySubscribeChannel(e.ChannelId, out var channel)) + { + //设定读取超时时间 + //channel.Timeout = TimeSpan.FromSeconds(30); + using (channel) + { + this.m_logger.Info("通道开始接收"); + + //此判断主要是探测是否有Hold操作 + while (channel.CanMoveNext) + { + long count = 0; + foreach (var byteBlock in channel) + { + //这里处理数据 + count += byteBlock.Length; + this.m_logger.Info($"通道已接收:{count}字节"); + } + + this.m_logger.Info($"通道接收结束,状态={channel.Status},短语={channel.LastOperationMes},共接收{count / (1048576.0):0.00}Mb字节"); + } + } + } + + await e.InvokeNext(); +} +``` + +### 4.3 终止语句 + +Channel的终止语句总共有4个: + +- **Complete**:完成,表示写入结束,接收端会收到完成的状态。 +- **Cancel**:取消,表示写入被取消,接收端会收到取消的状态。 +- **HoldOn**:等待继续,表示写入被中断,但是可以恢复传输,接收端会跳出接收,但是可以重新进入接收。 +- **Dispose**:释放,表示写入被释放,接收端会收到释放的状态。 + +其中`Complete`、`Cancel`、`Dispose`均为一次性终止语句,即调用以后,`Channel`将不可再被使用。如果想再使用`Channel`,则必须重新创建,重新订阅。 + +`HoldOn`为可恢复的终止语句,调用以后,`Channel`会进入等待状态,接收端会跳出接收,但是可以重新进入接收。这在多段数据传输传输时是有用的。 + +例如: + +下列示例中,我们使用`HoldOn`来模拟多段数据传输。每次发送10段1024字节的数据,然后调用`HoldOn`,循环100次。 + +```csharp {12-21} showLineNumbers +//HoldOn的使用,主要是解决同一个通道中,多个数据流传输的情况。 + +//1.创建通道,同时支持通道路由和元数据传递 +using (var channel = client.CreateChannel()) +{ + //设置限速 + //channel.MaxSpeed = 1024 * 1024; + + ConsoleLogger.Default.Info($"通道创建成功,即将写入"); + var bytes = new byte[1024]; + + for (var i = 0; i < 100; i++)//循环100次 + { + for (var j = 0; j < 10; j++) + { + //2.持续写入数据 + await channel.WriteAsync(bytes); + } + //3.在某个阶段完成数据传输时,可以调用HoldOn + await channel.HoldOnAsync("等一下下"); + } + //4.在写入完成后调用终止指令。例如:Complete、Cancel、HoldOn、Dispose等 + await channel.CompleteAsync("我完成了"); + + ConsoleLogger.Default.Info("通道写入结束"); +} +``` + +这样接收端会收到100次10段1024字节的数据。 + +```csharp {16-27} showLineNumbers +internal class MyPlugin : PluginBase, IDmtpCreatedChannelPlugin +{ + ... + + public async Task OnDmtpCreatedChannel(IDmtpActorObject client, CreateChannelEventArgs e) + { + if (client.TrySubscribeChannel(e.ChannelId, out var channel)) + { + //设定读取超时时间 + //channel.Timeout = TimeSpan.FromSeconds(30); + using (channel) + { + this.m_logger.Info("通道开始接收"); + + //此判断主要是探测是否有Hold操作 + while (channel.CanMoveNext) + { + long count = 0; + foreach (var byteBlock in channel) + { + //这里处理数据 + count += byteBlock.Length; + this.m_logger.Info($"通道已接收:{count}字节"); + } + + this.m_logger.Info($"通道接收结束,状态={channel.Status},短语={channel.LastOperationMes},共接收{count / (1048576.0):0.00}Mb字节"); + } + } + } + + await e.InvokeNext(); + } +} +``` + +### 4.3 终止语句短语 + +我们在调用终止指令时,同时可以传递一个字符串短语。 + +例如: + +```csharp showLineNumbers +await channel.CompleteAsync("我完成了"); +``` + +### 4.4 传输限速 + +传输限速,可以限制`Channel`的传输速度。 + +限速操作仅可以在发送端设置。 + +```csharp showLineNumbers +//设置限速 +channel.MaxSpeed = 1024 * 1024; +``` + +### 4.5 传输超时 + +`Channel`是基于写入和读取的异步机制。如果写入方迟迟没有数据写入,那读取方可能在一段时间的等待后超时。 + +或者如果读取方迟迟没有数据读取,那写入方在写入一部分数据后,会发生拥塞,如果时间较长,也会发生等待超时。 + +当然我们可以设置超时时间: + +```csharp showLineNumbers +//设定读取超时时间 +channel.Timeout = TimeSpan.FromSeconds(30); +``` + +### 4.6 注意事项 + +1. `Channel`的传输是有序的。 +2. `Channel`支持所有的`Dmtp`组件,但要求是该组件必须基于可靠协议,例如:`UdpDmtp`则不支持。 +3. `Channel`拥有自己的拥塞控制,当发生拥塞的时候,不会阻塞底层协议。但是应该也要避免发生拥塞。 +4. `Channel`在使用完成后,必须**显示释放**,所以建议使用using关键字。 + +[Channel示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Dmtp/DmtpChannelConsoleApp) + +## 五、使用心跳 + +`Dmtp`组件自带了`Ping`(或者`PingAsync`)方法。这个方法强制要求被`Ping`的一方无条件响应的。 + +所以,如果`Ping`返回`true`,则说明对方**必在线**。但是如果返回`false`,则**不一定代表对方不在线**。因为有可能是`Ping`超时,或者当前传输链路正在忙。 + +总之,使用者可以在业务中调用这个方法,来检测通讯是否通畅。 + +同时,库中基于此方法,封装了一个可用心跳插件。 + +其中可以配置**心跳间隔**和**最大失败次数**。 + +```csharp {3} showLineNumbers +.ConfigurePlugins(a => +{ + a.UseDmtpHeartbeat() + .SetTick(TimeSpan.FromSeconds(3)) + .SetMaxFailCount(3); +}) +``` + +:::tip 提示 + +`Ping`可以由客户端或服务器任意一端发起,然后要求对方回应。但是一般我们建议由客户端发起。由服务器响应即可。如果有特殊要求,则自行解决即可。 + +::: + +## 六、使用断线重连 + +```csharp showLineNumbers +.ConfigurePlugins(a => +{ + a.Add(); + + //使用重连 + a.UseDmtpReconnection() + .UsePolling(TimeSpan.FromSeconds(3))//使用轮询,每3秒检测一次 + .SetActionForCheck(async (c, i) =>//重新定义检活策略 + { + //方法1,直接判断是否在握手状态。使用该方式,最好和心跳插件配合使用。因为如果直接断网,则检测不出来 + //await Task.CompletedTask;//消除Task + //return c.Online;//判断是否在握手状态 + + //方法2,直接ping,如果true,则客户端必在线。如果false,则客户端不一定不在线,原因是可能当前传输正在忙 + if (await c.PingAsync()) + { + return true; + } + //返回false时可以判断,如果最近活动时间不超过3秒,则猜测客户端确实在忙,所以跳过本次重连 + else if (DateTime.Now - c.GetLastActiveTime() < TimeSpan.FromSeconds(3)) + { + return null; + } + //否则,直接重连。 + else + { + return false; + } + }); +}) +``` + +## 七、示例代码 + + + \ No newline at end of file diff --git a/handbook/versioned_docs/version-3.1/dmtpclient.mdx b/handbook/versioned_docs/version-3.1/dmtpclient.mdx new file mode 100644 index 000000000..913677ee8 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/dmtpclient.mdx @@ -0,0 +1,187 @@ +--- +id: dmtplient +title: 创建Dmtp客户端 +--- + +import Tag from "@site/src/components/Tag.js"; +import Pro from "@site/src/components/Pro.js"; +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import { TouchSocketProDmtpDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +Dmtp客户端对应的,也有不同协议的版本。各个版本之间功能基本一致。 + +## 二、可配置项 + +
+可配置项 +
+ +#### SetDmtpOption +设置Dmtp相关配置。其中包含: + +- VerifyToken:设置验证口令。 +- Id:连接时指定Id。 +- Metadata:连接时指定元数据。可以在连接时,指定一些字符串键值对。 + +
+
+ +## 三、支持插件接口 + +声明自定义插件类,实现`IPlugin`接口,或者继承`PluginBase`,然后实现所需插件接口,即可实现事务的触发。 + +| 插件方法 | 功能 | +| --- | --- | +| IDmtpHandshakingPlugin | 客户端在验证连接。默认情况下,框架会首先验证连接Token是否正确,如果不正确则直接拒绝。不会有任何投递。用户也可以使用Metadata进行动态验证。 | +| IDmtpHandshakedPlugin | 客户端完成握手连接验证 | +| IDmtpReceivedPlugin | 在收到Dmtp格式的数据包时触发 | +| IDmtpRoutingPlugin | 当需要路由数据时触发,并且必须返回e.IsPermitOperation=true时,才允许路由 | +| IDmtpCreatedChannelPlugin | 在收到创建通道的请求时候触发。 | +| IDmtpClosingPlugin | 即将断开连接时触发(仅主动断开时、或收到了Close报文时有效)。 | +| IDmtpClosedPlugin | 在Dmtp连接断开时触发。 | + + +## 四、创建 + +### 4.1 TcpDmtpClient + +`TcpDmtpClient`对应`TcpDmtpService`服务器。基本创建如下,支持[创建TcpClient](./tcpclient.mdx)的所有配置。 + + + +```csharp showLineNumbers +var client = new TcpDmtpClient(); +await client.SetupAsync(new TouchSocketConfig() + .SetRemoteIPHost("127.0.0.1:7789") + .SetDmtpOption(new DmtpOption() + { + VerifyToken = "Dmtp" + })); +await client.ConnectAsync(); +``` + +### 4.2 UdpDmtp + +`UdpDmtp`对应`UdpDmtp`服务器,即`UdpDmtp`即是服务器,又是客户端。基本创建如下,支持[创建UdpSession](./udpsession.mdx)的所有配置。 + + + +```csharp {1,4-6,11} showLineNumbers +var client = new UdpDmtp(); + + await client.SetupAsync(new TouchSocketConfig() + .SetRemoteIPHost("127.0.0.1:7797") + .UseUdpReceive() + .SetDmtpOption(new DmtpOption() + { + VerifyToken = "Dmtp" + }) + .ConfigureContainer(a => + { + }) + .ConfigurePlugins(a => + { + })); + client.Start(); +``` + +:::info 备注 + +`UdpDmtp`作为客户端时,需要设定默认的`SetRemoteIPHost`,同时需要主动`UseUdpReceive`,以开启udp接收。如果需要固定监听端口,可使用`SetBindIPHost`。 + +::: + +### 4.3 HttpDmtpClient + +`HttpDmtpClient`对应`HttpDmtpService`,或者`HttpMiddlewareDmtpService`服务器。基本创建如下,支持[创建HttpClient](./httpclient.mdx)的所有配置。 + + + +```csharp showLineNumbers +var client = new HttpDmtpClient(); +await client.SetupAsync(new TouchSocketConfig() + .SetRemoteIPHost("127.0.0.1:7789") + .SetDmtpOption(new DmtpOption() + { + VerifyToken = "Dmtp" + })); +await client.ConnectAsync(); +``` + +### 4.4 WebSocketDmtpClient + + + +```csharp showLineNumbers +var websocketDmtpClient = new WebSocketDmtpClient(); +websocketDmtpClient.Setup(new TouchSocketConfig() + .SetDmtpOption(new DmtpOption() + { + VerifyToken = "Dmtp" + }) + .SetRemoteIPHost("ws://localhost:5174/WebSocketDmtp")); +await websocketDmtpClient.ConnectAsync(); +``` + +[基于Aspnetcore的Dmtp示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Dmtp/DmtpWebApplication) + + +### 4.5 DmtpClient工厂 + +DmtpClient工厂工作模式是一种,由多个传输通讯连接组成的连接池。默认提供了两种类型的客户端工厂,分别为`TcpDmtpClientFactory`,`HttpDmtpClientFactory`。两者工作流程及配置基本相同,下面以`TcpDmtpClientFactory`为例。 + +【创建】 +```csharp showLineNumbers +var clientFactory = new TcpDmtpClientFactory() +{ + MinCount=5,//最小数量,在主连接器成功建立以后,会检测可用连接是否大于该值,否的话会自动建立。 + MaxCount = 10,//最大数量,当超过该数量的连接后,会等待指定时间。 + ConnectTimeout = TimeSpan.FromSeconds(10),//连接超时时间 + GetConfig = () => + { + return new TouchSocketConfig() + .SetRemoteIPHost("tcp://127.0.0.1:7789"); + } +}; +``` + +【使用】 + +```csharp {3} showLineNumbers +using (var clientFactoryResult = await clientFactory.GetClient()) +{ + var client = clientFactoryResult.Client; +} +``` + +:::tip 提示 + +默认情况下,`clientFactory.GetClient()`会最大等待1秒,如果超过1秒还没有可用的`client`,会再新建一个客户端,并返回。然后在使用结束后,会判断是否需要释放该`client`,如果池中的`client`数量`大于MaxCount`,则会释放该`client`。 + +::: + +### 4.6 NamedPipeDmtpClient + +`NamedPipeDmtpClient`对应`NamedPipeDmtpService`服务器。基本创建如下,支持[创建NamedPipeClient](./namedpipeclient.mdx)的所有配置。 + + + +```csharp showLineNumbers +var client = new NamedPipeDmtpClient(); +await client.SetupAsync(new TouchSocketConfig() + .SetPipeName("TouchSocketPipe")//设置管道名称 + .SetDmtpOption(new DmtpOption() + { + VerifyToken = "Dmtp" + })); +await client.ConnectAsync(); +``` + +[基于NamedPipe的Dmtp示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Dmtp/NamedPipeDmtpConsoleApp) \ No newline at end of file diff --git a/handbook/versioned_docs/version-3.1/dmtpcustomactor.mdx b/handbook/versioned_docs/version-3.1/dmtpcustomactor.mdx new file mode 100644 index 000000000..3c31338b5 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/dmtpcustomactor.mdx @@ -0,0 +1,531 @@ +--- +id: dmtpcustomactor +title: 自定义DmtpActor +--- + +import { TouchSocketDmtpDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +`DmtpActor`是`Dmtp`协议的`Actor`,负责`Dmtp`协议的交互。规范的`Actor`可以做到一次开发,多种协议适用,比如`DmtpRpcActor`、`DmtpFileTransferActor`等。所以学习`Dmtp的Actor`开发,是进阶使用`Dmtp`的必要途径。 + +## 二、使用Actor开发的优点 + +实际上,使用[协议扩展](./dmtpbase.mdx)即可扩展Dmtp的功能,但是使用Actor开发,则更具优点: + +1. 很好的分离了协议的交互逻辑和协议的实现逻辑。可以做到一次开发,多种协议适用。 +2. 按照规范的开发,可以非常好的支持客户端到服务器、服务器到客户端、客户端到客户端的交互。 +3. 更高等级的代码封装,让程序更易维护。 + +## 三、实现 + +`Actor`通讯的原理,就是当`DmtpActor`(主通讯器)收到`DmtpMessage`后,会依次投递给所有的Dmtp插件,当某个插件通过`ProtocolFlag`判断正确后,由本插件支持的`Actor`进行处理数据。 + +下面我们将通过实现一个最简单的Rpc调用来熟悉Actor通讯。 + +基本流程如下: + +### 3.1 声明Actor + +首先新建一个接口`ISimpleDmtpRpcActor`,继承`IActor`。然后新建一个类`SimpleDmtpRpcActor`,实现`ISimpleDmtpRpcActor`接口。 + +声明`m_invoke_Request`和`m_invoke_Response`两个变量,用于判断`ProtocolFlags`能否在当前Actor处理。 + +声明`TryFindMethod`委托,用于向外面插件通过方法名搜索Rpc方法。 + +```csharp showLineNumbers +interface ISimpleDmtpRpcActor : IActor +{ + void Invoke(string methodName); + void Invoke(string targetId, string methodName); +} + +class SimpleDmtpRpcActor : ISimpleDmtpRpcActor +{ + private ushort m_invoke_Request = 1000; + private ushort m_invoke_Response = 1001; + + public IDmtpActor DmtpActor { get; private set; } + public Func TryFindMethod { get; set; } + + public SimpleDmtpRpcActor(IDmtpActor dmtpActor) + { + this.DmtpActor = dmtpActor; + } + + public async Task InputReceivedData(DmtpMessage message) + { + var byteBlock = message.BodyByteBlock; + if (message.ProtocolFlags == this.m_invoke_Request) + { + try + { + var rpcPackage = new SimpleDmtpRpcPackage(); + rpcPackage.UnpackageRouter(byteBlock); + if (rpcPackage.Route && this.DmtpActor.AllowRoute) + { + if (await this.DmtpActor.TryRoute(new PackageRouterEventArgs(new RouteType("SimpleRpc"), rpcPackage))) + { + if (await this.DmtpActor.TryFindDmtpActor(rpcPackage.TargetId) is DmtpActor actor) + { + actor.SendAsync(this.m_invoke_Request, byteBlock); + return true; + } + else + { + rpcPackage.Status = 2; + } + } + else + { + rpcPackage.Status = 3; + } + + byteBlock.Reset(); + rpcPackage.SwitchId(); + + rpcPackage.Package(byteBlock); + this.DmtpActor.SendAsync(this.m_invoke_Response, byteBlock); + } + else + { + rpcPackage.UnpackageBody(byteBlock); + _ = Task.Factory.StartNew(this.InvokeThis, rpcPackage); + } + } + catch (Exception ex) + { + this.DmtpActor.Logger.Error(this, $"在protocol={message.ProtocolFlags}中发生错误。信息:{ex.Message}"); + } + return true; + } + else if (message.ProtocolFlags == this.m_invoke_Response) + { + try + { + var rpcPackage = new SimpleDmtpRpcPackage(); + rpcPackage.UnpackageRouter(byteBlock); + if (this.DmtpActor.AllowRoute && rpcPackage.Route) + { + if (await this.DmtpActor.TryFindDmtpActor(rpcPackage.TargetId) is DmtpActor actor) + { + actor.SendAsync(this.m_invoke_Response, byteBlock); + } + } + else + { + rpcPackage.UnpackageBody(byteBlock); + this.DmtpActor.WaitHandlePool.SetRun(rpcPackage); + } + } + catch (Exception ex) + { + this.DmtpActor.Logger.Error(this, $"在protocol={message.ProtocolFlags}中发生错误。信息:{ex.Message}"); + } + return true; + } + return false; + } + + private void InvokeThis(object obj) + { + var package = (SimpleDmtpRpcPackage)obj; + + var methodModel = this.TryFindMethod.Invoke(package.MethodName); + if (methodModel == null) + { + using (var byteBlock = new ByteBlock(1024*64)) + { + package.Status = 4; + package.SwitchId(); + package.Package(byteBlock); + this.DmtpActor.SendAsync(this.m_invoke_Response, byteBlock); + return; + } + } + + try + { + methodModel.Method.Invoke(methodModel.Target, default); + using (var byteBlock = new ByteBlock(1024*64)) + { + package.Status = 1; + package.SwitchId(); + package.Package(byteBlock); + this.DmtpActor.SendAsync(this.m_invoke_Response, byteBlock); + return; + } + } + catch (Exception ex) + { + using (var byteBlock = new ByteBlock(1024*64)) + { + package.Status = 5; + package.Message = ex.Message; + package.SwitchId(); + package.Package(byteBlock); + this.DmtpActor.SendAsync(this.m_invoke_Response, byteBlock); + return; + } + } + } + + private async Task TryFindDmtpRpcActor(string targetId) + { + if (targetId == this.DmtpActor.Id) + { + return this; + } + if (await this.DmtpActor.TryFindDmtpActor(targetId) is DmtpActor dmtpActor) + { + if (dmtpActor.GetSimpleDmtpRpcActor() is SimpleDmtpRpcActor newActor) + { + return newActor; + } + } + return default; + } + + public void Invoke(string methodName) + { + this.PrivateInvoke(default, methodName); + } + public async void Invoke(string targetId, string methodName) + { + if (string.IsNullOrEmpty(targetId)) + { + throw new ArgumentException($"“{nameof(targetId)}”不能为 null 或空。", nameof(targetId)); + } + + if (string.IsNullOrEmpty(methodName)) + { + throw new ArgumentException($"“{nameof(methodName)}”不能为 null 或空。", nameof(methodName)); + } + + if (this.DmtpActor.AllowRoute && await this.TryFindDmtpRpcActor(targetId) is SimpleDmtpRpcActor actor) + { + actor.Invoke(methodName); + return; + } + + this.PrivateInvoke(targetId, methodName); + } + + private void PrivateInvoke(string id, string methodName) + { + var package = new SimpleDmtpRpcPackage() + { + MethodName = methodName, + Route = id.HasValue(), + SourceId = this.DmtpActor.Id, + TargetId = id + }; + + var waitData = this.DmtpActor.WaitHandlePool.GetReverseWaitData(package); + + try + { + using (var byteBlock = new ByteBlock(1024*64)) + { + package.Package(byteBlock); + this.DmtpActor.SendAsync(this.m_invoke_Request, byteBlock); + } + switch (waitData.Wait(5000)) + { + case WaitDataStatus.SetRunning: + var result = (SimpleDmtpRpcPackage)waitData.WaitResult; + result.CheckStatus(); + return; + case WaitDataStatus.Overtime: + throw new TimeoutException(); + case WaitDataStatus.Canceled: + break; + case WaitDataStatus.Default: + case WaitDataStatus.Disposed: + default: + throw new Exception("未知异常"); + } + } + finally + { + this.DmtpActor.WaitHandlePool.Destroy(waitData); + } + } +} +``` + +额外模型类 + +`MethodModel`用于储存反射调用相关。 + +```csharp showLineNumbers +class MethodModel +{ + public MethodModel(MethodInfo method, object target) + { + this.Method = method; + this.Target = target; + } + + public MethodInfo Method { get; private set; } + public object Target { get; private set; } +} +``` + +`SimpleDmtpRpcPackage`类,用于打包Rpc请求,关于打包的使用详情,可以看[包序列化](./ipackage.mdx) + +```csharp showLineNumbers +class SimpleDmtpRpcPackage : WaitRouterPackage +{ + protected override bool IncludedRouter => true; + + public string MethodName { get; set; } + + public override void PackageBody(in ByteBlock byteBlock) + { + base.PackageBody(byteBlock); + byteBlock.Write(this.MethodName); + } + + public override void UnpackageBody(in ByteBlock byteBlock) + { + base.UnpackageBody(byteBlock); + this.MethodName = byteBlock.ReadString(); + } + + public void CheckStatus() + { + switch (this.Status) + { + case 0: + throw new TimeoutException(); + case 1: return; + case 2: throw new Exception("没有找到目标Id"); + case 3: throw new Exception("不允许路由"); + case 4: throw new Exception("没找到Rpc"); + case 5: throw new Exception($"其他异常:{this.Message}"); + } + } +} +``` + +### 3.2 声明扩展 + +扩展方法是为`IPluginsManager`、`IDmtpActor`、`IDmtpActorObject`等提供扩展功能的静态方法。 + +```csharp showLineNumbers +static class SimpleDmtpRpcExtension +{ + #region DependencyProperty + + /// + /// SimpleDmtpRpcActor + /// + public static readonly DependencyProperty SimpleDmtpRpcActorProperty = + DependencyProperty.Register("SimpleDmtpRpcActor", default); + + #endregion DependencyProperty + + #region 插件扩展 + + /// + /// 使用SimpleDmtpRpc插件 + /// + /// + /// + public static SimpleDmtpRpcFeature UseSimpleDmtpRpc(this IPluginsManager pluginsManager) + { + return pluginsManager.Add(); + } + #endregion 插件扩展 + + /// + /// 从中获取 + /// + /// + /// + public static ISimpleDmtpRpcActor GetSimpleDmtpRpcActor(this IDmtpActor smtpActor) + { + return smtpActor.GetValue(SimpleDmtpRpcActorProperty); + } + + /// + /// 从中获取,以实现Rpc调用功能。 + /// + /// + /// + /// + public static ISimpleDmtpRpcActor GetSimpleDmtpRpcActor(this IDmtpActorObject client) + { + var smtpRpcActor = client.DmtpActor.GetSimpleDmtpRpcActor(); + if (smtpRpcActor is null) + { + throw new ArgumentNullException(nameof(smtpRpcActor), "SimpleRpcAcotr为空,请检查是否已启用UseSimpleDmtpRpc"); + } + return smtpRpcActor; + } + + /// + /// 向中设置 + /// + /// + /// + internal static void SetSimpleDmtpRpcActor(this IDmtpActor smtpActor, ISimpleDmtpRpcActor smtpRpcActor) + { + smtpActor.SetValue(SimpleDmtpRpcActorProperty, smtpRpcActor); + } +} +``` + +### 3.3 声明功能插件 + +功能插件是为Actor提供创建时机,和必要的配置信息。如果有需要,还可以抛出插件信息(例如[文件传输](./dmtptransferfile.mdx) )。 + +```csharp showLineNumbers +class SimpleDmtpRpcFeature : PluginBase, IDmtpHandshakingPlugin, IDmtpReceivedPlugin +{ + readonly Dictionary m_pairs = new Dictionary(); + public async Task OnDmtpHandshaking(IDmtpActorObject client, DmtpVerifyEventArgs e) + { + var actor = new SimpleDmtpRpcActor(client.DmtpActor) + { + TryFindMethod = this.TryFindMethod + }; + client.DmtpActor.SetSimpleDmtpRpcActor(actor); + await e.InvokeNext(); + } + + private MethodModel TryFindMethod(string methodName) + { + if (this.m_pairs.TryGetValue(methodName, out var methodModel)) + { + return methodModel; + } + return default; + } + + public void RegisterRpc(object server) + { + if (server is null) + { + throw new ArgumentNullException(nameof(server)); + } + + foreach (var item in server.GetType().GetMethods(BindingFlags.Default | BindingFlags.Instance | BindingFlags.Public)) + { + m_pairs.Add(item.Name, new MethodModel(item, server)); + } + } + + public async Task OnDmtpReceived(IDmtpActorObject client, DmtpMessageEventArgs e) + { + if (client.DmtpActor.GetSimpleDmtpRpcActor() is SimpleDmtpRpcActor actor) + { + if (await actor.InputReceivedData(e.DmtpMessage)) + { + + return; + } + } + await e.InvokeNext(); + } +} +``` + +## 四、使用 + +使用非常简单,基本和现有的功能插件类似。 + +【服务器】 + +```csharp {15-16} +private static TcpDmtpService GetTcpDmtpService() +{ + var service = new TcpDmtpService(); + + var config = new TouchSocketConfig()//配置 + .SetListenIPHosts(new IPHost[] { new IPHost(7789) }) + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + + a.AddDmtpRouteService();//添加路由策略 + }) + .ConfigurePlugins(a => + { + a.UseSimpleDmtpRpc() + .RegisterRpc(new MyServer()); + }) + .SetDmtpOption(new DmtpOption() + { + VerifyToken = "Dmtp"//连接验证口令。 + }); + + await service.SetupAsync(config); + await service.StartAsync(); + service.Logger.Info("服务器成功启动"); + return service; +} +``` + +【客户端】 + +```csharp {12} +private static TcpDmtpClient GetTcpDmtpClient() +{ + var client = new TouchSocketConfig() + .SetRemoteIPHost("127.0.0.1:7789") + .SetDmtpOption(new DmtpOption() + { + VerifyToken = "Dmtp"//连接验证口令。 + }) + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .ConfigurePlugins(a => + { + a.UseSimpleDmtpRpc(); + + a.UseDmtpHeartbeat()//使用Dmtp心跳 + .SetTick(TimeSpan.FromSeconds(3)) + .SetMaxFailCount(3); + }) + .BuildWithTcpDmtpClient(); + + client.Logger.Info("连接成功"); + return client; +} +``` + +```csharp showLineNumbers +static void Main(string[] args) +{ + var service = GetTcpDmtpService(); + var client = GetTcpDmtpClient(); + + while (true) + { + string methodName = Console.ReadLine(); + var actor = client.GetSimpleDmtpRpcActor(); + + try + { + actor.Invoke(methodName); + Console.WriteLine("调用成功"); + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } + } +} +``` + +[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Dmtp/CustomDmtpActorConsoleApp) + diff --git a/handbook/versioned_docs/version-3.1/dmtpdescription.mdx b/handbook/versioned_docs/version-3.1/dmtpdescription.mdx new file mode 100644 index 000000000..d83dbea2a --- /dev/null +++ b/handbook/versioned_docs/version-3.1/dmtpdescription.mdx @@ -0,0 +1,163 @@ +--- +id: dmtpdescription +title: 产品及架构介绍 +--- +import Pro from "@site/src/components/Pro.js"; +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import { TouchSocketDmtpDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +DMTP(Duplex Message Transport Protocol双工消息传输协议)是一个简单易用,便捷高效,且易于扩展的**二进制数据协议**。 + + + +【协议格式】 + +|--Head--|--Flags--|--Length--|-----Data-----| + +|----2----|----2----|-----4------|-------n-------| + +协议格式非常简单。 + +- 协议头为2字节,一般为固定值,目前第一版为“dm”。 +- 协议标志位为2字节,表示本次协议的标志位。类型是`大端ushort无符号`类型。其中`0-19`的协议框架内部占用。其余的均可被自定义使用 +- 再4字节为`大端Int32`**有**符号类型,表示本次协议的载荷数据长度。 +- 其余数据为实际载荷数据。 + +DMTP核心特性: + +- **协议简洁**:仅包含协议头、标志位、数据长度及载荷四部分 +- **高效扩展**:支持自定义协议标志位(0-19为系统保留,20-65535开放扩展) +- **可靠传输**:基于底层传输协议实现消息确认机制 +- **跨协议支持**:可运行于TCP/UDP/HTTP/WebSocket/NamedPipe等多种传输层协议 + +```mermaid +--- +title: "Dmtp Packet" +--- +packet-beta +0-15: "Head" +16-31: "Flags" +32-63: "Length" +64-95: "Data (variable length)" + +``` + +## 二、协议架构 + +### 2.1 协议格式 + +| 字段 | 字节数 | 类型 | 说明 | +|-------------|--------|------------------|-------------------------------| +| Head | 2 | ASCII | 固定标识"dm" | +| Flags | 2 | Big-Endian ushort| 协议标志位(0-65535) | +| Length | 4 | Big-Endian int32 | 数据载荷长度(有符号) | +| Data | n | Binary | 实际传输数据 | + +### 2.2 协议层对比 + +| 特性 | TCP | DMTP | +|---------------------|-------------------|--------------------| +| 传输可靠性 | 连接级可靠 | 消息级可靠 | +| 消息确认机制 | 无 | 支持发送/接收确认 | +| 双工通信 | 基础支持 | 增强型双工支持 | +| 应用层功能 | 无 | 内置ID管理、路由等 | + +--- + +## 三、核心特性 + +### 3.1 基础服务 + +- **安全认证**:支持动态Token凭证验证与SSL/TLS加密 +- **节点管理**:自动同步全局唯一ID,支持ID重定向 +- **协议扩展**:开放65535个协议标志位供业务扩展 +- **数据隔离**:支持多通道并行数据传输 + +### 3.2 高级功能 + +- **跨协议路由**:支持服务器与客户端、客户端与客户端直连通信 +- **微服务支持**:内置反向RPC机制,支持服务网格架构 +- **流量控制**:提供QoS服务质量保障机制 +- **断线恢复**:支持会话状态保持与断线续传 + +--- + +## 四、适用场景 + +### 4.1 客户端应用 + +- Unity/WinForm/WPF/MAUI等桌面/移动端应用 +- 实时游戏通信(位置同步、状态更新) +- 工业控制系统的指令传输 + +### 4.2 服务端架构 + +- 微服务集群内部通信 +- 分布式系统节点协调 +- 边缘计算节点数据同步 + +### 4.3 特殊场景 + +- 进程间通信(NamedPipe版本) +- 浏览器双向通信(WebSocket版本) +- 内网穿透中继服务(UDP版本) + +--- + +## 五、协议实现 + +### 5.1 版本矩阵 + +| 版本类型 | 传输协议 | 性能指标 | 可靠性 | +|------------------|------------|----------|--------| +| TCP版 | TCP | ★★★★★ | 可靠 | +| UDP版 | UDP | ★★★★☆ | 可选 | +| NamedPipe版 | 命名管道 | ★★★★★★ | 可靠 | +| Http版 | HTTP | ★★★☆☆ | 可靠 | +| AspNetCore Http版 | HTTP | ★★★☆☆ | 可靠 | +| WebSocket版 | WebSocket | ★★★☆☆ | 可靠 | + +### 5.2 连接生命周期 + +```mermaid +sequenceDiagram + participant Client + participant Server + + Client->>Server: Tcp Connect + Client->>Server: 发送握手请求(包含验证Token、Metadata和预定Id等) + Server-->>Client: 响应握手请求(完成全局ID分配) + Note over Client,Server: Online=true + Client<<->>Server: 业务数据通信 + Server<<->>Client: 心跳维持 + Client->>Server: 请求断开 + Server-->>Client: 断开 + Note over Client,Server: Online=false +``` + +--- + +## 六、最佳实践 + +### 6.1 协议选择建议 + +1. **优先选择TCP版**:需要最高性能时 +2. **跨进程通信**:使用NamedPipe版本(速度比TCP快3倍) +3. **浏览器集成**:采用WebSocket版本 +4. **弱网环境**:使用Http版本穿透复杂网络 + +### 6.2 可靠性保障 + +- 始终检查`Online`状态后再进行业务操作 +- 配置合理的心跳间隔(默认60秒) + +### 6.3 性能优化 + +- 使用`BlockBlock`处理大数据分片 diff --git a/handbook/versioned_docs/version-3.1/dmtpredis.mdx b/handbook/versioned_docs/version-3.1/dmtpredis.mdx new file mode 100644 index 000000000..275b02758 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/dmtpredis.mdx @@ -0,0 +1,69 @@ +--- +id: dmtpredis +title: Redis缓存 +--- + +import { TouchSocketDmtpDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +REmote DIctionary Server(Redis) 是一个key-value存储系统,也是一个简单的非关系型数据库。 + +:::caution 警告 + +此组件是基于Dmtp协议的Redis。所以无法连接到常规Redis中。但是此组件无论是扩展性还是性能,都是远胜常规Redis的。 + +::: + +## 二、使用 + +Redis是由RedisFeature功能插件提供的,所以需要添加`UseDmtpRedis`。 + +```csharp showLineNumbers +.ConfigurePlugins(a => +{ + a.UseDmtpRedis();//添加Redis功能插件 +}) +``` + +【请求端】 + +```csharp showLineNumbers +var client = GetTcpDmtpClient(); + +//获取Redis +var redis = client.GetDmtpRedisActor(); + +//执行Set +var result = redis.Set("1", "1"); +client.Logger.Info($"Set result={result}"); +client.Logger.Info($"ContainsCache result={redis.ContainsCache("1")}"); + +//执行Get +var result1 = redis.Get("1"); +client.Logger.Info($"Get result={result}"); + +//执行Remove +result = redis.RemoveCache("1"); +client.Logger.Info($"Get result={result}"); +redis.ClearCache(); +``` + +## 三、缓存持久化 + +默认情况下,Redis组件会使用`MemoryCache`作为实际储存。所以要实现缓存持久化,只需要替换该缓存、或者保存该缓存即可。 + +```csharp showLineNumbers +.ConfigurePlugins(a => +{ + a.UseDmtpRedis()//必须添加Redis访问插件 + .SetCache(new MemoryCache());//这里可以设置缓存持久化,此处仍然是使用内存缓存。 +}) +``` + +[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Dmtp/DmtpRedisConsoleApp) \ No newline at end of file diff --git a/handbook/versioned_docs/version-3.1/dmtpremoteaccess.mdx b/handbook/versioned_docs/version-3.1/dmtpremoteaccess.mdx new file mode 100644 index 000000000..54b376a7a --- /dev/null +++ b/handbook/versioned_docs/version-3.1/dmtpremoteaccess.mdx @@ -0,0 +1,79 @@ +--- +id: dmtpremoteaccess +title: 远程文件系统 +--- +import Tag from "@site/src/components/Tag.js"; +import Pro from "@site/src/components/Pro.js"; + +## 一、说明 + +使用该插件,可以访问远程终端的文件系统。包括:创建、删除、复制、移动、获取信息等。 + +## 二、使用 + +该功能由`RemoteAccessFeature`功能插件提供。所以需要`客户端`和`服务器`都需要`UseRemoteAccess`。 + +```csharp showLineNumbers +.ConfigurePlugins(a => +{ + a.UseRemoteAccess(); +}); +``` + +以获取文件夹信息为例: + +【请求端】 + +任意Dmtp终端均可以调用GetRemoteAccessActor创建一个访问器。 + +同时可以传递一个元数据组。用于载入自定义消息。 + +```csharp showLineNumbers +var result = await this.m_client.GetRemoteAccessActor().GetDirectoryInfoAsync("c:/新建文件夹", millisecondsTimeout: 30 * 1000); + +//然后通过DirectoryInfo属性,可以获取到请求文件夹的信息,包括创建时间、修改时间、子文件夹、子文件等。 +``` + +:::tip 提示 + +此处可以访问远程终端的文件系统。包括:创建、删除、复制、移动、获取信息等 + +::: + + +【响应端】 + +响应端定义一个插件,实现`IDmtpRemoteAccessingPlugin`,然后实现接口。 + +```csharp showLineNumbers +public class MyRemoteAccessPlugin : PluginBase, IDmtpRemoteAccessingPlugin +{ + public async Task OnRemoteAccessing(IDmtpActorObject client, RemoteAccessingEventArgs e) + { + //Console.WriteLine($"有客户端正在请求远程操作"); + //Console.WriteLine($"类型:{e.AccessType},模式:{e.AccessMode}"); + //Console.WriteLine($"请求路径:{e.Path}"); + //Console.WriteLine($"目标路径:{e.TargetPath}"); + + //Console.WriteLine("请输入y/n决定是否允许其操作?"); + + //var input = Console.ReadLine(); + //if (input == "y") + //{ + // e.IsPermitOperation = true; + // return; + //} + + //如果当前插件无法处理,转至下一个插件 + await e.InvokeNext(); + } +} +``` + +:::caution 警告 + +远程文件系统访问,是属于比较隐私的功能,所以在开发这功能时,应该取得被访问端的访问许可,所以在每次访问时,都必须对`e.IsPermitOperation = true`做确认动作。 + +::: + +[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Dmtp/RemoteAccessApp) \ No newline at end of file diff --git a/handbook/versioned_docs/version-3.1/dmtpremotestream.mdx b/handbook/versioned_docs/version-3.1/dmtpremotestream.mdx new file mode 100644 index 000000000..e00cbdc75 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/dmtpremotestream.mdx @@ -0,0 +1,108 @@ +--- +id: dmtpremotestream +title: b.远程流映射 +--- + +import Tag from "@site/src/components/Tag.js"; +import Pro from "@site/src/components/Pro.js"; +import { TouchSocketProDmtpDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +可以在通信对方,创建一个Stream,然后映射到本地,由本地直接进行读、写等操作。 + + +## 二、场景 + +当远程主机拥有一个超大流数据(可能是文件,或者其他)时,本地只想访问其部分数据的话,就可以使用该功能。 +例如,假设C服务器有个10Gb的文件。A客户端需要其10000-20000字节之间的数据,那你此时可以使用该功能,直接进行读取。 + + +## 三、使用 + +该功能由`DmtpRemoteStreamFeature`功能插件提供。所以需要`客户端`和`服务器`都需要`UseDmtpRemoteStream`。 + +```csharp showLineNumbers +.ConfigurePlugins(a => +{ + a.UseDmtpRemoteStream(); +}); +``` + + +【请求端】 +任意Dmtp终端均可以调用LoadRemoteStream创建一个流数据映射。 +同时可以传递一个元数据组。用于载入自定义消息。 + +```csharp showLineNumbers +var metadata = new Metadata(); +metadata.Add("tag", "tag1"); + +var remoteStream = client.GetDmtpRemoteStreamActor().LoadRemoteStream(metadata); +``` + +【响应端】 +响应端定义一个插件,实现`IDmtpRemoteStreamPlugin`,然后实现需要载入的具体流信息。示例中是以`MemoryStream`作为流主体。 + +```csharp showLineNumbers +internal class MyRemoteStreamPlugin : PluginBase, IDmtpRemoteStreamPlugin +{ + public async Task OnLoadingStream(ITcpDmtpSessionClient client, LoadingStreamEventArgs e) + { + if (e.Metadata["tag"] == "tag1") + { + e.IsPermitOperation = true;//需要允许操作 + + client.Logger.Info("开始载入流"); + //当请求方请求映射流的时候,会触发此方法。 + using (var stream = new MemoryStream()) + { + await e.WaitingLoadStreamAsync(stream, TimeSpan.FromSeconds(60)); + + client.Logger.Info($"载入的流已被释放,流中信息:{Encoding.UTF8.GetString(stream.ToArray())}"); + } + + return; + } + + //如果不满足,调用下一个插件 + await e.InvokeNext(); + } +} +``` + +## 四、读写 + +当RemoteStream被成功创建以后,即可直接Read、Write。因为RemoteStream继承自Stream。 + +```csharp showLineNumbers +var client = GetClient(); +var remoteStream = client.GetDmtpRemoteStreamActor().LoadRemoteStream(new Metadata().AddOrUpdate("1", "1")); + +byte[] data = new byte[] { 0, 1, 2, 3, 4 }; +remoteStream.Write(data); + +remoteStream.Position = 0; +byte[] buffer=new byte[5]; +remoteStream.Read(buffer); + +remoteStream.SafeDispose(); +``` + +## 五、释放 + +当**断开连接**,或者请求方主动调用Dispose时,响应方的Stream均会被实际的Dispose掉。 + + +## 六、性能 + +图中示例为直接读取一个Window.iso文件所示。 + + + +[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Dmtp/RemoteStreamConsoleApp) diff --git a/handbook/versioned_docs/version-3.1/dmtprouterpackage.mdx b/handbook/versioned_docs/version-3.1/dmtprouterpackage.mdx new file mode 100644 index 000000000..f51c5bf64 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/dmtprouterpackage.mdx @@ -0,0 +1,129 @@ +--- +id: dmtprouterpackage +title: 路由包传输 +--- + +import Tag from "@site/src/components/Tag.js"; +import Pro from "@site/src/components/Pro.js"; +import { TouchSocketProDmtpDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +路由包传输模式是,手动版的Rpc,能够实现发送、然后同步等待响应式的通讯。但是与Rpc区别的是,路由包是自己把握数据序列化,可以完全使用内存池。极大的降低内存消耗。 + +包模式的应用场景,就是中小型二进制数据的传输。例如:传输100张照片,每张大约5Mb的。 + +用协议直接Send,怕对方没正确处理保存。没法拿到处理回执。 + +用Rpc,发送得序列化,解析得反序列化,而且5Mb数据也用不了内存池。内存会抖动的厉害。 + +用Rpc+Channel,又显得麻烦。 + +所以用路由包传输模式。高效,简单。 + +## 二、使用 + +路由包传输模式是由DmtpRouterPackageFeature功能插件提供的,所以需要添加`UseDmtpRouterPackage`。 + +```csharp showLineNumbers +.ConfigurePlugins(a => +{ + a.UseDmtpRouterPackage();//添加路由包功能插件 +}) +``` + +其次,要完成路由包传输,就得自己定义请求包和响应包。此处简单定义两个包用于测试。具体关于包的打包、解包详细操作可以看[包序列化](./ipackage.mdx) + +```csharp showLineNumbers +/// +/// 定义请求包 +/// +class MyRequestPackage : DmtpRouterPackage +{ + /// + /// 包尺寸大小。此值并非需要精准数值,只需要估计数据即可。其作用为申请内存池。所以数据应当大小合适。 + /// + public override int PackageSize => 1024 * 1024; + + /// + /// 自定义一个内存属性。 + /// + public ByteBlock ByteBlock { get; set; } + + public override void PackageBody(ByteBlock byteBlock) + { + base.PackageBody(byteBlock); + byteBlock.WriteByteBlock(this.ByteBlock); + } + + public override void UnpackageBody(ByteBlock byteBlock) + { + base.UnpackageBody(byteBlock); + this.ByteBlock = byteBlock.ReadByteBlock(1024*64); + } +} + +/// +/// 定义响应包 +/// +class MyResponsePackage : DmtpRouterPackage +{ + /// + /// 包尺寸大小。此值并非需要精准数值,只需要估计数据即可。其作用为申请内存池。所以数据应当大小合适。 + /// + public override int PackageSize => 1024; +} +``` + +【请求端】 + +```csharp showLineNumbers +using (ByteBlock byteBlock = new ByteBlock(1024*512)) +{ + byteBlock.SetLength(byteBlock.Capacity);//此处模拟一个大数据块 + MyRequestPackage requestPackage = new MyRequestPackage() + { + ByteBlock = byteBlock, + Metadata = new Metadata() { { "a", "a" } } + }; + var response = client.GetDmtpRouterPackageActor().Request(requestPackage); + + //client.Logger.Info(response.Message); +} +``` + +【响应端】 + +响应端除了`UseDmtpRouterPackage`之外,还需要添加一个插件,实现`IDmtpRouterPackagePlugin`功能,主要在里面实现响应的动作。 + +```csharp showLineNumbers +class MyPlugin : PluginBase, IDmtpRouterPackagePlugin +{ + private readonly ILog m_logger; + + public MyPlugin(ILog logger) + { + this.m_logger = logger; + } + public async Task OnReceivedRouterPackage(IDmtpActorObject client, RouterPackageEventArgs e) + { + //m_logger.Info($"收到包请求"); + await e.ResponseAsync(new MyResponsePackage() + { + Message = "Success" + }); + //m_logger.Info($"已响应包请求"); + + //一般在当前插件无法处理时调用下一插件。此处则不应该调用 + //await e.InvokeNext(); + } +} +``` + + +[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Dmtp/RouterPackageConsoleApp) \ No newline at end of file diff --git a/handbook/versioned_docs/version-3.1/dmtprpc.mdx b/handbook/versioned_docs/version-3.1/dmtprpc.mdx new file mode 100644 index 000000000..56cf98225 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/dmtprpc.mdx @@ -0,0 +1,1050 @@ +--- +id: dmtprpc +title: Rpc功能 +--- + +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import { TouchSocketDmtpDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +RPC(Remote Procedure Call)远程过程调用协议,一种通过网络从远程计算机上请求服务,而不需要了解底层网络技术的协议。RPC它假定某些协议的存在,例如TPC/UDP等,为通信程序之间携带信息数据。在OSI网络七层模型中,RPC跨越了传输层和应用层,RPC使得开发,包括网络分布式多程序在内的应用程序更加容易。 + +过程是什么? 过程就是业务处理、计算任务,更直白的说,就是程序,就是想调用本地方法一样调用远程的过程。 + +本Rpc是基于Dmtp协议的Rpc组件。其功能包括: + +- 支持客户端主动调用服务器。 +- 支持服务主动调用客户端。 +- 支持客户端之间互相调用。 +- 支持绝大多数数据类型及自定义实体类。 +- 支持自定义序列化。 + + + + + + + +## 二、使用Rpc服务 + +### 2.1 定义服务 + +1. 在**服务器**端中新建一个类名为`MyRpcServer`。 +2. 继承于`SingletonRpcServer`类、或实现`ISingletonRpcServer`。亦或者将服务器声明为**瞬时生命**的服务,继承`TransientRpcServer`、或`ITransientRpcServer`。 +3. 在该类中写**公共方法**,并用`DmtpRpc`属性标签标记。 + +```csharp showLineNumbers +public partial class MyRpcServer : SingletonRpcServer +{ + [Description("登录")]//服务描述,在生成代理时,会变成注释。 + [DmtpRpc(InvokeKey ="Login")]//服务注册的函数键,此处为显式指定。默认不传参的时候,为该函数类全名+方法名的全小写。 + public bool Login(string account, string password) + { + if (account == "123" && password == "abc") + { + return true; + } + + return false; + } +} +``` + +```csharp showLineNumbers +public partial class MyRpcServer : TransientRpcServer +{ + [Description("登录")]//服务描述,在生成代理时,会变成注释。 + [DmtpRpc(InvokeKey ="Login")]//服务注册的函数键,此处为显式指定。默认不传参的时候,为该函数类全名+方法名的全小写。 + public bool Login(string account,string password) + { + if (account=="123"&&password=="abc") + { + return true; + } + + return false; + } +} +``` + +:::info 信息 + +`ITransientRpcServer`和`ISingletonRpcServer`相比,意为瞬时生命服务,即实现`ITransientRpcServer`的服务,在每次被调用时,都会创建一个新的服务实例。其优点为可以直接通过`this.CallContext`属性获得调用上下文。其缺点则是每次调用时会多消耗一些性能。 + +::: + +### 2.2 启动Dmtp并注册Rpc服务 + +以下仅示例基于Tcp协议Dmtp。其他协议的服务器请看[创建Dmtp服务器](./dmtpservice.mdx) + +更多注册Rpc的方法请看[注册Rpc服务](./rpcregister.mdx) + +```csharp showLineNumbers +var service = new TcpDmtpService(); +var config = new TouchSocketConfig()//配置 + .SetListenIPHosts(7789) + .ConfigureContainer(a=> + { + a.AddRpcStore(store => + { + store.RegisterServer();//注册服务 + }); + }) + .ConfigurePlugins(a => + { + a.UseDmtpRpc(); + }) + .SetDmtpOption(new DmtpOption() + { + VerifyToken = "Rpc"//连接验证口令。 + }); + +await service.SetupAsync(config); + +await service.StartAsync(); + +service.Logger.Info($"{service.GetType().Name}已启动"); +``` + +### 2.3 调用Rpc + +#### 2.3.1 直接调用 + +直接调用,则是不使用**任何代理**,使用**字符串**和**参数**直接Call Rpc,使用比较简单。 + +下列以TcpDmtpClient为例,其他客户端请看[创建Dmtp客户端](./dmtpclient.mdx)。 + +```csharp showLineNumbers +var client = new TcpDmtpClient(); +await client.SetupAsync(new TouchSocketConfig() + .SetRemoteIPHost("127.0.0.1:7789") + .ConfigurePlugins(a => + { + a.UseDmtpRpc(); + }) + .SetDmtpOption(new DmtpOption() + { + VerifyToken = "Rpc"//连接验证口令。 + })); +await client.ConnectAsync(); + +bool result =(bool) client.GetDmtpRpcActor().Invoke(typeof(bool),"Login", InvokeOption.WaitInvoke, "123", "abc"); +``` + +:::info 信息 + +直接调用时,第一个参数为`返回值`类型,当没有返回值时则可以不用。第二个参数为`调用键`,调用键默认情况下为服务类的“`命名空间+类名+方法名`”的`全小写`。但在本案例中直接指定了以“Login”为调用键。第三个参数为`调用配置`参数,可设置调用超时时间,取消调用等功能。示例中使用的预设,实际上可以自行new InvokeOption()。后续参数为`调用参数`。 + +::: + +或者使用`InvokeT`的扩展方法调用。在有返回值时,可以直接泛型传参。 + +```csharp showLineNumbers +bool result =client.GetDmtpRpcActor().InvokeT("Login", InvokeOption.WaitInvoke, "123", "abc"); +``` + +#### 2.3.2 代理调用 + +代理调用的便捷在于,客户端不用再知道哪些服务可调,也不用再纠结调用的参数类型正不正确,因为这些,代理工具都会替你做好。 + + + + + +详细步骤: + +1. [生成代理文件](./rpcgenerateproxy.mdx) +2. 将生成的cs文件添加到调用端一起编译。 + +:::info 备注 + +以上示例,会生成下列代理代码。 + +::: + + +
+生成的代理 +
+ + +```csharp showLineNumbers +using System; +using TouchSocket.Core; +using TouchSocket.Sockets; +using TouchSocket.Rpc; +using TouchSocket.Dmtp.Rpc; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using System.Threading.Tasks; +namespace RpcProxy +{ + public interface IMyRpcServer : TouchSocket.Rpc.IRemoteServer + { + /// + ///登录 + /// + /// 调用超时 + /// Rpc调用异常 + /// 其他异常 + System.Boolean Login(System.String account, System.String password, IInvokeOption invokeOption = default); + /// + ///登录 + /// + /// 调用超时 + /// Rpc调用异常 + /// 其他异常 + Task LoginAsync(System.String account, System.String password, IInvokeOption invokeOption = default); + } + public class MyRpcServer : IMyRpcServer + { + public MyRpcServer(IRpcClient client) + { + this.Client = client; + } + public IRpcClient Client { get; private set; } + /// + ///登录 + /// + /// 调用超时 + /// Rpc调用异常 + /// 其他异常 + public System.Boolean Login(System.String account, System.String password, IInvokeOption invokeOption = default) + { + if (Client == null) + { + throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); + } + object[] parameters = new object[] { account, password }; + System.Boolean returnData = (System.Boolean)Client.Invoke(typeof(System.Boolean), "Login", invokeOption, parameters); + return returnData; + } + /// + ///登录 + /// + public async Task LoginAsync(System.String account, System.String password, IInvokeOption invokeOption = default) + { + if (Client == null) + { + throw new RpcException("IRpcClient为空,请先初始化或者进行赋值"); + } + object[] parameters = new object[] { account, password }; + return (System.Boolean)await Client.InvokeAsync(typeof(System.Boolean), "Login", invokeOption, parameters); + } + } + public static class MyRpcServerExtensions + { + /// + ///登录 + /// + /// 调用超时 + /// Rpc调用异常 + /// 其他异常 + public static System.Boolean Login(this TClient client, System.String account, System.String password, IInvokeOption invokeOption = default) where TClient : + TouchSocket.Rpc.IRpcClient + { + object[] parameters = new object[] { account, password }; + System.Boolean returnData = (System.Boolean)client.Invoke(typeof(System.Boolean), "Login", invokeOption, parameters); + return returnData; + } + /// + ///登录 + /// + public static async Task LoginAsync(this TClient client, System.String account, System.String password, IInvokeOption invokeOption = default) where TClient : + TouchSocket.Rpc.IRpcClient + { + object[] parameters = new object[] { account, password }; + return (System.Boolean)await client.InvokeAsync(typeof(System.Boolean), "Login", invokeOption, parameters); + } + } +} +``` + + +
+
+ + +使用代理扩展直接调用。 + +```csharp showLineNumbers +var client = new TcpDmtpClient(); +await client.SetupAsync(new TouchSocketConfig() + .SetRemoteIPHost("127.0.0.1:7789") + .ConfigurePlugins(a => + { + a.UseDmtpRpc(); + }) + .SetDmtpOption(new DmtpOption() + { + VerifyToken = "Rpc"//连接验证口令。 + })); +await client.ConnectAsync(); + +bool result = client.GetDmtpRpcActor().Login("123", "abc", InvokeOption.WaitInvoke);//Login是生成的代理扩展方法。可能需要额外添加命名空间。 +``` + +:::tip 提示 + +client.GetDmtpRpcActor()的操作,内部还需要执行字典的查询操作。所以,如果为效率考虑的话,在连接稳定的前提下,可以保存好client.GetDmtpRpcActor()的返回值对象,直接执行Rpc操作。但是需要的注意的是,一旦重新连接,则该对象也需要重新获取。 + +::: + +## 三、反向Rpc + +一般的rpc服务都是客户端发起,服务器响应。但是有时候也需要服务器发起,客户端响应,所以需要反向rpc。 + + + + +### 3.1 定义、发布反向Rpc服务 + +实际上,Dmtp的全称(Duplex Message Transport Protocol双工消息传输协议),Duplex意为双工,则表明,当Dmtp客户端连接到服务以后,拥有与服务器同等的通讯权限与功能。所以客户端发布Rpc服务的步骤和服务器完全一致。即:当客户端和服务器建立连接以后,就不再区分谁是客户端,谁是服务器了。只关心,**谁能提供服务,谁在调用服务**。 + +下列就以简单的示例下,由客户端声明服务,服务器调用服务。 + +具体步骤: + +1. 在**客户端项目**中定义Rpc服务,名为`ReverseCallbackServer`。 +2. 用**DmtpRpc**标记需要公开的公共方法。 + +```csharp showLineNumbers +public partial class ReverseCallbackServer : SingletonRpcServer +{ + [DmtpRpc(MethodInvoke = true)]//使用方法名作为调用键 + public string SayHello(string name) + { + return $"{name},hi"; + } +} +``` + +**【客户端注册发布服务】** + +```csharp showLineNumbers +var client = new TcpDmtpClient(); +await client.SetupAsync(new TouchSocketConfig() + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .ConfigureContainer(a => + { + a.AddRpcStore(store => + { + store.RegisterServer(); + }); + }) + .ConfigurePlugins(a => + { + a.UseDmtpRpc(); + }) + .SetRemoteIPHost("127.0.0.1:7789") + .SetDmtpOption(new DmtpOption() + { + VerifyToken = "Rpc"//连接验证口令。 + })); +await client.ConnectAsync(); +client.Logger.Info($"连接成功,Id={client.Id}"); +``` + +### 3.2 调用反向Rpc + +服务器回调客户端,最终必须通过**服务器辅助类客户端**(`ITcpSessionClient`的派生类),以`TcpDmtpService`为例,其辅助客户端为`TcpDmtpSessionClient`(或其接口:`ITcpDmtpSessionClient`)。 + +下列示例以TcpDmtpSessionClient为例,其余一致。 + + +#### 3.2.1 通过服务器直接获取 + +可以获取所有`TcpDmtpSessionClient`,进行广播式调用。 + +```csharp showLineNumbers +foreach (var item in service.GetClients()) +{ + item.GetDmtpRpcActor().InvokeT("SayHello", InvokeOption.WaitInvoke, "张三"); +} +``` + +也可以先筛选Id,然后再调用。 + +```csharp showLineNumbers +var id = service.GetIds().FirstOrDefault(a => a.Equals("特定id")); +if (service.TryGetClient(id, out var SessionClient)) +{ + SessionClient.GetDmtpRpcActor().InvokeT("SayHello", InvokeOption.WaitInvoke, "张三"); +} +``` + +#### 3.2.2 通过调用上下文获取 + +例如:下列声明在服务器端的Rpc服务MyRpcServer,使其使用瞬时服务(也可以通过函数注入服务)。 + +上下文的Caller,即为服务器辅助类终端,进行强转即可。 + +使用该方式可以实现,当客户端调用服务器的Add接口的时候,服务器又回调客户端的SayHello接口。 + +```csharp showLineNumbers +partial class MyRpcServer : TransientRpcServer +{ + [DmtpRpc(MethodInvoke = true)]//使用函数名直接调用 + public int Add(int a, int b) + { + if (this.CallContext.Caller is ITcpDmtpSessionClient SessionClient) + { + SessionClient.GetDmtpRpcActor().InvokeT("SayHello",InvokeOption.WaitInvoke,"张三"); + } + int sum = a + b; + return sum; + } +} +``` + +:::tip 提示 + +反向Rpc也可以使用代理调用。所有用法和常规Rpc一致。 + +::: + + +## 四、客户端互Call Rpc + +除了Rpc,反向Rpc,DmtpRpc还支持**客户端**之间互Call Rpc。服务的定义与Rpc一样。 + + + + +### 4.1 互Call RPC + +客户端1调用客户端2的方法,需要知道对方的**Id**。然后和调用Rpc方法一致。然后使用下列函数调用即可。 + +```csharp showLineNumbers +var client1 = GetTcpDmtpClient(); +var client2 = GetTcpDmtpClient(); + +client1.GetDmtpRpcActor().InvokeT(client2.Id,"Notice",InvokeOption.WaitInvoke,"Hello"); +``` + +亦或者 + +```csharp showLineNumbers +var targetRpcClient = client1.CreateTargetDmtpRpcActor(client2.Id); +targetRpcClient.InvokeT("Notice", InvokeOption.WaitInvoke, "Hello"); +``` + +:::tip 提示 + +使用上述的CreateTargetDmtpRpcActor(),获取到的targetRpcClient也能使用代理调用Rpc。 + +::: + +:::tip 提示 + +互Call Rpc也支持调用上下文。 + +::: + +:::caution 服务器注意 + +客户端互Call的时候,每个请求,都需要服务支持路由,且同意路由,才可以被转发。所以服务器需要配置路由策略和添加允许转发的插件。 + +::: + +配置路由。 + +```csharp {3} +.ConfigureContainer(a => +{ + a.AddDmtpRouteService(); + a.AddConsoleLogger(); +}) +``` + +同意转发路由数据。 + +```csharp showLineNumbers +internal class MyPlugin : PluginBase,IDmtpRoutingPlugin +{ + public async Task OnDmtpRouting(IDmtpActorObject client, PackageRouterEventArgs e) + { + if (e.RouterType == RouteType.Rpc) + { + e.IsPermitOperation = true; + return; + } + + await e.InvokeNext(); + } +} +``` + +## 五、调用配置 + +DmtpRpc支持单次调用配置。单次调用配置,就是在每次调用的时候,使用新建`DmtpInvokeOption`对象,然后在`Invoke`时,传入`invokeOption`即可。 + +其中详细介绍如下: + +### 5.1 FeedbackType + +FeedbackType是调用反馈类型,其枚举值分别有OnlySend、WaitSend、WaitInvoke。 + +- `OnlySend`意为只发送Rpc请求,不进行任何等待。这在通知类调用时是非常快速的。 +- `WaitSend`意为发送Rpc请求,并等待**接收**结果。即,返回时,仅表示对方收到了Rpc请求,但是具体执行如何,则不可知。这一般在不可靠协议中是有用的。 +- `WaitInvoke`意为发送Rpc请求,并等待**执行**结果。即,返回时,表示对方已经执行了Rpc请求,如果有执行返回值,则携带返回值。如果执行过程发生异常,则会将异常返回。 + + + +### 5.2 SerializationType (支持AOT) + +`SerializationType`是序列化类型,其枚举值有`FastBinary`、`Json`、`Xml`、`SystemBinary`。其特点如下: + +| FastBinary |Json |Xml |SystemBinary | +| ---- | ---- | ---- | ---- | + | 序列化方式速度快,数据量小,但是兼容的数据格式也比较有限。仅支持基础类型、自定义实体类、数组、List、字典 | 兼容性好,可读性强,但是受字符串影响,性能不出众,且数据量受限制 | 兼容性一般,可读性强,同样受字符串影响,性能不出众,且数据量受限制 | 序列化速度快。但是兼容性低。且要求类必须一致,不然需要重新指定图根。 | + + #### 5.2.1 配置默认序列化选择器 + + 默认序列化选择器在初始化时,可以配置相关属性。例如: + + - FastSerializerContext:快速序列化上下文属性 + - JsonSerializerSettings:Json序列化设置属性 + - SerializationBinder:系统二进制序列化绑定器 + + + + 例如: + + ```csharp showLineNumbers + a.UseDmtpRpc() +.ConfigureDefaultSerializationSelector(selector => +{ + selector.JsonSerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented; + selector.FastSerializerContext=default; + ... +}); + ``` + + #### 5.2.2 支持AOT的序列化选择器 + + DmtpRpc默认是支持AOT的,但是当参数或返回值是其他类型时,需要对序列化进行一些额外配置。下面介绍的几种就是支持AOT环境的序列化配置方法。 + + + + + + + + + #### 5.2.3 自定义序列化 + +
+自定义序列化 +
+ + +`Dmtp`除了上述的4中内置序列化,还支持自定义序列化。 + +首先,新建一个类,实现`ISerializationConverter`接口,然后实现相关方法。 + +下列将使用[MemoryPack](https://www.nuget.org/packages/MemoryPack) 序列化为例。 + +```csharp {3,10} showLineNumbers +public class MemoryPackSerializationSelector : ISerializationSelector +{ + public object DeserializeParameter(ref TByteBlock byteBlock, SerializationType serializationType, Type parameterType) where TByteBlock : IByteBlock + { + var len = byteBlock.ReadInt32(); + var span = byteBlock.ReadToSpan(len); + return MemoryPackSerializer.Deserialize(parameterType, span); + } + + public void SerializeParameter(ref TByteBlock byteBlock, SerializationType serializationType, in object parameter) where TByteBlock : IByteBlock + { + var pos = byteBlock.Position; + byteBlock.Seek(4, SeekOrigin.Current); + var memoryPackWriter = new MemoryPackWriter(ref byteBlock, null); + + MemoryPackSerializer.Serialize(parameter.GetType(), ref memoryPackWriter, parameter); + + var newPos = byteBlock.Position; + byteBlock.Position = pos; + byteBlock.WriteInt32(memoryPackWriter.WrittenCount); + byteBlock.Position = newPos; + } +} +``` + +然后配置序列化器 + +```csharp {4} +.ConfigurePlugins(a => +{ + a.UseDmtpRpc() + .SetSerializationSelector(new MemoryPackSerializationSelector()); +}) +``` + +:::caution 注意 + +序列化器的配置,必须是调用端和响应端**相同的配置**。不然序列化不统一,则无法进行反序列化。 + +::: + +最后就是使用序列化。 + +在上述代码中,我们并没有判断SerializationType,所以在调用时无需特指,它都会以MemoryPack序列化工作。 + +但有时候我们希望能保留内置序列化类型,所以可以参考[内置序列化选择器](https://gitee.com/RRQM_Home/TouchSocket/blob/master/src/TouchSocket.Dmtp/Features/Rpc/Serialization/DefaultSerializationSelector.cs),然后把新加的序列化再做一次判断。 + +例如: + +```csharp {53-58,135-148} showLineNumbers +internal sealed class DefaultSerializationSelector : ISerializationSelector +{ + /// + /// 根据指定的序列化类型反序列化字节块中的数据。 + /// + /// 包含序列化数据的字节块。 + /// 指定的序列化类型。 + /// 预期反序列化出的对象类型。 + /// 反序列化后的对象。 + /// 抛出当未识别序列化类型时。 + public object DeserializeParameter(ref TByteBlock byteBlock, SerializationType serializationType, Type parameterType) where TByteBlock : IByteBlock + { + // 根据序列化类型选择不同的反序列化方式 + switch (serializationType) + { + case SerializationType.FastBinary: + // 使用FastBinary格式进行反序列化 + return FastBinaryFormatter.Deserialize(ref byteBlock, parameterType); + case SerializationType.SystemBinary: + // 检查字节块是否为null + if (byteBlock.ReadIsNull()) + { + // 如果为null,则返回该类型的默认值 + return parameterType.GetDefault(); + } + + // 使用SystemBinary格式进行反序列化 + using (var block = byteBlock.ReadByteBlock(1024*64)) + { + // 将字节块转换为流并进行反序列化 + return SerializeConvert.BinaryDeserialize(block.AsStream()); + } + case SerializationType.Json: + // 检查字节块是否为null + if (byteBlock.ReadIsNull()) + { + // 如果为null,则返回该类型的默认值 + return parameterType.GetDefault(); + } + + // 使用Json格式进行反序列化 + return JsonConvert.DeserializeObject(byteBlock.ReadString(), parameterType); + + case SerializationType.Xml: + // 检查字节块是否为null + if (byteBlock.ReadIsNull()) + { + // 如果为null,则返回该类型的默认值 + return parameterType.GetDefault(); + } + // 使用Xml格式进行反序列化 + return SerializeConvert.XmlDeserializeFromBytes(byteBlock.ReadBytesPackage(), parameterType); + case (SerializationType)4: + { + var Length = byteBlock.ReadInt32(); + var span = byteBlock.ReadToSpan(Length); + return MemoryPackSerializer.Deserialize(parameterType, span); + } + default: + // 如果序列化类型未识别,则抛出异常 + throw new RpcException("未指定的反序列化方式"); + } + } + + /// + /// 序列化参数 + /// + /// 字节块引用,用于存储序列化后的数据 + /// 序列化类型,决定了使用哪种方式序列化 + /// 待序列化的参数对象 + /// 字节块类型,必须实现IByteBlock接口 + public void SerializeParameter(ref TByteBlock byteBlock, SerializationType serializationType, in object parameter) where TByteBlock : IByteBlock + { + // 根据序列化类型选择不同的序列化方法 + switch (serializationType) + { + case SerializationType.FastBinary: + { + // 使用FastBinaryFormatter进行序列化 + FastBinaryFormatter.Serialize(ref byteBlock, parameter); + break; + } + case SerializationType.SystemBinary: + { + // 参数为null时,写入空值标记 + if (parameter is null) + { + byteBlock.WriteNull(); + } + else + { + // 参数不为null时,标记并序列化参数 + byteBlock.WriteNotNull(); + using (var block = new ByteBlock(1024 * 64)) + { + // 使用System.Runtime.Serialization.BinaryFormatter进行序列化 + SerializeConvert.BinarySerialize(block.AsStream(), parameter); + // 将序列化后的字节块写入byteBlock + byteBlock.WriteByteBlock(block); + } + } + break; + } + case SerializationType.Json: + { + // 参数为null时,写入空值标记 + if (parameter is null) + { + byteBlock.WriteNull(); + } + else + { + // 参数不为null时,标记并转换为JSON字符串 + byteBlock.WriteNotNull(); + byteBlock.WriteString(JsonConvert.SerializeObject(parameter)); + } + break; + } + case SerializationType.Xml: + { + // 参数为null时,写入空值标记 + if (parameter is null) + { + byteBlock.WriteNull(); + } + else + { + // 参数不为null时,标记并转换为Xml字节 + byteBlock.WriteNotNull(); + byteBlock.WriteBytesPackage(SerializeConvert.XmlSerializeToBytes(parameter)); + } + break; + } + case (SerializationType)4: + { + var pos = byteBlock.Position; + byteBlock.Seek(4, SeekOrigin.Current); + var memoryPackWriter = new MemoryPackWriter(ref byteBlock, null); + + MemoryPackSerializer.Serialize(parameter.GetType(), ref memoryPackWriter, parameter); + + var newPos = byteBlock.Position; + byteBlock.Position = pos; + byteBlock.WriteInt32(memoryPackWriter.WrittenCount); + byteBlock.Position = newPos; + + break; + } + default: + // 抛出异常,提示未指定的序列化方式 + throw new RpcException("未指定的序列化方式"); + } + } +} +``` + +最后在使用时,因为序列化类型是枚举值,所以使用时需要强制转换一下。 + +```csharp {4} +var invokeOption = new DmtpInvokeOption()//调用配置 +{ + FeedbackType = FeedbackType.WaitInvoke,//调用反馈类型 + SerializationType = (SerializationType)4,//序列化类型 + Timeout = 5000,//调用超时设置 + Token = tokenSource.Token//配置可取消令箭 +}; +``` + +[序列化选择器Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Dmtp/SerializationSelectorConsoleApp) + +
+
+ +### 5.3 Timeout + +Timeout是超时时间,单位是毫秒。 + + + +### 5.4 CancellationToken + +CancellationToken是取消令箭源,可用于取消Rpc的调用。 + +:::tip 提示 + +取消调用时,其取消消息可以传递到被调用端,所以,被调用端可以通过ICallContext(调用上下文)获取到Token。 + +::: + +```csharp showLineNumbers +var client = GetTcpDmtpClient(); + +//设置调用配置 +var tokenSource = new CancellationTokenSource();//可取消令箭源,可用于取消Rpc的调用 +var invokeOption = new DmtpInvokeOption()//调用配置 +{ + FeedbackType = FeedbackType.WaitInvoke,//调用反馈类型 + SerializationType = SerializationType.FastBinary,//序列化类型 + Timeout = 5000,//调用超时设置 + Token = tokenSource.Token//配置可取消令箭 +}; + +var sum = client.GetDmtpRpcActor().InvokeT("Add", invokeOption, 10, 20); +client.Logger.Info($"调用Add方法成功,结果:{sum}"); +``` + +### 5.5 Metadata 元数据 + +Metadata是字符串键值对,其作用类似http的headers,用于传递一些附加信息。 + + + +在请求时可以通过DmtpInvokeOption进行传参。 + +```csharp {6} showLineNumbers +var invokeOption = new DmtpInvokeOption()//调用配置 +{ + FeedbackType = FeedbackType.WaitInvoke,//调用反馈类型 + SerializationType = SerializationType.FastBinary,//序列化类型 + Timeout = 5000,//调用超时设置 + Metadata=new Metadata(){{"a","a"}} +}; + +var metadata = client.GetDmtpRpcActor().InvokeT("CallContextMetadata", invokeOption); +``` + +在接收可以通过IDmtpCallContext(调用上下文)获取到Metadata。 + +```csharp {4} showLineNumbers +[DmtpRpc(MethodInvoke = true)] +public Metadata CallContextMetadata(IDmtpRpcCallContext callContext) +{ + return callContext.Metadata; +} +``` + +## 六、Rpc大数据传输 + +> **在Rpc中,并没有对传输的数据做限制,但是因为Rpc默认使用的固定包头适配器中,默认设置的可传递数据为10Mb,所以在Rpc中,用户可一次性传递的数据包大约为9.9Mb。所以,如果用户传递超出阈值的数据,适配器则会触发异常,而无法接收。但在实际上Rpc的使用中,大数据的传输也是很重要的一个环节,所以在此做了大数据的传输思路建议,希望能有效解决大家的麻烦。** + + + + + + +### 6.1 设置适配器参数(推荐指数:⭐) + +> 操作原理:在固定包头适配器中,默认限制了单次可发送数据包的最大值,所以可以修改此值实现目的。 + +该方法简单粗暴,能够解决一定程度的大数据问题,但并不建议这么做。 + + +```csharp showLineNumbers +TouchSocketConfig config = new TouchSocketConfig()//配置 + .SetMaxPackageSize(1024 * 1024 * 10) +``` + +:::caution 注意 + +客户端必须同样设置。 + +::: + + +### 6.2 Rpc嵌套Channel(推荐指数:⭐⭐⭐⭐⭐) + +> 操作原理:先利用Rpc让客户端与服务器约定特定的Channel,然后后续数据通过Channel传递,最后由Rpc返回结果。 + + + +#### 6.2.1 请求流数据 + +【Service端】 + +```csharp showLineNumbers +/// +/// 测试客户端请求,服务器响应大量流数据 +/// +/// +/// +[Description("测试客户端请求,服务器响应大量流数据")] +[DmtpRpc] +public async Task RpcPullChannel(ICallContext callContext, int channelID) +{ + var size = 0; + var package = 1024 * 64; + if (callContext.Caller is TcpDmtpSessionClient SessionClient) + { + if (SessionClient.TrySubscribeChannel(channelID, out var channel)) + { + for (var i = 0; i < 10; i++) + { + size += package; + await channel.WriteAsync(new byte[package]); + } + await channel.CompleteAsync();//必须调用指令函数,如Complete,Cancel,Dispose + } + } + return size; +} +``` + +【Client端】 + +```csharp showLineNumbers +using var client = GetTcpDmtpClient(); +ChannelStatus status = ChannelStatus.Default; +int size = 0; +var channel = await client.CreateChannelAsync(new Metadata() { {"rpc","rpc" } });//创建通道 +Task task = Task.Run(() =>//这里必须用异步 +{ + using (channel) + { + foreach (var currentByteBlock in channel) + { + var data = currentByteBlock.Span; + size += currentByteBlock.Length;//此处可以处理传递来的流数据 + } + status = channel.Status;//最后状态 + } +}); +int result =await client.GetDmtpRpcActor().RpcPullChannelAsync(channel.Id);//RpcPullChannel是代理方法,此处会阻塞至服务器全部发送完成。 +await task;//等待异步接收完成 +Console.WriteLine($"状态:{status},size={size}"); +``` + + + +#### 6.2.2 推送流数据 + +【Service端】 + +```csharp showLineNumbers +/// +/// "测试推送" +/// +/// +/// +[Description("测试客户端推送流数据")] +[DmtpRpc] +public async Task RpcPushChannel(ICallContext callContext, int channelID) +{ + int size = 0; + + if (callContext.Caller is TcpDmtpSessionClient SessionClient) + { + if (SessionClient.TrySubscribeChannel(channelID, out var channel)) + { + await foreach (var currentByteBlock in channel) + { + var data = currentByteBlock.Span; + size += currentByteBlock.Length;//此处处理流数据 + } + } + } + return size; +} +``` + +【Client端】 + +```csharp showLineNumbers +using var client = GetTcpDmtpClient(); +ChannelStatus status = ChannelStatus.Default; +int size = 0; +int package = 1024; +var channel = await client.CreateChannelAsync();//创建通道 +Task task = Task.Run(async () =>//这里必须用异步 +{ + for (int i = 0; i < 1024; i++) + { + size += package; + await channel.WriteAsync(new byte[package]); + } + await channel.CompleteAsync();//必须调用指令函数,如Complete,Cancel,Dispose + + status = channel.Status; +}); +int result = await client.GetDmtpRpcActor().RpcPushChannelAsync(channel.Id);//RpcPushChannel是代理方法,此处会阻塞至服务器全部完成。 +await task;//等待异步接收完成 +Console.WriteLine($"状态:{status},result={result}"); +``` + +## 七、限制代理接口 + +默认情况下,代理生成的接口,是面向`IRpcClient`的,即:面向所有`Rpc终端`。但是有时候我们希望不同的终端,只能调用不同的方法。甚至有时候希望,不同的终端可以重载方法。所以如果不对生成的接口做限制,就可能发生下图问题。 + + + +关于代理代码生成接口限制,请看[服务代理生成](./rpcgenerateproxy.mdx)或者[源服务代理生成](./rpcgenerateproxy.mdx) + +接下来就讲讲客户端如何实现接口限制。 + +首先按需求,声明多个继承`IDmtpRpcActor`的接口,此处有`IRpcClient1`与`IRpcClient2`两个。 + +然后新建类,命名为`MyDmtpRpcActor`,继承`DmtpRpcActor`,然后分别实现`IRpcClient1`与`IRpcClient2`两个接口。 + +```csharp showLineNumbers +interface IRpcClient1:IDmtpRpcActor +{ + +} + +interface IRpcClient2 : IDmtpRpcActor +{ + +} + +class MyDmtpRpcActor : DmtpRpcActor, IRpcClient1, IRpcClient2 +{ + public MyDmtpRpcActor(IDmtpActor smtpActor) : base(smtpActor) + { + } +} +``` + +然后在`UseDmtpRpc`时,设置`SetCreateDmtpRpcActor`,这样获得的实际实例则会是`MyDmtpRpcActor`类型。 + +```csharp {6} +var client = new TcpDmtpClient(); +await client.SetupAsync(new TouchSocketConfig() + .ConfigurePlugins(a => + { + a.UseDmtpRpc() + .SetCreateDmtpRpcActor((actor)=>new MyDmtpRpcActor(actor)); + }) + .SetRemoteIPHost("127.0.0.1:7789") + .SetDmtpOption(new DmtpOption() + { + VerifyToken = "Rpc"//连接验证口令。 + })); +await client.ConnectAsync(); +``` + +最后在获得RpcActor时,就可以按接口获取。然后配合服务器代码接口约束,就可以实现我们所期望的功能。 + +```csharp showLineNumbers +IRpcClient1 rpcClient1= client.GetDmtpRpcActor(); +IRpcClient2 rpcClient2= client.GetDmtpRpcActor(); +``` + +[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Dmtp/DmtpRpcServerConsoleApp) diff --git a/handbook/versioned_docs/version-3.1/dmtpservice.mdx b/handbook/versioned_docs/version-3.1/dmtpservice.mdx new file mode 100644 index 000000000..10cbf337a --- /dev/null +++ b/handbook/versioned_docs/version-3.1/dmtpservice.mdx @@ -0,0 +1,261 @@ +--- +id: dmtpservice +title: 创建Dmtp服务器 +--- + +import Tag from "@site/src/components/Tag.js"; +import Pro from "@site/src/components/Pro.js"; +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import { TouchSocketProDmtpDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +Dmtp的服务器有多种形式的host,每种服务器的创建都大同小异,且功能基本一致。 + + + +## 二、服务器架构 + +Dmtp服务器的架构与其所属的基础协议架构一致,例如,在基于tcp协议时,其架构就和tcp服务器一致。在收到**新客户端连接**时,会创建一个**TcpDmtpSessionClient**的类实例,与**客户端TcpDmtpClient**一一对应,后续的数据通信均由此实例负责。 + + +## 三、可配置项 + +
+可配置项 +
+ +#### SetDmtpOption +设置Dmtp相关配置。其中包含: + +- VerifyToken:设置验证口令。 +- VerifyTimeout:验证连接超时时间。仅用于服务器。意为:当服务器收到基础链接,在指定的时间内如果没有收到握手信息,则直接视为无效链接,直接断开。 + +
+
+ +## 四、支持插件接口 + +声明自定义插件类,实现`IPlugin`接口,或者继承`PluginBase`,然后实现所需插件接口,即可实现事务的触发。 + +| 插件方法 | 功能 | +| --- | --- | +| IDmtpHandshakingPlugin | 客户端在验证连接。默认情况下,框架会首先验证连接Token是否正确,如果不正确则直接拒绝。不会有任何投递。用户也可以使用Metadata进行动态验证。 | +| IDmtpHandshakedPlugin | 客户端完成握手连接验证 | +| IDmtpReceivedPlugin | 在收到Dmtp格式的数据包时触发 | +| IDmtpRoutingPlugin | 当需要路由数据时触发,并且必须返回e.IsPermitOperation=true时,才允许路由 | +| IDmtpCreatedChannelPlugin | 在收到创建通道的请求时候触发。 | +| IDmtpClosingPlugin | 即将断开连接时触发(仅主动断开时、或收到了Close报文时有效)。 | +| IDmtpClosedPlugin | 在Dmtp连接断开时触发。 | + + +## 五、创建服务器 + + +### 5.1 TcpDmtpService + +`TcpDmtpService`是基于`Tcp`协议的Dmtp。在可配置的基础之上,还可以配置与[TcpService可配置项](./tcpservice.mdx)相关的配置。 + + + +```csharp showLineNumbers +var service = new TcpDmtpService(); +var config = new TouchSocketConfig()//配置 + .SetListenIPHosts(7789) + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .SetDmtpOption(new DmtpOption() + { + VerifyToken = "Dmtp"//设定连接口令,作用类似账号密码 + }); + +await service.SetupAsync(config); + +await service.StartAsync(); + +service.Logger.Info($"{service.GetType().Name}已启动"); +``` + +### 5.2 UdpDmtp + +`UdpDmtp`是基于`Udp`协议Dmtp。在可配置的基础之上,还可以配置与[UdpSession可配置项](./udpsession.mdx)相关的配置。 + + + +```csharp showLineNumbers +var udpDmtp = new UdpDmtp(); + +var config = new TouchSocketConfig(); +config.SetBindIPHost(new IPHost(7789)) + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }); + +await udpDmtp.SetupAsync(config); + +await udpDmtp.StartAsync(); +``` + +:::info 备注 + +`UdpDmtp`作为服务器的时候,需要设定`SetBindIPHost`。 + +::: + +### 5.3 HttpDmtpService + +`HttpDmtpService`是基于`Http`升级协议。在该解析器中,配置设置[HttpService](./httpservice.mdx)一致。 + + + +```csharp showLineNumbers +var service = new HttpDmtpService(); +var config = new TouchSocketConfig()//配置 + .SetListenIPHosts(new IPHost[] { new IPHost(7789) }) + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .SetDmtpOption(new DmtpOption() + { + VerifyToken = "Dmtp" + }); + +await service.SetupAsync(config); + +await service.StartAsync(); + +service.Logger.Info($"{service.GetType().Name}已启动"); +``` + +### 5.4 基于AspNetCore的Websocket协议 + +具体步骤 + +1. nuget 安装`TouchSocket.AspNetCore`或者`TouchSocketPro.AspNetCore`。 +2. `IServiceCollection`添加`AddWebSocketDmtpService`,并进行相关配置(不用配置端口,会和asp使用同一端口)。 +3. `IApplicationBuilder`必须先使用`UseWebSockets`。 +4. `IApplicationBuilder`调用`UseWebSocketDmtp`,并传入url设置。 + + + +在Services时,添加`AddWebSocketDmtpService`,并且配置相关项。 + +```csharp {3} showLineNumbers +//添加WebSocket协议的Dmtp +//客户端使用WebSocketDmtpClient +builder.Services.AddWebSocketDmtpService(config => +{ + config + .SetDmtpOption(new DmtpOption() + { + VerifyToken = "Dmtp" + }) + .ConfigurePlugins(a => + { + //添加插件 + a.Add(); + }); +}); +``` + +启用中间件 + +首先必须先启用`WebSocket`。其次使用`UseWebSocketDmtp`即可。 + +```csharp showLineNumbers +var app = builder.Build(); +app.UseWebSockets(); +app.UseWebSocketDmtp("/WebSocketDmtp");//WebSocketDmtp必须在UseWebSockets之后使用。 +``` + +### 5.5 基于AspNetCore的Http协议 + +具体步骤 + +1. nuget 安装`TouchSocketPro.AspNetCore`。 +2. `IServiceCollection`添加`AddHttpMiddlewareDmtpService`,并进行相关配置(不用配置端口,会和asp使用同一端口)。 +3. `IApplicationBuilder`调用`UseHttpDmtp`。 + + + +在Services时,添加`AddWebSocketDmtpService`,并且配置相关项。 + +```csharp showLineNumbers +//Pro功能 +//添加基于Http升级协议的Dmtp。 +//客户端使用HttpDmtpClient +builder.Services.AddHttpMiddlewareDmtpService(config => +{ + config.SetDmtpOption(new DmtpOption() + { + VerifyToken = "Dmtp" + }) + .ConfigurePlugins(a => + { + //添加插件 + a.Add(); + }); +}); +``` + +启用中间件 + +使用`UseHttpDmtp`即可。 + +```csharp showLineNumbers +var app = builder.Build(); +app.UseHttpDmtp(); //HttpDmtp可以单独直接使用。不需要其他。 +``` + +:::tip 提示 + +在整个Apsnetcore的Host中,所有组件会共用一个容器。所以建议使用`ConfigureContainer`统一设置。 + +```csharp showLineNumbers +builder.Services.ConfigureContainer(container => +{ + container.AddConsoleLogger(); + container.AddDmtpRouteService(); +}); +``` + +::: + +[基于Aspnetcore的Dmtp示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Dmtp/DmtpWebApplication) + +### 5.6 基于NamedPipe协议 + +这是基于`NamedPipe`的Dmtp。在可配置的基础之上,还可以配置与[NamedPipeService可配置项](./namedpipeservice.mdx)相关的配置。 + + + +```csharp showLineNumbers +var service = new NamedPipeDmtpService(); +var config = new TouchSocketConfig()//配置 + .SetPipeName("TouchSocketPipe")//设置管道名称 + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .SetDmtpOption(new DmtpOption() + { + VerifyToken = "Dmtp"//设定连接口令,作用类似账号密码 + }); + +await service.SetupAsync(config); + +await service.StartAsync(); + +service.Logger.Info($"{service.GetType().Name}已启动"); +``` + +[基于NamedPipe的Dmtp示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Dmtp/NamedPipeDmtpConsoleApp) \ No newline at end of file diff --git a/handbook/versioned_docs/version-3.1/dmtptransferfile.mdx b/handbook/versioned_docs/version-3.1/dmtptransferfile.mdx new file mode 100644 index 000000000..d51a83ed2 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/dmtptransferfile.mdx @@ -0,0 +1,767 @@ +--- +id: dmtptransferfile +title: 传输文件 +--- + +import Tag from "@site/src/components/Tag.js"; +import Pro from "@site/src/components/Pro.js"; +import CardLink from "@site/src/components/CardLink.js"; +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import Definition from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +文件传输是每个框架都需要的功能,也是检验一个框架性能的非常重要的指标。 + +本组件则是基于Dmtp,开辟了全双工对点文件传输。即,当客户端连接服务器以后,客户端可以向服务器请求、推送文件,服务器也能向客户端请求,推送文件。甚至,客户端之间,也可以互相请求,推送文件。 + +其特点包括: + +- 全双工对点文件传输。即:客户端、服务器、其他客户端三者之间,可以互相推送、请求文件。 +- 高性能、低GC。整个传输过程,将内存池用到极致,极大的减少不必要的GC。本地电脑实测传输速度达到1.2Gb/秒。 +- 全平台支持。Windows、Android、Unity3D(除webgl)全部支持。 +- 支持任意大小的文件传输(实测100Gb没有问题)。 +- 支持断点续传。 +- 支持传输限速。 +- 支持文件多链路、多线程传输 。 + + + +## 二、支持插件 + +| 插件方法| 功能 | +| --- | --- | +| IDmtpFileTransferringPlugin | 文件传输之前触发。仅在响应端有效。 | +| IDmtpFileTransferredPlugin | 文件传输结束后可能触发,触发时不代表传输成功,具体状态查看`e.Result`属性。 仅在响应端有效。| + +## 三、性能 + +可以看到,下图正在上传一个Window的系统镜像文件,大约4.2Gb,传输速度已达到800Mb/s,GC基本上没有释放,性能非常强悍(中间有稍微停顿,因为程序在获取文件MD5值)。 + + + +## 四、产品应用场景 + +- 常规C/S应用使用场景:开发使用非常方便,连接验证,数据业务,文件传输等一系列功能完全集成。 +- Unity游戏场景:性能卓越,功能丰富,使用方便。 + + +## 五、传输流程及名词介绍 + + + +### 5.1 响应流程 + +1. 请求端(可能是客户端,也可能是服务器)调用Pull(请求)或Push(推送)。 +2. 响应方(可能是服务器,也可能是客户端)触发**FileTransferring**。 +3. 返回文件信息,然后检验是否续传等,然后开始传输。 +4. 传输完成或异常。 +5. 响应方**可能**触发**FileTransferred**。 +6. 请求端函数返回,`FileOperator`控制器状态改变。 + +:::caution 注意 + +响应方必须在订阅的**OnFileTransferring**函数中,同意每一个传输(e.IsPermitOperation = true),不然会直接拒绝请求。当然如果是有意拒绝,则可以通过e.Message返回拒绝的信息。 + +::: + +:::caution 注意 + +响应方订阅的**OnFileTransferred**事件的触发并**不意味着完成传输**,具体结果还要通过**Result**属性值进行判断。 + +::: + +:::tip 提示 + +`请求端`返回的**result**,在成功时,可以100%表明传输的文件已在磁盘上。如果想进一步确定文件的准确性,还需要自行再验证MD5或者其他Hash算法。 + +::: + + +### 5.2 传输控制器 + +`FileOperator`是本次文件传输的操作器,主要用于获取传输进度、速度、状态以及取消传输等操作。 + +可配置参数: + +#### (1)ResourcePath + +资源路径,在上传时,表示发起端的文件路径。在下载时,表示请求的文件路径。当该值为相对路径时,会与响应对点的RootPath组合路径。当为绝对路径时,则会直接访问路径文件。 + +:::tip 提示 + +如果是下载行为,响应方可在在订阅的**OnFileTransferring**函数中,随意重定向请求的文件路径(e.ResourcePath)。 + +::: + +:::danger 危险 + +当以绝对路径访问时,对方可能会请求到服务器电脑的**所有文件**,所以最好在**OnFileTransferring**里面进行安全的判断后再放行(e.IsPermitOperation = true;)。 + +::: + +#### (2)SavePath + +保存路径,在上传时,表示需要保存的文件路径。在下载时,表示本地保存的文件路径。同样,当该值为相对路径时,会与接收对点的RootPath组合路径。当为绝对路径时,则会直接生效。 + +:::tip 提示 + +如果是上传行为,响应方可在在订阅的**OnFileTransferring**函数中,随意重定向文件的保存路径(e.SavePath)。 + +::: + +#### (3)ResourceInfo +已存在的资源信息,当上个传输失败时,可以保存其ResourceInfo,然后重新传输时赋值,即可尝试断点续传。 + +:::caution 注意 + +当执行续传时,本次传输与前次传输间隔时间不应该超过响应默认值(60秒)。 + +::: + +#### (4)CompletedLength +已完成流长度。 + +#### (5)Speed 函数 +从上次获取到此次获得的速度。一般请每秒钟调用一次获取速度值。 + +:::caution 注意 + +当获取传输速度时,其值和获取时间完全相关。例如:假如实际每秒传输速度为100,当每隔一秒获取时,则为100.当每隔100毫秒获取时,则为10。 + +::: + +#### (6)Progress +传输进度,范围0-1。 + +#### (7) Result +获取传输状态以及状态信息。当ResultCode为Default时,意味着传输正在进行。 + +#### (8) Token +CancellationToken类型的可取消令箭。 + +#### (9) Metadata +string类型的键值对,用于和接收方交互数据。 + + + +## 六、 传输文件 + +传输文件功能,是`DmtpFileTransferFeature`功能插件基于Dmtp协议提供的功能。所以,`Dmtp服务器`和`客户端`都必须配置添加该功能插件。即调用`UseDmtpFileTransfer`。 + +```csharp {3} +config.ConfigurePlugins(a => +{ + a.UseDmtpFileTransfer()//必须添加文件传输插件 + //.SetRootPath("C:\\新建文件夹")//设置RootPath + .SetMaxSmallFileLength(1024 * 1024);//设置小文件的最大限制长度 + a.Add(); +}) +``` + +声明`MyPlugin`插件,是为了方便处理`OnDmtpFileTransferring`和`OnDmtpFileTransferred`函数。 + +```csharp showLineNumbers +internal class MyPlugin : PluginBase, IDmtpFileTransferringPlugin, IDmtpFileTransferredPlugin +{ + private readonly ILog m_logger; + + public MyPlugin(ILog logger) + { + this.m_logger = logger; + } + + /// + /// 该方法,会在每个文件被请求(推送)结束时触发。传输不一定成功,具体信息需要从e.Result判断状态。 + /// 其次,该方法也不一定会被执行,例如:在传输过程中,直接断网,则该方法将不会执行。 + /// + /// + /// + /// + public async Task OnDmtpFileTransferred(IDmtpActorObject client, FileTransferredEventArgs e) + { + //传输结束,但是不一定成功,甚至该方法都不一定会被触发,具体信息需要从e.Result判断状态。 + this.m_logger.Info($"传输文件结束,请求类型={e.TransferType},文件名={e.ResourcePath},请求状态={e.Result}"); + await e.InvokeNext(); + } + + + /// + /// 该方法,会在每个文件被请求(推送)时第一时间触发。 + /// 当请求文件时,可以重新指定请求的文件路径,即对e.ResourcePath直接赋值。 + /// 当推送文件时,可以重新指定保存文件路径,即对e.SavePath直接赋值。 + /// + /// 注意:当文件夹不存在时,需要手动创建。 + /// + /// + /// + /// + public async Task OnDmtpFileTransferring(IDmtpActorObject client, FileTransferringEventArgs e) + { + foreach (var item in e.Metadata.Keys) + { + Console.WriteLine($"Key={item},Value={e.Metadata[item]}"); + } + e.IsPermitOperation = true;//每次传输都需要设置true,表示允许传输 + //有可能是上传,也有可能是下载 + this.m_logger.Info($"请求传输文件,请求类型={e.TransferType},请求文件名={e.ResourcePath}"); + await e.InvokeNext(); + } +} +``` + +### 6.1 客户端向服务器请求文件 + + + +【客户端代码】 + +```csharp showLineNumbers +var filePath = "ClientPullFileFromService.Test"; +var saveFilePath = "SaveClientPullFileFromService.Test"; + +var metadata = new Metadata();//传递到服务器的元数据 +metadata.Add("1", "1"); +metadata.Add("2", "2"); + +var fileOperator = new FileOperator()//实例化本次传输的控制器,用于获取传输进度、速度、状态等。 +{ + SavePath = saveFilePath,//客户端本地保存路径 + ResourcePath = filePath,//请求文件的资源路径 + Metadata = metadata,//传递到服务器的元数据 + Timeout = TimeSpan.FromSeconds(60),//传输超时时长 + TryCount = 10,//当遇到失败时,尝试次数 + FileSectionSize = 1024 * 512//分包大小,当网络较差时,应该适当减小该值 +}; + +fileOperator.MaxSpeed=1024 * 1024;//设置最大限速为1Mb。 + +//此处的作用相当于Timer,定时每秒输出当前的传输进度和速度。 +var loopAction = LoopAction.CreateLoopAction(-1, 1000, (loop) => +{ + if (fileOperator.IsEnd) + { + loop.Dispose(); + } + client.Logger.Info($"进度:{fileOperator.Progress},速度:{fileOperator.Speed()}"); +}); + +loopAction.RunAsync(); + +//此方法会阻塞,直到传输结束,也可以使用PullFileAsync +IResult result =await client.GetDmtpFileTransferActor().PullFileAsync(fileOperator); +``` + +### 6.2 客户端向服务器推送文件 + + + +```csharp showLineNumbers +var filePath = "ClientPushFileFromService.Test"; +var saveFilePath = "SaveClientPushFileFromService.Test"; + +var metadata = new Metadata();//传递到服务器的元数据 +metadata.Add("1", "1"); +metadata.Add("2", "2"); + +var fileOperator = new FileOperator()//实例化本次传输的控制器,用于获取传输进度、速度、状态等。 +{ + SavePath = saveFilePath,//服务器本地保存路径 + ResourcePath = filePath,//客户端本地即将上传文件的资源路径 + Metadata = metadata,//传递到服务器的元数据 + Timeout = TimeSpan.FromSeconds(60),//传输超时时长 + TryCount = 10,//当遇到失败时,尝试次数 + FileSectionSize = 1024 * 512//分包大小,当网络较差时,应该适当减小该值 +}; + +fileOperator.MaxSpeed=1024 * 1024;//设置最大限速为1Mb。 + +//此处的作用相当于Timer,定时每秒输出当前的传输进度和速度。 +var loopAction = LoopAction.CreateLoopAction(-1, 1000, (loop) => +{ + if (fileOperator.IsEnd) + { + loop.Dispose(); + } + client.Logger.Info($"进度:{fileOperator.Progress},速度:{fileOperator.Speed()}"); +}); + +loopAction.RunAsync(); + +//此方法会阻塞,直到传输结束,也可以使用PushFileAsync +IResult result =await client.GetDmtpFileTransferActor().PushFileAsync(fileOperator); +``` + +### 6.3 服务器向客户端请求文件 + + + +服务器主动向客户端请求文件,必须通过Id,找到其`SessionClient`的派生类。 + +```csharp showLineNumbers +string targetId="targetId"; +if (!service.TryGetClient(targetId, out var SessionClient)) +{ + throw new Exception($"没有找到Id={targetId}的客户端"); +} + +var filePath = "ServicePullFileFromClient.Test"; +var saveFilePath = "SaveServicePullFileFromClient.Test"; + +var metadata = new Metadata();//传递到客户端的元数据 +metadata.Add("1", "1"); +metadata.Add("2", "2"); + +var fileOperator = new FileOperator()//实例化本次传输的控制器,用于获取传输进度、速度、状态等。 +{ + SavePath = saveFilePath,//服务器本地保存路径 + ResourcePath = filePath,//请求客户端文件的资源路径 + Metadata = metadata,//传递到客户端的元数据 + Timeout = TimeSpan.FromSeconds(60),//传输超时时长 + TryCount = 10,//当遇到失败时,尝试次数 + FileSectionSize = 1024 * 512//分包大小,当网络较差时,应该适当减小该值 +}; + +fileOperator.MaxSpeed=1024 * 1024;//设置最大限速为1Mb。 + +//此处的作用相当于Timer,定时每秒输出当前的传输进度和速度。 +var loopAction = LoopAction.CreateLoopAction(-1, 1000, (loop) => +{ + if (fileOperator.IsEnd) + { + loop.Dispose(); + } + SessionClient.Logger.Info($"进度:{fileOperator.Progress},速度:{fileOperator.Speed()}"); +}); + +loopAction.RunAsync(); + +//此方法会阻塞,直到传输结束,也可以使用PullFileAsync +IResult result =await SessionClient.GetDmtpFileTransferActor().PullFileAsync(fileOperator); +``` + +### 6.4 服务器向客户端推送文件 + +服务器主动向客户端推送文件,必须通过Id,找到其`SessionClient`的派生类。 + +```csharp showLineNumbers +if (!service.TryGetClient(targetId, out var SessionClient)) +{ + throw new Exception($"没有找到Id={targetId}的客户端"); +} + +var filePath = "ServicePushFileFromClient.Test"; +var saveFilePath = "SaveServicePushFileFromClient.Test"; + +var metadata = new Metadata();//传递到客户端的元数据 +metadata.Add("1", "1"); +metadata.Add("2", "2"); + +var fileOperator = new FileOperator()//实例化本次传输的控制器,用于获取传输进度、速度、状态等。 +{ + SavePath = saveFilePath,//客户端本地保存路径 + ResourcePath = filePath,//服务器文件的资源路径 + Metadata = metadata,//传递到客户端的元数据 + Timeout = TimeSpan.FromSeconds(60),//传输超时时长 + TryCount = 10,//当遇到失败时,尝试次数 + FileSectionSize = 1024 * 512//分包大小,当网络较差时,应该适当减小该值 +}; + +fileOperator.MaxSpeed=1024 * 1024;//设置最大限速为1Mb。 + +//此处的作用相当于Timer,定时每秒输出当前的传输进度和速度。 +var loopAction = LoopAction.CreateLoopAction(-1, 1000, (loop) => +{ + if (fileOperator.IsEnd) + { + loop.Dispose(); + } + SessionClient.Logger.Info($"进度:{fileOperator.Progress},速度:{fileOperator.Speed()}"); +}); + +loopAction.RunAsync(); + +//此方法会阻塞,直到传输结束,也可以使用PushFileAsync +IResult result =await SessionClient.GetDmtpFileTransferActor().PushFileAsync(fileOperator); +``` + +### 6.5 客户端之间传输文件 + + + +该功能支持客户端之间传输文件,使用方法基本一致,只需要在请求`PullFile`或者`PushFile`时额外增加目标Id即可。 + +此外,**响应中介(一般是服务器)**需要添加`路由策略`和`同意路由`。 + +【添加路由策略】 + +```csharp {3} +.ConfigureContainer(a => +{ + a.AddDmtpRouteService();//添加路由策略 +}) +``` + +【同意路由】 + +```csharp {5} +internal class MyPlugin : PluginBase, IDmtpRoutingPlugin +{ + public async Task OnDmtpRouting(IDmtpActorObject client, PackageRouterEventArgs e) + { + e.IsPermitOperation = true;//允许路由 + await e.InvokeNext(); + } +} +``` + +:::caution 注意 + +如果不添加`DmtpRouteService`策略,将不会转发任何路由请求,即意味着请求会被直接响应。 + +::: + +其他`PullFile`或者`PushFile`操作均和上述一致。 + +## 七、断点续传 + +本文件传输,支持断点续传。能够在传输过程中,暂停(实际上就是取消)传输,下次继续传输。 + +断点续传的使用步骤如下: + +1. 当本次传输失败、或主动取消时,可以保存`fileOperator.ResourceInfo`对象属性。 +2. 当下次再次请求传输时,可以将已保存的`ResourceInfo`对象,先赋值给`fileOperator.ResourceInfo`属性。那么如果保存路径一致,且文件大小符合续传要求,则可以继续传输。 + + + +```csharp showLineNumbers +//关于断点续传 +//在执行完PullFile(fileOperator)或PushFile(fileOperator)时。只要返回的结果不是Success。 +//那么就意味着传输没有完成。 +//而续传的机制就是,在执行传输之前,如果fileOperator.ResourceInfo为空,则开始新的传输。如果不为空,则尝试续传。 +//而对于失败的传输,未完成的信息都在fileOperator.ResourceInfo中。 +//所以我们可以使用一个变量(或字典)来存fileOperator.ResourceInfo的值。 +//亦或者可以把ResourceInfo的值持久化。 +//然后在重新发起请求传输值前,先对fileOperator.ResourceInfo做有效赋值。即可尝试断点传输。 + +byte[] cacheBytes;//这就是持久化后的数据。你可以将此数据写入到文件或数据库。 +using (var byteBlock=new ByteBlock(1024*64)) +{ + fileOperator.ResourceInfo.Save(byteBlock); + + cacheBytes = byteBlock.ToArray(); +} + +//然后想要续传的时候。先把缓存数据转为FileResourceInfo。 +using (var byteBlock=new ByteBlock(cacheBytes)) +{ + var resourceInfo = new FileResourceInfo(byteBlock); + //然后把resourceInfo赋值给新建的FileOperator的ResourceInfo属性。 +} +``` + +:::caution 注意 + +本断点续传不验证文件唯一性,假如在续传前后文件大小不一致,则会导致续传失败。如果文件大小一致,但是内容已发送变化,则可能得到一个错误的文件。所以建议在续传时要么确定文件不会再变化,要么需要在传输完成后进行文件唯一性验证(例如MD5)。 + +::: + +:::tip 提示 + +文件续传适用于所有传输类型,无论是客户端对服务器,还是服务器对客户端,还是客户端对客户端。 + +::: + +:::tip 关于续传持久化 + +一般来说,续传信息是不需要持久化的。只需要使用一个变量(或字典存一下即可)。但是如果考虑进程退出,或程序崩溃等情况,则需要持久化续传信息。 + +::: + +## 八、取消传输 + +传输取消,指的是,在传输过程中,用户主动取消传输。 + + +### 8.1 Token传递 + +使用方法非常简单,因为`FileOperator`中允许传入一个可取消令箭。所以只需要通过`CancellationTokenSource`,将`Token`传入,然后直接使用`CancellationTokenSource`取消即可。 + +```csharp {1,7,13} showLineNumbers +var tokenSource = new CancellationTokenSource(); + +_=Task.Run(async () => +{ + //此处模拟五秒后自动取消传输 + await Task.Delay(5000); + tokenSource.Cancel(); +}); + +var fileOperator = new FileOperator()//实例化本次传输的控制器,用于获取传输进度、速度、状态等。 +{ + ... + Token = tokenSource.Token +}; + +IResult result =await SessionClient.GetDmtpFileTransferActor().PullFileAsync(fileOperator); +``` + +### 8.2 使用CancellationFileOperator + +`CancellationFileOperator`是继承`FileOperator`并且已经实现取消传输的操作器。原理和`Token`一致,这里只是做了一次封装。 + +```csharp {1,7} showLineNumbers +var fileOperator = new CancellationFileOperator()//实例化本次传输的控制器,用于获取传输进度、速度、状态等。 +{ + ... +}; + +//模拟五秒后自动取消传输,或者直接Cancel +fileOperator.CancelAfter(TimeSpan.FromSeconds(5)); + +IResult result =await SessionClient.GetDmtpFileTransferActor().PullFileAsync(fileOperator); +``` + +:::tip 提示 + +`取消传输`和`断点续传`结合,就能模拟`暂停传输`功能。这样,用户就可以在传输过程中,随时暂停和继续传输。并且没有暂停时长限制。 + +::: + + +## 九、小文件传输 + +小文件传输是指,当传输文件小于设定大小(默认1024*1024字节)时的传输。 + +为什么要设立小文件传输?与常规文件传输相比,优点在哪里? + +常规传输,建立一个传输通道,大约需要传输两端,往返通信4-6次。这在本地局域网中,显得无所谓。但是在互联网环境中,一次ping延迟平均50ms,那么建立一个传输,就大约需要200-300ms。这也就意味着,即使一个文件只有一字节,也需要200ms-300。所以,这明显是不合理的。所以又新增了小文件传输,只要文件在1Mb以内,仅往返1次,就可以完成传输。 + + + +### 9.1 拉取小文件 + +1. 直接调用PullSmallFile或者PullSmallFileAsync,获取到实际的文件数据。 +2. 通过Save方法,将数据写入文件。也可以自行保存。 + +```csharp showLineNumbers +var filePath = "PullSmallFileFromService.Test"; +var saveFilePath = "SavePullSmallFileFromService.Test"; + +var metadata = new Metadata();//传递到服务器的元数据 +metadata.Add("1", "1"); +metadata.Add("2", "2"); + +//此方法会阻塞,直到传输结束,也可以使用PullSmallFileAsync +PullSmallFileResult result =await client.GetDmtpFileTransferActor().PullSmallFileAsync(filePath, metadata); +byte[] data = result.Value;//此处即是下载的小文件的实际数据 +result.Save(saveFilePath,overwrite:true);//将数据保存到指定路径。 +``` + +### 9.2 推送小文件 + +【推送文件】 + +1. 直接调用PushSmallFile或者PushSmallFileAsync。 +2. 返回值即表示是否成功。 + +```csharp showLineNumbers +var filePath = "PushSmallFileFromService.Test"; +var saveFilePath = "SavePushSmallFileFromService.Test"; +if (!File.Exists(filePath))//创建测试文件 +{ + using (var stream = File.OpenWrite(filePath)) + { + stream.SetLength(1024); + } +} + +var metadata = new Metadata();//传递到服务器的元数据 +metadata.Add("1", "1"); +metadata.Add("2", "2"); + +//此方法会阻塞,直到传输结束,也可以使用PullSmallFileAsync +var result =await client.GetDmtpFileTransferActor().PushSmallFileAsync(saveFilePath,new FileInfo(filePath), metadata); +if (result.IsSuccess()) +{ + //成功 +} +``` + +:::tip 提示 + +小文件传输也支持服务器主推至客户端,和客户端之间相互传输。 + +::: + +## 十、多线程文件传输 + +多线程文件传输,顾名思义,就是多个连接链路,共同传输一个文件。 + +多线程传输的优点是什么?和常规文件传输相比,场景有哪些不同? + +首先,常规文件传输是基于单个连接链路的,所以,单个连接的传输速率上限,就是常规传输的上限。一般来说,局域网当中,单个连接即可占满所有带宽,所以这时候多线程传输和常规传输并无差别。但是,在云服务器,或者在有流量均衡算法的网络中,每个连接的最大速率不是带宽的最大速率,那么这时候,两个差距是比较大的。 + +例如,我自己租的一个单核云服务器,它的单个连接速率只有1Mb,但是弹性带宽却有10Mb。宏观表象就是,一个客户端连接时,可以用1Mb带宽,两个客户端连接时,就可以用2Mb。那么这时候,多线程传输就显得格外重要了。 + + + +### 10.1 创建多链路连接器 + +因为是多链路传输,所以,就必须建立多个客户端的连接到服务器。这里使用已经封装好的通信模型ClientFactory。 + +ClientFactory的通信模型使用的是一个主通信端+多个传输客户端。 + +对于客户端的配置,请详细参考[创建DmtpClient工厂](./dmtpclient.mdx) + +```csharp showLineNumbers +var clientFactory = new TcpDmtpClientFactory() +{ + MinCount=5,//最小数量,在主连接器成功建立以后,会检测可用连接是否大于该值,否的话会自动建立。 + MaxCount = 10,//最大数量,当超过该数量的连接后,会等待指定时间,或者永久等待。 + ConnectTimeout = TimeSpan.FromSeconds(10),//连接超时时间 + GetConfig = () => + { + return new TouchSocketConfig() + .SetRemoteIPHost("127.0.0.1:7789") + .ConfigurePlugins(a => + { + a.UseDmtpFileTransfer() + .SetMaxSmallFileLength(1024 * 1024); + + a.AddDmtpFileTransferringPlugin(async (c, e) => + { + e.IsPermitOperation = true; //允许传输操作 + await e.InvokeNext(); + }); + }) + .SetDmtpOption(new DmtpOption() + { + VerifyToken = "Dmtp", + }); + } +}; +``` + +### 10.2 多线程请求文件 + +```csharp showLineNumbers +using var clientFactory = CreateClientFactory(); +ConsoleLogger.Default.Info("开始从服务器下载文件"); +var filePath = "MultithreadingClientPullFileFromService.Test"; +var saveFilePath = "SaveMultithreadingClientPullFileFromService.Test"; + +var metadata = new Metadata();//传递到服务器的元数据 +metadata.Add("1", "1"); +metadata.Add("2", "2"); + +var fileOperator = new MultithreadingFileOperator//实例化本次传输的控制器,用于获取传输进度、速度、 +{ + SavePath = saveFilePath,//客户端本地保存路径 + ResourcePath = filePath,//请求文件的资源路径 + Metadata = metadata,//传递到服务器的元数据 + Timeout = TimeSpan.FromSeconds(60),//传输超时时长 + TryCount = 10,//当遇到失败时,尝试次数 + FileSectionSize = 1024 * 512,//分包大小,当网络较差时,应该适当减小该值 + MultithreadingCount = 10//多线程数量 +}; + +fileOperator.SetMaxSpeed(1024*1024);//设置最大限速为1Mb。 + +//此处的作用相当于Timer,定时每秒输出当前的传输进度和速度。 +var loopAction = LoopAction.CreateLoopAction(-1, 1000, (loop) => +{ + if (fileOperator.IsEnd) + { + loop.Dispose(); + } + ConsoleLogger.Default.Info($"进度:{fileOperator.Progress},速度:{fileOperator.Speed()}"); +}); + +loopAction.RunAsync(); + +//此方法会阻塞,直到传输结束 +IResult result =await clientFactory.PullFileAsync(fileOperator); + +ConsoleLogger.Default.Info($"从服务器下载文件结束,{result}"); +``` + +### 10.3 多线程推送文件 + +```csharp showLineNumbers +using var clientFactory = CreateClientFactory(); +ConsoleLogger.Default.Info("开始向服务器推送文件"); +var filePath = "MultithreadingClientPushFileFromService.Test"; +var saveFilePath = "SaveMultithreadingClientPushFileFromService.Test"; + +var metadata = new Metadata();//传递到服务器的元数据 +metadata.Add("1", "1"); +metadata.Add("2", "2"); + +var fileOperator = new MultithreadingFileOperator//实例化本次传输的控制器,用于获取传输进度、速度、状态等 +{ + SavePath = saveFilePath,//客户端本地保存路径 + ResourcePath = filePath,//请求文件的资源路径 + Metadata = metadata,//传递到服务器的元数据 + Timeout = TimeSpan.FromSeconds(60),//传输超时时长 + TryCount = 10,//当遇到失败时,尝试次数 + FileSectionSize = 1024 * 512,//分包大小,当网络较差时,应该适当减小该值 + MultithreadingCount = 10//多线程数量 +}; + +fileOperator.SetMaxSpeed(1024*1024);//设置最大限速为1Mb。 + +//此处的作用相当于Timer,定时每秒输出当前的传输进度和速度。 +var loopAction = LoopAction.CreateLoopAction(-1, 1000, (loop) => +{ + if (fileOperator.IsEnd) + { + loop.Dispose(); + } + ConsoleLogger.Default.Info($"进度:{fileOperator.Progress},速度:{fileOperator.Speed()}"); +}); + +loopAction.RunAsync(); + +//此方法会阻塞,直到传输结束 +IResult result =await clientFactory.PushFileAsync(fileOperator); +``` + +### 10.4 客户端之间传输文件 + +该功能也支持客户端之间互相传输。使用方法基本一致,需要额外指定目标Id,以及**获取传输的Id集合**即可。 + +多线程的客户端之间传输文件,不像其他操作类型那么简单。因为除了需要指定目的Id外,还需要指定获取目标Id的,传输客户端的Id集合,不然,获取数据的时候,仍然会是单线程工作的。 + + +【获取目标传输客户端的Id集合】 +在TcpDmtpClientFactory属性中,有个OnFindTransferIds。通过实现该属性,使其能够获取到对应客户端的传输客户端Id集合(下列代码为模拟值,要具体实现该功能,还得自行实现)。 + + + + +```csharp showLineNumbers +clientFactory.SetFindTransferIds((client, targetId) => +{ + //此处的操作不唯一,可能需要rpc实现。 + //其目的比较简单,就是获取到targetId对应的主客户端的所有传输客户端的Id集合。 + //这样就实现了多个客户端向多个客户端传输文件的目的。 + + return new string[] { targetId };//此处为模拟结果。 +}); +``` + +:::info 信息 + +多线程传输,目前暂不支持服务器主动向客户端传输。 + +::: + +## 十一、示例代码 + + \ No newline at end of file diff --git a/handbook/versioned_docs/version-3.1/donate.mdx b/handbook/versioned_docs/version-3.1/donate.mdx new file mode 100644 index 000000000..70a9fcb16 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/donate.mdx @@ -0,0 +1,167 @@ +--- +id: donate +title: 支持作者 +--- + + +## 赞助TouchSocket项目 + +### 随手烙一大饼 + +:::tip 感谢 + +您的支持就是我不懈努力的动力。 + +::: + +[Reward through PayPal](https://paypal.me/rrqm?country.x=C2&locale.x=zh_XC) + +![打赏支付.png](@site/static/img/docs/donate-1.png) + +## 爱心助厨名单(以下排名只按照打赏时间顺序) + +|序号|打赏人|打赏金额|打赏日期| +|:----: |:----: |:----: |:----: | +|1|Bobo Joker|200¥|2025年1月1日前| +|2|UnitySir|66¥|2025年1月1日前| +|3|Coffee|100¥|2025年1月1日前| +|4|Ninety|50¥|2025年1月1日前| +|5|*琼|100¥|2025年1月1日前| +|6|**安|5¥|2025年1月1日前| +|7|**文|200+200¥|2025年1月1日前| +|8|tonychen899|50¥|2025年1月1日前| +|9|*平|50¥|2025年1月1日前| +|10|杜|400+100¥|2025年1月1日前| +|11|施*双|666¥|2025年1月1日前| +|12|*童|20¥|2025年1月1日前| +|13|Tom|88¥|2025年1月1日前| +|14|*强|200¥|2025年1月1日前| +|15|*潮|10+20+1+2¥|2025年1月1日前| +|16|*星|20¥|2025年1月1日前| +|17|舔狗反咬事件|66¥|2025年1月1日前| +|18|美少女酱|30¥|2025年1月1日前| +|19|流水游鱼|9.99¥|2025年1月1日前| +|20|华丽谢幕|33+20¥|2025年1月1日前| +|21|阁主悦澜殇|50+50¥|2025年1月1日前| +|22|烈日|20+20¥|2025年1月1日前| +|23|Silent|50¥|2025年1月1日前| +|24|月华散|50¥|2025年1月1日前| +|25|黄*德|PayPal:1000NT$|2025年1月1日前| +|26|一头大狮子|20¥|2025年1月1日前| +|27|蒋*秋|188¥|2025年1月1日前| +|28|**发|100+100¥|2025年1月1日前| +|29|梦想遥不可及|128+98¥|2025年1月1日前| +|30|可爱又善良的我|100¥|2025年1月1日前| +|31|*君|6.6¥|2025年1月1日前| +|32|*于|30¥|2025年1月1日前| +|33|J*n|100¥|2025年1月1日前| +|34|D*Y|20¥|2025年1月1日前| +|35|*人|50¥|2025年1月1日前| +|36|*光|66¥|2025年1月1日前| +|37|Estel|100¥|2025年1月1日前| +|38|1|200¥|2025年1月1日前| +|39|**阳|100¥|2025年1月1日前| +|40|chenqiang|6.6¥|2025年1月1日前| +|41|Azure|50¥|2025年1月1日前| +|42|广东-小白|1.5¥|2025年1月1日前| +|43|D*Y|20¥|2025年1月1日前| +|44|高端融合|8.88¥|2025年1月1日前| +|45|张*伊|100¥|2025年1月1日前| +|46|庄**|550¥|2025年1月1日前| +|47|Liwen.St|20¥|2025年1月1日前| +|48|日暮途远|88+28¥|2025年1月1日前| +|49|正在升级中请稍后|5¥|2025年1月1日前| +|50|shack2|18¥|2025年1月1日前| +|51|放开那女孩让我来|50+50¥|2025年1月1日前| +|52|微信昵称|30¥|2025年1月1日前| +|53|ccdfz|100¥|2025年1月1日前| +|54|若白|20¥|2025年1月1日前| +|55|执友者|20¥|2025年1月1日前| +|56|经不起夸奖|100¥|2025年1月1日前| +|57|sky/sun|50¥|2025年1月1日前| +|58|[青岛]Json|66¥|2025年1月1日前| +|59|速达软件|18¥|2025年1月1日前| +|60|曹校林|58+38.8+28.8+28.8¥|2025年1月1日前| +|61|zjm|298¥|2025年1月1日前| +|62|⑥阿太⑥|1000¥|2025年1月1日前| +|63|匿名|66¥|2023年7月5日| +|64|匿名|10¥|2023年8月2日| +|65|匿名|30¥|2023年8月8日| +|66|匿名|52¥|2023年8月11日| +|67|匿名|20¥|2023年8月15日| +|68|匿名|8.8¥|2023年8月19日| +|69|匿名|188¥|2023年9月28日| +|70|匿名|66¥|2023年10月2日| +|71|匿名|5¥|2023年10月11日| +|72|匿名|5¥|2023年10月13日| +|73|匿名|200¥|2023年10月13日| +|74|匿名|200¥|2023年11月29日| +|75|匿名|50¥|2023年12月12日| +|76|匿名|188¥|2023年12月31日| +|77|匿名|202.4¥|2024年2月7日| +|78|匿名|20¥|2024年2月24日| +|79|*乔|66¥|2025年1月1日前| +|80|**坤|233¥|2025年1月1日前| +|81|**邈|66¥|2025年1月1日前| +|82|**君|20¥|2025年1月1日前| +|83|小蚂蚁|298¥|2025年1月1日前| +|84|张*|200¥|2025年1月1日前| +|85|嗷|100¥|2025年1月1日前| +|86|**苗|99¥|2025年1月1日前| +|87|*攀|200¥|2025年1月1日前| +|88|昔**|8.88¥|2025年1月1日前| +|89|stweily|20¥|2025年1月1日前| +|90|赋あ|20.00¥|2025年1月1日前| +|91|无锡-洲|20.00¥|2025年1月1日前| +|92|(丶)|6.00¥|2025年1月1日前| +|93|毛毛虫|5.00¥|2025年1月1日前| +|94|李飞|5.00¥|2025年1月1日前| +|95|小骨|10.00¥|2025年1月1日前| +|96|Bug|5.00¥|2025年1月1日前| +|97|冬日里的小雪球|8.04¥|2025年1月1日前| +|98|Yuyuko|10.00¥|2025年1月1日前| +|99|水中蕓|5.00¥|2025年1月1日前| +|100|Tom|10.00¥|2025年1月1日前| +|101|🔥|12.00¥|2025年1月1日前| +|102|为爱,唯爱|50.00¥|2025年1月1日前| +|103|玄炫璇|20.00¥|2025年1月1日前| +|104|匿名|28.00¥|2025年1月1日前| +|105|匿名|8.88¥|2025年1月1日前| +|106|匿名|20¥|2025年1月1日前| +|107|+**|13¥|2025年1月1日前| +|108|昔**|6.66¥|2025年1月1日前| +|109|匿名|10¥|2025年1月1日前| +|110|YF|50¥|2025年1月1日前| +|111|Miracles|100¥|2025年1月1日前| +|112|广州痕迹-代表WPF机器视觉活动全体成员|2000¥|2025年1月1日前| +|113|广州一号码农|150¥|2025年1月1日前| +|114|鱼|88.88+100¥|2025年1月1日前| +|115|李**|100¥|2025年1月1日前| +|116|玄炫璇|50.00¥|2025年1月1日前| +|118|J**.R|188¥|2025年1月1日前| +|119|玄炫璇|20.00¥|2025年1月1日前| +|120|科|100.00¥|2025年1月1日前| +|121|李*华|50.00¥|2025年1月1日前| +|122|zjm|200.00¥|2025年1月1日前| +|123|北京碧水江河|298.00¥|2025年1月1日前| +|124|黄*德|PayPal:58.79$|2025年1月1日前| +|125|朱**|5¥|2025年1月1日前| +|126|朱**|66¥|2025年1月1日前| +|127|曹校林|28.8¥|2025年1月1日前| +|128|Goldslime|50¥|2025年1月1日前| +|129|为**|100¥|2025年1月1日前| +|130|一方白水|500¥|2025年1月1日前| +|131|朱**|20¥|2025年1月1日前| +|132|无**|10¥|2025年1月1日前| +|133|**清|20¥|2024年11月19日| +|134|**强|66¥|2024年11月27日| +|135|**均|50¥|2024年12月5日| +|136|蓝**|100¥|2025年1月1日前| +|137|蓝**|100¥|2025年1月25日| +|138|*荣|20¥|2025年2月17日| +|139|T**|1¥|2025年3月11日| +|140|赵**|5¥|2025年3月18日| +|140|四**|20¥|2025年3月18日| +|142|阳**|50¥|2025年3月21日| +|143|**伟|200¥|2025年4月18日| +|144|A***|88¥|2025年6月2日| diff --git a/handbook/versioned_docs/version-3.1/dynamicmethod.mdx b/handbook/versioned_docs/version-3.1/dynamicmethod.mdx new file mode 100644 index 000000000..6763c9006 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/dynamicmethod.mdx @@ -0,0 +1,485 @@ +--- +id: dynamicmethod +title: 动态方法调用(DynamicMethod) +--- + +import CardLink from "@site/src/components/CardLink.js"; +import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; + + +### 定义 + + + + +## 一、核心概念 + +动态方法调用模块提供高效、灵活的方法反射调用方案,支持多种底层实现方式,显著提升反射调用性能。特别针对AOT(Ahead-of-Time)编译环境优化,同时保持传统反射场景的高性能表现。 + +## 二、核心特性 + +- **多引擎支持**: + - IL代码生成(DynamicBuilderType.IL) + - 表达式树(DynamicBuilderType.Expression) + - 传统反射(DynamicBuilderType.Reflect) + - 源生成(DynamicBuilderType.SourceGenerator) +- **性能卓越**:相比原生反射调用,性能提升10倍性能 +- **AOT友好**:源生成模式实现零反射,完美支持iOS/Android等AOT环境 +- **智能异步支持**:自动识别Task/ValueTask返回值类型 +- **全参数支持**:支持ref/out参数、泛型参数、参数默认值 +- **灵活扩展**:支持自定义动态方法特性标记 + +## 三、快速开始 + +### 3.1 基本声明 + +```csharp showLineNumbers +public class MyClass +{ + [DynamicMethod] + public void SimpleMethod() + { + Console.WriteLine("Method executed"); + } +} +``` + +### 3.2 基础调用 + +```csharp showLineNumbers +// 创建方法包装器 +var method = new Method(typeof(MyClass), nameof(MyClass.SimpleMethod)); + +// 实例化对象 +var instance = new MyClass(); + +// 执行方法调用 +method.Invoke(instance); +``` + +## 四、核心功能详解 + +### 4.1 构建器类型选择 + +```csharp showLineNumbers +// 使用IL生成 +var ilMethod = new Method( + typeof(MyClass), + nameof(MyClass.SimpleMethod), + DynamicBuilderType.IL +); + +// 使用表达式树 +var exprMethod = new Method( + typeof(MyClass), + nameof(MyClass.SimpleMethod), + DynamicBuilderType.Expression +); + +// 使用源生成(性能最强,AOT环境推荐) +var sourceGenMethod = new Method( + typeof(MyClass), + nameof(MyClass.SimpleMethod), + DynamicBuilderType.SourceGenerator +); +``` + +:::tip 构建器选择建议 + +- **任何时候**:使用SourceGenerator,无论性能还是AOT环境,都是首选。 + +::: + +### 4.2 异步方法支持 + +```csharp showLineNumbers +public class MyClass +{ + [DynamicMethod] + public async Task GetDataAsync() + { + await Task.Delay(100); + return 42; + } +} + +// 异步调用 +var method = new Method(typeof(MyClass), nameof(MyClass.GetDataAsync)); +var result = await method.InvokeAsync(instance); +Console.WriteLine($"Result: {result}"); // 输出 42 +``` + +### 4.3 复杂参数处理 + +```csharp showLineNumbers +public class MyClass +{ + [DynamicMethod] + public void ProcessData( + string input, + ref int counter, + out string result) + { + counter++; + result = $"{input}_{counter}"; + } +} + +// 调用示例 +var parameters = new object[] { "data", 0, null }; +method.Invoke(instance, parameters); + +Console.WriteLine($"Result: {parameters[2]}"); // 输出 "data_1" +``` + +## 五、性能优化 + +### 5.1 性能对比测试 + +:::tip 10000次调用性能分析 + +**核心结论** + +1. **同步方法(Add)性能** + - **直接调用** 最快(~2.1µs),无内存分配。 + - **源生成器(SourceGeneratorRun)** 在 .NET 8+ 接近直接调用性能(~3.1µs),较 IL/表达式树快 6-7 倍,较反射快 28-30 倍。 + - **反射(MethodInfo)** 性能最差(.NET 6: 400µs → .NET 8: 88µs),.NET 8+ 反射性能显著优化。 + +2. **异步方法(AddAsync)性能** + - **直接调用** 仍最优(.NET 8: 38µs,.NET 9: 39µs)。 + - **源生成器** 表现接近 IL/表达式树(.NET 8: 91µs vs 103µs),较反射快 2-3 倍(.NET 8: 91µs vs 184µs)。 + - **.NET Framework 4.8.1** 异步性能最差(反射达 1,305µs),内存分配显著高于其他版本。 + +3. **内存分配** + - 同步方法:源生成器与 IL/表达式树分配 80B,反射额外多 1B(.NET 6)。 + - 异步方法:所有动态方法分配 ~720KB(.NET Core)或 ~802KB(Framework),反射在 Framework 分配超 1MB。 + +**附:关键数据对比(.NET 8)** + +| 方法 | 同步耗时 | 异步耗时 | 内存分配 | +|----------------------|---------|---------|----------| +| DirectRun | 2.1µs | 38µs | 56B | +| SourceGeneratorRun | 3.1µs | 91µs | 80B | +| MethodInfoRun | 88µs | 184µs | 80B | + +::: + +```csharp {7,12,18,23,29,34,40,45} showLineNumbers +| Method | Job | Runtime | Mean | Error | StdDev | Gen0 | Gen1 | Allocated | +|----------------------------- |--------------------- |--------------------- |-------------:|----------:|----------:|---------:|-------:|----------:| +| DirectRun_Add | .NET 6.0 | .NET 6.0 | 2.132 us | 0.0031 us | 0.0027 us | - | - | 56 B | +| MethodILRun_Add | .NET 6.0 | .NET 6.0 | 23.887 us | 0.0795 us | 0.0744 us | - | - | 80 B | +| MethodExpressionRun_Add | .NET 6.0 | .NET 6.0 | 20.108 us | 0.1542 us | 0.1443 us | - | - | 80 B | +| MethodInfoRun_Add | .NET 6.0 | .NET 6.0 | 399.999 us | 0.3781 us | 0.3537 us | - | - | 81 B | +| SourceGeneratorRun_Add | .NET 6.0 | .NET 6.0 | 17.648 us | 0.0945 us | 0.0884 us | - | - | 80 B | +| DirectRun_AddAsync | .NET 6.0 | .NET 6.0 | 46.567 us | 0.4623 us | 0.4098 us | 45.8984 | - | 720080 B | +| MethodILRun_AddAsync | .NET 6.0 | .NET 6.0 | 141.010 us | 0.8145 us | 0.7619 us | 45.8984 | - | 720080 B | +| MethodExpressionRun_AddAsync | .NET 6.0 | .NET 6.0 | 142.758 us | 0.8197 us | 0.7266 us | 45.8984 | - | 720080 B | +| MethodInfoRun_AddAsync | .NET 6.0 | .NET 6.0 | 563.645 us | 1.8681 us | 1.6560 us | 45.8984 | - | 720081 B | +| SourceGeneratorRun_AddAsync | .NET 6.0 | .NET 6.0 | 141.399 us | 0.8781 us | 0.8214 us | 45.8984 | - | 720080 B | +| StaticMethodRun | .NET 6.0 | .NET 6.0 | 2.130 us | 0.0018 us | 0.0017 us | - | - | 56 B | +| DirectRun_Add | .NET 8.0 | .NET 8.0 | 2.140 us | 0.0116 us | 0.0109 us | - | - | 56 B | +| MethodILRun_Add | .NET 8.0 | .NET 8.0 | 19.332 us | 0.0491 us | 0.0459 us | - | - | 80 B | +| MethodExpressionRun_Add | .NET 8.0 | .NET 8.0 | 12.889 us | 0.1189 us | 0.1054 us | - | - | 80 B | +| MethodInfoRun_Add | .NET 8.0 | .NET 8.0 | 88.879 us | 0.1784 us | 0.1581 us | - | - | 80 B | +| SourceGeneratorRun_Add | .NET 8.0 | .NET 8.0 | 3.160 us | 0.0073 us | 0.0068 us | 0.0038 | - | 80 B | +| DirectRun_AddAsync | .NET 8.0 | .NET 8.0 | 38.401 us | 0.1199 us | 0.1063 us | 45.8984 | - | 720080 B | +| MethodILRun_AddAsync | .NET 8.0 | .NET 8.0 | 103.627 us | 1.2387 us | 1.1587 us | 45.8984 | - | 720080 B | +| MethodExpressionRun_AddAsync | .NET 8.0 | .NET 8.0 | 101.368 us | 0.7190 us | 0.6725 us | 45.8984 | - | 720080 B | +| MethodInfoRun_AddAsync | .NET 8.0 | .NET 8.0 | 183.882 us | 0.7238 us | 0.6044 us | 45.8984 | - | 720080 B | +| SourceGeneratorRun_AddAsync | .NET 8.0 | .NET 8.0 | 91.024 us | 0.6482 us | 0.6063 us | 45.8984 | - | 720080 B | +| StaticMethodRun | .NET 8.0 | .NET 8.0 | 2.150 us | 0.0073 us | 0.0068 us | - | - | 56 B | +| DirectRun_Add | .NET 9.0 | .NET 9.0 | 2.119 us | 0.0024 us | 0.0022 us | - | - | 56 B | +| MethodILRun_Add | .NET 9.0 | .NET 9.0 | 19.348 us | 0.0133 us | 0.0124 us | - | - | 80 B | +| MethodExpressionRun_Add | .NET 9.0 | .NET 9.0 | 11.753 us | 0.0625 us | 0.0585 us | - | - | 80 B | +| MethodInfoRun_Add | .NET 9.0 | .NET 9.0 | 91.756 us | 0.2344 us | 0.2193 us | - | - | 80 B | +| SourceGeneratorRun_Add | .NET 9.0 | .NET 9.0 | 3.145 us | 0.0056 us | 0.0046 us | 0.0038 | - | 80 B | +| DirectRun_AddAsync | .NET 9.0 | .NET 9.0 | 39.219 us | 0.1669 us | 0.1561 us | 45.8984 | - | 720080 B | +| MethodILRun_AddAsync | .NET 9.0 | .NET 9.0 | 107.920 us | 0.4728 us | 0.4191 us | 45.8984 | - | 720080 B | +| MethodExpressionRun_AddAsync | .NET 9.0 | .NET 9.0 | 106.539 us | 0.3894 us | 0.3642 us | 45.8984 | - | 720080 B | +| MethodInfoRun_AddAsync | .NET 9.0 | .NET 9.0 | 189.294 us | 0.7231 us | 0.6764 us | 45.8984 | - | 720080 B | +| SourceGeneratorRun_AddAsync | .NET 9.0 | .NET 9.0 | 99.195 us | 0.3677 us | 0.3440 us | 45.8984 | - | 720080 B | +| StaticMethodRun | .NET 9.0 | .NET 9.0 | 2.125 us | 0.0036 us | 0.0034 us | - | - | 56 B | +| DirectRun_Add | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 2.134 us | 0.0047 us | 0.0042 us | 0.0076 | - | 56 B | +| MethodILRun_Add | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 30.162 us | 0.0629 us | 0.0588 us | - | - | 80 B | +| MethodExpressionRun_Add | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 56.063 us | 0.0559 us | 0.0467 us | - | - | 80 B | +| MethodInfoRun_Add | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 770.931 us | 5.9446 us | 5.5606 us | 50.7813 | - | 321027 B | +| SourceGeneratorRun_Add | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 17.521 us | 0.0350 us | 0.0310 us | - | - | 80 B | +| DirectRun_AddAsync | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 41.866 us | 0.1300 us | 0.1216 us | 127.5024 | 0.0610 | 802442 B | +| MethodILRun_AddAsync | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 523.507 us | 2.1568 us | 1.9120 us | 126.9531 | - | 802445 B | +| MethodExpressionRun_AddAsync | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 554.333 us | 2.3657 us | 2.2129 us | 126.9531 | - | 802445 B | +| MethodInfoRun_AddAsync | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 1,304.826 us | 1.4392 us | 1.2758 us | 177.7344 | - | 1123389 B | +| SourceGeneratorRun_AddAsync | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 508.533 us | 0.8991 us | 0.8410 us | 126.9531 | - | 802445 B | +| StaticMethodRun | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 3.267 us | 0.0070 us | 0.0062 us | 0.0114 | - | 80 B | +``` + +
+基准测试代码 +
+ +```csharp showLineNumbers +[SimpleJob(RuntimeMoniker.Net481)] +[SimpleJob(RuntimeMoniker.Net60)] +[SimpleJob(RuntimeMoniker.Net80)] +[SimpleJob(RuntimeMoniker.Net90)] +[MemoryDiagnoser] +public class BenchmarkInvokeMethodClass +{ + public BenchmarkInvokeMethodClass() + { + var methodInfo_Add = typeof(MyMethodClass1).GetMethod(nameof(MyMethodClass1.Add)); + var methodInfo_AddAsync = typeof(MyMethodClass1).GetMethod(nameof(MyMethodClass1.AddAsync)); + + this.m_method_Add_IL = new Method(methodInfo_Add, DynamicBuilderType.IL); + this.m_method_Add_Expression = new Method(methodInfo_Add, DynamicBuilderType.Expression); + this.m_method_Add_SourceGenerator = new Method(methodInfo_Add, DynamicBuilderType.SourceGenerator); + this.m_method_Add_Reflect = new Method(methodInfo_Add, DynamicBuilderType.Reflect); + + this.m_method_AddAsync_IL = new Method(methodInfo_AddAsync, DynamicBuilderType.IL); + this.m_method_AddAsync_Expression = new Method(methodInfo_AddAsync, DynamicBuilderType.Expression); + this.m_method_AddAsync_SourceGenerator = new Method(methodInfo_AddAsync, DynamicBuilderType.SourceGenerator); + this.m_method_AddAsync_Reflect = new Method(methodInfo_AddAsync, DynamicBuilderType.Reflect); + + var AddStatic = typeof(MyMethodClass1).GetProperty("AddStaticAction"); + } + + private readonly Method m_method_Add_IL; + private readonly Method m_method_Add_Expression; + private readonly Method m_method_Add_SourceGenerator; + private readonly Method m_method_Add_Reflect; + + private readonly Method m_method_AddAsync_IL; + private readonly Method m_method_AddAsync_Expression; + private readonly Method m_method_AddAsync_SourceGenerator; + private readonly Method m_method_AddAsync_Reflect; + + public int Count=10000; + + [Benchmark] + public void DirectRun_Add() + { + var myClass1 = new MyMethodClass1(); + + var a = new object(); + + var objects = new object[] { a }; + + for (var i = 0; i < this.Count; i++) + { + myClass1.Add(a); + } + } + + [Benchmark] + public void MethodILRun_Add() + { + var myClass2 = new MyMethodClass1(); + + var a = new object(); + + var objects = new object[] { a }; + for (var i = 0; i < this.Count; i++) + { + this.m_method_Add_IL.Invoke(myClass2, objects); + } + } + + [Benchmark] + public void MethodExpressionRun_Add() + { + var myClass2 = new MyMethodClass1(); + + var a = new object(); + + var objects = new object[] { a }; + for (var i = 0; i < this.Count; i++) + { + this.m_method_Add_Expression.Invoke(myClass2, objects); + } + } + + [Benchmark] + public void MethodInfoRun_Add() + { + var myClass2 = new MyMethodClass1(); + + var a = new object(); + + var objects = new object[] { a }; + for (var i = 0; i < this.Count; i++) + { + this.m_method_Add_Reflect.Invoke(myClass2, objects); + } + } + + [Benchmark] + public void SourceGeneratorRun_Add() + { + var myClass2 = new MyMethodClass1(); + + var a = new object(); + + var objects = new object[] { a }; + for (var i = 0; i < this.Count; i++) + { + this.m_method_Add_SourceGenerator.Invoke(myClass2, objects); + } + } + + [Benchmark] + public async Task DirectRun_AddAsync() + { + var myClass1 = new MyMethodClass1(); + + var a = new object(); + + var objects = new object[] { a }; + + for (var i = 0; i < this.Count; i++) + { + await myClass1.AddAsync(a); + } + } + + [Benchmark] + public async Task MethodILRun_AddAsync() + { + var myClass2 = new MyMethodClass1(); + + var a = new object(); + + var objects = new object[] { a }; + for (var i = 0; i < this.Count; i++) + { + await this.m_method_Add_IL.InvokeAsync(myClass2, objects); + } + } + + [Benchmark] + public async Task MethodExpressionRun_AddAsync() + { + var myClass2 = new MyMethodClass1(); + + var a = new object(); + + var objects = new object[] { a }; + for (var i = 0; i < this.Count; i++) + { + await this.m_method_Add_Expression.InvokeAsync(myClass2, objects); + } + } + + [Benchmark] + public async Task MethodInfoRun_AddAsync() + { + var myClass2 = new MyMethodClass1(); + + var a = new object(); + + var objects = new object[] { a }; + for (var i = 0; i < this.Count; i++) + { + await this.m_method_Add_Reflect.InvokeAsync(myClass2, objects); + } + } + + [Benchmark] + public async Task SourceGeneratorRun_AddAsync() + { + var myClass2 = new MyMethodClass1(); + + var a = new object(); + + var objects = new object[] { a }; + for (var i = 0; i < this.Count; i++) + { + await this.m_method_Add_SourceGenerator.InvokeAsync(myClass2, objects); + } + } + + [Benchmark] + public void StaticMethodRun() + { + var myClass1 = new MyMethodClass1(); + + var a = new object(); + + var objects = new object[] { a }; + + for (var i = 0; i < this.Count; i++) + { + MyMethodClass1.AddStatic(myClass1, objects); + } + } +} + + +public class MyMethodClass1 +{ + public static object AddStatic(object myClass, object[] ps) + { + var a = ps[0]; + var result = ((MyMethodClass1)myClass).Add(a); + return result; + } + + //这个是被调用函数。 + [DynamicMethod] + public object Add(object obj) + { + return obj; + } + [DynamicMethod] + public Task AddAsync(object obj) + { + return Task.FromResult(obj); + } +} +``` + + + + + +### 5.2 缓存策略 + +```csharp showLineNumbers +// 推荐在应用初始化时预加载方法 +public static class MethodCache +{ + public static readonly Method ProcessMethod = new Method( + typeof(MyClass), + nameof(MyClass.ProcessData), + DynamicBuilderType.IL + ); +} + +// 后续重复使用缓存实例 +MethodCache.ProcessMethod.Invoke(instance); +``` + +## 六、高级用法 + +### 6.1 自定义特性标记 + +```csharp showLineNumbers +// 定义自定义特性 +[AttributeUsage(AttributeTargets.Method)] +public class MyDynamicAttribute : DynamicMethodAttribute {} + +// 标记方法 +public class CustomService +{ + [MyDynamic] + public void CustomOperation() { } +} + +// 获取自定义标记方法 +var methods = typeof(CustomService) + .GetMethods() + .Where(m => m.IsDefined(typeof(MyDynamicAttribute))); +``` + +## 七、示例项目 + + diff --git a/handbook/docs/engineertoolbox.mdx b/handbook/versioned_docs/version-3.1/engineertoolbox.mdx similarity index 100% rename from handbook/docs/engineertoolbox.mdx rename to handbook/versioned_docs/version-3.1/engineertoolbox.mdx diff --git a/handbook/versioned_docs/version-3.1/enterprise.mdx b/handbook/versioned_docs/version-3.1/enterprise.mdx new file mode 100644 index 000000000..de89bb825 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/enterprise.mdx @@ -0,0 +1,221 @@ +--- +id: enterprise +title: Pro相关 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import Tag from "@site/src/components/Tag.js"; +import Paypal from '@site/src/components/Paypal.js'; +import Pro from "@site/src/components/Pro.js"; + +:::tip Pro限免相关 + +`TouchSocketPro`**限定版本免费**活动意味着您只要使用的是特定版本(v1.4、v2.0-beta),则享有永久免费使用的权利。使用方法见“限时测试”。 + +限定版本免费使用活动于2023年3月12日起,无终止时间,只要您使用的是特定版本,则享有永久免费使用的权利。如果后续考虑升级,只要您升级的目标版本仍然是特定版本,则同样享有永久免费使用的权利。 + +::: + +## 一、说明 + +`TouchSocketPro`是`TouchSocket`系的加强版本。其基础功能完全包含`TouchSocket`,除此之外,还有一些附加功能,这需要**付费购买密钥**,然后才能使用。具体详细区别**如下表格**所示。 + +同时`TouchSocketPro`还提供**企业定制服务**及必要的**远程协助**,具体收费可以咨询作者若汝棋茗,联系方式:QQ:505554090。 + +:::info 备注 + +所有以`TouchSocket`开头的程序集都是在开源网站开源的,只要你循序[使用协议](./description.mdx),即可完全免费使用。 + +::: + +## 二、TouchSocket与TouchSocketPro + +### 2.1 Tcp组件 + +- [轮询式断线重连](./reconnection.mdx) +- [TLV适配器](./tlvdatahandlingadapter.mdx) +- 其余功能 + +### 2.2 NAT组件 + +- [转发客户端重连](./natservice.mdx) +- 其余功能 + +### 2.3 UDP组件 + +- 所有功能 + +### 2.4 JsonRpc + +- 自定义解析 +- 其余功能 + +### 2.5 WebApi + +- 所有功能 + +### 2.6 XmlRpc + +- 所有功能 + +### 2.7 TouchRpc(tcp、udp、http、websocket) + +- [远程文件操作](./dmtpremoteaccess.mdx) +- [远程流访问](./dmtpremotestream.mdx) +- 文件传输功能 +- [多线程文件传输](./dmtptransferfile.mdx) +- [小文件传输](./dmtptransferfile.mdx) +- 文件传输限速 +- Redis + +### 2.8 Http组件 + +- 超大文件传输 +- 多通道文件续传 +- 静态网页展示 +- 文件传输限速 + +### WebSocket + +- 全部功能 + +:::tip 提示 + +上述的功能中,所有带有 的标识,均为`TouchSocketPro`包含的内容。其余功能`TouchSocket`也均支持。 + +::: + +## 三、能提供的个性服务 + +### 3.1 数据处理适配器的重写 + +在TouchSocketPro中,可以通过适配器对数据进行预处理和对象解析,目前TouchSocketPro拥有的适配器仅有`固定包头`、`固定长度`、`终止分割`、`Json字符串解析`、`Http对象解析`五种适配器。但是往往这些适配器不是我们想要的,例如:串口信号、AGV数据格式等。那么我们可以为您提供解析数据格式(对象)的服务。 + +### 3.2 增加或限制某个功能 + +程序库为的是能提供基础服务,所以某个功能的出现,均是为了具备更好的普适性,但是有时候也会与您的需求背道而驰,那么我们也可以为您定制某个功能(或禁用某个功能)。 + +## 四、TouchSocketPro + +| **类型** | **个人独立授权** | **个人企业授权** | **企业授权** | +| --- | --- | --- | --- | +| **功能** | 全部功能 | 全部功能 | 全部功能 | +| **使用期限** | 永久 | 永久 | 永久 | +| **授权归属** | 个人 | 个人 | 企业 | +| **协助服务** | 无 | 无 | 全部现有功能协助 | +| **个性化功能扩展** | 支持 | 支持 | 支持 | +| **激活方式** | 密钥激活 | 密钥激活 | 密钥激活和源码引用 | +| **后续升级** | Nuget升级 | Nuget升级 | Nuget升级或随时索要最新源码 | +| **源代码开放** | 不开放 | 不开放 | 开放 | +| **用于盈利** | 允许 | 允许 | 允许 | +| **个性化功能扩展** | 支持 | 支持 | 支持 | +| **开具发票** | 开具电子普票 | 开具电子普票 | 开具电子普票 | +| **赠品** | 送您1束玫瑰 | 送您2束玫瑰 | 送您3束玫瑰,和一个自定义适配器,或复杂度相同的个性化服务。 | +| **价格** | 298¥ | 已停售 | 998¥ | + + +### 4.1 个人独立授权 + +授权归属于购买者个人所有,规定购买者可将所购产品只能应用于所属个人的任何软件(产品)上,可以以此盈利,但必须遵守[个人使用协议](./description.mdx)。 + +### 4.2 个人企业授权 + +授权归属于购买者个人所有,规定购买者可将所购产品应用于购买者服务(工作)的企业的任何软件(产品)上,但授权期限与购买者服务(工作)期限一致,一旦购买者离职(或不再服务于企业),授权将在30个工作日后失效。同时,购买者在将所购产品应用于企业时,有必要告知义务,在离职(或不再服务于企业)时,也应当再次告知企业详情。 + +**(个人企业版在2023.1.1日后不再售卖。已售卖的个人企业版原始功能不变。或者联系作者,可免费升级至企业版)** + +### 4.3 企业授权 + +- 当授权归属于企业所有时,永久授权。且仅企业享有授权,所有职员均无授权。 +- 当授权归属个人所有时,永久授权。且保留一次企业冠名权益,现有授权自动降为“**个人企业授权**”条款。。 + +## 五、密钥使用 + +**首先请确保所有的项目完全卸载删除TouchSocket,并且在需要的项目中安装了TouchSocketPro。** + +当购买密钥后,您会获得类似“D1D1D1D1D1D1D1D1D1D1D1D1D1D1D1D1D1D1”这样的密钥。然后在程序**初始化**(例如Main函数)时。使用以下代码即可。 + +```csharp showLineNumbers + Enterprise.LicenceKey = "密钥"; +``` + +AspNetCore中使用时,建议自定义服务注入的方式实现。步骤如下: + +1. 新建项目,引用`Microsoft.Extensions.DependencyInjection`和`TouchSocketPro.AspNetCore`。 +2. 新建静态类**ServiceCollectionExtension**,创建IServiceCollection的扩展方法。 +3. 在IServiceCollection的扩展方法中,注入密钥。 +4. 在AspNetCore引用新建的项目。 +5. 在服务中注入。 + +部分代码示例如下: + +```csharp showLineNumbers +public static class ServiceCollectionExtension +{ + public static void AddLicence(this IServiceCollection service) + { + Enterprise.LicenceKey = "D1D1D1D1D1D1D1"; + } +} +``` + +```csharp showLineNumbers +public void ConfigureServices(IServiceCollection services) +{ + services.AddLicence(); +} + +``` + +## 六、限时测试 + +为方便大家测试,TouchSocketPro提供**限时1小时**的测试功能,当时间结束时Pro功能关闭,**重启进程**即可再次试用1小时,以此往复。 + +**调用ForTest时,会抛出可控异常。如果坚持使用Pro,使用Try拦截即可。** + +```csharp showLineNumbers +try +{ + Enterprise.ForTest(); +} +catch (Exception ex) +{ + Console.WriteLine(ex.Message); +} +``` + +## 七、购买通道 + +购买可通过以下方式。**购买前请先联系作者若汝棋茗**。 + +- QQ:505554090。 +- Email:505554090@qq.com。 + + + + + + + + + +点击[淘宝链接](https://item.taobao.com/item.htm?spm=a2126o.success.result.1.382c4831HDDIvA&id=691874706840)。 + + + + + + + + + + diff --git a/handbook/versioned_docs/version-3.1/fastbinaryformatter.mdx b/handbook/versioned_docs/version-3.1/fastbinaryformatter.mdx new file mode 100644 index 000000000..1071c494a --- /dev/null +++ b/handbook/versioned_docs/version-3.1/fastbinaryformatter.mdx @@ -0,0 +1,561 @@ +--- +id: fastbinaryformatter +title: 高性能二进制序列化 +--- + +import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +该序列化以二进制方式进行序列化,内存和性能都非常好。并且在序列化和反序列化时支持兼容类型,甚至可以像Json一样不同类型也可以。 + +目前支持的类型有: +- 基础类型 +- 自定义实体类、结构体 +- 元组 +- 支持类型组成的数组、字典、List等。 + +:::tip 提示 + +实际上经过自定义转化器,可以实现对**任意类型**的序列化和反序列化。 + +::: + +## 二、基本使用 + +### 2.1 简单使用 + +一般的,可以非常简单的对支持类型进行序列化和反序列化。 + +```csharp showLineNumbers +var bytes = FastBinaryFormatter.SerializeToBytes(10); +var newObj = FastBinaryFormatter.Deserialize(bytes); +``` + +### 2.2 使用内存池块 + +在使用过程中,如果使用到频繁的序列化、反序列化,可以使用内存块,可以减少内存的申请和释放。 + +```csharp {6,12} showLineNumbers +//申请内存块,并指定此次序列化可能使用到的最大尺寸。 +//合理的尺寸设置可以避免内存块扩张。 +using (var block = new ByteBlock(1024*64)) +{ + //将数据序列化到内存块 + FastBinaryFormatter.Serialize(block, 10); + + //在反序列化前,将内存块数据游标移动至正确位。 + block.SeekToStart(); + + //反序列化 + var newObj = FastBinaryFormatter.Deserialize(block); +} +``` + +### 2.3 使用值类型内存池块 + +常规内存块是“类”,所以在使用时,自身对象会产生GC垃圾,如果追求极致序列化,则可以使用值类型内存池块,可以做到**零GC**分配。 + +```csharp {8,14} showLineNumbers +//申请内存块,并指定此次序列化可能使用到的最大尺寸。 +//合理的尺寸设置可以避免内存块扩张。 +var block = new ValueByteBlock(1024 * 64); + +try +{ + //将数据序列化到内存块 + FastBinaryFormatter.Serialize(ref block, 10); + + //在反序列化前,将内存块数据游标移动至正确位。 + block.SeekToStart(); + + //反序列化 + var newObj = FastBinaryFormatter.Deserialize(ref block); +} +finally +{ + //因为使用了ref block,所以无法使用using,只能使用try-finally + block.Dispose(); +} +``` + +## 三、常规配置 + +`FastBinaryFormatter`默认情况下,支持的自定义类型必须具有**公共无参构造函数**。对于成员,仅支持**公共的属性和字段**。并且对于属性,要求必须是**可读可写**,对于只读属性,默认也是不做序列化和反序列化的。 + +例如:下列类型中,只有`P1`和`P3`成员将有效。 + +```csharp showLineNumbers +public class MyClass1 +{ + private int m_p5; + + //公共属性,有效 + public int P1 { get; set; } + + //自动公共属性,即使包含set访问器,但private,无效 + public int P2 { get; private set; } + + //公共字段,有效 + public int P3; + + //私有字段,无效 + private int P4; + + //公共属性,不包含set访问器,无效 + public int P5 => m_p5; + + public void SetP4(int value) + { + this.P4 = value; + } + + public void SetP2(int value) + { + this.P2 = value; + } + public void SetP5(int value) + { + this.m_p5 = value; + } +} +``` + +### 3.1 忽略成员 + +忽略成员,可以通过特性`[FastNonSerialized]`来忽略。 + +例如: + +```csharp {6} showLineNumbers +public class MyClass1 +{ + ... + + //公共属性,但忽略,无效 + [FastNonSerialized] + public int P6 { get; set; } + +} +``` + +### 3.2 强制成员 + +对于只读成员,有时候也需要序列化时,可以通过特性`[FastSerialized]`来强制。 + +例如: + +```csharp {6} showLineNumbers +public class MyClass1 +{ + ... + + //自动公共属性,包含set访问器,即使private,但因为FastSerialized后,有效 + [FastSerialized] + public int P7 { get;private set; } +} +``` + +:::caution 注意 + +强制特性虽然可以将成员添加在操作行列,但是如果成员绝对不可写(或者不可读)时,执行相应操作则会抛出异常。 + +::: + +## 四、兼容类型 + +在序列化和反序列化时,并不要求类型一致,只要类型成员名称一致,且对应名称的基础类型一致,即可进行转换。 + +例如:下列`MyClass2`与`MyClass3`是两个不同类型 + +```csharp showLineNumbers +public class MyClass2 +{ + public int P1 { get; set; } +} + +public class MyClass3 +{ + public int P1 { get; set; } + public string P2 { get; set; } +} +``` + +也可以互相序列化和反序列化。 + +```csharp showLineNumbers +var myClass2 = new MyClass2() +{ + P1 = 10 +}; +var bytes = FastBinaryFormatter.SerializeToBytes(myClass2); + +var newObj = FastBinaryFormatter.Deserialize(bytes); +``` + +反序列化后的`MyClass3` + +```csharp showLineNumbers +{"P1":10,"P2":null} +``` + +但如果是成员名称一致,但基础类型不一致的,则不会成功,且可能会抛出异常。 + +例如: + +```csharp {3,8} showLineNumbers +public class MyClass2 +{ + public int P1 { get; set; } +} + +public class MyClass3 +{ + public string P1 { get; set; } +} +``` + +:::tip 提示 + +兼容类型的使用,可以一定程度的解决一些兼容性问题,尤其是增加、或移除成员时都可以兼容。但是当修改成员类型时,可能会导致序列化数据丢失。 + +::: + +## 五、特性成员 + +默认情况下,确定成员的方式的是**成员名称**。当成员名称较长时(最大255字节),可能会大大增加序列化后的体积。 + +那这时候,就可以使用特性来确定成员。它使用的是一个`byte`值,来确定成员。 + +例如: + +对于下列类,如果不使用特性,序列化体积可达**40字节**。 + +```csharp showLineNumbers +public class MyClass4 +{ + public int MyProperty1 { get; set; } + public int MyProperty2 { get; set; } +} +``` + +使用特性后,体积可以减少到**18字节**。 + +```csharp {1,4,7} showLineNumbers +[FastSerialized(EnableIndex =true)] +public class MyClass4 +{ + [FastMember(1)] + public int MyProperty1 { get; set; } + + [FastMember(2)] + public int MyProperty2 { get; set; } +} +``` + +:::info 信息 + +使用特性来确定成员唯一性时,使用的是byte类型的值,所以它只允许最多有*255*个成员(即属性和字段的总数量)。 + +::: + + +## 六、自定义转换器 + +使用自定义转化器,可以解决**所有类型**的序列化与反序列化,并且可以对特定类型进行优化。 + +例如: + +对于下列类,只有两个int类属性是有效值。 + +```csharp showLineNumbers +public class MyClass5 +{ + public int P1 { get; set; } + public int P2 { get; set; } +} +``` + +所以,我们需要自定义一个转换器。来将这2个`int`值,转换成有效数据。 + +首先,声明一个转换器类,继承`FastBinaryConverter`,或者实现`IFastBinaryConverter`接口。 + +然后实现`Read`和`Write`方法。实现逻辑如下: + +```csharp {3,15} showLineNumbers +public sealed class MyClass5FastBinaryConverter : FastBinaryConverter +{ + protected override MyClass5 Read(ref TByteBlock byteBlock, Type type) + { + //此处不用考虑为null的情况 + //我们只需要把有效信息按写入的顺序,读取即可。 + + var myClass5 = new MyClass5(); + myClass5.P1 = byteBlock.ReadInt32(); + myClass5.P2 = byteBlock.ReadInt32(); + + return myClass5; + } + + protected override void Write(ref TByteBlock byteBlock, in MyClass5 obj) + { + //此处不用考虑为null的情况 + //我们只需要把有效信息写入即可。 + //对于MyClass5类,只有两个属性是有效的。 + + //所以,依次写入属性值即可 + byteBlock.WriteInt32(obj.P1); + byteBlock.WriteInt32(obj.P2); + + } +} +``` + +:::info 信息 + +在转化器中,我们不需要考虑操作对象为`null`的情况。但是得考虑属性值为`null`的情况。 + +::: + +最后附加转换器即可 + +```csharp {1} +[FastConverter(typeof(MyClass5FastBinaryConverter))] +public class MyClass5 +{ + public int P1 { get; set; } + public int P2 { get; set; } +} +``` + +或者直接往`FastBinaryFormatter`中添加转换器。 + +```csharp showLineNumbers +FastBinaryFormatter.AddFastBinaryConverter(typeof(MyClass5),new MyClass5FastBinaryConverter()); +``` + +:::caution 注意 + +使用自定义转化器后,所有的类型兼容问题,都必须自己解决。如示例所示,我们是按顺序写入和读取的,所以一般来说,新增属性是可以的,但是移除属性时,可能得手动解决一些问题。 + +::: + +## 七、包模式序列化 + +`FastBinaryFormatter`支持一种特殊的序列化方式,[包序列化模式](./ipackage.mdx) 。可以解决一些特殊场景下的序列化,其性能更高。 + +并且,默认情况下,已经内置了转换器。 + +只需要对需要转换的对象实现`IPackage`接口(或继承`PackageBase`)即可。 + +例如: + +下列类,实现了`IPackage`接口。在序列化时,会调用`Package`方法,反序列化时,会调用`Unpackage`方法。 + +```csharp {6,12} showLineNumbers +public class MyClass6:PackageBase +{ + public int P1 { get; set; } + public int P2 { get; set; } + + public override void Package(ref TByteBlock byteBlock) + { + byteBlock.WriteInt32(this.P1); + byteBlock.WriteInt32(this.P2); + } + + public override void Unpackage(ref TByteBlock byteBlock) + { + this.P1 = byteBlock.ReadInt32(); + this.P2 = byteBlock.ReadInt32(); + } +} +``` + +当然,在包模式的**源生成**可用时,也可以直接用源生成的方式实现更多细节。 + +```csharp {1} showLineNumbers +[GeneratorPackage] +public partial class MyClass6:PackageBase +{ + public int P1 { get; set; } + public int P2 { get; set; } +} +``` + +
+由源生成的代码 +
+ +```csharp showLineNumbers +/* +此代码由SourceGenerator工具直接生成,非必要请不要修改此处代码 +*/ +#pragma warning disable +using System; +using System.Diagnostics; +using TouchSocket.Core; +using System.Threading.Tasks; + +namespace FastBinaryFormatterConsoleApp +{ + [global::System.CodeDom.Compiler.GeneratedCode("TouchSocket.SourceGenerator", "2.1.0.0")] + partial class MyClass6 + { + public override void Package(ref TByteBlock byteBlock) + { + byteBlock.WriteInt32(P1); + byteBlock.WriteInt32(P2); + } + + public override void Unpackage(ref TByteBlock byteBlock) + { + P1 = byteBlock.ReadInt32(); + P2 = byteBlock.ReadInt32(); + } + } +} +``` + +
+
+ +:::caution 注意 + +当使用包模式序列化时,类型兼容性将跟随包类型一致。一般来说,新增属性是允许的,但是修改或移除属性是不允许的。 + +::: + +## 八、性能测试 + +### 8.1 简单测试 + +**待测试类型** + +```csharp showLineNumbers +[Serializable] +public class MyPackPerson +{ + public int Age { get; set; } + public string Name { get; set; } +} +``` + +**结果** + +以下测试是执行10000次序列化和反序列的结果。 + +![](@site/static/img/docs/fastbinaryformatter-1.png) + +### 8.2 复杂类型测试 + +**待测试类** + +```csharp showLineNumbers + [Serializable] +public class Student +{ + public int P1 { get; set; } + public string P2 { get; set; } + public long P3 { get; set; } + public byte P4 { get; set; } + public DateTime P5 { get; set; } + public double P6 { get; set; } + public byte[] P7 { get; set; } + + public List List1 { get; set; } + public List List2 { get; set; } + public List List3 { get; set; } + + public Dictionary Dic1 { get; set; } + public Dictionary Dic2 { get; set; } + public Dictionary Dic3 { get; set; } + public Dictionary Dic4 { get; set; } +} + +[Serializable] +public class Arg +{ + public Arg(int myProperty) + { + this.MyProperty = myProperty; + } + + public Arg() + { + Person person = new Person(); + person.Name = "张三"; + person.Age = 18; + } + + public int MyProperty { get; set; } +} +[Serializable] +public class Person +{ + public string Name { get; set; } + public int Age { get; set; } +} +``` + +**赋值** + +```csharp showLineNumbers +Student student = new Student(); +student.P1 = 10; +student.P2 = "若汝棋茗"; +student.P3 = 100; +student.P4 = 0; +student.P5 = DateTime.Now; +student.P6 = 10; +student.P7 = new byte[1024 * 64]; + +Random random = new Random(); +random.NextBytes(student.P7); + +student.List1 = new List(); +student.List1.Add(1); +student.List1.Add(2); +student.List1.Add(3); + +student.List2 = new List(); +student.List2.Add("1"); +student.List2.Add("2"); +student.List2.Add("3"); + +student.List3 = new List(); +student.List3.Add(new byte[1024]); +student.List3.Add(new byte[1024]); +student.List3.Add(new byte[1024]); + +student.Dic1 = new Dictionary(); +student.Dic1.Add(1, 1); +student.Dic1.Add(2, 2); +student.Dic1.Add(3, 3); + +student.Dic2 = new Dictionary(); +student.Dic2.Add(1, "1"); +student.Dic2.Add(2, "2"); +student.Dic2.Add(3, "3"); + +student.Dic3 = new Dictionary(); +student.Dic3.Add("1", "1"); +student.Dic3.Add("2", "2"); +student.Dic3.Add("3", "3"); + +student.Dic4 = new Dictionary(); +student.Dic4.Add(1, new Arg(1)); +student.Dic4.Add(2, new Arg(2)); +student.Dic4.Add(3, new Arg(3)); +``` + +**结果** + +Fast的效率比System自带的,快了近7倍,比System.Text.Json快了4倍多,比NewtonsoftJson快了近30倍。 + +![](@site/static/img/docs/fastbinaryformatter-2.png) diff --git a/handbook/versioned_docs/version-3.1/filepool.mdx b/handbook/versioned_docs/version-3.1/filepool.mdx new file mode 100644 index 000000000..734bc37d2 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/filepool.mdx @@ -0,0 +1,77 @@ +--- +id: filepool +title: 文件流池 +--- + +import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +文件在读,或写的时候,一直都是独占状态。这个问题在不同进程中,似乎是合理的,但是如果在相同进程里,就会显得很呆。例如:我们在下载文件的时候,希望能同一时间多个读取同一个文件。且能有一个闭环的管理。那么,使用FilePool,就显得非常必要了。 + +## 二、使用读 +从FilePool.GetReader的静态函数中,获取一个**线程安全**的文件读取访问器,该访问器具有读,和相关的操作属性。在每次读取后,Position会递增。 + +使用完成后,可以随时释放。 + +```csharp showLineNumbers +int Length = 0; +byte[] buffer = new byte[1024 * 1024]; + +using (var reader = FilePool.GetReader(path)) +{ + while (true) + { + int r = reader.Read(buffer, 0, buffer.Length); + if (r == 0) + { + break; + } + Length += r; + } +} + +Console.WriteLine(Length); +``` + +## 三、使用写 +从FilePool.GetWriter的静态函数中,获取一个文件写入访问器线程安全,该访问器,具有写,和相关的操作属性。在每次写入后,Position会递增。 + +使用完成后,可以随时释放。 + +注意默认调用**Dispose**后,文件会根据创建类型是否为**单一访问**而决定是否立即释放。 +```csharp showLineNumbers +byte[] buffer = new byte[1024]; + +using (var writer = FilePool.GetWriter(path,true)) +{ + writer.Position = num * package; + int surLen = package; + while (surLen > 0) + { + int r = Math.Min(surLen, buffer.Length); + writer.Write(buffer, 0, r); + surLen -= r; + } +} +Console.WriteLine("完成"); +``` + +## 四、手动释放文件资源 +当某个文件没有及时释放,或者由于不可知异常而没有释放时,可以调用FilePool.TryReleaseFile减少引用,并尝试释放资源。 + +减少引用的意思是,当某个文件,被创建多个访问器时,会递增其引用数,当引用数不为0时,是不会释放的。所以当调用FilePool.TryReleaseFile时,首先会减少引用,然后才会判断是否可以释放。 + +当需要强制释放某个文件时,可以采取下列措施。 +```csharp showLineNumbers +while (FilePool.TryReleaseFile(fileName, 0).ResultCode!= ResultCode.Success) +{ + +} +``` + diff --git a/handbook/docs/filesynchronization.mdx b/handbook/versioned_docs/version-3.1/filesynchronization.mdx similarity index 100% rename from handbook/docs/filesynchronization.mdx rename to handbook/versioned_docs/version-3.1/filesynchronization.mdx diff --git a/handbook/docs/fpsgame.mdx b/handbook/versioned_docs/version-3.1/fpsgame.mdx similarity index 100% rename from handbook/docs/fpsgame.mdx rename to handbook/versioned_docs/version-3.1/fpsgame.mdx diff --git a/handbook/versioned_docs/version-3.1/generateproxysourcegeneratordemo.mdx b/handbook/versioned_docs/version-3.1/generateproxysourcegeneratordemo.mdx new file mode 100644 index 000000000..4a0ad47e9 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/generateproxysourcegeneratordemo.mdx @@ -0,0 +1,147 @@ +--- +id: generateproxysourcegeneratordemo +title: 源生成代理推荐写法 +--- + +import { TouchSocketRpcDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、声明接口 + +在`TouchSocket`中,关于`Rpc`,我们有更为推荐的写法。详细步骤如下: + +(1)新建类库项目,命名为`RpcClassLibrary`。然后在该程序集中,定义服务接口,和接口参数实体类。 + +```csharp showLineNumbers +/// +/// 定义服务接口。 +/// +[GeneratorRpcProxy] +public interface IUserServer:ISingletonRpcServer +{ + [DmtpRpc] + LoginResponse Login(LoginRequest request); +} +``` + +```csharp showLineNumbers +public class LoginRequest:RequestBase +{ + public string Account { get; set; } + public string Password { get; set; } +} + +public class LoginResponse : ResponseBase +{ +} + +//下面两个是请求和响应的基类,可以根据业务增加其他字段 +public class RequestBase +{ +} + +public class ResponseBase +{ + public Result Result { get; set; } +} +``` + +## 二、实现接口 + +新建类库项目,命名`RpcImplementationClassLibrary`,引用`RpcClassLibrary`项目,然后用于实现接口。 + +```csharp showLineNumbers +public class UserServer : IUserServer +{ + public LoginResponse Login(LoginRequest request) + { + //返回假逻辑 + return new LoginResponse() { Result=Result.Success}; + } +} +``` + +然后新建类文件,命名为`AssemblyInfo.cs`,用于存放程序集相关配置。此处的目的是设置`Rpc`服务自动注册。 + +所以类文件中,需要添加如下代码: + +```csharp showLineNumbers +using TouchSocket.Rpc; + +[assembly: GeneratorRpcServerRegister] +``` + +## 三、服务注册、启动 + +新建控制台项目,作为服务器,需要同时引用`RpcImplementationClassLibrary`和`RpcClassLibrary`。 + +如果作为服务器,需要按接口注册服务 + +```csharp showLineNumbers +var service = new TcpDmtpService(); +var config = new TouchSocketConfig()//配置 + .SetListenIPHosts(new IPHost[] { new IPHost(7789) }) + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + a.AddRpcStore(store => + { + ////此处使用限定名称,因为源代码生成时,也会生成TouchSocket.Rpc.Generators.IUserServer的接口 + //store.RegisterServer(); + + //此处使用的是源生成注册,具体可看文档》Rpc》注册服务 + store.RegisterAllFromRpcImplementationClassLibrary(); + }); + }) + .ConfigurePlugins(a => + { + a.UseDmtpRpc(); + }) + .SetDmtpOption(new DmtpOption() + { + VerifyToken = "Dmtp" + });//设定连接口令,作用类似账号密码 + +await service.SetupAsync(config); +await service.StartAsync(); + +service.Logger.Info($"{service.GetType().Name}已启动"); +``` + +## 四、创建客户端 + +作为客户端仅引用`RpcClassLibrary`即可。直接调用即可。 + +```csharp {15} +var client = new TcpDmtpClient(); +await client.SetupAsync(new TouchSocketConfig() + .SetRemoteIPHost("127.0.0.1:7789") + .ConfigurePlugins(a => + { + a.UseDmtpRpc(); + }) + .SetDmtpOption(new DmtpOption() + { + VerifyToken = "Dmtp" + })); +await client.ConnectAsync(); + +//Login即为在RpcClassLibrary中自动生成的项目 +var response = client.GetDmtpRpcActor().Login(new RpcClassLibrary.Models.LoginRequest() { Account = "Account", Password = "Account" }); +Console.WriteLine(response.Result); +``` + +## 五、结束 + +推荐写法的演示,是为实际项目编写提供参考。经过框架的搭建,后续的开发将变得简单。 + +例如:当需要添加一个功能时,只需要在`RpcClassLibrary`中添加一个接口服务,或者在现有服务中添加函数,然后在`RpcImplementationClassLibrary`实现接口即可。其余注册工作将自动完成。 + +同时,当你需要调用Rpc时,只需要把`RpcClassLibrary`作为项目引用,或者dll引用。因为`RpcClassLibrary`中仅包含服务接口、参数实例和源生成的调用方法,所以不会泄漏敏感数据(前提是您在编写代码时,并无在`RpcClassLibrary`中包含敏感信息)。 + +[推荐写法示例](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Dmtp) + diff --git a/handbook/versioned_docs/version-3.1/generichost.mdx b/handbook/versioned_docs/version-3.1/generichost.mdx new file mode 100644 index 000000000..43457c069 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/generichost.mdx @@ -0,0 +1,320 @@ +--- +id: generichost +title: 通用主机 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import Definition from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +`Hosting`通用主机,是在创建一个[HostBuilder](https://learn.microsoft.com/zh-cn/dotnet/core/extensions/generic-host) 之后,可以通过Add的服务的形式创建`TouchSocket`的一些组件。如`TcpService`、`TcpClient`、`NamedPipeService`、`NamedPipeclient`等。 + + + +## 二、安装Nuget包 + +在安装`Nuget`之前,最好先确认目标项目是一个主机项目。例如:[辅助角色服务模板](https://learn.microsoft.com/zh-cn/dotnet/core/extensions/generic-host)、 [AspNetCore](https://learn.microsoft.com/zh-cn/aspnet/core)等。如果是其他项目,请自行解决依赖。 + +使用`Nuget`安装`TouchSocket.Hosting`、`TouchSocketPro.Hosting`、`TouchSocket.AspNetCore`、`TouchSocketPro.AspNetCore`其中的**任意一个**。 + +:::info 备注 + +`TouchSocket.Hosting`、`TouchSocketPro.Hosting`、`TouchSocket.AspNetCore`、`TouchSocketPro.AspNetCore`都可以安装使用。主要区别就是:Hosting的只包含基础扩展。AspNetCore的会对Web项目有更多扩展。 + +::: + +:::tip 建议 + +一般建议`Web`项目使用`TouchSocket.AspNetCore`、`TouchSocketPro.AspNetCore`。辅助角色项目、`MAUI`项目等使用`TouchSocket.Hosting`、`TouchSocketPro.Hosting`。 + +::: + +## 三、添加服务器类 + +一些常用的服务器组件,都已经被封装了,可以直接使用。例如: + +- TcpService +- UdpService +- TcpDmtpService(由TouchSocket.AspNetCore提供) + +:::info 备注 + +没被封装的组件只是没直接提供扩展方法,而不是不能使用。具体操作请看下文。 + +::: + +### 3.1 在Aspnetcore中添加 + +下列示例只配置了监听端口,更多配置请看[TcpService](./tcpservice.mdx)。 + +```csharp {7-12} showLineNumbers +public static void Main(string[] args) +{ + var builder = WebApplication.CreateBuilder(args); + + ... + + #region 添加Tcp服务器 + builder.Services.AddTcpService(config => + { + config.SetListenIPHosts(7789); + }); + #endregion + + var app = builder.Build(); + + ... + + app.Run(); +} +``` + +### 3.2 在辅助角色项目中添加 + +辅助角色项目、MAUI项目等使用`TouchSocket.Hosting`、`TouchSocketPro.Hosting`。 + + + + +```csharp {6-11} showLineNumbers +public static void Main(string[] args) +{ + IHost host = Host.CreateDefaultBuilder(args) + .ConfigureServices(services => + { + #region 添加Tcp服务器 + services.AddTcpService(config => + { + config.SetListenIPHosts(7789); + }); + #endregion + services.AddHostedService(); + }) + .Build(); + + host.Run(); +} +``` + + + + +```csharp {5-10} showLineNumbers +public static void Main(string[] args) +{ + var builder = Host.CreateApplicationBuilder(args); + builder.Services.AddHostedService(); + #region 添加Tcp服务器 + builder.Services.AddTcpService(config => + { + config.SetListenIPHosts(7789); + }); + #endregion + var host = builder.Build(); + host.Run(); +} + +``` + + + + + +### 3.3 添加自定义服务器 + +通过上述的方式添加的服务器,会使用固定的接口作为注册类型。例如:通过默认的`AddTcpService`来添加服务器,则会使用`ITcpService`作为注册类型。 + +所以当想要添加两个服务器时,由于容器问题,第二个就会把第一个覆盖掉。所以就需要重新实现一个`ITcpService`接口,来添加自定义的服务器。 + +例如: + +我们可以先需要声明一个接口`IMyTcpService`,继承自`ITcpService`接口,然后声明自定义类`MyTcpService`,继承`TcpService`,同时实现`IMyTcpService`。 + +```csharp showLineNumbers +class MyTcpService : TcpService, IMyTcpService +{ + +} + +interface IMyTcpService : ITcpService +{ + +} +``` + +然后调用`AddTcpService`泛型注册即可。 + +```csharp showLineNumbers +services.AddTcpService(config => +{ + config.SetListenIPHosts(7790); +}); +``` + +:::info 备注 + +这里可以使用`AddTcpService`泛型注册的原因是`MyTcpService`是`TcpService`的派生类。如果不是,则需要使用`AddServiceHostedService`来注册。 + +::: + +### 3.4 添加其他生命周期的服务 + +默认情况下,框架内提供的所有IService类的注册服务(即:`AddServiceHostedService`),都是单例注册。且生命周期跟随整个Host。当Host启动时,所有注册的服务都会被创建。并且也会调用Start。当Host关闭时,所有注册的服务都会被销毁(当然可以提前注销,但是无法控制Start)。 + +但有的时候,我们需要将一个服务注册为瞬态,然后自己完全管理生命周期。那么我们可以通过下列方式来实现。 + +- AddSingletonSetupConfigObject:单例 +- AddTransientSetupConfigObject:瞬态 +- AddScopedSetupConfigObject:区域 + +```csharp {6-11} showLineNumbers +public static void Main(string[] args) +{ + IHost host = Host.CreateDefaultBuilder(args) + .ConfigureServices(services => + { + //添加瞬态Tcp服务器。 + //瞬态服务器,在host启动时,不会自己start,需要手动调用 + services.AddTransientSetupConfigObject(config => + { + config.SetListenIPHosts(7789); + }); + services.AddHostedService(); + }) + .Build(); + + host.Run(); +} +``` + +:::tip 提示 + +通过这三中方式直接注册的服务,在Host启动时,不会自动调用Start。所以需要自己手动调用。 + +::: + + + + +## 四、添加客户端类 + +一些常用的客户端组件,都已经被封装了,可以直接使用。例如: + +- TcpClient +- TcpDmtpClient(由TouchSocket.AspNetCore提供) + +:::info 备注 + +没被封装的组件只是没直接提供扩展方法,而不是不能使用。具体操作请看下文。 + +::: + +### 4.1 添加TcpClient + +客户端类的添加,和上面服务类的添加,基本一致。只不过默认就区分了瞬态、单例等注册方式。 + +```csharp {6-9} showLineNumbers +public static void Main(string[] args) +{ + IHost host = Host.CreateDefaultBuilder(args) + .ConfigureServices(services => + { + services.AddTransientTcpClient(config => + { + config.SetRemoteIPHost("127.0.0.1:7789"); + }); + + services.AddHostedService(); + }) + .Build(); + + host.Run(); +} +``` + +:::tip 提示 + +客户端类的服务,在Host启动时,不会自动调用Connect。所以需要自己手动调用。 + +::: + + +## 五、添加没有预设的服务 + +实际上所有的组件,都可以通过下列方法注册: + +- AddSingletonSetupConfigObject:单例 +- AddTransientSetupConfigObject:瞬态 +- AddScopedSetupConfigObject:区域 + +但是对于服务器类的,可以根据其继承类,来选择更贴切的服务器注册。这样就不用自己管理生命周期。 + +例如:对于TcpDmtpService。他是继承自TcpService,所以也可以直接使用AddTcpService**泛型**来注册。 + +```csharp {6-9} showLineNumbers +public static void Main(string[] args) +{ + IHost host = Host.CreateDefaultBuilder(args) + .ConfigureServices(services => + { + services.AddTcpService(config => + { + config.SetListenIPHosts(8848); + }); + }) + .Build(); + + host.Run(); +} +``` + +## 六、选项模式配置服务器 + +通过选项模式,可以方便的对服务器进行配置。 + + + + +## 七、实战 + + + + + + + +## 八、部署 + +### 8.1 部署到Windows + + + + + +### 8.2 部署到Linux + + + + +### 8.3 部署到Docker + + + + + +[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples_host/Examples.Host) \ No newline at end of file diff --git a/handbook/versioned_docs/version-3.1/httpclient.mdx b/handbook/versioned_docs/version-3.1/httpclient.mdx new file mode 100644 index 000000000..f6776d72c --- /dev/null +++ b/handbook/versioned_docs/version-3.1/httpclient.mdx @@ -0,0 +1,207 @@ +--- +id: httpclient +title: 创建HttpClient +--- + +import CardLink from "@site/src/components/CardLink.js"; +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import { TouchSocketHttpDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +HttpClient是Http客户端类。主要用于请求Http报文。与.net的HttpClient不同的是,此处的HttpClient,是基于单个连接的客户端,且有明显的连接、断开连接等动作。 + + +## 二、可配置项 + +继承TcpClient + + + +## 三、支持插件接口 + +支持**ITcpPlugin**接口。 + + + +## 四、创建HttpClient + +### 4.1 创建常规HttpClient +```csharp showLineNumbers +var client = new HttpClient(); +await client.ConnectAsync("http://localhost:7219");//先做连接 +``` + + + +### 4.2 创建Ssl的HttpClient(Https) + +```csharp showLineNumbers +var client = new HttpClient(); +await client.ConnectAsync("https://localhost:7219");//先做连接 +``` + +如果是自定义证书,则需要手动加载证书 + +```csharp showLineNumbers +var client = new HttpClient(); + +var config = new TouchSocketConfig(); +config.SetRemoteIPHost("https://localhost:7219") + .SetClientSslOption(new ClientSslOption() + { + ClientCertificates = new X509CertificateCollection() { new X509Certificate2("Socket.pfx", "Socket") }, + SslProtocols = SslProtocols.Tls12, + TargetHost = "localhost", + CertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => { return true; } + }); ; + +//配置config +await client.SetupAsync(config); +await client.ConnectAsync();//先做连接 +``` + + + +:::info 备注 + +实际上直接使用`ConnectAsync("https://localhost:7219")`的方式是合并了`SetupAsync`与`ConnectAsync`。所以当需要额外配置时,应当遵循所有配置都在`TouchSocketConfig`的约定。这样代码也比较简单明了。 + +::: + +## 五、发送请求 + +因为我的HttpClient是面向连接的,所以,在请求时,请求url只需要填写host之后的部分即可(即路由部分)。 + +### 5.1 发起Get请求到字符串 + +```csharp showLineNumbers +//直接发起一个Get请求,然后返回Body字符串。 +var body = await client.GetStringAsync("/WeatherForecast"); +``` + +### 5.2 发起Get请求到字节数组 + +```csharp showLineNumbers +//直接发起一个Get请求,然后返回Body数组。 +var bodyBytes = await client.GetByteArrayAsync("/WeatherForecast"); +``` + +### 5.3 发起Get请求到流数据 + +```csharp showLineNumbers +//直接发起一个Get请求文件,然后写入到流中。 +using (var stream=File.Create("1.txt")) +{ + await client.GetFileAsync("/WeatherForecast",stream); +} +``` + + + +### 5.4 构建自定义请求 + +```csharp showLineNumbers +//创建一个请求 +var request = new HttpRequest(); +request.InitHeaders() + .SetUrl("/WeatherForecast") + .SetHost(client.RemoteIPHost.Host) + .AsGet(); + + +using (var responseResult = await client.RequestAsync(request, 1000 * 10)) +{ + var response = responseResult.Response; + Console.WriteLine(await response.GetBodyAsync());//将接收的数据,一次性转为utf8编码的字符串 +} +``` + +### 5.5 构建自定义请求,持续读取大数据 + +```csharp showLineNumbers +//创建一个请求 +var request = new HttpRequest(); +request.InitHeaders() + .SetUrl("/WeatherForecast") + .SetHost(client.RemoteIPHost.Host) + .AsGet(); + + +using (var responseResult = await client.RequestAsync(request, 1000 * 10)) +{ + var response = responseResult.Response; + + while (true) + { + using (var blockResult = await response.ReadAsync()) + { + //每次读到的数据 + var memory = blockResult.Memory; + Console.WriteLine(memory.Length); + + if (blockResult.IsCompleted) + { + //数据读完成 + break; + } + } + } +} +``` + +### 5.6 构建自定义Post请求,持续写入流 + +```csharp {5} showLineNumbers +using (var stream=File.OpenRead("TouchSocket.dll")) +{ + //创建一个请求 + var request = new HttpRequest(); + request.SetContent(new StreamHttpContent(stream));//设置流内容 + request.InitHeaders() + .SetUrl("/bigwrite") + .SetHost(client.RemoteIPHost.Host) + .AsPost(); + + using (var responseResult = await client.RequestAsync(request, 1000 * 10)) + { + var response = responseResult.Response; + } + Console.WriteLine("完成"); +} +``` + + + +:::tip 提示 + +在构建自定义请求时,如果使用`AsGet`、`AsPost`等方法可以直接设置当前请求为`Get`、`Post`等。如果没有扩展方法可以使用时,可以使用`AsMethod`来实现,例如:`AsMethod("GET")`。 + +::: + +## 六、传输文件 + +### 6.1 下载文件 + +```csharp {4} showLineNumbers +//直接发起一个Get请求,然后写入到流中。 +using (var stream=File.Create("1.txt")) +{ + await client.GetFileAsync("/WeatherForecast",stream); +} +``` + +### 6.2 上传文件 + +```csharp showLineNumbers +await client.UploadFileAsync("/upfile", new FileInfo("filePath")); +``` + +## 七、本文示例Demo + + \ No newline at end of file diff --git a/handbook/versioned_docs/version-3.1/httpservice.mdx b/handbook/versioned_docs/version-3.1/httpservice.mdx new file mode 100644 index 000000000..6d488c8d6 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/httpservice.mdx @@ -0,0 +1,525 @@ +--- +id: httpservice +title: 创建HttpService +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import CardLink from "@site/src/components/CardLink.js"; +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import { TouchSocketHttpDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +**HttpService**是能够提供Http相关服务的基础类型。 + + + +## 二、产品特点 + +- 支持HTTPS。 +- **多种数据接收模式** +- **多地址监听**(可以一次性监听多个IP及端口) +- **高性能** +- **支持跨域**(CORS) +- **支持AOT,且体积比AspNetCore小50%** + + + + +## 三、产品应用场景 + +- HTTP基础使用场景:可跨平台、跨语言使用。 + + +## 四、服务器架构 + +服务器在收到新客户端连接时,会创建一个`HttpSessionClient`的派生类实例,与远程`HttpClient`对应,后续的数据通信均由此实例负责。 + +```mermaid +flowchart TD; + Service-->HttpSessionClient-1; + Service-->HttpSessionClient-2; + Service-->HttpSessionClient-3; + HttpSessionClient-1-->HttpClient-1; + HttpSessionClient-2-->HttpClient-2; + HttpSessionClient-3-->HttpClient-3; + +``` + + + +## 五、支持插件接口 + +声明自定义实例类,然后实现`IHttpPlugin`接口,即可实现下列事务的触发。或者继承自`PluginBase`类,重写相应方法即可。 + +| 插件方法| 功能 | +| --- | --- | +| IHttpPlugin | 当收到所有Http请求时。| + + +## 六、创建HttpService + +`HttpService`的创建,基本和`TcpService`一致,也可以通过继承实现,下列仅演示最简单实现。 + +`HttpService`的相关事务,会通过**插件**触发。 + +```csharp showLineNumbers +var service = new HttpService(); +await service.SetupAsync(new TouchSocketConfig()//加载配置 + .SetListenIPHosts(7789) + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .ConfigurePlugins(a => + { + //此处添加插件逻辑,插件可以使用PluginBase类实现IHttpPlugin接口完成。 + //这里使用委托直接完成 + a.Add(typeof(IHttpPlugin), async (HttpContextEventArgs e) => + { + var request = e.Context.Request;//http请求体 + var response = e.Context.Response;//http响应体 + + //判断url + if (request.UrlEquals("/say")) + { + await response + .SetStatus(200, "success") + .SetContent("hello") + .AnswerAsync(); + return; + } + + //如果上述url没有处理,则转到下一插件处理 + await e.InvokeNext(); + }); + + //default插件应该最后添加,其作用是 + //1、为找不到的路由返回404 + //2、处理header为Option的探视跨域请求。 + a.UseDefaultHttpServicePlugin(); + })); + +await service.StartAsync(); +``` + +:::tip 提示 + +`DefaultHttpServicePlugin`插件最好添加在插件中,如果没有添加的话,最好自己做好缺省路由配置。 + +::: + + + +## 七、获取请求 + +每个`HttpClient`连接建立时,系统除了会创建一个`HttpSessionClient`与之对应之外,还会创建一个`HttpContext`实例与之对应。 +所以,对于一个连接而言,后续的所有`Http`交互,都会反复投递同一个`HttpContext`实例。 + +所以我们可以通过其`Request`与`Response`属性获取到本次Http的请求和即将响应的响应体。 + +```csharp showLineNumbers +var request = e.Context.Request;//http请求体 +var response = e.Context.Response;//http响应体 +``` + + + +### 7.1 获取Query参数 + +```csharp showLineNumbers +string value = e.Context.Request.Query["key"]; +``` + + + +### 7.2 获取Header参数 + +```csharp showLineNumbers +string value = e.Context.Request.Headers["key"]; +``` + +亦或者 + +```csharp showLineNumbers +string value = e.Context.Request.Headers[HttpHeaders.Cookie]; +``` + + + +### 7.3 获取Form参数 + +```csharp showLineNumbers +var multifileCollection =await e.Context.Request.GetFormCollectionAsync(); +foreach (var item in multifileCollection) +{ + Console.WriteLine($"key={item.Key},value={item.Value}"); +} +``` + + + + +### 7.4 获取字符串Body内容 + +```csharp showLineNumbers +string bodyString = await e.Context.Request.GetBodyAsync(); +``` + +### 7.5 获取小体量字节Body内容 + +```csharp showLineNumbers +ReadOnlyMemory content = await e.Context.Request.GetContentAsync(); +``` + + + + +### 7.6 持续读取Body内容 + +当数据太大时,可持续读取 + +```csharp showLineNumbers +while (true) +{ + var buffer = new byte[1024 * 64]; + + using (var blockResult = await e.Context.Request.ReadAsync()) + { + //这里可以一直处理读到的数据。 + blockResult.Memory.CopyTo(buffer); + + if (blockResult.IsCompleted) + { + //结束 + break; + } + } +} +``` + + + +### 7.7 获取Body持续写入Stream中 + +当数据太大时,可持续读取数据直接到流容器中。 + +```csharp showLineNumbers +using (var stream = new MemoryStream()) +{ + // + await e.Context.Request.ReadCopyToAsync(stream); +} +``` + + + + +### 7.8 获取Body小文件 + +当Body内容为小文件集合时,可以使用该功能。 + +```csharp {11,13-19} showLineNumbers +if (e.Context.Request.ContentLength > 1024 * 1024 * 100)//全部数据体超过100Mb则直接拒绝接收。 +{ + await e.Context.Response + .SetStatus(403, "数据过大") + .AnswerAsync(); + return; +} + +//此操作会先接收全部数据,然后再分割数据。 +//所以上传文件不宜过大,不然会内存溢出。 +var multifileCollection =await e.Context.Request.GetFormCollectionAsync(); + +foreach (var file in multifileCollection.Files) +{ + var stringBuilder = new StringBuilder(); + stringBuilder.Append($"文件名={file.FileName}\t"); + stringBuilder.Append($"数据长度={file.Length}"); + client.Logger.Info(stringBuilder.ToString()); +} + +await e.Context.Response + .SetStatusWithSuccess() + .FromText("Ok") + .AnswerAsync(); +``` + + + +## 八、响应请求 + +当收到`Http`请求,处理完成业务后,即可使用`e.Context.Response`直接参与本次响应。 + +### 8.1 设置响应状态 + +```csharp showLineNumbers +e.Context.Response.SetStatus(200,"success"); +``` + +### 8.2 设置响应Header + +```csharp showLineNumbers +e.Context.Response.AddHeader("key","value"); +``` + +或者 + +```csharp showLineNumbers +e.Context.Response.AddHeader(HttpHeaders.Origin, "*"); +``` + +### 8.3 设置响应内容 + +```csharp showLineNumbers +e.Context.Response.SetContent("hello"); +``` + +或者直接返回`Json`、`Xml`、`Text`等内容。使用此快捷方式,会同时添加对应的`ContentType`Header。 + +```csharp showLineNumbers +e.Context.Response.FromJson("{}"); +``` + + + +### 8.4 开始响应内容 + +当通过上述步骤,完成了响应体的构建后,即可使用`AnswerAsync`直接进行响应。 + +例如:响应一个`hello`文本内容,代码大致如下 + +```csharp showLineNumbers +await e.Context.Response + .SetStatus(200, "success") + .AddHeader("key", "value")//如需要 + .FromText("hello") + .AnswerAsync(); +``` + + + +### 8.5 插件响应Get请求 + +```csharp showLineNumbers +public class MyHttpPlug1 : PluginBase, IHttpPlugin +{ + public async Task OnHttpRequest(IHttpSessionClient client, HttpContextEventArgs e) + { + var request = e.Context.Request;//http请求体 + var response = e.Context.Response;//http响应体 + + if (request.IsGet()&&request.UrlEquals("/success")) + { + //直接响应文字 + await response + .SetStatus(200, "success") + .FromText("Success") + .AnswerAsync();//直接回应 + Console.WriteLine("处理/success"); + return; + } + + //无法处理,调用下一个插件 + await e.InvokeNext(); + } +} +``` + +### 8.6 响应文件请求 + +```csharp showLineNumbers +public class MyHttpPlug2 : PluginBase, IHttpPlugin +{ + public async Task OnHttpRequest(IHttpSessionClient client, HttpContextEventArgs e) + { + var request = e.Context.Request;//http请求体 + var response = e.Context.Response;//http响应体 + if (request.IsGet() && request.UrlEquals("/file")) + { + try + { + //直接回应文件。 + + var fileInfo = new FileInfo(@"D:\System\Windows.iso"); + var fileName = fileInfo.Name;//可以重新制定文件名称,届时,header中会添加Content-Disposition内容 + var maxSpeed = 1024 * 1024;//最大传输速度 + var bufferLength = 1024 * 64;//一般该值越大,效率越高,但同时内存占用也更大 + var autoGzip = true;//自动判断是否应用gzip压缩。 + + await response + .SetStatusWithSuccess()//必须要有状态 + .FromFileAsync(fileInfo, e.Context.Request, fileName, maxSpeed, bufferLength, autoGzip); + + //或者直接使用HttpContext + //await e.Context.FromFileAsync(fileInfo, fileName, maxSpeed, bufferLength, autoGzip); + } + catch (Exception ex) + { + await response.SetStatus(403, "error") + .FromText(ex.Message) + .AnswerAsync(); + } + + return; + } + await e.InvokeNext(); + } +} +``` + +:::caution 注意 + +当响应的文件,希望浏览器直接显示时(例如:html,js,css),不应该指定文件名,不然浏览器会调用下载保存操作,而非直接显示。 + +::: + +:::tip 提示 + +在响应文件时,传入请求的`request`,主要是当请求包含断点续传时,能成功续传。所以,应当应可能的满足该功能。 + +::: + +:::tip 提示 + +该操作支持大型文件,也支持断点续传、支持迅雷加速等。 + +::: + + + + + + +### 8.7 响应页面请求 + +```csharp showLineNumbers +public class MyHttpPlug3 : PluginBase, IHttpPlugin +{ + public async Task OnHttpRequest(IHttpSessionClient client, HttpContextEventArgs e) + { + var request = e.Context.Request;//http请求体 + var response = e.Context.Response;//http响应体 + if (request.IsGet() && request.UrlEquals("/html")) + { + //构建html + var sb = new StringBuilder(); + sb.Append(""); + sb.Append(""); + sb.Append(""); + sb.Append(" "); + sb.Append(" TouchSocket绚丽展示"); + sb.Append(" "); + sb.Append(""); + sb.Append(""); + sb.Append("

TouchSocket

"); + sb.Append(""); + sb.Append(""); + + //回应html + await response + .SetStatusWithSuccess()//必须要有状态 + .SetContentTypeByExtension(".html") + .SetContent(sb.ToString()) + .AnswerAsync(); + return; + } + + await e.InvokeNext(); + } +} +``` + + + +## 九、进阶响应操作 + +### 9.1 响应有长度大数据 + +当响应的数据,在响应时,已知数据长度的话,可以使用此方法。 + +```csharp showLineNumbers +var request = e.Context.Request;//http请求体 +var response = e.Context.Response;//http响应体 + +//先设置需要响应的地方 +response.SetStatus(200, "success"); +//然后设置数据总长度 +response.ContentLength = 1024 * 1024; + +for (int i = 0; i < 1024; i++) +{ + //将数据持续写入 + await response.WriteAsync(new byte[1024]); +} +``` + +### 9.2 响应不知长度数据(Chunk模式) + +当响应的数据,在响应时,不知数据长度的话,可以使用此方法。 + +```csharp showLineNumbers +var request = e.Context.Request;//http请求体 +var response = e.Context.Response;//http响应体 + +//先设置需要响应的地方 +response.SetStatus(200, "success"); +//设置使用Chunk模式 +response.IsChunk = true; + +for (int i = 0; i < 1024; i++) +{ + //将数据持续写入 + await response.WriteAsync(new byte[1024]); +} + +//在正式数据传输完成后,调用此方法,客户端才知道数据结束了 +await response.CompleteChunkAsync(); +``` + + + + +## 九、创建加密Ssl的HttpsService + +Https服务器,和http服务器几乎一样,只不过增加了一个Ssl的配置。具体的Ssl配置,请参考:[TcpService Ssl](./tcpservice.mdx)。 + +```csharp showLineNumbers +.SetServiceSslOption(new ServiceSslOption() +{ + Certificate = new X509Certificate2("Socket.pfx", "Socket"), + SslProtocols = SslProtocols.Tls12 +}) +``` + +## 十、本文示例Demo + + + \ No newline at end of file diff --git a/handbook/versioned_docs/version-3.1/httpstaticpageplugin.mdx b/handbook/versioned_docs/version-3.1/httpstaticpageplugin.mdx new file mode 100644 index 000000000..57db79c99 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/httpstaticpageplugin.mdx @@ -0,0 +1,118 @@ +--- +id: httpstaticpageplugin +title: 静态页面插件 +--- + +import { TouchSocketHttpDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +静态页面功能是指`Web`服务器通过`HTTP`(超文本传输协议)或`HTTPS`(安全的超文本传输协议)向客户端(通常是浏览器)提供预定义的内容。这些内容通常包括HTML文件、CSS样式表、JavaScript脚本以及图片等多媒体资源。与动态页面不同,静态页面在服务器端不会进行任何处理或计算;它们是以文件形式存储在服务器上的,并且当用户请求时,服务器直接将这些文件发送给客户端。 + +## 二、静态页面的特点 + +1. **加载速度快**:由于服务器不需要执行任何脚本来生成页面内容,因此响应速度通常更快。 +2. **易于维护**:对于内容不经常变化的网站,使用静态页面可以减少维护成本,因为无需担心后端逻辑的更新和数据库管理等问题。 +3. **成本效益**:对于访问量较小的站点,使用静态页面可以节省服务器资源和带宽,降低运营成本。 + +## 三、静态页面的使用场景 + +- **个人博客**:如果博主主要发布文字内容,且更新频率不高,那么采用静态页面构建个人博客是一个不错的选择。 +- **企业介绍**:对于那些只需要展示公司信息、联系方式等基本内容的企业网站来说,静态页面能够满足需求同时保持简洁高效。 +- **项目展示**:艺术家、设计师等可以通过静态页面来创建个人作品集,以直观地向潜在客户展示自己的能力和风格。 + +总之,静态页面因其简单、快速和安全的特性,在特定的应用场景下具有明显优势。然而,对于需要频繁更新内容或实现复杂交互功能的网站,则可能更适合选择动态页面技术。 + +## 四、使用 + +### 4.1 常规使用 + +在创建`HttpService`实例后,只需要使用`UseHttpStaticPage`插件,然后指定根文件夹路径即可。 + +```csharp showLineNumbers +var service = new HttpService(); + +var config = new TouchSocketConfig(); +config.SetListenIPHosts(new IPHost[] { new IPHost(7789) }) + .ConfigurePlugins(a => + { + a.UseHttpStaticPage()//添加静态页面文件夹 + .AddFolder("../../../../../api"); + + }); + +await service.SetupAsync(config); + +await service.StartAsync(); + +Console.WriteLine("Http服务器已启动"); +``` + +:::tip 提示 + +`UseHttpStaticPage`插件可以多次添加,也可以添加多个文件夹。 + +::: + +### 4.2 请求资源定向 + +在使用`UseHttpStaticPage`插件时,可以指定请求资源定向。例如,当请求的`URL`为`/api`时,将重定向到`/api/index.html`。 + +```csharp {4-7} showLineNumbers +a.UseHttpStaticPage() +.SetNavigateAction(request => +{ + if (request.RelativeURL.EndsWith("/")) + { + return $"{request.RelativeURL}/index.html"; + } + //此处可以设置重定向 + return request.RelativeURL; +}) +.AddFolder("api/"); +``` + +:::info 信息 + +默认情况下,会将`/`、`/index`都定向到`/index.html`。 + +::: + +### 4.3 响应配置 + +可以通过配置响应头,例如添加自定义头。 + +```csharp {2-5} showLineNumbers +a.UseHttpStaticPage() +.SetResponseAction(response => +{ + //可以设置响应头 +}) +.AddFolder("api/"); +``` + +### 4.4 配置ContentType + +默认情况下,会将常见的文件后缀名映射到对应的`ContentType`。例如,`.html`文件会被映射为`text/html`。详情请见[ContentTypeMapper](https://gitee.com/RRQM_Home/TouchSocket/blob/master/src/TouchSocket.Http/StaticPage/FileExtensionContentTypeProvider.cs)。 + +但是,也可以通过配置`ContentTypeMapper`来覆盖默认的映射关系。 + +```csharp {2-5} showLineNumbers +a.UseHttpStaticPage() +.SetContentTypeProvider(mapper => +{ + mapper.Add(".txt", "text/plain"); +}) +.AddFolder("api/"); +``` + +:::info 信息 + +在一些浏览器中,可能会出现编码错误的问题,可以通过设置`ContentTypeMapper`来修正。例如,将`.txt`文件映射为`text/plain; charset=UTF-8`,这样就不会出现乱码了。 + +::: diff --git a/handbook/versioned_docs/version-3.1/ilog.mdx b/handbook/versioned_docs/version-3.1/ilog.mdx new file mode 100644 index 000000000..c63c76ac5 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/ilog.mdx @@ -0,0 +1,384 @@ +--- +id: ilog +title: 日志记录器 +--- + +import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +日志记录(Logging)在软件开发和系统管理中起着至关重要的作用。以下是日志记录的一些主要作用: + +1. **调试和故障排除**:当应用程序出现错误或未按预期工作时,日志文件可以帮助开发者追踪问题的根源。日志记录可以提供详细的运行时信息,包括错误发生的时间点、错误类型以及相关的变量状态等,这些都是解决技术问题的关键信息。 + +2. **监控系统性能**:通过记录系统的操作和响应时间,日志可以帮助管理员监控系统的健康状况和性能水平。这对于检测性能瓶颈、资源消耗情况以及预测系统负载至关重要。 + +3. **安全审计**:日志记录有助于追踪用户的活动和系统事件,这在发生安全事件时非常重要。例如,它可以用来识别未经授权的访问尝试或数据泄露等问题。 + +4. **合规性**:许多行业都有严格的法律法规要求保留操作日志以证明遵守了特定的标准或法规。在这种情况下,日志提供了必要的证据来满足审计需求。 + +5. **行为分析**:对于网站和应用来说,用户活动的日志记录可以用于分析用户行为模式,从而改进产品设计和服务质量。 + +6. **历史记录**:长期保存的日志可以作为历史数据,帮助理解系统的演变过程,以及随时间变化的趋势。 + +7. **自动化操作**:基于日志中的信息,可以触发自动化的响应动作,比如警报通知、备份操作或者其他维护任务。 + +8. **优化用户体验**:通过分析日志数据,可以发现用户体验中的不足之处,并据此进行改进,提升用户满意度。 + +综上所述,良好的日志记录实践是确保软件系统稳定、安全且高效运行的基础之一。 + + +## 二、日志输出等级 + +日志输出等级(LogLevel)是用来分类和过滤日志信息的一种机制,它允许开发者和运维人员根据不同的情况和需求来决定哪些信息应该被记录下来。 + +框架提供了以下日志等级: + +- Trace: 用于记录详细的执行步骤,通常在开发阶段使用。 +- Debug: 用于记录调试信息,一般不在生产环境中启用。 +- Info: 记录普通的信息消息,用以跟踪应用程序的一般流程。 +- Warning: 记录警告信息,表示可能出现的问题,但不影响应用程序继续运行。 +- Error: 记录错误信息,表明发生了错误,可能需要采取措施。 +- Critical: 记录关键性错误或异常,通常意味着不可恢复的错误。 +- None: 表示不记录任何日志。 + +### 2.1 日志等级工作机制 + +框架默认使用日志等级为Debug。意为示默认情况下,框架只会输出Debug及以上级别的日志,例如Info、Warning直到Critical。 + +如果你输出Trace类日志,则日志将被忽略,不会输出任何内容。 + + +## 三、日志记录器 + +框架简单实现了一个日志接口ILog,以满足日常开发需求。目前拥有: + +- 控制台日志记录器(ConsoleLogger) +- 文件日志记录器(FileLogger) +- 日志组记录器(LoggerGroup) +- 简易日志记录器(EasyLogger)。 + +使用者也可以自定义日志记录器。只需要实现ILog接口即可。 + +## 四、控制台日志记录器 + +控制台日志记录器,是将日志信息输出到控制台。因为直接输出的控制台在当前进程中只会有一个,所以不可以多个创建控制台日志,只能使用其默认实例。 + +```csharp {1} showLineNumbers +var logger = ConsoleLogger.Default; +logger.Info("Message"); +logger.Warning("Warning"); +logger.Error("Error"); +``` + +### 4.1 可配置属性 + +| 属性 | 描述 | 默认值 | +| --- | --- | --- | +| LogLevel | 日志输出等级 | LogLevel.Debug | +| DateTimeFormat | 日志输出时间戳格式 | "yyyy-MM-dd HH:mm:ss ffff" | + +:::caution 注意 + +控制台日志记录器,在一些跨平台环境下,可能无法正常输出(例如:`MAUI`)。请确认是否在目标平台下支持,然后再使用。 + +::: + +## 五、文件日志记录器 + +文件日志记录器负责将日志信息输出到文件中。目前,该记录器具备以下功能: + +- **指定日志文件路径**:可以根据需要设定日志文件的存储位置。 +- **设置日志文件大小**:可以限定单个日志文件的最大容量。 +- **自动滚动日志文件**:当单个日志文件的大小超过设定值时,系统会自动创建一个新的日志文件。 + +### 5.1 可配置属性 + +| 属性 | 描述 | 默认值 | +| --- | --- | --- | +| LogLevel | 日志输出等级 | LogLevel.Debug | +| DateTimeFormat | 日志输出时间戳格式 | "yyyy-MM-dd HH:mm:ss ffff" | +| FileNameFormat | 日志文件格式,会把int类型的序号进行字符化。 | "0000" | +| CreateLogFolder | 日志文件目录回调委托。 | Fun | +| MaxSize | 单个日志文件大小。 | 1024*1024字节 | + +### 5.2 默认配置 + +在默认配置下,文件日志记录器具有如下特点: + +- **最大日志文件大小**:每个日志文件的最大容量默认为1MB。 +- **日志文件滚动机制**:当达到最大容量时,系统会自动创建一个新的日志文件,并按顺序编号。 +- **日志文件存储路径**:日志文件默认存储在可执行文件所在的目录下。 +- **目录结构**:在默认路径下,首先创建一个名为“logs”的目录;在此目录内,再根据当前日期(格式为“[yyyy-MM-dd]”)创建相应的子目录。 +- **日志文件命名**:最终的日志文件按照“0001.log”的格式命名,并根据需要添加序号区分不同的滚动文件。 + +```csharp showLineNumbers +var logger = new FileLogger(); +logger.Info("Message"); +logger.Warning("Warning"); +logger.Error("Error"); +``` + +```csharp showLineNumbers +logs\[2024-09-08]\0000.log +``` + +### 5.3 配置单个文件大小 + +可以直接对FileLogger实例的MaxSize属性进行设置。 + +```csharp {3} showLineNumbers +var logger = new FileLogger() +{ + MaxSize = 1024 * 1024 * 2 +}; +``` + +### 5.4 配置文件路径 + +可以通过设置FileLogger的CreateLogFolder**回调属性**来指定日志文件的存储路径。 + +默认配置的实现如下: + +```csharp {5} showLineNumbers +var logger = new FileLogger() +{ + CreateLogFolder = (logLevel) => + { + return $"logs\\{DateTime.Now:[yyyy-MM-dd]}"; + } +}; +``` + +例如,你可以实现按**日志类型**,来分类输出日志到单独文件。 + +```csharp {5} showLineNumbers +var logger = new FileLogger() +{ + CreateLogFolder = (logLevel) => + { + return $"logs\\{DateTime.Now:[yyyy-MM-dd]}\\{logLevel}"; + } +}; +``` + +### 5.5 使用容器时配置 + +当文件日志被添加到容器中时,可以通过如下方式进行配置。 + +```csharp {3-7} showLineNumbers +.ConfigureContainer(a => +{ + a.AddFileLogger(fileLogger => + { + fileLogger.MaxSize = 1024 * 1024; + fileLogger.LogLevel = LogLevel.Debug; + }); +}) +``` + +或者 + +```csharp {5-9} showLineNumbers +.ConfigureContainer(a => +{ + a.AddLogger(logger => + { + logger.AddFileLogger(fileLogger => + { + fileLogger.MaxSize = 1024 * 1024; + fileLogger.LogLevel = LogLevel.Debug; + }); + }); +}) +``` + + +## 六、简易日志记录器 + +简易日志记录器,是框架提供的一个便捷的字符串日志记录器,它将日志信息输出到回调委托中,方便使用者在需要时,直接输出日志信息。 + +### 6.1 可配置属性 + +| 属性 | 描述 | 默认值 | +| --- | --- | --- | +| LogLevel | 日志输出等级 | LogLevel.Debug | +| DateTimeFormat | 日志输出时间戳格式 | "yyyy-MM-dd HH:mm:ss ffff" | + + +```csharp {1} showLineNumbers +var logger = new EasyLogger(LoggerOutput); +logger.Info("Message"); +logger.Warning("Warning"); +logger.Error("Error"); +``` + +```csharp showLineNumbers +private void LoggerOutput(string loggerString) +{ + Console.WriteLine(loggerString); + + //或者如果是winform程序,可以直接输出到TextBox +} +``` + +## 七、自定义日志记录器 + +框架提供了ILog接口,使用者可以自定义日志记录器。只需要实现ILog接口即可。 + +```csharp {5-8} showLineNumbers +class MyLogger : ILog +{ + public LogLevel LogLevel { get; set; } = LogLevel.Debug; + + public void Log(LogLevel logLevel, object source, string message, Exception exception) + { + //此处可以自由实现逻辑。 + } +} +``` + +:::caution 注意事项 + +自定义实现日志,必须要满足,无论任何情况,日志记录处必须无任何异常抛出,因为日志记录时,可能是在框架异常时执行的。如果此时再抛出异常,可能会导致整个程序崩溃。 + +::: + +### 7.1 集成Log4net + +Log4net是一个非常优秀的日志记录框架,可以非常方便的实现日志记录。接下来,我们以Log4net为例,介绍如何将Log4net集成到框架日志中。 + +首先,需要引用Log4net的NuGet包,版本使用最新即可。 + +``` +Install-Package log4net +``` + +然后新建一个类文件Mylog4netLogger,继承TouchSocket.Core.ILog接口,并实现其方法。 + +```csharp showLineNumbers +internal class Mylog4netLogger : TouchSocket.Core.ILog +{ + private readonly log4net.ILog m_logger; + + public Mylog4netLogger() + { + this.m_logger = log4net.LogManager.GetLogger("Test"); + } + + public LogLevel LogLevel { get; set; } + + public void Log(LogLevel logLevel, object source, string message, Exception exception) + { + //此处就是实际的日志输出 + + switch (logLevel) + { + case LogLevel.Trace: + this.m_logger.Debug(message, exception); + break; + + case LogLevel.Debug: + this.m_logger.Debug(message, exception); + break; + + case LogLevel.Info: + this.m_logger.Info(message, exception); + break; + + case LogLevel.Warning: + this.m_logger.Warn(message, exception); + break; + + case LogLevel.Error: + this.m_logger.Error(message, exception); + break; + + case LogLevel.Critical: + this.m_logger.Error(message, exception); + break; + + case LogLevel.None: + default: + break; + } + } +} +``` + +然后可能还需要一些关于log4net的日志配置,此处省略了。 + +最后就可以直接使用Mylog4netLogger了。 + + +## 八、使用日志记录器 + +框架提供的日志记录器比较简单,可以使用实例直接使用。 + +### 8.1 直接使用 + +例如:控制台日志 + +```csharp showLineNumbers +var logger = ConsoleLogger.Default; +logger.Info("Message"); +logger.Warning("Warning"); +logger.Error("Error"); +``` + +### 8.2 注入容器使用 + +框架提供了容器注入日志记录器,以TcpService为例,使用方式如下: + +需要在配置容器时(ConfigureContainer),添加日志记录器。 + +```csharp {1,4-6,11} showLineNumbers +var service = new TcpService(); +service.Received = async (client, e) => +{ + //从客户端收到信息 + var mes = e.ByteBlock.Span.ToString(Encoding.UTF8); + service.Logger.Info($"服务器已从{client.Id}接收到信息:{mes}"); + + await client.SendAsync(mes);//将收到的信息直接返回给发送方 +}; + +await service.SetupAsync(new TouchSocketConfig()//载入配置 + .SetListenIPHosts(7789) + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + //a.AddFileLogger(); + }) + .ConfigurePlugins(a => + { + //a.Add();//此处可以添加插件 + })); +await service.StartAsync();//启动 +``` + +:::tip 提示 + +使用容器注入日志记录器时,不可以Add多个日志记录器。因为他们使用的同一个接口注册的,后面注册的日志记录器会覆盖前面注册的日志记录器。 + +::: + +### 8.2 注入多日志记录器 + +为实现多日志记录器,需要使用日志组(LoggerGroup),方法如下: + +```csharp {5-6} showLineNumbers +.ConfigureContainer(a => +{ + a.AddLogger(logger => + { + logger.AddConsoleLogger(); + logger.AddFileLogger(); + }); +}) +``` \ No newline at end of file diff --git a/handbook/versioned_docs/version-3.1/independentusedatahandlingadapter.mdx b/handbook/versioned_docs/version-3.1/independentusedatahandlingadapter.mdx new file mode 100644 index 000000000..b749179ee --- /dev/null +++ b/handbook/versioned_docs/version-3.1/independentusedatahandlingadapter.mdx @@ -0,0 +1,66 @@ +--- +id: independentusedatahandlingadapter +title: 独立使用适配器 +--- + +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import { TouchSocketDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +适配器的机制,是非常好的解封包机制,那这么好的机制,我们在设计的时候,也想到了单独使用适配器的情况。例如: + + + +## 二、使用 + +```csharp showLineNumbers +FixedHeaderPackageAdapter adapter = new FixedHeaderPackageAdapter(); + +bool sendCallBack = false; +bool receivedCallBack = false; + +byte[] sentData = null; +adapter.SendCallBack = (buffer, offset, length) => +{ + //此处会回调发送的最终调用。例如:此处使用固定包头,则发送的数据为4+n的封装。 + sentData = new byte[length]; + Array.Copy(buffer, offset, sentData, 0, length); + if (length == 4 + 4) + { + sendCallBack = true; + } +}; + +adapter.ReceivedCallBack = (byteBlock, requestInfo) => +{ + //此处会回调接收的最终触发,例如:此处使用的固定包头,会解析4+n的数据为n。 + + if (byteBlock.Length == 4) + { + receivedCallBack = true; + } +}; + +byte[] data = Encoding.UTF8.GetBytes("RRQM"); + +adapter.SendInput(data, 0, data.Length);//模拟输入,会在SendCallBack中输出最终要发送的数据。 + +using (ByteBlock block = new ByteBlock(1024*64)) +{ + block.Write(sentData); + block.Pos = 0; + adapter.ReceivedInput(block);//模拟输出,会在ReceivedCallBack中输出最终收到的实际数据。 +} +``` + +:::tip 提示 + +上述仅仅是以固定包头适配器示例的,实际上对于其他所有的适配器均可以使用。 + +::: \ No newline at end of file diff --git a/handbook/versioned_docs/version-3.1/ioc.mdx b/handbook/versioned_docs/version-3.1/ioc.mdx new file mode 100644 index 000000000..d688afafb --- /dev/null +++ b/handbook/versioned_docs/version-3.1/ioc.mdx @@ -0,0 +1,268 @@ +--- +id: ioc +title: 依赖注入容器(IOC) +--- + +import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +依赖注入,是指程序运行过程中,如果需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部的注入。 + +通俗来讲,就是把有依赖关系的类放到容器中,然后在我们需要这些类时,容器自动解析出这些类的实例。 + +依赖注入最大的好处时实现类的解耦,利于程序拓展、单元测试、自动化模拟测试等。依赖注入的英文为:`Dependency Injection`,简称 `DI`。(说明来自网络) + +`TouchSocket`内置了`Container`容器。 + +## 二、特点 + +- 支持构造函数、属性、方法三种注入方式,可以选择其中部分生效。 +- 支持`Singleton`、`Transient`两种生命周期。 +- 支持单接口,多实现注入。 +- 支持当获取类型是可实例类型时,即使不注册,也能成功构造。 +- 支持默认参数注入。 +- 支持构建参数注入。 +- 支持标签参数注入。 +- 支持泛型注入。 +- 支持Object注入。 +- 支持**源生成注入**,全面接入AOT模式。 + +## 三、注入方式 + +### 3.1 构造函数注入 + +【定义类型】 + +```csharp showLineNumbers +class MyClass1 +{ + +} + +class MyClass2 +{ + public MyClass2(MyClass1 myClass1) + { + this.MyClass1 = myClass1; + } + + public MyClass1 MyClass1 { get; } +} +``` + +【注册和获取】 + +```csharp showLineNumbers +var container = new Container(); +container.RegisterSingleton(); +container.RegisterSingleton(); + +var myClass1 = container.Resolve(); +var myClass2 = container.Resolve(); +``` + +### 3.2 属性注入 + +使用`DependencyInject`标记属性,即可注入。 + +【定义类型】 + +```csharp showLineNumbers +class MyClass3 +{ + /// + /// 直接按类型,默认方式获取 + /// + [DependencyInject] + public MyClass1 MyClass1 { get; set; } + + /// + /// 获得指定类型的对象,然后赋值到object + /// + [DependencyInject(typeof(MyClass2))] + public object MyClass2 { get; set; } + + /// + /// 按照类型+Key获取 + /// + [DependencyInject("key")] + public MyClass1 KeyMyClass1 { get; set; } +} +``` + +【注册和获取】 + +```csharp showLineNumbers +var container = new Container(); +container.RegisterSingleton(); +container.RegisterSingleton("key"); +container.RegisterSingleton(); + +container.RegisterSingleton(); + +var myClass3 = container.Resolve(); +``` + +:::tip 提示 + +`DependencyInject`特性中,`Type`和`Key`,可以同时使用,也可以只使用其中一个。 + +::: + +### 3.3 方法注入 + +使用`DependencyInject`标记属性,即可对方法注入。 + +【定义类型】 + +```csharp showLineNumbers +class MyClass4 +{ + public MyClass1 MyClass1 { get;private set; } + + [DependencyInject] + public void MethodInject(MyClass1 myClass1) + { + this.MyClass1 = myClass1; + } +} +``` + +【注册和获取】 + +```csharp showLineNumbers +var container = new Container(); +container.RegisterSingleton(); +container.RegisterSingleton(); + +var myClass4 = container.Resolve(); +``` + +:::tip 提示 + +`DependencyInject`特性,也可以在方法注入时,对参数进行使用。 + +::: + +### 3.4 注入筛选 + +对于一个类,默认情况下,会支持`构造函数`、`属性`、`方法`三种注入方式。但是,当明确知道该类型仅会使用其中部分方式注入时,可以设置注入类型,以此节约性能。 + +```csharp {4} +/// +/// 让MyClass1仅支持构造函数和属性注入 +/// +[DependencyType(DependencyType.Constructor | DependencyType.Property)] +class MyClass1 +{ + +} +``` + +:::info 备注 + +`Constructor`构造函数配置不管配不配置,默认都是支持的。 + +::: + +## 四、源生成IOC容器 + +使用源生成IOC容器,能实现100%代码静态生成。能够有效解决运行时代码效率问题和AOT反射问题。 + +然后声明自己的容器,遵循如下: + +1. 必须继承`ManualContainer`。 +2. 必须使用`partial`修饰为部分类。 +3. 必须添加特性`GeneratorContainer`。 + +```csharp showLineNumbers + [GeneratorContainer] + partial class MyContainer : ManualContainer + { + + } +``` + +### 4.1 注册类型 + +注册类型,可以直接添加特性即可: + +```csharp showLineNumbers +[AutoInjectForSingleton]//将声明的类型直接标识为单例注入 +class MyClass12 +{ + +} + +[AutoInjectForTransient]//将声明的类型直接标识为瞬态注入 +class MyClass13 +{ + +} +``` + +### 4.2 注册现有类型 + +现有类型已经完成声明,所以无法直接添加特性。所以只能将特性添加在`ManualContainer`的继承类上。 + +```csharp showLineNumbers +[AddSingletonInject(typeof(IInterface10), typeof(MyClass10))]//直接添加现有类型为单例 +[AddTransientInject(typeof(IInterface11), typeof(MyClass11))]//直接添加现有类型为瞬态 +[GeneratorContainer] +partial class MyContainer : ManualContainer +{ + +} +``` + +:::tip 提示 + +源生成IOC容器,同样支持属性、方法、Key等注册配置,使用规则和第三节一致。 + +::: + + +## 五、生命周期 + +生命周期是对注入构造的实例的有效性而言的。`TouchSocket`支持两种生命周期。 + +- `Singleton`:单例注入,当注入,并且实例化以后,全局唯一实例。 +- `Transient`:瞬时注入,每次获取的实例都是新实例。 + +对于这两种,熟悉IOC的同学,相信都知道到。 + +:::tip 提示 + +`Container`默认情况下不支持`Scoped`生命周期。所以如果需要,可以安装`TouchSocket.Core.DependencyInjection`实现。 + +::: + +## 六、使用ServiceCollection + +首先,需要安装NuGet包:`TouchSocket.Core.DependencyInjection`。 + +然后直接使用`AspNetCoreContainer`替代`Container`。 + +```csharp showLineNumbers +static IContainer GetContainer() +{ + //return new Container();//默认IOC容器 + + return new AspNetCoreContainer(new ServiceCollection());//使用Aspnetcore的容器 +} +``` + +Config使用 + +```csharp showLineNumbers +var config=new TouchSocketConfig()//载入配置 + .UseAspNetCoreContainer(new ServiceCollection());//ServiceCollection可以使用现有的 +``` + +[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Core/IocConsoleApp) diff --git a/handbook/versioned_docs/version-3.1/ipackage.mdx b/handbook/versioned_docs/version-3.1/ipackage.mdx new file mode 100644 index 000000000..746f07216 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/ipackage.mdx @@ -0,0 +1,538 @@ +--- +id: ipackage +title: 包序列化模式 +--- + +import CardLink from "@site/src/components/CardLink.js"; +import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +包序列化模式是为了解决**极限序列化**的问题。常规序列化的瓶颈,主要是反射、表达式树、创建对象等几个方面,这几个问题在运行时阶段,都没有一个好的解决方案。目前在`net6`以后,微软大力支持源生成,这使得这类问题得到了很大程度的解决。所以,这时候包序列化模式就显得非常需要了。 + +## 二、特点 + +### 2.1 优点 + +1. 简单、可靠、高效 +2. 可以支持所有类型(需要自己编写代码) +3. 数据量最少(从理论来说这是占数据量最轻量的设计) + +### 2.2 缺点 + +1. 不支持跨语言。 +2. 类型版本兼容性比较差,简单来说就是高版本只能新增属性,不能删除属性,不能修改属性类型(如果类型长度一致,则可以修改类型,例如:`int` -> `float`)。 + +## 三、使用 + +### 3.1 简单类型 + +例如: + +下列类型`MyClass`,有一个`int`类属性和一个`string`类属性。 + +```csharp showLineNumbers +public class MyClass +{ + public int P1 { get; set; } + public string P2 { get; set; } +} +``` + +我们可以使用包序列化模式,将`MyClass`序列化成二进制流,或者反序列化成`MyClass`。 + +那么首先需要实现`IPackage`接口(或者继承`PackageBase`),然后依次将属性写入到`ByteBlock`中,或者从`ByteBlock`中读取属性。 + +```csharp {9-10,16-17} showLineNumbers +public class MyClass:PackageBase +{ + public int P1 { get; set; } + public string P2 { get; set; } + + public override void Package(ref TByteBlock byteBlock) + { + //将P1与P2属性按类型依次写入 + byteBlock.WriteInt32(this.P1); + byteBlock.WriteString(this.P2); + } + + public override void Unpackage(ref TByteBlock byteBlock) + { + //将P1与P2属性按类型依次读取 + this.P1 = byteBlock.ReadInt32(); + this.P2 = byteBlock.ReadString(); + } +} +``` + +### 3.2 数组(列表)类型 + +对于数组、列表等类型,需要先判断是否为`null`,然后再写入有效值。 + +如果有效值是自定义类型,则也需要实现`IPackage`接口,然后依次写入。 + +```csharp {5,22} showLineNumbers +public class MyArrayClass : PackageBase +{ + public int[] P5 { get; set; } + + public override void Package(ref TByteBlock byteBlock) + { + //集合类型,可以先判断集合是否为null + byteBlock.WriteIsNull(P5); + if (P5 != null) + { + //如果不为null + //就先写入集合长度 + //然后遍历将每个项写入 + byteBlock.WriteInt32(P5.Length); + foreach (var item in P5) + { + byteBlock.WriteInt32(item); + } + } + } + + public override void Unpackage(ref TByteBlock byteBlock) + { + var isNull_P5 = byteBlock.ReadIsNull(); + if (!isNull_P5) + { + //有值 + var count = byteBlock.ReadInt32(); + var array = new int[count]; + for (int i = 0; i < count; i++) + { + array[i]=byteBlock.ReadInt32(); + } + + //赋值 + this.P5 = array; + } + } +} +``` + +### 3.3 字典类型 + +字典类型基本上和数组类似,也是先判断是否为`null`,然后再写入有效值。 + +```csharp {5,23} showLineNumbers +public class MyDictionaryClass : PackageBase +{ + public Dictionary P6 { get; set; } + + public override void Package(ref TByteBlock byteBlock) + { + //字典类型,可以先判断是否为null + byteBlock.WriteIsNull(P6); + if (P6 != null) + { + //如果不为null + //就先写入字典长度 + //然后遍历将每个项,按键、值写入 + byteBlock.WriteInt32(P6.Count); + foreach (var item in P6) + { + byteBlock.WriteInt32(item.Key); + byteBlock.WritePackage(item.Value);//因为值MyClassModel实现了IPackage,所以可以直接写入 + } + } + } + + public override void Unpackage(ref TByteBlock byteBlock) + { + var isNull_6 = byteBlock.ReadIsNull(); + if (!isNull_6) + { + int count = byteBlock.ReadInt32(); + var dic = new Dictionary(count); + for (int i = 0; i < count; i++) + { + dic.Add(byteBlock.ReadInt32(), byteBlock.ReadPackage()); + } + this.P6 = dic; + } + } +} +``` + +:::tip 提示 + +属性的读取和写入时,没有先后顺序,只要保证读取的顺序与写入的顺序一致即可。 + +::: + + +## 四、打包和解包 + +### 4.1 使用内存块 + +使用内存块,使用`ByteBlock`类。 + +```csharp {12,20} showLineNumbers +//声明内存大小。 +//在打包时,一般会先估算一下包的最大尺寸,避免内存块扩张带来的性能损失。 +using (var byteBlock = new ByteBlock(1024 * 64)) +{ + //初始化对象 + var myClass = new MyClass() + { + P1 = 10, + P2 = "RRQM" + }; + + myClass.Package(byteBlock); + Console.WriteLine($"打包完成,长度={byteBlock.Length}"); + + //在解包时,需要把游标移动至正确位置,此处为0. + byteBlock.SeekToStart(); + + //先新建对象 + var newMyClass = new MyClass(); + newMyClass.Unpackage(byteBlock); + Console.WriteLine($"解包完成,{newMyClass.ToJsonString()}"); +} +``` + +### 4.2 使用值类型内存块 + +使用值类型内存块,使用`ValueByteBlock`类。 + +```csharp {15,23} showLineNumbers +//声明内存大小。 +//在打包时,一般会先估算一下包的最大尺寸,避免内存块扩张带来的性能损失。 + +var byteBlock = new ValueByteBlock(1024 * 64); + +try +{ + //初始化对象 + var myClass = new MyClass() + { + P1 = 10, + P2 = "RRQM" + }; + + myClass.Package(ref byteBlock); + Console.WriteLine($"打包完成,长度={byteBlock.Length}"); + + //在解包时,需要把游标移动至正确位置,此处为0. + byteBlock.SeekToStart(); + + //先新建对象 + var newMyClass = new MyClass(); + newMyClass.Unpackage(ref byteBlock); + Console.WriteLine($"解包完成,{newMyClass.ToJsonString()}"); +} +finally +{ + byteBlock.Dispose(); +} +``` + + +## 五、使用源生成 + +如果源生成可用(一般指`vs2019`最新版和`vs2022`,`Rider`),使用源代码生成方式,可以实现**自动**的打包和解包。 + +例如上述类型,我们只需要使用`GeneratorPackage`特性标记即可。 + +### 5.1 生成特性 + +```csharp {5} showLineNumbers +/// +/// 使用源生成包序列化。 +/// 也就是不需要手动Package和Unpackage +/// +[GeneratorPackage] +internal partial class MyGeneratorPackage : PackageBase +{ + public int P1 { get; set; } + public string P2 { get; set; } + public char P3 { get; set; } + public double P4 { get; set; } + public List P5 { get; set; } + public Dictionary P6 { get; set; } +} +``` + +
+源生成的代码 +
+ +```csharp showLineNumbers +/* +此代码由SourceGenerator工具直接生成,非必要请不要修改此处代码 +*/ +#pragma warning disable +using System; +using System.Diagnostics; +using TouchSocket.Core; +using System.Threading.Tasks; + +namespace PackageConsoleApp +{ + [global::System.CodeDom.Compiler.GeneratedCode("TouchSocket.SourceGenerator", "2.1.1.0")] + partial class MyGeneratorPackage + { + public override void Package(ref TByteBlock byteBlock) + { + byteBlock.WriteInt32(P1); + byteBlock.WriteString(P2); + byteBlock.WriteChar(P3); + byteBlock.WriteDouble(P4); + byteBlock.WriteIsNull(P5); + if (P5 != null) + { + byteBlock.WriteVarUInt32((uint)P5.Count); + foreach (var item0 in P5) + { + byteBlock.WriteInt32(item0); + } + } + + byteBlock.WriteIsNull(P6); + if (P6 != null) + { + byteBlock.WriteVarUInt32((uint)P6.Count); + foreach (var item1 in P6) + { + byteBlock.WriteInt32(item1.Key); + byteBlock.WritePackage(item1.Value); + } + } + } + + public override void Unpackage(ref TByteBlock byteBlock) + { + P1 = byteBlock.ReadInt32(); + P2 = byteBlock.ReadString(); + P3 = byteBlock.ReadChar(); + P4 = byteBlock.ReadDouble(); + if (!byteBlock.ReadIsNull()) + { + var item0 = (int)byteBlock.ReadVarUInt32(); + var item1 = new System.Collections.Generic.List(item0); + for (var item2 = 0; item2 < item0; item2++) + { + item1.Add(byteBlock.ReadInt32()); + } + + P5 = item1; + } + + if (!byteBlock.ReadIsNull()) + { + var item3 = (int)byteBlock.ReadVarUInt32(); + var item4 = new System.Collections.Generic.Dictionary(item3); + for (var item5 = 0; item5 < item3; item5++) + { + var item6 = byteBlock.ReadInt32(); + PackageConsoleApp.MyClassModel item7 = default; + if (!byteBlock.ReadIsNull()) + { + item7 = new PackageConsoleApp.MyClassModel(); + item7.Unpackage(ref byteBlock); + } + + item4.Add(item6, item7); + } + + P6 = item4; + } + } + } +} +``` + +
+
+ +:::tip 注意 + +使用源代码生成方式时,当包类型是结构体时,才可以直接实现`IPackage`接口。如果是实例类,则需要直接或间接使用`PackageBase`作为基类。 + +::: + +## 六、源生成Member配置 + +### 6.1 成员可见性 + +默认情况下,使用源代码生成方式时,只会将公共成员(公共属性、private set属性和公共字段)进行打包和解包。如果需要对某些成员进行过滤,或者对私有成员进行强制,可以使用`PackageMember`特性进行配置。 + +【忽略成员】 + +```csharp {4} showLineNumbers +[GeneratorPackage] +internal partial class MyGeneratorPackage : PackageBase +{ + [PackageMember(Behavior = PackageBehavior.Ignore)] + public int P1 { get; set; } + ... +} +``` + +【强制成员】 + +```csharp {5} showLineNumbers +[GeneratorPackage] +internal partial class MyGeneratorPackage : PackageBase +{ + ... + [PackageMember(Behavior = PackageBehavior.Include)] + private int P8; +} +``` + +### 6.2 成员顺序 + +`Package`的工作逻辑是读取成员,然后按照成员名**顺序**进行打包。 + +但是有时候希望可以自定义顺序,目的是为了更好的兼容性。 + +则可以先使用`PackageMember`特性对成员进行排序。 + + +```csharp {4,7,10} showLineNumbers +[GeneratorPackage] +internal partial class MyGeneratorIndexPackage : PackageBase +{ + [PackageMember(Index = 2)] + public int P1 { get; private set; } + + [PackageMember(Index = 0)] + public string P2 { get; set; } + + [PackageMember(Index = 1)] + public char P3 { get; set; } +} +``` + +
+源生成的代码 +
+ +```csharp showLineNumbers +/* +此代码由SourceGenerator工具直接生成,非必要请不要修改此处代码 +*/ +#pragma warning disable +using System; +using System.Diagnostics; +using TouchSocket.Core; +using System.Threading.Tasks; + +namespace PackageConsoleApp +{ + partial class MyGeneratorIndexPackage + { + public override void Package(ref TByteBlock byteBlock) + { + byteBlock.WriteString(P2); + byteBlock.WriteChar(P3); + byteBlock.WriteInt32(P1); + } + + public override void Unpackage(ref TByteBlock byteBlock) + { + P2 = byteBlock.ReadString(); + P3 = byteBlock.ReadChar(); + P1 = byteBlock.ReadInt32(); + } + } +} +``` + +
+
+ + +### 6.3 自定义类型转换 + +`Package`在源生成打包时,是支持自定义类型的,但是要求是自定义的类型也必须实现`IPackage`接口。 + +但是,有时候,有些成员类型是已存在的第三方类型,所以就需要使用自定义转换器来实现。 + +例如:对于`Rectangle`类型,这是一个在`System.Drawing`中记录矩形的数据类型。 + +```csharp {4} showLineNumbers +[GeneratorPackage] +internal partial class MyGeneratorConvertPackage : PackageBase +{ + public Rectangle P1 { get; set; } +} +``` + +默认情况下,是无法使用源生成打包的。 + +这时候就需要自定转换器。 + +首先,新建一个类,继承`FastBinaryConverter`,指定泛型为`Rectangle`,然后实现`Read`和`Write`方法。 + +```csharp showLineNumbers +class RectangleConverter : FastBinaryConverter +{ + protected override Rectangle Read(ref TByteBlock byteBlock, Type type) + { + var rectangle = new Rectangle(byteBlock.ReadInt32(), byteBlock.ReadInt32(), byteBlock.ReadInt32(), byteBlock.ReadInt32()); + return rectangle; + } + + protected override void Write(ref TByteBlock byteBlock, in Rectangle obj) + { + byteBlock.WriteInt32(obj.X); + byteBlock.WriteInt32(obj.Y); + byteBlock.WriteInt32(obj.Width); + byteBlock.WriteInt32(obj.Height); + } +} +``` + +:::info 信息 + +在实现时,不需要考虑对象为`null`的情况,无论是类还是结构体,都会自动判断。所以,触发到转换器时,一定是不为`null`。 + +::: + +然后,在成员中,添加`[PackageMember(Converter =typeof(RectangleConverter))]`即可。 + +```csharp {4} showLineNumbers +[GeneratorPackage] +internal partial class MyGeneratorConvertPackage : PackageBase +{ + [PackageMember(Converter =typeof(RectangleConverter))] + public Rectangle P1 { get; set; } +} +``` + + +## 七、性能评测 + +基准测试表明: + +包序列化模式比`MemoryPack`快30%。 +比`json`方式快了20倍多。 +比微软的`json`快了近10倍。 +比微软的二进制快了近100倍。 + +```csharp showLineNumbers +| Method | Job | Runtime | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio | +|---------------------- |--------- |--------- |-----------:|----------:|----------:|-------:|--------:|-----------:|----------:|----------:|------------:| +| DirectNew | .NET 6.0 | .NET 6.0 | 1.647 ms | 0.0099 ms | 0.0088 ms | 1.00 | 0.00 | 509.7656 | - | 7.63 MB | 1.00 | +| MemoryPack | .NET 6.0 | .NET 6.0 | 4.495 ms | 0.0135 ms | 0.0113 ms | 2.73 | 0.02 | 484.3750 | - | 7.25 MB | 0.95 | +| Package | .NET 6.0 | .NET 6.0 | 3.832 ms | 0.0344 ms | 0.0322 ms | 2.33 | 0.03 | 390.6250 | - | 5.88 MB | 0.77 | +| NewtonsoftJson | .NET 6.0 | .NET 6.0 | 64.311 ms | 0.2367 ms | 0.1848 ms | 39.04 | 0.26 | 4875.0000 | - | 73.93 MB | 9.69 | +| SystemTextJson | .NET 6.0 | .NET 6.0 | 33.841 ms | 0.1921 ms | 0.1797 ms | 20.55 | 0.18 | 1533.3333 | - | 23.5 MB | 3.08 | +| FastBinarySerialize | .NET 6.0 | .NET 6.0 | 7.531 ms | 0.0194 ms | 0.0162 ms | 4.57 | 0.03 | 578.1250 | - | 8.7 MB | 1.14 | +| SystemBinarySerialize | .NET 6.0 | .NET 6.0 | 253.637 ms | 1.5709 ms | 1.3118 ms | 153.95 | 1.04 | 28000.0000 | 1000.0000 | 420.51 MB | 55.11 | +``` + +## 八、本文示例Demo + + \ No newline at end of file diff --git a/handbook/versioned_docs/version-3.1/jsonrpc.mdx b/handbook/versioned_docs/version-3.1/jsonrpc.mdx new file mode 100644 index 000000000..e06105a89 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/jsonrpc.mdx @@ -0,0 +1,436 @@ +--- +id: jsonrpc +title: 产品及架构介绍 +--- + +import Tag from "@site/src/components/Tag.js"; +import CardLink from "@site/src/components/CardLink.js"; +import { TouchSocketJsonRpcDefinition } from "@site/src/components/Definition.js"; + + +### 定义 + + + + +## 一、说明 + +JsonRpc是**通用**的Rpc规范,与**编程语言无关、操作系统无关**。详细说明请参阅[JsonRpc 2.0 官方文档](https://www.jsonrpc.org/specification),在TouchSocket中封装了**前后端**,使其使用更加方便、高效。 + +目前支持`Tcp`、`Http`、`WebSocket`三种协议调用。 + + +## 二、特点: + +- **异常反馈** 。 +- 插件支持。 +- 支持自定义类型。 +- 支持类型嵌套。 +- 支持js、Android等调用。 +- 支持服务器主动调用客户端 + + +## 三、定义服务 + +在**服务器**端中新建一个类,继承于`SingletonRpcServer`类(或实现`ISingletonRpcServer`),然后在该类中写**公共方法**,并用**JsonRpc**特性标签标记。 + + +```csharp showLineNumbers +public partial class JsonRpcServer : SingletonRpcServer +{ + /// + /// 使用调用上下文。 + /// 可以从上下文获取调用的SessionClient。从而获得IP和Port等相关信息。 + /// + /// + /// + /// + [JsonRpc(MethodInvoke =true)] + public string TestGetContext(ICallContext callContext, string str) + { + if (callContext.Caller is IHttpSessionClient SessionClient) + { + if (SessionClient.Protocol == Protocol.WebSocket) + { + Console.WriteLine("WebSocket请求"); + var client = callContext.Caller as IHttpSessionClient; + var ip = client.IP; + var port = client.Port; + Console.WriteLine($"WebSocket请求{ip}:{port}"); + } + else + { + Console.WriteLine("HTTP请求"); + var client = callContext.Caller as IHttpSessionClient; + var ip = client.IP; + var port = client.Port; + Console.WriteLine($"HTTP请求{ip}:{port}"); + } + } + else if (callContext.Caller is ITcpSessionClient) + { + Console.WriteLine("Tcp请求"); + var client = callContext.Caller as ITcpSessionClient; + var ip = client.IP; + var port = client.Port; + Console.WriteLine($"Tcp请求{ip}:{port}"); + } + return "RRQM" + str; + } + + [JsonRpc(MethodInvoke = true)] + public JObject TestJObject(JObject obj) + { + return obj; + } + + [JsonRpc(MethodInvoke = true)] + public string TestJsonRpc(string str) + { + return "RRQM" + str; + } +} +``` + +:::info 备注 + +设置`MethodInvoke = true`,即以方法名作为调用键,这也是JsonRpc规范所规定。但同时框架内部还支持另一种方式,即默认情况下会使用方法的**全名称小写**作为调用键(即:命名空间.类名.方法名) + +::: + + +## 四、启动服务器 + +JsonRpc支持多个基本协议的服务器,所以下面将一一介绍。 + +更多注册Rpc的方法请看[注册Rpc服务](./rpcregister.mdx) + +### 4.1 以Tcp为基础协议 + +当以Tcp为基础协议时,支持Tcp的任何操作。包括但不限于`设置适配器`等。 + +下列代码创建的就是一个最普通Tcp协议下的JsonRpc服务器。该服务支持任何未处理的Tcp协议的JsonRpc数据包调用。 + +```csharp showLineNumbers +var service = new TcpService(); +await service.SetupAsync(new TouchSocketConfig() + .SetListenIPHosts(7705) + .ConfigureContainer(a => + { + a.AddRpcStore(store => + { + store.RegisterServer(); + }); + }) + .ConfigurePlugins(a => + { + /* + 使用tcp服务器的时候,默认情况下会把所有连接的协议都转换为JsonRpcUtility.TcpJsonRpc。 + 这样所有的数据都会被尝试解释为JsonRpc。 + 如果不需要该功能,可以调用NoSwitchProtocol()。 + */ + a.UseTcpJsonRpc(); + })); + +await service.StartAsync(); +``` + +:::caution 注意 + +因为上述服务器中没有使用任何适配器,所以在实际使用中,可能会发生数据包粘包、分包等问题。所以不建议直接使用。要想投入生产使用,最简单也建议使用`.SetTcpDataHandlingAdapter(() => new TerminatorPackageAdapter("\r\n"))`换行符分割等适配器。 + +::: + + +### 4.2 使用Http协议服务器 + +创建后,如果想使用Http调用,只需要以Post方式,将调用Json字符串路由到设定路由地址即可(下文示例“/jsonRpc”)。 + +```csharp showLineNumbers +var service = new HttpService(); + +await service.SetupAsync(new TouchSocketConfig() + .SetListenIPHosts(7706) + .ConfigureContainer(a => + { + a.AddRpcStore(store => + { + store.RegisterServer(); + }); + }) + .ConfigurePlugins(a => + { + a.UseHttpJsonRpc() + .SetJsonRpcUrl("/jsonRpc"); + })); + +await service.StartAsync(); +``` + +### 4.3 使用WebSocket协议服务器 + +如果想使用Websocket调用,只需要以**文本**形式,传递到服务器即可。 + +```csharp showLineNumbers +var service = new HttpService(); + +await service.SetupAsync(new TouchSocketConfig() + .SetListenIPHosts(7707) + .ConfigureContainer(a => + { + a.AddRpcStore(store => + { + store.RegisterServer(); + }); + }) + .ConfigurePlugins(a => + { + a.UseWebSocket() + .SetWSUrl("/ws"); + + a.UseWebSocketJsonRpc() + .SetAllowJsonRpc((SessionClient, context) => + { + //此处的作用是,通过连接的一些信息判断该ws是否执行JsonRpc。 + //当然除了此处可以设置外,也可以通过SessionClient.SetJsonRpc(true)直接设置。 + return true; + }); + })); + +await service.StartAsync(); +``` + +:::tip 提示 + +`WebSocket`协议服务器和`Http`协议服务器可以合并为一个。 + +::: + +## 五、通用调用 + +因为`JsonRpc`是通用调用协议,所以只要**适配基础协议**,即可直接使用`Json`字符串调用。 + +以下字符串只是示例,具体的method参数,应当遵循当前路由。 + +### 5.1 Tcp协议直接调用 + +在`Tcp`协议时,按照适配器,选择性的是否以`\r\n`结尾。 + +```csharp showLineNumbers +{"jsonrpc": "2.0", "method": "testjsonrpc", "params":["RRQM"], "id": 1} +``` + +### 5.2 Http协议直接调用 + +在`Http`协议时,以`Url+Post`方式即可 + +```csharp showLineNumbers +{"jsonrpc": "2.0", "method": "testjsonrpc", "params":["RRQM"], "id": 1} +``` + +### 5.3 Websocket协议直接调用 + +在`Websocket`协议时,以`文本类型`,直接发送到服务器即可。 + +```csharp showLineNumbers +{"jsonrpc": "2.0", "method": "testjsonrpc", "params":["RRQM"], "id": 1} +``` + + +## 六、客户端直接调用 + +框架内部提供了`JsonRpc`的专属客户端,可以直接调用,也可以生成代理调用。下列将详细介绍。 + +### 6.1 Tcp协议 + +```csharp showLineNumbers +var client = new TcpJsonRpcClient(); +await client.SetupAsync(new TouchSocketConfig() + .SetRemoteIPHost("127.0.0.1:7705")); +await client.ConnectAsync(); + +string result = client.InvokeT("TestJsonRpc", InvokeOption.WaitInvoke, "RRQM"); +``` + +### 6.2 Http协议 + +```csharp showLineNumbers +var client = new HttpJsonRpcClient(); +await client.SetupAsync(new TouchSocketConfig() + .SetRemoteIPHost("http://127.0.0.1:7706/jsonrpc")); +await client.ConnectAsync(); + +string result = client.InvokeT("TestJsonRpc", InvokeOption.WaitInvoke, "RRQM"); +``` + +### 6.3 Websocket协议 + +```csharp showLineNumbers +var client = new WebSocketJsonRpcClient(); +await client.SetupAsync(new TouchSocketConfig() + .SetRemoteIPHost("ws://127.0.0.1:7707/ws"));//此url就是能连接到websocket的路径。 +await client.ConnectAsync(); + +string result = client.InvokeT("TestJsonRpc", InvokeOption.WaitInvoke, "RRQM"); +``` + +### 6.4 生成代理调用 + +在服务器端,注册完服务后,就可以生成客户端调用代码了。详细的操作可以查看[服务端代理生成](./rpcgenerateproxy.mdx) + +```csharp {8-9} +a.AddRpcStore(store => +{ + store.RegisterServer(); + #if DEBUG + //下列代码,会生成客户端的调用代码。 + var codeString = store.GetProxyCodes("JsonRpcServerProxy", typeof(JsonRpcAttribute)); + File.WriteAllText("../../../JsonRpcServerProxy.cs", codeString); + #endif +}); +``` + +然后把生成的`.cs`文件复制(或链接)到客户端项目。然后客户端直接使用同名`扩展方法`即可调用。 + +```csharp showLineNumbers +var sum3 = client.TestJsonRpc("RRQM"); +``` + +### 6.5 使用DispatchProxy代理调用 + +使用`DispatchProxy`代理调用,可以实现动态代理,详情请看[DispatchProxy代理生成](./rpcgenerateproxy.mdx) + +首先,需要声明一个基类,用于通讯基础。 + +```csharp showLineNumbers +/// +/// 新建一个类,继承JsonRpcDispatchProxy,亦或者RpcDispatchProxy基类。 +/// 然后实现抽象方法,主要是能获取到调用的IRpcClient派生接口。 +/// +internal class MyJsonRpcDispatchProxy : JsonRpcDispatchProxy +{ + private readonly IJsonRpcClient m_client; + + public MyJsonRpcDispatchProxy() + { + this.m_client = CreateJsonRpcClientByTcp().GetFalseAwaitResult(); + } + + public override IJsonRpcClient GetClient() + { + return this.m_client; + } + + private static async Task CreateJsonRpcClientByTcp() + { + var client = new TcpJsonRpcClient(); + await client.SetupAsync(new TouchSocketConfig() + .SetRemoteIPHost("127.0.0.1:7705") + .SetTcpDataHandlingAdapter(() => new TerminatorPackageAdapter("\r\n"))); + await client.ConnectAsync(); + return client; + } +} +``` + +:::tip 提示 + +此处其他协议的`JsonRpc`也是完全支持的。 + +::: + + +然后按照服务,定义一个相同的代理接口。 + +```csharp showLineNumbers +interface IJsonRpcServer +{ + [JsonRpc(MethodInvoke = true)] + string TestJsonRpc(string str); +} +``` + +最后生成代理,并按照接口调用。 + +```csharp {1} +var rpc = MyJsonRpcDispatchProxy.Create(); + +while (true) +{ + var result = rpc.TestJsonRpc(Console.ReadLine()); + Console.WriteLine(result); +} +``` + +## 七、反向Rpc(服务器主动调用客户端) + +框架提供了反向`Rpc`,即**服务器主动调用客户端**。该功可以用于`Web`等多端。 + +反向Rpc必须在全双工协议下使用,如`WebSocket`、`Tcp`等。 + +具体使用如下: + +首先,需要在*客户端*像常规`Rpc`一样声明一个`Rpc`服务。然后需要使用`JsonRpc`特性表示。 + +```csharp {3} showLineNumbers +public partial class ReverseJsonRpcServer : SingletonRpcServer +{ + [JsonRpc(MethodInvoke = true)] + public int Add(int a, int b) + { + return a + b; + } +} +``` + +然后注册服务。 + +```csharp {5-8} showLineNumbers +var jsonRpcClient = new WebSocketJsonRpcClient(); +await jsonRpcClient.SetupAsync(new TouchSocketConfig() + .ConfigureContainer(a => + { + a.AddRpcStore(store => + { + store.RegisterServer(); + }); + }) + .SetRemoteIPHost("ws://127.0.0.1:7707/ws"));//此url就是能连接到websocket的路径。 +await jsonRpcClient.ConnectAsync(); +``` + +在服务器端中,拿到`IHttpSessionClient`对象。然后调用`GetJsonRpcActionClient`扩展方法获取到`IJsonRpcClient`。然后调用`Invoke`等。 + +下列示例演示的是,当`WebSocket`连接上时,服务器主动调用客户端。 + +```csharp {8,10} showLineNumbers +class MyPluginClass : PluginBase, IWebSocketHandshakedPlugin +{ + public async Task OnWebSocketHandshaked(IHttpSessionClient client, HttpContextEventArgs e) + { + try + { + //获取JsonRpcActionClient,用于执行反向Rpc + var jsonRpcClient = client.GetJsonRpcActionClient(); + + var result = await jsonRpcClient.InvokeTAsync("Add", InvokeOption.WaitInvoke, 10, 20); + Console.WriteLine($"反向调用成功,结果={result}"); + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } + await e.InvokeNext(); + } +} +``` + +:::tip + +反向JsonRpc也能使用代理。 + +::: + +## 八、本文示例Demo + + diff --git a/handbook/docs/jsonserialize.mdx b/handbook/versioned_docs/version-3.1/jsonserialize.mdx similarity index 100% rename from handbook/docs/jsonserialize.mdx rename to handbook/versioned_docs/version-3.1/jsonserialize.mdx diff --git a/handbook/versioned_docs/version-3.1/modbusmaster.mdx b/handbook/versioned_docs/version-3.1/modbusmaster.mdx new file mode 100644 index 000000000..7c6e909ce --- /dev/null +++ b/handbook/versioned_docs/version-3.1/modbusmaster.mdx @@ -0,0 +1,450 @@ +--- +id: modbusmaster +title: Modbus主站(Master) +--- + +import Tag from "@site/src/components/Tag.js"; +import Pro from "@site/src/components/Pro.js"; +import { TouchSocketModbusDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +`Modbus`是OSI模型第7层上的应用层报文传输协议,它在连接至不同类型总线或网络的设备之间提供客户机/服务器通信。 + +自从 1979 年出现工业串行链路的事实标准以来,`Modbus`使成千上万的自动化设备能够通信。目前,继续增加对简单而雅观的`Modbus`结构支持。互联网组织能够使TCP/IP栈上的保留系统端口502 访问`Modbus`。 + +所以总结来说`Modbus`是一个请求/应答的总线协议。 + +所以我们开发了这个组件,方便大家使用。 + +## 二、特点 + +- 简单易用。 +- 内存池支持 +- 高性能 +- 易扩展。 +- **支持全数据类型的读写**。 + +## 三、产品应用场景 + +- 所有`Modbus`使用场景:可跨平台使用。 + +## 四、可配置项 + +无单独配置项。 + +## 五、支持插件 + +无单独支持插件。 + +## 六、创建 + +目前`TouchSocket.Modbus`支持`Tcp`、`Udp`、`Rtu`、`RtuOverTcp`、`RtuOverUdp`等协议。下面会一一介绍创建过程。 + +#### 6.1 创建ModbusTcpMaster + +```csharp showLineNumbers +var client = new ModbusTcpMaster(); +await client.ConnectAsync("127.0.0.1:502"); +``` + +#### 6.2 创建ModbusUdpMaster + +```csharp showLineNumbers +var client = new ModbusUdpMaster(); +await client.SetupAsync(new TouchSocketConfig() + .UseUdpReceive() + .SetRemoteIPHost("127.0.0.1:502")); +await client.StartAsync(); +``` + +#### 6.3 创建ModbusRtuMaster + +```csharp showLineNumbers +var client = new ModbusRtuMaster(); +await client.SetupAsync(new TouchSocketConfig() + .SetSerialPortOption(new SerialPortOption() + { + BaudRate = 9600, + DataBits = 8, + Parity = System.IO.Ports.Parity.Even, + PortName = "COM2", + StopBits = System.IO.Ports.StopBits.One + })); +await client.ConnectAsync(); +``` + +#### 6.4 创建ModbusRtuOverTcpMaster + +```csharp showLineNumbers +var client = new ModbusRtuOverTcpMaster(); +await client.ConnectAsync("127.0.0.1:502"); +``` + +#### 6.5 创建ModbusRtuOverUdpMaster + +```csharp showLineNumbers +var client = new ModbusRtuOverUdpMaster(); +await client.SetupAsync(new TouchSocketConfig() + .UseUdpReceive() + .SetRemoteIPHost("127.0.0.1:502")); +await client.StartAsync(); +``` + +## 七、读写操作 + +### 7.1 原生接口操作 + +所有的`Modbus`主站都支持以下原生接口操作: + +```csharp showLineNumbers +//异步发送Modbus请求,并等待响应 +Task SendModbusRequestAsync(ModbusRequest request, int millisecondsTimeout, CancellationToken token); +``` + +以读线圈操作为例: + +```csharp showLineNumbers +ModbusRequest modbusRequest = new ModbusRequest(FunctionCode.ReadCoils); +modbusRequest.SetSlaveId(1);//设置站号。如果是Tcp可以不设置 +modbusRequest.SetStartingAddress(0);//设置起始 +modbusRequest.SetQuantity(1);//设置数量 +//modbusRequest.SetValue(false);//如果是写入类操作,可以直接设定值 + +var response =await master.SendModbusRequestAsync(modbusRequest, 1000, CancellationToken.None); + +bool[] bools = response.CreateReader().ToBoolensFromBit().ToArray(); +``` + +### 7.2 快捷扩展实现 + +因为`Modbus`的操作一般比较固化,所以`ModbusMaster`扩展了以下快捷操作: + +读取线圈(FC1)。 + +```csharp showLineNumbers +bool[] bools =await master.ReadCoilsAsync(0, 1); +``` + +读取离散输入(FC2)。 + +```csharp showLineNumbers +bool[] bools =await master.ReadDiscreteInputsAsync(0, 1); +``` + +读取保持寄存器(FC3)。 + +```csharp showLineNumbers +var response =await master.ReadHoldingRegistersAsync(0, 1); +var reader = response.CreateReader(); +var value=reader.ReadInt16(); +``` + +读取输入寄存器(FC4)。 + +```csharp showLineNumbers +var response =await master.ReadInputRegistersAsync(0, 1); +var reader = response.CreateReader(); +var value=reader.ReadInt16(); +``` + +写入单个线圈(FC5)。 + +```csharp showLineNumbers +await master.WriteSingleCoilAsync(0, true); +``` + +写入单个寄存器(FC6)。 + +```csharp showLineNumbers +await master.WriteSingleRegisterAsync(0, (short)100); +``` + +写入多个线圈(FC15)。 + +```csharp showLineNumbers +await master.WriteMultipleCoilsAsync(0, new bool[] { true, false, true }); +``` + +写入多个寄存器(FC16)。 + +```csharp showLineNumbers +using (var valueByteBlock = new ValueByteBlock(1024)) +{ + valueByteBlock.WriteUInt16((ushort)2, EndianType.Big);//ABCD端序 + valueByteBlock.WriteUInt16((ushort)2000, EndianType.Little);//DCBA端序 + valueByteBlock.WriteInt32(int.MaxValue, EndianType.BigSwap);//BADC端序 + valueByteBlock.WriteInt64(long.MaxValue, EndianType.LittleSwap);//CDAB端序 + + //写入到寄存器 + await master.WriteMultipleRegistersAsync(1, 2, valueByteBlock.ToArray()); +} +``` + +:::tip 提示 + +以上扩展方法还有更多重载。例如:站号、超时时间、可取消令箭等参数。 + +::: + +## 八、更多写入与读取 + +线圈与离散输入的写入与读取比较单一,上述操作即可满足大部分需求。下面介绍读写保持寄存器、读取输入寄存器的多元化方式。 + + +读取寄存器到集合。如果该集合中的数据是一类数据,例如全是uint32类型。那么可以使用下列方式: + +```csharp showLineNumbers +//读取寄存器 +var response =await master.ReadHoldingRegistersAsync(1, 0, 10);//站点1,从0开始读取10个寄存器 + +//创建一个读取器 +var reader = response.CreateReader(); + +//将数据全部读为无符号32为,且使用大端序,即ABCD +uint[] values=reader.ToUInt32s(EndianType.Big).ToArray(); +``` + +:::tip 提示 + +该集合支持全部基础数据类型,以及DataTime和TimeSpan。 + +::: + +当寄存器的数据不规则时,可能需要依次读取。例如: + +当写入下列数据时: + +```csharp showLineNumbers +using (var valueByteBlock = new ValueByteBlock(1024)) +{ + valueByteBlock.WriteUInt16((ushort)2, EndianType.Big);//ABCD端序 + valueByteBlock.WriteUInt16((ushort)2000, EndianType.Little);//DCBA端序 + valueByteBlock.WriteInt32(int.MaxValue, EndianType.BigSwap);//BADC端序 + valueByteBlock.WriteInt64(long.MaxValue, EndianType.LittleSwap);//CDAB端序 + + //写入到寄存器 + await master.WriteMultipleRegistersAsync(1, 2, valueByteBlock.ToArray()); +} +``` + +就需要依次读取: + +```csharp showLineNumbers +//读取寄存器 +var response =await master.ReadHoldingRegistersAsync(1, 0, 1 + 1 + 2 + 4); + +//创建一个读取器 +var reader = response.CreateReader(); + +//依次读取 +Console.WriteLine(reader.ReadInt16(EndianType.Big)); +Console.WriteLine(reader.ReadInt16(EndianType.Little)); +Console.WriteLine(reader.ReadInt32(EndianType.BigSwap)); +Console.WriteLine(reader.ReadInt64(EndianType.LittleSwap)); +``` + +读写字符串: + +```csharp showLineNumbers +using (var valueByteBlock = new ValueByteBlock(1024)) +{ + //写入字符串,会先用4字节表示字符串长度,然后按utf8编码写入字符串 + valueByteBlock.WriteString("Hello"); + + //写入到寄存器 + await master.WriteMultipleRegistersAsync(1, 0, valueByteBlock.ToArray()); +} + +//读取寄存器 +var response =await master.ReadHoldingRegistersAsync(1, 0, 5);//5个长度,10字节 + +//创建一个读取器 +var reader = response.CreateReader(); +Console.WriteLine(reader.ReadString()); +``` + +读写任意类型: + +配合序列化模块,可以任意读写任意类型。 + + +:::tip 提示 + +上述所有类型可以任意组合使用,只需要读取的时候按序读取即可。 + +::: + +[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Modbus/ModbusMasterConsoleApp) + +## 九、ModbusObject操作 + +### 9.1 基本使用 + +一般的,我们使用`Modbus`,都是通过Master直接`Read`或`Write`。所以有时候需要维护的代码会非常多,而且容易出错。 + +所以,我们提供了`ModbusObject`,可以简化Modbus的读写操作。 + +`ModbusObject`可以理解为一个实体类,我们只需要定义需要读写的属性,然后就可以直接读写了。 + +例如:我们需要读写线圈。 + +则声明一个新建类`MyModbusObject`,继承`ModbusObject`。 + +然后声明一个属性`MyProperty1`。类型为`bool`。并使用`ModbusProperty`特性标记,同时指定站号、数据区、起始地址、超时时间等。 + +然后在属性实现中使用`GetValue`和`SetValue`方法。 + +```csharp showLineNumbers +class MyModbusObject : ModbusObject +{ + /// + /// 声明一个来自线圈的bool属性。 + /// + /// 配置:站号、数据区、起始地址、超时时间 + /// + /// + [ModbusProperty(SlaveId = 1, Partition = Partition.Coils, StartAddress = 0, Timeout = 1000)] + public bool MyProperty1 + { + get { return this.GetValue(); } + set { this.SetValue(value); } + } +} +``` + +然后我们可以通过`IModbusMaster`的扩展方法来创建该对象。 + +**然后可以像访问属性那样的访问`Modbus`。** + +```csharp showLineNumbers +var master = GetModbusTcpMaster(); + +var myModbusObject = master.CreateModbusObject(); + +myModbusObject.MyProperty1 = true;//直接赋值线圈 + +Console.WriteLine(myModbusObject.MyProperty1.ToJsonString());//读取,然后以json格式化 +``` + +### 9.2 读写寄存器 + +对于寄存器,我们也可以以属性的方式直接读写基础类型。 + +例如下列,我们可以直接读写`short`类型。 + +在配置时,除了可配置站号、数据区、起始地址、超时时间外,还可以配置端序。 + +```csharp showLineNumbers +class MyModbusObject : ModbusObject +{ + /// + /// 声明一个来自保持寄存器的short属性。 + /// + /// 配置:站号、数据区、起始地址、超时时间、端序 + /// + /// + [ModbusProperty(SlaveId = 1, Partition = Partition.HoldingRegisters, StartAddress = 0, Timeout = 1000, EndianType = TouchSocket.Core.EndianType.Big)] + public short MyProperty3 + { + get { return this.GetValue(); } + set { this.SetValue(value); } + } + + /// + /// 声明一个来自输入寄存器的short属性。 + /// + /// 配置:站号、数据区、起始地址、超时时间、端序 + /// + /// + [ModbusProperty(SlaveId = 1, Partition = Partition.InputRegisters, StartAddress = 0, Timeout = 1000, EndianType = TouchSocket.Core.EndianType.Big)] + public short MyProperty4 + { + get { return this.GetValue(); } + } +} +``` + +:::tip 提示 + +常用的数据类型,基本都支持,例如:int16、uint16、int32、uint32、int64、uint64、float、double、char等。 + +::: + +### 9.3 读写数组 + +当操作的数据是数组时,也可以直接读写。但是需要使用`GetValueArray`和`SetValueArray`方法。 + +使用数组时,需要指定数组的长度。也就是`Quantity`。 + +在线圈和离散输入中,该值就是读取的数量。 + +在寄存器中,该值是读取时的数组长度,并非寄存器个数。例如:当读取int32数组时,如果该值是5,那就是需要读取10个寄存器。 + +```csharp showLineNumbers + class MyModbusObject : ModbusObject + { + + /// + /// 声明一个来自线圈的bool数组属性。 + /// + /// 配置:站号、数据区、起始地址、超时时间、数量 + /// + /// + [ModbusProperty(SlaveId = 1, Partition = Partition.Coils, StartAddress = 1, Timeout = 1000, Quantity = 9)] + public bool[] MyProperty11 + { + get { return this.GetValueArray(); } + set { this.SetValueArray(value); } + } + + + /// + /// 声明一个来自离散输入的bool数组属性。 + /// + /// 配置:站号、数据区、起始地址、超时时间、数量 + /// + /// + [ModbusProperty(SlaveId = 1, Partition = Partition.DiscreteInputs, StartAddress = 1, Timeout = 1000, Quantity = 9)] + public bool MyProperty22 + { + get { return this.GetValue(); } + } + + /// + /// 声明一个来自保持寄存器的short数组属性。 + /// + /// 配置:站号、数据区、起始地址、超时时间、端序、数组长度 + /// + /// + [ModbusProperty(SlaveId = 1, Partition = Partition.HoldingRegisters, StartAddress = 1, Timeout = 1000, EndianType = TouchSocket.Core.EndianType.Big, Quantity = 9)] + public short[] MyProperty33 + { + get { return this.GetValueArray(); } + set { this.SetValueArray(value); } + } + + + /// + /// 声明一个来自输入寄存器的short数组属性。 + /// + /// 配置:站号、数据区、起始地址、超时时间、端序、数组长度 + /// + /// + [ModbusProperty(SlaveId = 1, Partition = Partition.InputRegisters, StartAddress = 0, Timeout = 1000, EndianType = TouchSocket.Core.EndianType.Big, Quantity = 10)] + public short[] MyProperty44 + { + get { return this.GetValueArray(); } + } +} +``` + +[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Modbus/ModbusObjectConsoleApp) + + + diff --git a/handbook/versioned_docs/version-3.1/modbusslave.mdx b/handbook/versioned_docs/version-3.1/modbusslave.mdx new file mode 100644 index 000000000..f343eba04 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/modbusslave.mdx @@ -0,0 +1,246 @@ +--- +id: modbusslave +title: Modbus从站(Slave) +--- + +import Tag from "@site/src/components/Tag.js"; +import Pro from "@site/src/components/Pro.js"; +import { TouchSocketProModbusDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +Modbus是主从通讯的。所以我们开发了Modbus服务器组件,方便大家使用。 + + +## 二、特点 + +- 简单易用。 +- 内存池支持 +- 高性能 +- 易扩展。 +- **支持全数据类型的读写**。 + +## 三、产品应用场景 + +- 所有Modbus使用场景:可跨平台使用。 + +## 四、可配置项 + +无单独配置项。 + +## 五、支持插件 + +| 插件方法| 功能 | +| --- | --- | +| IModbusSlaveExecutingPlugin | 当有主站请求读写该从站时触发。如果想要拒绝请求,可以通过e.IsPermitOperation = false执行。并且e.ErrorCode可以携带返回错误码。| +| IModbusSlaveExecutedPlugin | 当有主站完成请求读写该从站时触发 | + +## 六、创建 + +目前`TouchSokcet.Modbus`从站支持`Tcp`、`Udp`、`Rtu`、`RtuOverTcp`、`RtuOverUdp`等协议。下面会一一介绍创建过程。 + +#### 6.1 创建ModbusTcpSlave + +```csharp showLineNumbers +var slave = new ModbusTcpSlave(); +await slave.SetupAsync(new TouchSocketConfig() + //监听端口 + .SetListenIPHosts(7808) + .ConfigurePlugins(a => + { + a.AddModbusSlavePoint()//添加一个从站站点 + .SetSlaveId(1)//设置站点号 + //.UseIgnoreSlaveId()//忽略站号验证 + .SetModbusDataLocater(new ModbusDataLocater(10, 10, 10, 10));//设置数据区 + }) + ); +await slave.StartAsync(); +Console.WriteLine("服务已启动"); +``` + +#### 6.2 创建ModbusUdpSlave + +```csharp showLineNumbers +var slave = new ModbusUdpSlave(); +await slave.SetupAsync(new TouchSocketConfig() + //监听端口 + .SetBindIPHost(7809) + .ConfigurePlugins(a => + { + a.Add(); + + a.AddModbusSlavePoint()//添加一个从站站点 + .SetSlaveId(1)//设置站点号 + .UseIgnoreSlaveId()//忽略站号验证 + .SetModbusDataLocater(new ModbusDataLocater(10, 10, 10, 10));//设置数据区 + }) + ); +await slave.StartAsync(); +Console.WriteLine("服务已启动"); +``` + +#### 6.3 创建ModbusRtuSlave + +```csharp showLineNumbers +var slave = new ModbusRtuSlave(); +await slave.SetupAsync(new TouchSocketConfig() + //设置串口 + .SetSerialPortOption(new SerialPortOption() + { + BaudRate = 9600, + DataBits = 8, + Parity = System.IO.Ports.Parity.Even, + PortName = "COM1", + StopBits = System.IO.Ports.StopBits.One + }) + .ConfigurePlugins(a => + { + a.Add(); + + a.AddModbusSlavePoint()//添加一个从站站点 + .SetSlaveId(1)//设置站点号 + //.UseIgnoreSlaveId()//如果不调用,默认会进行站号验证 + .SetModbusDataLocater(new ModbusDataLocater(10, 10, 10, 10));//设置数据区 + }) + ); + +await slave.ConnectAsync(); +Console.WriteLine("已连接COM端口"); +``` + +#### 6.4 创建ModbusRtuOverTcpSlave + +```csharp showLineNumbers +var slave = new ModbusRtuOverTcpSlave(); +await slave.SetupAsync(new TouchSocketConfig() + //监听端口 + .SetListenIPHosts(7810) + .ConfigurePlugins(a => + { + a.Add(); + + a.AddModbusSlavePoint()//添加一个从站站点 + .SetSlaveId(1)//设置站点号 + .UseIgnoreSlaveId()//忽略站号验证 + .SetModbusDataLocater(new ModbusDataLocater(10, 10, 10, 10));//设置数据区 + }) + ); +await slave.StartAsync(); +Console.WriteLine("服务已启动"); +``` + +#### 6.5 创建ModbusRtuOverUdpSlave + +```csharp showLineNumbers +var slave = new ModbusRtuOverUdpSlave(); +await slave.SetupAsync(new TouchSocketConfig() + //监听端口 + .SetBindIPHost(7811) + .ConfigurePlugins(a => + { + a.Add(); + + a.AddModbusSlavePoint()//添加一个从站站点 + .SetSlaveId(1)//设置站点号 + .UseIgnoreSlaveId()//忽略站号验证 + .SetModbusDataLocater(new ModbusDataLocater(10, 10, 10, 10));//设置数据区 + }) + ); +await slave.StartAsync(); +Console.WriteLine("服务已启动"); +``` + +## 七、添加多个站点 + +`Modbus`是一主多从的架构。在实际使用时,一个`ModbusSlave`部署至一个机器(这里不考虑虚拟机),即视为一个从机。但事实上,按照`Modbus`协议,一个`ModbusSlave`可以有多个站点。以`ModbusTcpSlave`为例,他可以通过`IP地址`确定到唯一的设备,同时还可以通过`SlaveId`区分不同的站点。 + +所以,我们的`ModbusSlave`也可以有多个站点。以`ModbusTcpSlave`为例,具体操作如下: + +```csharp {18-28} showLineNumbers +static ModbusTcpSlave CreateModbusTcpSlave() +{ + var service = new ModbusTcpSlave(); + await service.SetupAsync(new TouchSocketConfig() + //监听端口 + .SetListenIPHosts(7808) + .ConfigurePlugins(a => + { + a.Add(); + + //当添加多个站点时,需要禁用IgnoreSlaveId的设定 + + a.AddModbusSlavePoint()//添加一个从站站点 + .SetSlaveId(1)//设置站点号 + //.UseIgnoreSlaveId()//忽略站号验证 + .SetModbusDataLocater(new ModbusDataLocater(10,10,10,10));//设置数据区 + + a.AddModbusSlavePoint()//再添加一个从站站点 + .SetSlaveId(2)//设置站点号 + //.UseIgnoreSlaveId()//忽略站号验证 + .SetModbusDataLocater(new ModbusDataLocater()//设置数据区 + { + //下列配置表示,起始地址从1000开始,10个长度 + Coils = new BooleanDataPartition(1000, 10), + DiscreteInputs = new BooleanDataPartition(1000, 10), + HoldingRegisters = new ShortDataPartition(1000, 10), + InputRegisters = new ShortDataPartition(1000, 10) + }); + }) + ); + await service.StartAsync(); + Console.WriteLine("服务已启动"); + return service; +} +``` + +:::caution 警告 + +当添加多个站点时,需要**禁用**`IgnoreSlaveId`的设定。 + +::: + +:::tip 提示 + +所有的`ModbusSlave`均支持多站点访问。且多个站点还能共用同一个`ModbusDataLocater`。 + +::: + +## 八、本地读写操作 + +所有的数据区均在`IModbusSlavePoint`中。所以需要先找到`IModbusSlavePoint`实例。 + +一般的,如果你使用了插件`IModbusSlaveExecutingPlugin`或者`IModbusSlaveExecutedPlugin`。插件的sender即为`IModbusSlavePoint`。 + +如果你只能访问到`IModbusSlave`接口(例如:`ModbusTcpSlave`),那么你可以通过`ModbusSlave`的`GetSlavePointBySlaveId`方法获取到`IModbusSlavePoint`。 + +```csharp showLineNumbers +var modbusSlavePoint= slave.GetSlavePointBySlaveId(slaveId: 1); +``` + +然后在`IModbusSlavePoint`接口中有`ModbusDataLocater`属性,该属性是`Modbus`数据存储区,即`线圈`、`离散输入`、`保持寄存器`、`输入寄存器`。 + +该属性是可读可写的。所以,即使是不同`IModbusSlavePoint`。也可以二次赋值,使其实现更多功能。 + +同时。可以通过该属性,创建一个本地`ModbusMaster`,用于直接读写。 + +具体读写操作和[ModbusMaster读写](./modbusmaster.mdx)一致。 + +```csharp showLineNumbers +var localMaster = modbusSlavePoint.ModbusDataLocater.CreateDataLocaterMaster(); +var coils = localMaster.ReadCoils(0, 1); +``` + +:::tip 提示 + +通过`modbusSlavePoint.ModbusDataLocater.CreateDataLocaterMaster()`创建的Master,具备[ModbusMaster](./modbusmaster.mdx)的所有功能(包括`ModbusObject`操作)。且是直接读取内存的,中间没有任何通信,所以速度非常快。 + +::: + + +[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Modbus/ModbusSlaveConsoleApp) + diff --git a/handbook/versioned_docs/version-3.1/mqttclient.mdx b/handbook/versioned_docs/version-3.1/mqttclient.mdx new file mode 100644 index 000000000..ccc81b7a0 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/mqttclient.mdx @@ -0,0 +1,295 @@ +--- +id: mqttclient +title: Mqtt客户端使用 +--- + +import Tag from "@site/src/components/Tag.js"; +import { TouchSocketMqttDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +`MqttClient`是遵循Mqtt协议的消息客户端,支持连接Mqtt Broker、订阅主题、发布消息等功能,支持QoS 0/1/2三种消息质量等级。 + +## 二、特点 + +- 轻量级协议支持 +- QoS消息质量保障 +- 遗嘱消息支持 +- 保留消息处理 +- TLS加密通信 +- 主题通配符匹配(+/#) + +## 三、应用场景 + +- IoT设备数据上报 +- 跨平台消息推送 +- 低带宽环境通信 +- 设备状态同步 + +## 四、可配置项 + +继承所有[TcpClient](./tcpclient.mdx)的配置。除此之外还支持Mqtt的一些专有配置。 + +
+可配置项 +
+ +#### SetMqttConnectOptions + +`MqttConnectOptions` 类用于配置 Mqtt 客户端连接到 Mqtt 服务端时的参数,支持 Mqtt 3.1.1 及 5.0+ 协议版本。以下是各配置项的详细说明: + +**基础连接选项(通用)** + +| 属性名 | Mqtt 版本支持 | 说明 | +|-------------------------|---------------------|------------------------------------------------------------------------------------------| +| `CleanSession` | 3.1.1+ | 是否清理会话(断开后删除未完成的遗嘱和离线消息)。 | +| `ClientId` | 3.1.1+ | 客户端唯一标识符(服务端用于识别客户端);Mqtt 3.1.1 中必填(长度 1-23 字节,ASCII 字符);5.0+ 允许空字符串(仅当服务端允许时)。 | +| `KeepAlive` | 3.1.1+ | 心跳保活时长(单位:秒),表示客户端与服务端保持连接的最长时间;需与服务端配置兼容(服务端可能拒绝过大或过小的值),默认值通常为 60 秒。 | +| `Password` | 3.1.1+ | 连接认证密码(与 `UserName` 配合使用);若 `UserName` 为空则无效;5.0+ 支持二进制数据(需通过 `AuthenticationData` 传递)。 | +| `ProtocolName` | 3.1.1+ | Mqtt 协议名称(固定为 "MQTT"),默认值为 "MQTT",通常无需手动设置。 | +| `UserName` | 3.1.1+ | 连接认证用户名(与 `Password` 配合使用);5.0+ 支持二进制数据(需通过 `AuthenticationData` 传递)。 | +| `Version` | 3.1.1+/5.0+ | Mqtt 协议版本(如 `MqttProtocolVersion.V311` 或 `V500`);需根据服务端支持的版本设置,否则可能导致连接失败。 | + +--- + +**遗嘱(Will)消息配置(可选)** + +当 `WillFlag` 为 `true` 时,服务端会在客户端异常断开时发布遗嘱消息。仅部分属性在 `WillFlag=true` 时生效。 + +| 属性名 | Mqtt 版本支持 | 说明 | +|-----------------------------|---------------------|------------------------------------------------------------------------------------------| +| `WillFlag` | 3.1.1+ | 是否启用遗嘱消息(若为 `true`,需配置遗嘱相关属性)。 | +| `WillQos` | 3.1.1+ | 遗嘱消息的服务质量等级(0/1/2);需与服务端支持的 QoS 等级兼容。 | +| `WillRetain` | 3.1.1+ | 是否保留遗嘱消息(服务端保留最新遗嘱消息供新订阅者接收);默认值为 `false`;若为 `true`,服务端需支持保留消息功能。 | +| `WillMessage` | 3.1.1+ | 遗嘱消息的内容(文本类型,Mqtt 5.0+ 支持二进制数据,需通过 `WillContentType` 和 `WillPayloadFormatIndicator` 配合)。 | +| `WillTopic` | 3.1.1+ | 遗嘱消息发布的主题(需符合 Mqtt 主题命名规则);主题需提前订阅才能接收遗嘱消息。 | +| `WillContentType` | 5.0+ | 遗嘱消息内容的 MIME 类型(如 "text/plain"、"application/json");描述 `WillMessage` 的格式(仅 5.0+ 有效)。 | +| `WillPayloadFormatIndicator`| 5.0+ | 遗嘱消息负载格式指示符(如 `Raw`、`Utf8`);更细粒度描述负载格式(仅 5.0+ 有效)。 | +| `WillResponseTopic` | 5.0+ | 遗嘱消息触发后,服务端向客户端发送响应的主题;需客户端提前订阅该主题以接收响应。 | +| `WillCorrelationData` | 5.0+ | 遗嘱消息关联的上下文数据(用于匹配请求与响应);需与服务端约定格式(仅 5.0+ 有效)。 | +| `WillDelayInterval` | 5.0+ | 遗嘱消息延迟发布的时间间隔(单位:秒);遗嘱触发后,延迟指定时间再发布(仅 5.0+ 有效)。 | +| `WillMessageExpiryInterval` | 5.0+ | 遗嘱消息的有效期(单位:秒,过期后服务端删除);超过此时间未发布的遗嘱消息将被丢弃(仅 5.0+ 有效)。 | + +--- + +**Mqtt 5.0+ 扩展配置(可选)** + +仅当使用 Mqtt 5.0+ 协议时生效,用于更细粒度的连接控制。 + +| 属性名 | Mqtt 版本支持 | 说明 | +|-----------------------------|---------------------|------------------------------------------------------------------------------------------| +| `UserProperties` | 5.0+ | 用户自定义属性(键值对,用于传递业务元数据);键值对需符合 Mqtt 5.0 规范(键为 UTF-8 字符串,值支持多种类型)。 | +| `AuthenticationMethod` | 5.0+ | 认证方法名称(如 "OAuth2"、"Custom");需与服务端支持的认证方法一致(仅 5.0+ 支持扩展认证)。 | +| `AuthenticationData` | 5.0+ | 认证方法的附加数据(如令牌、签名等);配合 `AuthenticationMethod` 使用(仅 5.0+ 有效)。 | +| `MaximumPacketSize` | 5.0+ | 客户端请求的最大数据包大小(单位:字节);服务端会返回其支持的最大值(客户端需遵守)。 | +| `ReceiveMaximum` | 5.0+ | 客户端能接收的最大 QoS 1/2 消息数量(防止消息堆积);默认值通常为 65535(需与服务端配置兼容)。 | +| `RequestProblemInformation` | 5.0+ | 是否请求服务端在出错时返回详细问题信息(如原因码、原因字符串);默认值为 `true`(建议开启以便排查问题)。 | +| `RequestResponseInformation`| 5.0+ | 是否请求服务端返回连接响应的额外信息(如会话过期时间);默认值为 `false`(仅当需要时启用)。 | +| `SessionExpiryInterval` | 5.0+ | 会话过期时间间隔(单位:秒,0 表示立即过期);客户端断开后,服务端保留会话的时间(仅当 `CleanSession=false` 时有效)。 | +| `TopicAliasMaximum` | 5.0+ | 客户端允许的最大主题别名值(服务站不能使用超过此值的别名);默认值为 0(表示不使用主题别名)。 | + +--- + +**注意事项** + +1. **协议版本兼容性**:部分属性(如 `UserProperties`、`AuthenticationMethod`)仅在 Mqtt 5.0+ 中有效,需根据服务端版本选择配置。 +2. **遗嘱消息生效条件**:`WillFlag` 必须为 `true`,且 `WillTopic` 和 `WillMessage` 需有效配置,否则遗嘱不会被发送。 +3. **认证扩展**:5.0+ 的 `AuthenticationData` 需配合自定义认证插件使用,具体格式由服务端定义。 +4. **只读属性**:`WillMessage` 和 `WillTopic` 标记为 `internal set`,通常通过构造函数或专用方法设置(如 `MqttApplicationMessage`)。 + +
+
+ +## 五、支持插件 + +| 插件方法| 功能 | +| --- | --- | +| IMqttConnectingPlugin | 当Mqtt客户端正在连接之前调用此方法。 | +| IMqttConnectedPlugin | 当Mqtt客户端连接成功时调用。 | +| IMqttClosingPlugin | 当Mqtt客户端正在关闭时调用。| +| IMqttClosedPlugin | 当Mqtt客户端断开连接后触发。 | +| IMqttReceivingPlugin | 在收到Mqtt所有消息时触发,可以通过`e.MqttMessage`获取到`Mqtt`的所有消息,包括订阅、订阅确认、发布、发布确认等。 | +| IMqttReceivedPlugin | 当接收到Mqtt发布消息,且成功接收时触发。可以通过`e.MqttMessage`获取到`Mqtt`的发布消息。 | + + +## 六、创建Mqtt客户端 + +```csharp showLineNumbers +var client = new MqttTcpClient(); +await client.SetupAsync(new TouchSocketConfig() + .SetRemoteIPHost("tcp://127.0.0.1:7789") + .SetMqttConnectOptions(options => + { + options.ClientId = "TestClient"; + options.UserName = "TestUser"; + options.Password = "TestPassword"; + options.ProtocolName = "MQTT"; + options.Version = MqttProtocolVersion.V311; + options.KeepAlive = 60; + options.CleanSession = true; + + // v5可以继续配置其他 + + // options.UserProperties = new[] + // { + // new MqttUserProperty("key1","value1"), + // new MqttUserProperty("key2","value2") + // }; + }) + .ConfigurePlugins(a => + { + a.AddMqttConnectingPlugin(async (mqttSession, e) => + { + Console.WriteLine($"Client Connecting:{e.ConnectMessage.ClientId}"); + await e.InvokeNext(); + }); + + a.AddMqttConnectedPlugin(async (mqttSession, e) => + { + Console.WriteLine($"Client Connected:{e.ConnectMessage.ClientId}"); + await e.InvokeNext(); + }); + + a.AddMqttReceivingPlugin(async (mqttSession, e) => + { + await e.InvokeNext().ConfigureAwait(EasyTask.ContinueOnCapturedContext); + }); + + a.AddMqttReceivedPlugin(async (mqttSession, e) => + { + var message = e.Message; + await e.InvokeNext().ConfigureAwait(EasyTask.ContinueOnCapturedContext); + }); + + a.AddMqttClosingPlugin(async (mqttSession, e) => + { + Console.WriteLine($"Client Closing:{e.MqttMessage.MessageType}"); + await e.InvokeNext(); + }); + + a.AddMqttClosedPlugin(async (mqttSession, e) => + { + Console.WriteLine($"Client Closed:{e.Message}"); + await e.InvokeNext(); + }); + + }));//载入配置 + +await client.ConnectAsync();//连接 +``` + + +## 七、订阅与取消订阅 + +### 7.1 订阅 + +按照Mqtt协议,客户端可以通过`Subscribe`指令订阅一个或多个主题,服务端会返回订阅结果。 + +```csharp {10} showLineNumbers +var topic1 = "topic1"; +var topic2 = "topic2"; +SubscribeRequest subscribeRequest1=new SubscribeRequest(topic1, QosLevel.AtLeastOnce);//订阅请求 +SubscribeRequest subscribeRequest2=new SubscribeRequest(topic2, QosLevel.AtMostOnce);//可以设置不同的Qos级别 + +//多个订阅请求 +var mqttSubscribeMessage = new MqttSubscribeMessage(subscribeRequest1, subscribeRequest2); + +//执行订阅 +var mqttSubAckMessage = await client.SubscribeAsync(mqttSubscribeMessage); + +//输出订阅结果 +foreach (var item in mqttSubAckMessage.ReturnCodes) +{ + Console.WriteLine($"ReturnCode:{item}"); +} +``` + +### 7.2 取消订阅 + +按照Mqtt协议,客户端可以通过`Unsubscribe`指令取消订阅一个或多个主题,服务端会返回所有取消订阅结果。 + +```csharp {5} showLineNumbers +var topic1 = "topic1"; +var topic2 = "topic2"; + +//取消订阅 +var mqttUnsubAckMessage = await client.UnsubscribeAsync(new MqttUnsubscribeMessage(topic1,topic2)); +``` + + +## 八、接收消息 + +Mqtt组件的消息都是通过插件抛出的。你可以订阅`IMqttReceivingPlugin`插件和`IMqttReceivedPlugin`插件来接收消息。 + +正如插件说明所示,`IMqttReceivingPlugin`插件可以在收到所有消息时触发,`IMqttReceivedPlugin`插件可以在收到发布消息时触发。 + +所以如果你只关心发布成功的消息,那么可以只订阅`IMqttReceivedPlugin`插件即可。 + +你可以直接使用委托实现订阅: + +```csharp {3-17} showLineNumbers +.ConfigurePlugins(a => +{ + a.AddMqttReceivedPlugin(async (client, e) => + { + var mqttMessage = e.MqttMessage; + Console.WriteLine("Reved:" + mqttMessage); + + //订阅消息的主题 + var topicName = mqttMessage.TopicName; + + //订阅消息的Qos级别 + var qosLevel = mqttMessage.QosLevel; + + //订阅消息的Payload + var payload = mqttMessage.Payload; + await e.InvokeNext(); + }); +}) +``` + +或者使用插件类来继承接口实现: + +```csharp showLineNumbers +class MyMqttReceivedPlugin : PluginBase, IMqttReceivedPlugin +{ + public async Task OnMqttReceived(IMqttSession client, MqttReceivedEventArgs e) + { + var mqttMessage = e.MqttMessage; + + //订阅消息的主题 + var topicName = mqttMessage.TopicName; + + //订阅消息的Qos级别 + var qosLevel = mqttMessage.QosLevel; + + //订阅消息的Payload + var payload = mqttMessage.Payload; + await e.InvokeNext(); + } +} +``` + +然后把这个类添加到插件即可: + +```csharp {3} showLineNumbers +.ConfigurePlugins(a => +{ + a.Add();//添加自定义插件 +}) +``` + +## 九、发布消息 + +```csharp {3} showLineNumbers +MqttPublishMessage message = new(topic1, false, QosLevel.AtLeastOnce, Encoding.UTF8.GetBytes("Hello World")); + +await client.PublishAsync(message); +``` + + diff --git a/handbook/versioned_docs/version-3.1/mqttservice.mdx b/handbook/versioned_docs/version-3.1/mqttservice.mdx new file mode 100644 index 000000000..29c3fd9aa --- /dev/null +++ b/handbook/versioned_docs/version-3.1/mqttservice.mdx @@ -0,0 +1,186 @@ +--- +id: mqttservice +title: Mqtt服务器 +--- + +import Tag from "@site/src/components/Tag.js"; +import { TouchSocketMqttDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +`MqttTcpService` 是基于 Mqtt 协议的消息服务端,支持客户端接入管理、订阅关系维护、消息路由转发、遗嘱消息处理等功能,兼容 Mqtt 3.1.1 及 5.0+ 协议版本。 + +## 二、特点 + +- 多协议版本支持(v3.1.1/v5.0) +- 高性能异步架构设计 +- 主题树形管理机制 +- 精确的 QoS 保障 +- 遗嘱消息转发 +- 插件化扩展体系 +- TLS 加密通信 +- 客户端黑白名单控制 + +## 三、应用场景 + +- 工业物联网平台 +- 实时数据监控中心 +- 智慧城市中枢系统 +- 私有化消息总线 +- 设备远程管理服务 + +## 四、可配置项 + +继承所有 [TcpService](./tcpservice.mdx) 的配置。除此之外还支持 Mqtt 服务端专有配置。 + +
+可配置项 +
+ +
+
+ +## 五、支持插件 + +| 插件方法| 功能 | +| --- | --- | +| IMqttConnectingPlugin | 当Mqtt客户端正在连接之前调用此方法。 | +| IMqttConnectedPlugin | 当Mqtt客户端连接成功时调用。 | +| IMqttClosingPlugin | 当Mqtt客户端正在关闭时调用。| +| IMqttClosedPlugin | 当Mqtt客户端断开连接后触发。 | +| IMqttReceivingPlugin | 在收到Mqtt所有消息时触发,可以通过`e.MqttMessage`获取到`Mqtt`的所有消息,包括订阅、订阅确认、发布、发布确认等。 | +| IMqttReceivedPlugin | 当接收到Mqtt发布消息,且成功接收时触发。可以通过`e.MqttMessage`获取到`Mqtt`的发布消息。 | + +## 六、创建Mqtt服务端 + +```csharp showLineNumbers +var service = new MqttTcpService(); +await service.SetupAsync(new TouchSocketConfig() + .SetListenIPHosts(7789) // 监听所有网卡 + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .ConfigurePlugins(a => + { + a.AddMqttClientConnectedPlugin(async (service, e) => + { + Console.WriteLine($"Client Connected:{e.ClientId}"); + await e.InvokeNext(); + }); + + a.AddMqttClientDisconnectedPlugin(async (service, e) => + { + Console.WriteLine($"Client Disconnected:{e.ClientId}"); + await e.InvokeNext(); + }); + })); +await service.StartAsync(); +``` + + +## 七、接收消息 + +在Mqtt服务端创建好之后,Mqtt组件的消息都是通过插件抛出的。你可以订阅`IMqttReceivingPlugin`插件和`IMqttReceivedPlugin`插件来接收消息。 + +正如插件说明所示,`IMqttReceivingPlugin`插件可以在收到所有消息时触发,`IMqttReceivedPlugin`插件可以在收到发布消息时触发。 + +所以如果你只关心发布成功的消息,那么可以只订阅`IMqttReceivedPlugin`插件即可。 + +但是对于服务器来说,也可能需要获取到的消息可能是订阅、发布、取消订阅等。所以你可以通过`IMqttReceivingPlugin`的`e.MqttMessage`来获取到具体的消息类型。 + +下列将简单演示。 + +### 7.1 订阅消息 + +```csharp showLineNumbers +.ConfigurePlugins(a => +{ + a.AddMqttReceivingPlugin(async (client, e) => + { + switch (e.MqttMessage) + { + case MqttSubscribeMessage message: + { + //订阅消息 + Console.WriteLine("Reving:" + e.MqttMessage.MessageType); + foreach (var subscribeRequest in message.SubscribeRequests) + { + var topic = subscribeRequest.Topic; + var qosLevel = subscribeRequest.QosLevel; + //或者其他属性 + Console.WriteLine($"Subscribe Topic:{topic},QosLevel:{qosLevel}"); + } + break; + } + default: + break; + } + Console.WriteLine("Reving:" + e.MqttMessage.MessageType); + await e.InvokeNext(); + }); +}) +``` + +### 7.2 取消订阅消息 + +```csharp showLineNumbers +.ConfigurePlugins(a => +{ + a.AddMqttReceivingPlugin(async (client, e) => + { + switch (e.MqttMessage) + { + case MqttUnsubscribeMessage message: + { + + //取消订阅消息 + Console.WriteLine("Reving:" + e.MqttMessage.MessageType); + foreach (var topic in message.TopicFilters) + { + //取消订阅的主题 + Console.WriteLine($"Unsubscribe Topic:{topic}"); + } + break; + } + default: + break; + } + Console.WriteLine("Reving:" + e.MqttMessage.MessageType); + await e.InvokeNext(); + }); +}) +``` + +### 7.3 发布消息 + +所以如果你只关心发布成功的消息,那么可以只订阅`IMqttReceivedPlugin`插件即可。 + +你可以直接使用委托实现订阅: + +```csharp {3-17} showLineNumbers +.ConfigurePlugins(a => +{ + a.AddMqttReceivedPlugin(async (client, e) => + { + var mqttMessage = e.MqttMessage; + Console.WriteLine("Reved:" + mqttMessage); + + //订阅消息的主题 + var topicName = mqttMessage.TopicName; + + //订阅消息的Qos级别 + var qosLevel = mqttMessage.QosLevel; + + //订阅消息的Payload + var payload = mqttMessage.Payload; + await e.InvokeNext(); + }); +}) +``` + diff --git a/handbook/versioned_docs/version-3.1/namedpipeclient.mdx b/handbook/versioned_docs/version-3.1/namedpipeclient.mdx new file mode 100644 index 000000000..85bb7ed81 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/namedpipeclient.mdx @@ -0,0 +1,282 @@ +--- +id: namedpipeclient +title: 创建NamedPipeClient +--- + +import Tag from "@site/src/components/Tag.js"; +import { TouchSocketNamedPipeDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +NamedPipeClient是命名管道系客户端基类,他直接参与管道的连接、发送、接收、处理、断开等,他的业务与服务器的**NamedPipeSessionClient**是一一对应的。 + +## 二、特点 + +- 简单易用。 +- 内存池支持 +- 高性能 +- 适配器预处理,一键式解决**分包**、**粘包**、对象解析(即适用于Tcp的一切适配器)等。 +- 超简单的同步发送、异步发送、接收等操作。 +- 基于委托、插件驱动,让每一步都能执行AOP。 + +## 三、产品应用场景 + +- 所有本机IPC(进程通讯)基础使用场景:可跨平台、跨语言使用。 + +## 四、可配置项 + +
+可配置项 +
+ +#### SetMaxPackageSize + +数据包最大值(单位:byte),默认1024×1024×10。该值会在适当时间,直接作用DataHandlingAdapter.MaxPackageSize。 + +#### SetPipeName +设置管道名称。 + +
+
+ +## 五、支持插件 + +| 插件方法| 功能 | +| --- | --- | +| INamedPipeConnectingPlugin | 此时管道实际上已经完成连接,但是并没有启动接收,然后触发。 | +| INamedPipeConnectedPlugin | 同意连接,且成功启动接收后触发 | +| INamedPipeClosingPlugin | 当客户端主动调用Close时触发 | +| INamedPipeClosedPlugin | 当客户端断开连接后触发 | +| INamedPipeReceivingPlugin | 在收到原始数据时触发,所有的数据均在ByteBlock里面。 | +| INamedPipeReceivedPlugin | 在收到适配器数据时触发,根据适配器类型,数据可能在ByteBlock或者IRequestInfo里面。 | +| INamedPipeSendingPlugin | 当即将发送数据时,调用该方法在适配器之后,接下来即会发送数据。 | + +## 六、创建NamedPipeClient + +#### 6.1 简单创建 + +简单的处理逻辑可通过**Connecting**、**Connected**、**Received**等委托直接实现。 + +代码如下: + +```csharp showLineNumbers +var client = new NamedPipeClient(); +client.Connecting = (client, e) => { return EasyTask.CompletedTask; };//即将连接到服务器,此时已经创建管道,但是还未建立连接 +client.Connected = (client, e) => { return EasyTask.CompletedTask; };//成功连接到服务器 +client.Closing = (client, e) => { return EasyTask.CompletedTask; };//即将从服务器断开连接。此处仅主动断开才有效。 +client.Closed = (client, e) => { return EasyTask.CompletedTask; };//从服务器断开连接,当连接不成功时不会触发。 +client.Received = (client, e) => +{ + //从服务器收到信息。但是一般byteBlock和requestInfo会根据适配器呈现不同的值。 + string mes = e.ByteBlock.Span.ToString(Encoding.UTF8); + client.Logger.Info($"客户端接收到信息:{mes}"); + return EasyTask.CompletedTask; +}; + +//载入配置 +await client.SetupAsync(new TouchSocketConfig() + .SetPipeName("TouchSocketPipe")//设置命名管道名称 + .ConfigureContainer(a => + { + a.AddConsoleLogger();//添加一个日志注入 + })); + +await client.ConnectAsync();//调用连接,当连接不成功时,会抛出异常。 + +//Result result =await client.TryConnectAsync();//或者可以调用TryConnect +//if (result.IsSuccess()) +//{ + +//} + +client.Logger.Info("客户端成功连接"); +``` + +#### 6.2 继承实现 + +一般继承实现的话,可以从NamedPipeClientBase继承。 + +```csharp showLineNumbers +class MyNamedPipeClient : NamedPipeClient +{ + protected override async Task OnNamedPipeReceived(ReceivedDataEventArgs e) + { + //此处逻辑单线程处理。 + + //此处处理数据,功能相当于Received委托。 + string mes = e.ByteBlock.Span.ToString(Encoding.UTF8); + Console.WriteLine($"已接收到信息:{mes}"); + + await base.OnNamedPipeReceived(e); + } +} +``` + +```csharp showLineNumbers +var client = new MyNamedPipeClient(); +//载入配置 +await client.SetupAsync(new TouchSocketConfig() + .SetPipeName("TouchSocketPipe")//设置命名管道名称 + .ConfigureContainer(a => + { + a.AddConsoleLogger();//添加一个日志注入 + })); + +await client.ConnectAsync();//调用连接,当连接不成功时,会抛出异常。 +``` + +## 七、接收数据 + +在`NamedPipeClient`中,接收数据的方式有很多种。多种方式可以组合使用。 + +### 7.1 Received委托处理 + +当使用`NamedPipeClient`创建客户端时,内部已经定义好了一个外置委托Received,可以通过该委托直接接收数据。 + +```csharp showLineNumbers +var client = new NamedPipeClient(); +client.Received = (client, e) => +{ + //从服务器收到信息 + string mes = e.ByteBlock.Span.ToString(Encoding.UTF8); + Console.WriteLine($"接收到信息:{mes}"); + return EasyTask.CompletedTask; +}; +await client.ConnectAsync("TouchSocketPipe"); +``` + +### 7.2 插件处理 推荐 + +按照TouchSocket的设计理念,使用插件处理数据,是一项非常简单,且高度解耦的方式。步骤如下: + +(1)声明插件 + +插件可以先继承`PluginBase`,然后再实现需要的功能插件接口,可以按需选择泛型或者非泛型实现。 + +如果已经有继承类,直接实现`IPlugin`接口即可。 + +```csharp showLineNumbers +public class MyPlugin : PluginBase, INamedPipeReceivedPlugin +{ + public async Task OnNamedPipeReceived(INamedPipeSession client, ReceivedDataEventArgs e) + { + //这里处理数据接收 + //根据适配器类型,e.ByteBlock与e.RequestInfo会呈现不同的值,具体看文档=》适配器部分。 + ByteBlock byteBlock = e.ByteBlock; + IRequestInfo requestInfo = e.RequestInfo; + + ////表示该数据已经被本插件处理,无需再投递到其他插件。 + + await e.InvokeNext(); + } +} +``` + +(2)创建使用插件处理的客户端 + +```csharp showLineNumbers +var client = new NamedPipeClient(); +await client.SetupAsync(new TouchSocketConfig() + .SetPipeName("TouchSocketPipe")//设置命名管道名称 + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .ConfigurePlugins(a => + { + a.Add(); + })); + +await client.ConnectAsync(); +``` + +## 八、发送数据 + +`NamedPipeClient`已经内置了发送方法,直接调用就可以发送,如果发送失败,则会立即抛出异常。 + +```csharp showLineNumbers +//原生 +public Task SendAsync(string id, ReadOnlyMemory memory); +public Task SendAsync(string id, IRequestInfo requestInfo); +``` + +:::tip 提示 + +框架不仅内置了字节的发送,也扩展了字符串等常见数据的发送。而且还包括了`TrySend`等不会抛出异常的发送方法。 + +::: + +:::caution 注意 + +所有的发送,框架内部实际上**只实现了异步发送**,但是为了兼容性,仍然保留了同步发送的扩展。但是强烈建议如有可能,请**务必使用异步发送来提高效率**。 + +::: + + +# 九、断线重连 + +断线重连,即tcp客户端在断开服务器后,主动发起的再次连接请求。 + +### 9.1 触发型重连 + +触发型重连,依靠的是NamedPipe断开事件(Closed)发生时,再次尝试连接。所以,这就要求客户端在初始时,至少完成一次连接。 + +```csharp {8,11} showLineNumbers +var client = new NamedPipeClient(); +await client.SetupAsync(new TouchSocketConfig() + .SetPipeName("TouchSocketPipe")//设置命名管道名称 + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .ConfigurePlugins(a => + { + a.UseNamedPipeReconnection(); + })); + +await client.ConnectAsync(); +``` +:::caution 注意 + +**触发重连,必须满足以下几个要求:** + +1. 必须完成第一次连接。 +2. 必须是被动断开,如果是客户端主动调用Close、Disposed等方法主动断开的话,一般不会生效。 +3. 必须有显式的断开信息,也就是说,直接拔网线的话,不会立即生效,会等tcp保活到期后再生效。 + +::: + + +### 9.2 使用Polling轮询连接插件 + +使用Polling断线重连,是一种无人值守的连接方式,它不要求首次连接。 + +```csharp showLineNumbers +.ConfigurePlugins(a => +{ + a.UseNamedPipeReconnection() + .UsePolling(TimeSpan.FromSeconds(1)); +}) +``` + +:::caution 注意 + +**Polling重连,必须满足以下几个要求:** + +1. 必须有显式的断开信息,不然不会生效。 + +::: + +:::tip 提示 + +UseReconnection插件,可以通过设置SetActionForCheck,自己规定检查活性的方法。默认情况下,只会检验Online属性,所以无法检验出断网等情况。如果自己控制,则可以发送心跳包,以保证在线状态。 + +::: + + +[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/NamedPipe) diff --git a/handbook/versioned_docs/version-3.1/namedpipedescription.mdx b/handbook/versioned_docs/version-3.1/namedpipedescription.mdx new file mode 100644 index 000000000..d2b4396ab --- /dev/null +++ b/handbook/versioned_docs/version-3.1/namedpipedescription.mdx @@ -0,0 +1,32 @@ +--- +id: namedpipedescription +title: 命名管道描述 +--- + +## 一、说明 + +“命名管道”又名“命名管线”(Named Pipes),是一种简单的进程间通信(IPC)机制,之前Microsoft Windows大都提供了对它的支持(但不包括Windows CE),但目前已经与.net可以跨平台使用。命名管道可在同一台计算机的不同进程之间或在跨越一个网络的不同计算机的不同进程之间,支持可靠的、单向或双向的数据通信。推荐用命名管道作为进程通信方案的一项重要的原因是它们充分利用了Windows内建的安全特性(ACL等)。 + +用命名管道来设计跨计算机应用程序实际非常简单,并不需要事先深入掌握底层网络传送协议(如TCP、UDP、IP、IPX)的知识。这是由于命名管道利用了微软网络提供者(MSNP)重定向器通过同一个网络在各进程间建立通信,这样一来,应用程序便不必关心网络协议的细节。 + +命名管道是一个具有名称,可以单向或双面在一个服务器和一个或多个客户端之间进行通讯的管道。命名管道的所有实例拥有相同的名称,但是每个实例都有其自己的缓冲区和句柄,用来为不同客户端通许提供独立的管道。使用实例可使多个管道客户端同时使用相同的命名管道。 + +命名管道的名称在本系统中是唯一的。 + +命名管道可以被任意符合权限要求的进程访问。 + +命名管道只能在本地创建。 + +命名管道的客户端可以是本地进程(本地访问:\.\pipe\PipeName)或者是远程进程(访问远程:\ServerName\pipe\PipeName)。 + +命名管道使用比匿名管道灵活,服务端、客户端可以是任意进程,匿名管道一般情况下用于父子进程通讯。 + + +## 二、性能测试 + + +如下图,创建了一个命名管道服务器和一个客户端,进行简单的接收,流速达到了6.5Gb/s,效率大概是tcp的三倍。且无任何GC。 + + + +[性能测试Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/NamedPipe/NamedPipeStressTestingConsoleApp) \ No newline at end of file diff --git a/handbook/versioned_docs/version-3.1/namedpipeservice.mdx b/handbook/versioned_docs/version-3.1/namedpipeservice.mdx new file mode 100644 index 000000000..e4e75ccf6 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/namedpipeservice.mdx @@ -0,0 +1,545 @@ +--- +id: namedpipeservice +title: 创建命名管道服务器 +--- + +import Tag from "@site/src/components/Tag.js"; +import { TouchSocketNamedPipeDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +NamedPipeService是命名管道系服务器基类,它不参与实际的数据交互,只是配置、激活、管理、注销、重建**NamedPipeSessionClient**类实例。而**NamedPipeSessionClient**是当**NamedPipeClient(客户端)**成功连接服务器以后,由服务器新建的一个实例类,后续的所有通信,也都是通过该实例完成的。 + +## 二、特点 + +- 简单易用。 +- 异步执行。 +- 内存池支持 +- 高性能(实测服务器单客户端单线程,每秒可接收数据流量可达6.5GB/s)。 +- **多管道名称监听**(可以一次性监听多个管道名称) +- 适配器预处理,一键式解决**分包**、**粘包**、对象解析等(即适用于Tcp的一切适配器)。 +- 超简单的同步发送、异步发送、接收等操作。 +- 基于委托、[插件](./pluginsmanager.mdx)驱动,让每一步都能执行AOP。 + +## 三、产品应用场景 + +- 所有本机IPC(进程通讯)基础使用场景:可跨平台、跨语言使用。 + +## 四、服务器架构 + +服务器在收到**新客户端连接**时,会创建一个`NamedPipeSessionClient`的派生类实例,与客户端`NamedPipeClient`一一对应,后续的数据通信均由此实例负责。 + +`NamedPipeSessionClient`在Service里面以字典映射。Id为键,`NamedPipeSessionClient`本身为值。 + +```mermaid +flowchart TD; + Service-->NamedPipeSessionClient-1; + Service-->NamedPipeSessionClient-2; + Service-->NamedPipeSessionClient-3; + NamedPipeSessionClient-1-->NamedPipeClient-1; + NamedPipeSessionClient-2-->NamedPipeClient-2; + NamedPipeSessionClient-3-->NamedPipeClient-3; + +``` + +## 五、可配置项 + +
+可配置项 +
+ +#### SetMaxPackageSize +数据包最大值(单位:byte),默认1024×1024×10。该值会在适当时间,直接作用DataHandlingAdapter.MaxPackageSize。 + +#### SetPipeName +设置管道名称。 + +#### SetNamedPipeListenOptions +设置独立的管道监听,可以独立控制当前监听的个性化配置。 + +#### SetServerName +服务器标识名称,无实际使用意义。 + +#### SetMaxCount +最大可连接数,默认为10000 + +
+
+ + +## 六、支持插件 + +| 插件方法| 功能 | +| --- | --- | +| INamedPipeConnectingPlugin | 此时管道实际上已经完成连接,但是并没有启动接收,然后触发。 | +| INamedPipeConnectedPlugin | 同意连接,且成功启动接收后触发 | +| INamedPipeClosingPlugin | 当客户端主动调用Close时触发 | +| INamedPipeClosedPlugin | 当客户端断开连接后触发 | +| INamedPipeReceivingPlugin | 在收到原始数据时触发,所有的数据均在ByteBlock里面。 | +| INamedPipeReceivedPlugin | 在收到适配器数据时触发,根据适配器类型,数据可能在ByteBlock或者IRequestInfo里面。 | +| INamedPipeSendingPlugin | 当即将发送数据时,调用该方法在适配器之后,接下来即会发送数据。 | +| IIdChangedPlugin | 当NamedPipeSessionClient的Id发生改变时触发。 | + +## 七、创建NamedPipeService + +### 7.1 简单创建 + +直接初始化NamedPipeService,会使用默认的**NamedPipeSessionClient**。 +简单的处理逻辑可通过**Connecting**、**Connected**、**Received**等委托直接实现。 + +代码如下: + +```csharp showLineNumbers +var service = new NamedPipeService(); +service.Connecting = (client, e) => { return EasyTask.CompletedTask; };//有客户端正在连接 +service.Connected = (client, e) => { return EasyTask.CompletedTask; };//有客户端成功连接 +service.Closing = (client, e) => { return EasyTask.CompletedTask; };//有客户端正在断开连接,只有当主动断开时才有效。 +service.Closed = (client, e) => { return EasyTask.CompletedTask; };//有客户端断开连接 +service.Received = async (client, e) => +{ + //从客户端收到信息 + string mes = e.ByteBlock.Span.ToString(Encoding.UTF8); + client.Logger.Info($"已从{client.Id}接收到信息:{mes}"); + + await client.SendAsync(mes);//将收到的信息直接返回给发送方 + + //await client.SendAsync("id",mes);//将收到的信息返回给特定ID的客户端 + + ////将收到的信息返回给在线的所有客户端。 + ////注意:这只是个设计建议,实际上群发应该使用生产者消费者的设计模式 + //var ids = service.GetIds(); + //foreach (var clientId in ids) + //{ + // if (clientId != client.Id)//不给自己发 + // { + // await service.SendAsync(clientId, mes); + // } + //} +}; + +await service.SetupAsync(new TouchSocketConfig()//载入配置 + .SetPipeName("TouchSocketPipe")//设置命名管道名称 + .ConfigureContainer(a => + { + a.AddConsoleLogger();//添加一个控制台日志注入(注意:在maui中控制台日志不可用) + }) + .ConfigurePlugins(a => + { + //a.Add();//此处可以添加插件 + })); + +await service.StartAsync();//启动 +``` + +:::info 温馨提示 + +Service.StartAsync()方法并不会阻塞当前运行,所以当在控制台运行时,可能需要使用Console.ReadKey()等操作进行阻塞。 + +::: + +### 7.2 泛型创建 + +通过泛型创建服务器,可以实现很多有意思,且能**重写**一些有用的功能。下面就演示,如何通过泛型创建服务器。 + +代码如下: + +(1)建立`NamedPipeSessionClient`继承类。 + +```csharp showLineNumbers +public class MySessionClient : NamedPipeSessionClient +{ + protected override async Task OnNamedPipeReceived(ReceivedDataEventArgs e) + { + //此处逻辑单线程处理。 + + //此处处理数据,功能相当于Received委托。 + string mes = e.ByteBlock.Span.ToString(Encoding.UTF8); + Console.WriteLine($"已接收到信息:{mes}"); + await base.OnNamedPipeReceived(e); + } +} +``` + +(2)建立`NamedPipeService`继承类。实际上如果业务不涉及服务器配置的话,可以省略该步骤,使用**NamedPipeService的泛型**直接创建。 + +```csharp showLineNumbers +public class MyService : NamedPipeService +{ + protected override void LoadConfig(TouchSocketConfig config) + { + //此处加载配置,用户可以从配置中获取配置项。 + base.LoadConfig(config); + } + + protected override MySessionClient NewClient() + { + return new MySessionClient(); + } + + protected override Task OnNamedPipeConnecting(MySessionClient socketClient, ConnectingEventArgs e) + { + //此处逻辑会多线程处理。 + + //e.Id:对新连接的客户端进行ID初始化,默认情况下是按照设定的规则随机分配的。 + //但是按照需求,您可以自定义设置,例如设置为其IP地址。但是需要注意的是id必须在生命周期内唯一。 + + //e.IsPermitOperation:指示是否允许该客户端链接。 + return base.OnNamedPipeConnecting(socketClient, e); + } +} +``` + +(3)创建服务器(包含MyService)。 + +```csharp showLineNumbers +var service = new MyService(); +await service.StartAsync("TouchSocketPipe");//设置命名管道名称,启动 +``` + +:::tip 建议 + +由上述代码可以看出,通过继承,可以更加灵活的实现扩展。但实际上,很多业务我们希望大家能通过插件完成。 + +::: + +## 八、配置监听 + +### 8.1 Config直接配置 + +服务器在配置监听时,有多种方式实现。其中最简单、最常见的配置方式就是通过Config直接配置。 + +```csharp showLineNumbers +var service = new NamedPipeService(); +await service.SetupAsync(new TouchSocketConfig()//载入配置 + .SetPipeName("TouchSocketPipe"));//设置命名管道名称 + +await service.StartAsync();//启动 +``` + +### 8.2 直接添加监听配置 + +直接添加监听配置是更加个性化的监听配置,它可以单独控制指定监听地址的具体配置,例如:使用何种适配器等。 + +```csharp showLineNumbers +var service = new NamedPipeService(); +await service.SetupAsync(new TouchSocketConfig()//载入配置 + .SetPipeName("TouchSocketPipe")//设置默认命名管道名称 + .SetNamedPipeListenOptions(list => + { + //如果想实现多个命名管道的监听,即可这样设置,一直Add即可。 + list.Add(new NamedPipeListenOption() + { + Adapter = () => new NormalDataHandlingAdapter(), + Name = "TouchSocketPipe2"//管道名称 + }); + + list.Add(new NamedPipeListenOption() + { + Adapter = () => new NormalDataHandlingAdapter(), + Name = "TouchSocketPipe3"//管道名称 + }); + }) + .ConfigureContainer(a =>//容器的配置顺序应该在最前面 + { + a.AddConsoleLogger();//添加一个控制台日志注入(注意:在maui中控制台日志不可用) + }) + .ConfigurePlugins(a => + { + //a.Add();//此处可以添加插件 + })); + +await service.StartAsync();//启动 +``` + +:::info 温馨提示 + +`SetPipeName`可以和`SetNamedPipeListenOptions`可以同时使用,但是需要注意的是,Config的全局配置仅会对`SetPipeName`单独生效的。`SetNamedPipeListenOptions`的地址配置均是单独配置的。 + +::: + + +### 8.3 动态添加、移除监听配置 + +服务器支持在运行时,动态添加,和移除监听配置,这极大的为灵活监听提供了方便,并且还不影响现有连接。可以轻量级的实现Stop操作。 + +```csharp {5,16} +var service = new NamedPipeService(); +await service.SetupAsync(new TouchSocketConfig()); + +await service.StartAsync();//启动 + +service.AddListen(new NamedPipeListenOption()//在Service运行时,可以调用,直接添加监听 +{ + Name = "TouchSocketPipe4",//名称用于区分监听 + Adapter = () => new FixedHeaderPackageAdapter(),//可以单独对当前地址监听,配置适配器,还有其他可配置项,都是单独对当前地址有效。 +}); + +foreach (var item in service.Monitors) +{ + service.RemoveListen(item);//在Service运行时,可以调用,直接移除现有监听 +} +``` + +## 九、接收数据 + +在NamedPipeService中,接收数据的方式有很多种。多种方式可以组合使用。 + +### 9.1 Received委托处理 + +当使用NamedPipeService(非泛型)创建服务器时,内部已经定义好了一个外置委托Received,可以通过该委托直接接收数据。 + +```csharp showLineNumbers +var service = new NamedPipeService(); +service.Received = (client, e) => +{ + //从客户端收到信息 + string mes = e.ByteBlock.Span.ToString(Encoding.UTF8); + client.Logger.Info($"已从{client.Id}接收到信息:{mes}"); + return EasyTask.CompletedTask; +}; + +await service.StartAsync("TouchSocketPipe");//启动 +``` + +### 9.2 重写NamedPipeSessionClient处理 + +正如6.2所示,可以直接在MySessionClient的重写**ReceivedData**中直接处理数据。 + +### 9.3 插件处理 推荐 + +按照TouchSocket的设计理念,使用插件处理数据,是一项非常简单,且高度解耦的方式。步骤如下: + +(1)声明插件 + +插件可以先继承`PluginBase`,然后再实现需要的功能插件接口,可以按需选择泛型或者非泛型实现。 + +如果已经有继承类,直接实现`IPlugin`接口即可。 + +```csharp showLineNumbers +class MyNamedPipePlugin : PluginBase, INamedPipeConnectedPlugin, INamedPipeClosedPlugin, INamedPipeReceivedPlugin +{ + private readonly ILog m_logger; + + public MyNamedPipePlugin(ILog logger) + { + this.m_logger = logger; + } + + public async Task OnNamedPipeClosed(INamedPipeSession client, ClosedEventArgs e) + { + this.m_logger.Info("Disconnected"); + await e.InvokeNext(); + } + + public async Task OnNamedPipeConnected(INamedPipeSession client, ConnectedEventArgs e) + { + this.m_logger.Info("Connected"); + await e.InvokeNext(); + } + + public async Task OnNamedPipeReceived(INamedPipeSession client, ReceivedDataEventArgs e) + { + this.m_logger.Info(e.ByteBlock.ToString()); + await e.InvokeNext(); + } +} +``` + +(2)创建使用插件处理的服务器 + +```csharp {10} +var service = new NamedPipeService(); +await service.SetupAsync(new TouchSocketConfig() + .SetPipeName("TouchSocketPipe")//设置命名管道名称 + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .ConfigurePlugins(a => + { + a.Add(); + })); + +await service.StartAsync(); +``` + +### 9.4 异步阻塞接收 推荐 + +异步阻塞接收,即使用`await`的方式接收数据。其特点是能在代码上下文中,直接获取到收到的数据。 + +只是在服务器使用异步阻塞时,建议直接在`Connected`触发时相关使用。 + +下列将以插件为例: + +```csharp showLineNumbers +class NamedPipeServiceReceiveAsyncPlugin : PluginBase, INamedPipeConnectedPlugin +{ + public async Task OnNamedPipeConnected(INamedPipeSession client, ConnectedEventArgs e) + { + if (client is INamedPipeSessionClient sessionClient) + { + //receiver可以复用,不需要每次接收都新建 + using (var receiver = sessionClient.CreateReceiver()) + { + while (true) + { + //receiverResult每次接收完必须释放 + using (var receiverResult = await receiver.ReadAsync(CancellationToken.None)) + { + //收到的数据,此处的数据会根据适配器投递不同的数据。 + var byteBlock = receiverResult.ByteBlock; + var requestInfo = receiverResult.RequestInfo; + + if (receiverResult.IsCompleted) + { + //断开连接了 + Console.WriteLine($"断开信息:{receiverResult.Message}"); + return; + } + + //如果数据是从ByteBlock投递 + if (byteBlock != null) + { + Console.WriteLine(byteBlock.Span.ToString(Encoding.UTF8)); + } + + //如果是适配器信息,则可以直接处理requestInfo; + } + } + } + } + + } +} +``` + +在异步阻塞接收时,当接收的数据不满足解析条件时,还可以缓存起来,下次一起处理。 + +例如:下列将演示接收字符串,当没有发现“\r\n”时,将缓存数据,直到发现重要字符。 + +其中,`CacheMode`与`MaxCacheSize`是启用缓存的重要属性。`byteBlock.Seek`则是将已读取的数据游标移动至指定位置。 + +```csharp showLineNumbers +class NamedPipeServiceReceiveAsyncPlugin : PluginBase, INamedPipeConnectedPlugin +{ + public async Task OnNamedPipeConnected(INamedPipeSession client, ConnectedEventArgs e) + { + if (client is INamedPipeSessionClient sessionClient) + { + //receiver可以复用,不需要每次接收都新建 + using (var receiver = sessionClient.CreateReceiver()) + { + receiver.CacheMode = true; + receiver.MaxCacheSize = 1024 * 1024; + + var rn = Encoding.UTF8.GetBytes("\r\n"); + while (true) + { + //receiverResult每次接收完必须释放 + using (var receiverResult = await receiver.ReadAsync(CancellationToken.None)) + { + //收到的数据,此处的数据会根据适配器投递不同的数据。 + var byteBlock = receiverResult.ByteBlock; + var requestInfo = receiverResult.RequestInfo; + + if (receiverResult.IsCompleted) + { + //断开连接了 + Console.WriteLine($"断开信息:{receiverResult.Message}"); + return; + } + + //在CacheMode下,byteBlock将不可能为null + + var index = 0; + while (true) + { + var r = byteBlock.Span.Slice(index).IndexOf(rn); + if (r < 0) + { + break; + } + + var str = byteBlock.Span.Slice(index, r).ToString(Encoding.UTF8); + Console.WriteLine(str); + + index += rn.Length; + index += r; + } + + byteBlock.Seek(index); + } + } + } + } + } +} +``` + +:::tip 提示 + +异步阻塞接收,在等待接收数据时,不会阻塞线程资源,所以即使大量使用,也不会影响性能。 + +::: + +## 十、发送数据 + +按照架构图,每个客户端成功连接后,**服务器**都会创建一个派生自**NamedPipeSessionClient**的实例,并将其存以生成的Id为键,存在一个字典中。 + +所以,service提供了一下原生方法,可以通过id直接将数据发送至指定客户端。 + +```csharp showLineNumbers +//原生 +public Task SendAsync(string id, ReadOnlyMemory memory); +public Task SendAsync(string id, IRequestInfo requestInfo); +``` + +例如: + +```csharp showLineNumbers +await service.SendAsync("id",Encoding.UTF8.GetBytes("hello")); +``` + +亦或者,可以先用id查到对应的`NamedPipeSessionClient`,然后用其提供的方法直接发送。 + +例如: + +```csharp showLineNumbers +//尝试性获取 +if (service.TryGetClient("id", out var sessionClient)) +{ + await sessionClient.SendAsync("hello"); +} +``` + +```csharp showLineNumbers +//直接获取,如果id不存在,则会抛出异常 +var sessionClient = service.GetClient("id"); +await sessionClient.SendAsync("hello"); +``` + +:::caution 注意 + +由于`NamedPipeSessionClient`的生命周期是由框架控制的,所以最好尽量不要直接引用该实例,可以引用`NamedPipeSessionClient.Id`,然后再通过服务器查找。 + +::: + +:::caution 注意 + +所有的发送,框架内部实际上**只实现了异步发送**,但是为了兼容性,仍然保留了同步发送的扩展。但是强烈建议如有可能,请**务必使用异步发送来提高效率**。 + +::: + +:::tip 提示 + +框架不仅内置了字节的发送,也扩展了**字符串**等常见数据的发送。而且还包括了`TrySend`等不会抛出异常的发送方法。 + +::: + +[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/NamedPipe) + diff --git a/handbook/versioned_docs/version-3.1/natservice.mdx b/handbook/versioned_docs/version-3.1/natservice.mdx new file mode 100644 index 000000000..16dfe4803 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/natservice.mdx @@ -0,0 +1,194 @@ +--- +id: natservice +title: Tcp端口转发 +--- +import Tag from "@site/src/components/Tag.js"; +import { TouchSocketDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +**NatService**是具有转发功能的TCP服务器。他的职能是将收到的TCP数据转发到多个目标服务器。也能将多个目标服务器的数据转发到连接客户端。 + +## 二、常见使用场景 + +- **调试场景**在生产环境中,想要调试客户端,要么中断服务器,要么就将实际数据转发到Nat,然后在不影响实际场景的情况下进行调试。 +- **内网穿透场景**一般tcp都会使用转发式的内网穿透。 + +要使用`NatService`进行网络地址转换(NAT),您需要遵循以下步骤来设置和运行服务。此示例是基于C#语言,并使用了TouchSocket库来简化网络编程。 + + +## 三、创建服务 + +### 3.1 创建服务类 + +创建一个继承自`NatService`的类,并重写必要的方法。在这个例子中,我们创建了一个名为`MyNatService`的类。 + +```csharp showLineNumbers +internal class MyNatService : NatService +{ + protected override MyNatSessionClient NewClient() + { + return new MyNatSessionClient(); + } +} +``` + +### 3.2 创建会话客户端类 + +创建一个继承自`NatSessionClient`的类,并根据需要重写其中的方法。在这个例子中,我们创建了一个名为`MyNatSessionClient`的类。 + +```csharp showLineNumbers +class MyNatSessionClient : NatSessionClient +{ + #region 抽象类必须实现 + protected override async Task OnNatConnected(ConnectedEventArgs e) + { + try + { + await this.AddTargetClientAsync(config => + { + config.SetRemoteIPHost("127.0.0.1:7789"); + //还可以配置其他,例如断线重连,具体可看文档tcpClient部分 + }); + } + catch (Exception ex) + { + //目标客户端无法连接,也就是无法转发 + this.Logger.Exception(ex); + } + } + + protected override async Task OnTargetClientClosed(NatTargetClient client, ClosedEventArgs e) + { + //可以自己重连,或者其他操作 + + //或者直接移除 + this.RemoveTargetClient(client); + await EasyTask.CompletedTask; + } + #endregion +} +``` + +## 四、使用 + +在主函数中初始化服务并开始监听指定端口。这里我们监听的是7788端口,并且设置了日志记录。 + +```csharp showLineNumbers +private static async Task Main(string[] args) +{ + var service = new MyNatService(); + await service.SetupAsync(new TouchSocketConfig() + .SetListenIPHosts(7788) + .ConfigureContainer(a => + { + a.AddLogger(logger => + { + logger.AddConsoleLogger(); + logger.AddFileLogger(); + }); + })); + + await service.StartAsync(); + + service.Logger.Info("转发服务器已启动。已将7788端口转发到127.0.0.1:7789地址"); + + while (true) + { + Console.ReadKey(); + } +} +``` + +:::tip 提示 + +`NatService`支持客户端适配器和`Ssl`。也支持**IRequestInfo转发**和**转发Ssl**。 + +::: + +## 五、实现一转多 + +`NatService`支持将客户端数据转发到多个目标服务器。实现方法也比较简单,只需要使用`NatSessionClient`直接进行添加目标客户端即可。 + +```csharp {7-18} showLineNumbers +class MyNatSessionClient : NatSessionClient +{ + protected override async Task OnNatConnected(ConnectedEventArgs e) + { + try + { + await this.AddTargetClientAsync(config => + { + config.SetRemoteIPHost("127.0.0.1:7789"); + //还可以配置其他,例如断线重连,具体可看文档tcpClient部分 + }); + + //也可以再添加个转发端,实现一对多转发 + await this.AddTargetClientAsync(config => + { + config.SetRemoteIPHost("127.0.0.1:7790"); + //还可以配置其他,例如断线重连,具体可看文档tcpClient部分 + }); + } + catch (Exception ex) + { + //目标客户端无法连接,也就是无法转发 + this.Logger.Exception(ex); + } + } +} +``` + +## 六、实现多转一 + +`NatService`支持将多个客户端数据转发到单个目标服务器。主要实现方式如下: + +首先,需要独立初始化目标客户端,然后自行管理其创建和释放。 + +```csharp showLineNumbers +static class MyClientClass +{ + public static NatTargetClient TargetClient { get; } + + //初始化步骤可以在任意地方先调用 + public static async Task InitAsync() + { + //使用独立模式初始化,这样当NatSessionClient断开时不会释放该资源 + var client = new NatTargetClient(true); + await client.ConnectAsync("127.0.0.1:7789"); + } +} +``` + +然后在`NatSessionClient`中再添加。 + +```csharp {5} showLineNumbers +class MultipleToOneNatSessionClient : NatSessionClient +{ + protected override async Task OnNatConnected(ConnectedEventArgs e) + { + await this.AddTargetClientAsync(MyClientClass.TargetClient); + } + + protected override async Task OnTargetClientClosed(NatTargetClient client, ClosedEventArgs e) + { + //不做任何处理 + await e.InvokeNext(); + } +} +``` + +:::tip 提示 + +使用静态类管理目标客户端,仅仅是演示目的。在实际使用时,可以考虑使用容器,更加方便的管理。 + +::: + +## 六、实现多转多 + +`NatService`支持将多个客户端数据转发到多个目标服务器。实现方法与实现多对一转发类似,只需要使用`NatSessionClient`将多个固定的目标客户端直接进行添加即可。 \ No newline at end of file diff --git a/handbook/versioned_docs/version-3.1/othercore.mdx b/handbook/versioned_docs/version-3.1/othercore.mdx new file mode 100644 index 000000000..825bb336f --- /dev/null +++ b/handbook/versioned_docs/version-3.1/othercore.mdx @@ -0,0 +1,139 @@ +--- +id: othercore +title: 其他相关功能类 +--- + +import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、Crc计算 +**TouchSocket**从网上搜集了Crc1-23的计算方法。并封装在了Crc类中。 +以最常用的Crc16为例。 +```csharp showLineNumbers +byte[] data = new byte[10]; +byte[] result = Crc.Crc16(data, 0, data.Length); +``` + +## 二、时间测量器(TimeMeasurer) +功能:封装的Stopwatch,测量运行Action的时间。 +```csharp showLineNumbers +TimeSpan timeSpan = TimeMeasurer.Run(() => + { + Thread.Sleep(1000); + }); + +``` + +## 三、MD5计算 +```csharp showLineNumbers +string str = MD5.GetMD5Hash("TouchSocket"); +bool b = MD5.VerifyMD5Hash("TouchSocket",str); +``` + +## 四、16进制相关 + +【将16进制的字符转换为数组】 +```csharp showLineNumbers + public static byte[] ByHexStringToBytes(this string hexString, string splite = default) +``` +【将16进制的字符转换为int32】 +```csharp showLineNumbers + public static int ByHexStringToInt32(this string hexString) +``` + +## 五、雪花Id生成 + +雪花Id,会生成long类型的不重复Id。 +```csharp showLineNumbers +SnowflakeIDGenerator generator = new SnowflakeIDGenerator(4); +long id=generator.NextID(); +``` + +## 六、数据压缩 +内部封装了Gzip的压缩。使用静态方法即可完成。 +```csharp showLineNumbers + +byte[] data = new byte[1024]; +new Random().NextBytes(data); + +using (ByteBlock byteBlock=new ByteBlock(1024*64)) +{ + GZip.Compress(byteBlock,data,0,data.Length);//压缩 + var decompressData2 = GZip.Decompress(byteBlock.ToArray());//解压 +} + + +``` + +压缩接口 +内部还定义了一个IDataCompressor的压缩接口。目的是为了向成熟框架传递压缩方法(例如TcpClient)。 +默认配备了一个GZipDataCompressor。可以直接使用。当然大家可以自由扩展其他压缩方法。 + +```csharp showLineNumbers +class MyDataCompressor : IDataCompressor +{ + public byte[] Compress(ArraySegment data) + { + //此处实现压缩 + throw new NotImplementedException(); + } + + public byte[] Decompress(ArraySegment data) + { + //此处实现压缩 + throw new NotImplementedException(); + } +} +``` + +## 七、短时间戳 + +一般的,时间可由long类型作为唯一时间戳,但是有时候,我们也需要短类型的时间戳(uint),所以您可以使用**DateTimeExtensions**类实现,或者使用其扩展方法。 + +```csharp showLineNumbers +uint timestamp= DateTimeExtensions.ConvertTime(DateTime.Now); +timestamp= DateTime.Now.ConvertTime();//扩展方法 +``` + +## 八、读写锁using + +一般的,我们都会使用**ReaderWriterLockSlim**读写锁,进行成对的Enter和Exit。所以我们一般会使用下列代码。 + +```csharp showLineNumbers +ReaderWriterLockSlim lockSlim = new ReaderWriterLockSlim(); +try +{ + lockSlim.EnterReadLock(); + //do something +} +finally +{ + lockSlim.ExitReadLock(); +} +``` + +但是会显得代码非常臃肿。所以我们可以简化使用using实现。 + +```csharp showLineNumbers +ReaderWriterLockSlim lockSlim = new ReaderWriterLockSlim(); + +using (new ReadLock(lockSlim)) +{ + //do something +} + +using (new WriteLock(lockSlim)) +{ + //do something +} +``` + +:::tip 提示 + +**ReadLock**和**WriteLock**均为struct类型,所以几乎不会影响性能。 + +::: diff --git a/handbook/versioned_docs/version-3.1/packageadapter.mdx b/handbook/versioned_docs/version-3.1/packageadapter.mdx new file mode 100644 index 000000000..1dae31e0d --- /dev/null +++ b/handbook/versioned_docs/version-3.1/packageadapter.mdx @@ -0,0 +1,474 @@ +--- +id: packageadapter +title: 内置包适配器 +--- + +import CardLink from "@site/src/components/CardLink.js"; +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +内置包适配器,是框架内置的,用于解决粘、分包问题的现成适配器。它能一键式解决粘、分包问题。目前内置的包适配器有: + +| 适配器 | 名称 |功能特点| +| ---- | ---- |---- | +| FixedHeaderPackageAdapter | 固定包头数据处理适配器 |固定包头数据处理适配器是处理粘包、分包问题的**最有力**、**最可靠**、**最高效**、**最稳定**的一种方案,它基本上适用于**所有场景**。即使**跨语言**使用,也只需要在其他语言中设计**相同算法**就可以。| +| FixedSizePackageAdapter | 固定长度数据处理适配器 |固定长度数据处理适配器是将发送的数据通过分割、填补的操作,以达到每次发送、接收的数据都是固定的长度来处理粘包、分包问题。这种方案一般适用于机械臂,机器人控制等场景。 | +| TerminatorPackageAdapter | 终止因子数据处理适配器 |终止因子数据处理适配器是通过**特殊字符或数值**的方式,来达到处理粘包、分包的目的。可随意设置分割因子的值,以及编码方式。不仅如此,还有异常数据设置,在达到设定值时,如果还没有发现分割因子,则抛弃数据。其稳定性仅次于固定包头,且使用场景也比较广泛。| +| PeriodPackageAdapter | 周期数据处理适配器 |周期数据处理适配器是通过**时间周期**的方式,来处理分包的目的(不包括粘包)。可处理任意数据。但是这也只是一定程度的处理。| +| JsonPackageAdapter | Json格式数据处理适配器 |Json格式数据处理适配器,是一个非常不错的解决**纯Json字符串**粘、分包的方案,它能将符合Json标准的数据准确地分割出来。并且能把其中的杂质数据一起提取出来。| + + + +## 二、特点 + +### 2.1 固定包头数据处理适配器 + +1. 最有力的解决粘包。分包问题。 +2. 是自定义协议的不二选择。 +3. 支持指定包头长度,`Byte`、`Ushort`、`Int`三种类型作为包头。 +4. 最好在客户端与服务器均使用`TouchSocket`组件时使用。不然就需要非`TouchSocket`的一方适配包头算法。 + + + +### 2.2 固定长度数据处理适配器 + +1. 无论何时,发送与接收的数据长度永远为设定值。 +2. 算法简单,可以比较轻松的实现跨语言、跨框架。 +3. 一般适用于业务数据固定场景, + + + +### 2.3 终止因子数据处理适配器 + +1. 最适用于字符串类(`Json`,`Xml`等)的信息交互。 +2. 算法简单,非常容易实现跨语言、跨框架。 +3. 发送普通流数据时,有很小的概率发生提前终止的情况(可设置复杂终止因子来解决)。 + + + + +### 2.4 周期数据处理适配器 + +1. 可处理任意数据。 +2. 只能解决分包问题,无法解决粘包问题。 +3. 处理效率会有一定延迟。 + + + +### 2.5 Json格式数据处理适配器 + +1. 能够处理任意标准`Json`数据。 +2. 能够提取出信息中的杂质数据。 +3. 支持单个`Object`数据、或者`Array`数据。 +4. 支持类型嵌套格式。 + + + +## 三、算法解释 + +### 3.1 固定包头算法 + +- Byte包头算法:以第一个字节作为后续整个数据的长度,整个数据长度区间为[0,255]。 +- Ushort包头算法:前2个字节,且为[默认端序(小端)](./touchsocketbitconverter.mdx)的排列,作为后续整个数据的长度,整个数据长度区间为[0,65535]。 +- Int包头算法(默认配置):前4个字节,且为[默认端序(小端)](./touchsocketbitconverter.mdx)排列,作为后续整个数据的长度,整个数据长度区间为[0,2^31]。 + +### 3.2 固定长度数据处理算法 + +固定长度数据处理算法比较简单,就是事先约定发送数据的长度无论何时都是一致的。 + +### 3.3 终止因子分割数据算法 + +终止因子分割数据算法,就是通过事先约定,发送的数据是以特定数据的组合作为结束的。例如:`redis`协议,就是以`\r\n`作为结束。不过值得注意的是,框架内置的不仅可以用字符串作为终止字符,还能以16进制甚至二进制作为终止字符。 + +### 3.4 周期数据算法 + +周期数据处理适配器,就是通过判断收到数据的时间间隔,将极短时间内收到的数据进行合并。能够一定程度的解决分包问题。 + +### 3.5 Json格式数据处理算法 + +Json格式数据处理算法,就是对接收的字符串进行大括号和中括号的计数,当成对的括号组合,来确定一个完整的json数据。 + + +## 四、使用 + +### 4.1 使用固定包头适配器 + +步骤 + +1. `TouchSocketConfig`配置中设置(可以同时指定`HeaderType`等属性) +2. 通过`Received`(事件、方法、插件)中的`ByteBlock`读取数据。 + +```csharp {7,31} showLineNumbers +private static async Task CreateClient() +{ + var client = new TcpClient(); + //载入配置 + await client.SetupAsync(new TouchSocketConfig() + .SetRemoteIPHost("127.0.0.1:7789") + .SetTcpDataHandlingAdapter(() => new FixedHeaderPackageAdapter() { FixedHeaderType= FixedHeaderType.Int }) + .ConfigureContainer(a => + { + a.AddConsoleLogger();//添加一个日志注入 + })); + + await client.ConnectAsync();//调用连接,当连接不成功时,会抛出异常。 + client.Logger.Info("客户端成功连接"); + return client; +} + +private static async Task CreateService() +{ + var service = new TcpService(); + service.Received = (client, e) => + { + //从客户端收到信息 + var mes = e.ByteBlock.Span.ToString(Encoding.UTF8); + client.Logger.Info($"已从{client.Id}接收到信息:{mes}"); + return Task.CompletedTask; + }; + + await service.SetupAsync(new TouchSocketConfig()//载入配置 + .SetListenIPHosts("tcp://127.0.0.1:7789", 7790)//同时监听两个地址 + .SetTcpDataHandlingAdapter(() => new FixedHeaderPackageAdapter() { FixedHeaderType= FixedHeaderType.Int }) + .ConfigureContainer(a => + { + a.AddConsoleLogger();//添加一个控制台日志注入(注意:在maui中控制台日志不可用) + }) + .ConfigurePlugins(a => + { + //a.Add();//此处可以添加插件 + })); + await service.StartAsync();//启动 + service.Logger.Info("服务器已启动"); + return service; +} +``` + +:::caution 注意 + +1. 使用该适配器时,最好服务器与客户端均使用`TouchSocket`,这样更好维护。 + +2. 同时在发送`SendAsync`数据时,会自动封装数据头,所以不需要手动封装。 + +::: + +:::tip 提示 + +该适配器,客户端与服务器均适用。 + +::: + +### 4.2 使用固定长度适配器 + +步骤 + +1. `TouchSocketConfig`配置中设置,同时指定数据的长度。 +2. 通过`Received`(事件、方法、插件)中的`ByteBlock`读取数据(注意:数据长度是`byteBlock.Length`)。 + +```csharp {7,31} showLineNumbers +private static async Task CreateClient() +{ + var client = new TcpClient(); + //载入配置 + await client.SetupAsync(new TouchSocketConfig() + .SetRemoteIPHost("127.0.0.1:7789") + .SetTcpDataHandlingAdapter(() => new FixedSizePackageAdapter(10)) + .ConfigureContainer(a => + { + a.AddConsoleLogger();//添加一个日志注入 + })); + + await client.ConnectAsync();//调用连接,当连接不成功时,会抛出异常。 + client.Logger.Info("客户端成功连接"); + return client; +} + +private static async Task CreateService() +{ + var service = new TcpService(); + service.Received = (client, e) => + { + //从客户端收到信息 + var mes = e.ByteBlock.Span.ToString(Encoding.UTF8); + client.Logger.Info($"已从{client.Id}接收到信息:{mes}"); + return Task.CompletedTask; + }; + + await service.SetupAsync(new TouchSocketConfig()//载入配置 + .SetListenIPHosts("tcp://127.0.0.1:7789", 7790)//同时监听两个地址 + .SetTcpDataHandlingAdapter(() => new FixedSizePackageAdapter(10)) + .ConfigureContainer(a => + { + a.AddConsoleLogger();//添加一个控制台日志注入(注意:在maui中控制台日志不可用) + }) + .ConfigurePlugins(a => + { + //a.Add();//此处可以添加插件 + })); + await service.StartAsync();//启动 + service.Logger.Info("服务器已启动"); + return service; +} +``` + +:::caution 注意 + +该适配器,在发送数据时,应该事前约定固定的长度,并在发送数据时严格遵守。 + +::: + +:::caution 注意 + +该适配器,在发送`SendAsync`数据时,会自动封装数据头,所以不需要手动封装。 + +::: + + +:::tip 提示 + +该适配器,客户端与服务器均适用。 + +::: + +### 4.3 使用终止因子分割适配器 + +客户端与服务器均适用。下列以服务器为例。 + +步骤 + +1. `TouchSocketConfig`配置中设置,同时指定数据的长度。 +2. 通过`Received`(事件、方法、插件)中的`ByteBlock`读取数据(注意:数据长度是`byteBlock.Length`)。 + +```csharp {7,31} showLineNumbers +private static async Task CreateClient() +{ + var client = new TcpClient(); + //载入配置 + await client.SetupAsync(new TouchSocketConfig() + .SetRemoteIPHost("127.0.0.1:7789") + .SetTcpDataHandlingAdapter(() => new TerminatorPackageAdapter("\r\n")) + .ConfigureContainer(a => + { + a.AddConsoleLogger();//添加一个日志注入 + })); + + await client.ConnectAsync();//调用连接,当连接不成功时,会抛出异常。 + client.Logger.Info("客户端成功连接"); + return client; +} + +private static async Task CreateService() +{ + var service = new TcpService(); + service.Received = (client, e) => + { + //从客户端收到信息 + var mes = e.ByteBlock.Span.ToString(Encoding.UTF8); + client.Logger.Info($"已从{client.Id}接收到信息:{mes}"); + return Task.CompletedTask; + }; + + await service.SetupAsync(new TouchSocketConfig()//载入配置 + .SetListenIPHosts("tcp://127.0.0.1:7789", 7790)//同时监听两个地址 + .SetTcpDataHandlingAdapter(() => new TerminatorPackageAdapter("\r\n")) + .ConfigureContainer(a => + { + a.AddConsoleLogger();//添加一个控制台日志注入(注意:在maui中控制台日志不可用) + }) + .ConfigurePlugins(a => + { + //a.Add();//此处可以添加插件 + })); + await service.StartAsync();//启动 + service.Logger.Info("服务器已启动"); + return service; +} +``` + +:::caution 注意 + +该适配器,在发送`SendAsync`数据时,会自动追加分割符。 + +::: + + +:::tip 提示 + +默认情况下终止因子不会保留在数据中,用户可通过`ReserveTerminatorCode`属性,设为`true`,来保留终止因子。 + +::: + +:::tip 提示 + +该适配器,客户端与服务器均适用。 + +::: + +### 4.4 使用周期适配器 + +客户端与服务器均适用。下列以服务器为例。 + +步骤 + +1. `TouchSocketConfig`配置中设置,同时指定数据的长度。 +2. 通过`Received`(事件、方法、插件)中的`ByteBlock`读取数据(注意:数据长度是`byteBlock.Length`)。 + +```csharp {7,31} showLineNumbers +private static async Task CreateClient() +{ + var client = new TcpClient(); + //载入配置 + await client.SetupAsync(new TouchSocketConfig() + .SetRemoteIPHost("127.0.0.1:7789") + .SetTcpDataHandlingAdapter(() => new PeriodPackageAdapter() { CacheTimeout=TimeSpan.FromSeconds(1) }) + .ConfigureContainer(a => + { + a.AddConsoleLogger();//添加一个日志注入 + })); + + await client.ConnectAsync();//调用连接,当连接不成功时,会抛出异常。 + client.Logger.Info("客户端成功连接"); + return client; +} + +private static async Task CreateService() +{ + var service = new TcpService(); + service.Received = (client, e) => + { + //从客户端收到信息 + var mes = e.ByteBlock.Span.ToString(Encoding.UTF8); + client.Logger.Info($"已从{client.Id}接收到信息:{mes}"); + return Task.CompletedTask; + }; + + await service.SetupAsync(new TouchSocketConfig()//载入配置 + .SetListenIPHosts("tcp://127.0.0.1:7789", 7790)//同时监听两个地址 + .SetTcpDataHandlingAdapter(() => new PeriodPackageAdapter() { CacheTimeout=TimeSpan.FromSeconds(1) }) + .ConfigureContainer(a => + { + a.AddConsoleLogger();//添加一个控制台日志注入(注意:在maui中控制台日志不可用) + }) + .ConfigurePlugins(a => + { + //a.Add();//此处可以添加插件 + })); + await service.StartAsync();//启动 + service.Logger.Info("服务器已启动"); + return service; +} +``` + +:::tip 提示 + +该适配器,客户端与服务器均适用。 + +::: + +### 4.5 使用Json格式数据处理适配器 + +客户端与服务器均适用。下列以服务器为例。 + +步骤 + +1. `TouchSocketConfig`配置中设置,同时指定数据的长度。 +2. 通过`Received`(事件、方法、插件)中的`IRequestInfo`,强制转为`JsonPackage`,然后读取数据。 + +对于`JsonPackage` + +- **Data**:获取`json`二进制数据。 +- **DataString**:获取`json`字符串数据。 +- **Kind**:数据类型,分为:`Object`、`Array`两种。 +- **ImpurityData**:杂质数据,一般当`json`数据中间有其他数据时,会保存在这里。 +- **Encoding**:编码类型。 + +```csharp {7,24-32,38} showLineNumbers +private static async Task CreateClient() +{ + var client = new TcpClient(); + //载入配置 + await client.SetupAsync(new TouchSocketConfig() + .SetRemoteIPHost("127.0.0.1:7789") + .SetTcpDataHandlingAdapter(()=>new JsonPackageAdapter(Encoding.UTF8)) + .ConfigureContainer(a => + { + a.AddConsoleLogger();//添加一个日志注入 + })); + + await client.ConnectAsync();//调用连接,当连接不成功时,会抛出异常。 + client.Logger.Info("客户端成功连接"); + return client; +} + +private static async Task CreateService() +{ + var service = new TcpService(); + service.Received = (client, e) => + { + //从客户端收到信息 + if (e.RequestInfo is JsonPackage jsonPackage) + { + StringBuilder sb = new StringBuilder(); + sb.Append($"已从{client.Id}接收到数据。"); + sb.Append($"数据类型:{jsonPackage.Kind},"); + sb.Append($"数据:{jsonPackage.DataString},"); + sb.Append($"杂质数据:{jsonPackage.ImpurityData.Span.ToString(Encoding.UTF8)}"); + client.Logger.Info(sb.ToString()); + } + return Task.CompletedTask; + }; + + await service.SetupAsync(new TouchSocketConfig()//载入配置 + .SetListenIPHosts("tcp://127.0.0.1:7789", 7790)//同时监听两个地址 + .SetTcpDataHandlingAdapter(()=>new JsonPackageAdapter(Encoding.UTF8)) + .ConfigureContainer(a => + { + a.AddConsoleLogger();//添加一个控制台日志注入(注意:在maui中控制台日志不可用) + }) + .ConfigurePlugins(a => + { + //a.Add();//此处可以添加插件 + })); + await service.StartAsync();//启动 + service.Logger.Info("服务器已启动"); + return service; +} +``` + +:::tip 提示 + +`Json`格式数据处理适配器不对发送的数据做处理,仅仅对接收到的数据做处理。 + +::: + +:::tip 提示 + +该适配器,客户端与服务器均适用。 + +::: + +## 五、可设置参数 + +| 属性 | 描述 |默认值 | +| ---- | ---- |---- | +| MaxPackageSize | 适配器能接收的最大数据包长度 |1024\*1024\*1024字节| +| CanSendRequestInfo | 是否允许发送`IRequestInfo`对象 |false| +| CanSplicingSend | 拼接发送 |false| +| CacheTimeoutEnable | 是否启用缓存超时。 |true| +| CacheTimeout | 缓存超时时间。 |1秒| +| UpdateCacheTimeWhenRev | 是否在收到数据时,即刷新缓存时间。当设为`true`时,将弱化`CacheTimeout`的作用,只要一直有数据,则缓存不会过期。当设为`false`时,则在`CacheTimeout`的时效内。必须完成单个缓存的数据 |true| + + + + + +## 六、本文示例Demo + + diff --git a/handbook/docs/pipelinedatahandlingadapter.mdx b/handbook/versioned_docs/version-3.1/pipelinedatahandlingadapter.mdx similarity index 100% rename from handbook/docs/pipelinedatahandlingadapter.mdx rename to handbook/versioned_docs/version-3.1/pipelinedatahandlingadapter.mdx diff --git a/handbook/versioned_docs/version-3.1/plcbridgedescription.mdx b/handbook/versioned_docs/version-3.1/plcbridgedescription.mdx new file mode 100644 index 000000000..78154b61c --- /dev/null +++ b/handbook/versioned_docs/version-3.1/plcbridgedescription.mdx @@ -0,0 +1,266 @@ +--- +id: plcbridgedescription +title: Plc 桥接服务说明 +--- + +## 一、为什么你需要 PLC Bridge?解决工业自动化中的核心痛点 + +在工业自动化开发中,直接读写 PLC 看起来是最直接的方式,但随着系统复杂度增加,这种方式会带来一系列严重问题。TouchSocketPro.PlcBridges 正是为解决这些核心痛点而生。 + +## 二、直接读写 PLC 的五大痛点 + +### 2.1 多设备协同的复杂性噩梦 + +当系统需要连接多个 PLC 设备时: +```csharp +// 伪代码示例:直接连接多个 PLC +var plc1 = new SiemensPLC("192.168.1.10"); +var plc2 = new OmronPLC("192.168.1.11"); +var plc3 = new ModbusPLC("192.168.1.12"); + +// 写入数据需要分别处理每个设备 +await plc1.WriteRegister(0, value1); +await plc2.WriteRegister(5, value2); +await plc3.WriteCoil(10, true); +``` + +**痛点分析**: +- 需要了解每个 PLC 的特定协议和地址映射 +- 错误处理逻辑重复且复杂 +- 设备间的数据依赖难以管理 + +### 2.2 性能瓶颈:高频读写效率低下 + +当需要频繁读写时: +```csharp +// 伪代码示例:直接读写大量数据点 +for (int i = 0; i < 100; i++) +{ + var value = await plc.ReadRegister(i); + ProcessData(value); +} +``` + +**痛点分析**: +- 每次读写都需要建立/断开连接 +- 小数据包导致网络带宽浪费 +- 无法合并相邻地址的读写请求 + +### 2.3 数据类型转换的繁琐工作 + +处理不同数据类型时: + +```csharp +// 伪代码示例:手动处理数据类型转换 +var bytes = await plc.ReadBytes(0, 4); +float temperature = BitConverter.ToSingle(bytes, 0); + +var intBytes = await plc.ReadBytes(4, 2); +int pressure = BitConverter.ToInt16(intBytes, 0); +``` + +**痛点分析**: +- 需要手动处理字节序(大端/小端) +- 复杂类型(浮点数、结构体)转换代码冗长 +- 容易因类型处理错误导致数据错误 + +### 2.4 系统扩展的困难 + +当需要添加新设备时: + +```csharp +// 伪代码示例:添加新设备需要重构代码 +// 原有代码 +if (deviceType == "Siemens") +{ + // Siemens 特定逻辑 +} +else if (deviceType == "Omron") +{ + // Omron 特定逻辑 +} + +// 添加 Modbus 支持 +else if (deviceType == "Modbus") +{ + // 新增 Modbus 逻辑 +} +``` + +**痛点分析**: +- 每次添加新设备类型都需要修改核心逻辑 +- 系统变得臃肿难以维护 +- 测试覆盖率难以保证 + +### 2.5 实时性难以保障 + +```csharp +// 伪代码示例:直接读写无法保证实时性 +var task1 = ReadSensorData(); +var task2 = WriteControlSignal(); + +await Task.WhenAll(task1, task2); // 无法控制执行顺序 +``` + +**痛点分析**: +- 无法保证关键指令的执行顺序 +- 缺乏优先级管理机制 +- 并发操作可能导致资源冲突 + +## 三、PLC Bridge如何解决这些痛点 + +### 3.1 统一访问接口(解决多设备协同问题) + +```csharp +// 使用 PLC Bridge 统一访问不同设备 +var plcOperator = plcBridge.CreateOperator(); + +// 无论底层是什么PLC设备,使用相同API +await plcOperator.WriteAsync(new WritableValue(0, 100)); +var result = await plcOperator.ReadAsync(new ReadableValue(0, 1)); +``` + +**优势**: +- 统一接口简化开发 +- 设备更换无需修改业务代码 +- 支持热插拔添加/移除设备 + +### 3.2 智能请求合并(解决性能瓶颈) + +```csharp +// PLC Bridge 自动合并相邻请求 +var writableValues = new WritableValueCollection( + new WritableValue(0, 100), // 地址0 + new WritableValue(1, 200), // 地址1(相邻) + new WritableValue(5, 300) // 地址5(间隔=4) +); + +// 实际只产生1次通信(地址0-5批量写入) +await plcOperator.WriteAsync(writableValues); +``` + +**优势**: +- 减少70-90%的通信次数 +- 最大程度利用网络带宽 +- 通过 MaxGap 参数精细控制合并策略 + +### 3.3. 自动类型转换(解决数据类型问题) + +```csharp +// PLC Bridge 自动处理类型转换 +float[] temperatures = {23.5f, 24.1f, 22.8f}; + +// 自动转换为字节流写入 +await plcOperator.WriteAsync(new WritableValue(0, temperatures)); + +// 自动转换回原始类型 +var result = await plcOperator.ReadAsync(new ReadableValue(0, 3)); +``` + +**优势**: +- 支持所有非托管类型 +- 自动处理字节序转换 +- 复杂结构体一键序列化 + +### 3.4 设备抽象层(解决扩展性问题) + +```csharp +// 添加新PLC设备只需实现驱动器接口 +public class CustomPlcDrive : IPlcDrive +{ + // 实现驱动器接口 +} + +// 注册到PLC Bridge +plcBridge.AddDriveAsync(new CustomPlcDrive(/* 配置 */)); +``` + +**优势**: +- 新设备支持不影响业务逻辑 +- 插件式架构便于扩展 +- 核心系统保持稳定 + +### 3.5 执行控制(解决实时性问题) + +```csharp +// 通过分组控制执行顺序 +var criticalDrive = new MemoryPlcDrive( + new PlcDriveOption { Group = "Critical" }); + +var normalDrive = new MemoryPlcDrive( + new PlcDriveOption { Group = "Normal" }); + +// Critical组任务优先执行且串行处理 +``` + +**优势**: +- 分组控制关键任务执行顺序 +- 支持任务优先级管理 +- 内置超时和重试机制 + +## 四、PLC Bridge 的独特价值 + +### 4.1 架构优化前后对比 + +| **场景** | **直接读写 PLC** | **使用 PLC Bridge** | +|----------|------------------|---------------------| +| 多设备协同 | 每个设备独立处理 | 统一接口管理所有设备 | +| 高频数据采集 | 频繁小包通信,性能低下 | 智能合并请求,减少70%+通信量 | +| 系统扩展 | 修改核心代码,风险高 | 添加驱动器,业务零修改 | +| 实时控制 | 无执行顺序保障 | 分组控制关键任务 | +| 数据类型处理 | 手动转换,易出错 | 自动处理所有类型转换 | +| 错误处理 | 分散在各处 | 统一结果对象(Result) | + +### 4.2 PLC Bridge 的核心价值矩阵 + +```mermaid +graph TD + A[PLC Bridge 价值] --> B(开发效率提升) + A --> C(系统性能优化) + A --> D(维护成本降低) + A --> E(系统可靠性增强) + + B --> B1(减少70%+代码量) + B --> B2(统一访问接口) + B --> B3(快速集成新设备) + + C --> C1(请求合并优化) + C --> C2(内存池技术) + C --> C3(异步批量处理) + + D --> D1(核心业务与设备解耦) + D --> D2(配置驱动扩展) + D --> D3(集中错误处理) + + E --> E1(执行顺序保障) + E --> E2(自动重试机制) + E --> E3(数据一致性保护) +``` + +## 五、何时需要 PLC Bridge? + +### 5.1 适用场景 +1. **多PLC协同系统**:连接多种品牌/协议的PLC设备 +2. **高频数据采集**:需要优化通信性能的场景 +3. **大型SCADA系统**:需要统一设备管理接口 +4. **关键过程控制**:需要保障执行顺序和实时性 +5. **快速迭代项目**:需要灵活扩展设备支持 + +### 5.2 使用建议 +- **简单系统**(单PLC,少量读写):直接读写可能更简单 +- **复杂系统**(多设备,高频读写):PLC Bridge 是必备架构组件 +- **关键任务系统**:PLC Bridge 提供必需的可靠性和实时性保障 + +## 六、结论 + +PLC Bridge 不是简单的通信封装,而是工业自动化领域的**架构解决方案**。它解决了直接读写 PLC 方式在复杂系统中暴露的核心痛点: + +1. 通过**统一接口**消除多设备协同复杂度 +2. 通过**智能合并**优化高频读写性能 +3. 通过**自动转换**简化数据类型处理 +4. 通过**抽象层**实现无缝系统扩展 +5. 通过**执行控制**保障关键任务实时性 + +在工业4.0和IIoT时代,随着系统复杂度不断增加,PLC Bridge 已成为构建可靠、高效、可扩展工业自动化系统的**必备基础设施**。 + + diff --git a/handbook/versioned_docs/version-3.1/plcbridgemodbus.mdx b/handbook/versioned_docs/version-3.1/plcbridgemodbus.mdx new file mode 100644 index 000000000..006577566 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/plcbridgemodbus.mdx @@ -0,0 +1,271 @@ +--- +id: plcbridgemodbus +title: PlcBridge Modbus 集成指南 +--- + +import Pro from "@site/src/components/Pro.js"; + +import CardLink from "@site/src/components/CardLink.js"; +import Definition from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +TouchSocket PLC Bridge 提供了强大的工业自动化设备集成能力,尤其适合处理**多协议**、**多设备**的复杂场景。本指南将结合示例代码,详细说明如何使用 PLC Bridge 整合 Modbus TCP、UDP 和串口设备,创建一个统一的设备访问接口。 + +## 二、核心优势 + +1. **统一访问接口**:通过单一API访问多种Modbus设备(TCP/UDP/串口) +2. **智能数据映射**:将不同设备的寄存器地址映射到统一的虚拟地址空间 +3. **高性能处理**:自动合并读写请求,优化通信效率 +4. **类型安全访问**:支持复杂数据类型(long, float等)的自动转换 +5. **可扩展架构**:轻松添加新设备或调整配置 + +## 三、准备工作 + +### 3.1 硬件环境 + +- Modbus TCP 设备(端口502) +- Modbus UDP 设备(端口503) +- Modbus 串口设备(COM2) +- 安装 Modbus 设备模拟器 + + +## 四、设备桥接实现步骤 + +### 4.1 初始化 PLC Bridge 服务 + +```csharp +var plcBridge = new PlcBridgeService(); +await plcBridge.SetupAsync(new TouchSocketConfig()); +``` + +### 4.2 配置 Modbus TCP 设备桥接 + +```csharp +// 连接 TCP Modbus 设备 +var modbusTcpMaster = new ModbusTcpMaster(); +await modbusTcpMaster.ConnectAsync("127.0.0.1:502"); + +// 映射第一个寄存器区段 (0-20) +var plcDrive1 = new MyModbusHoldingRegistersDrive(modbusTcpMaster, new ModbusDriveOption() +{ + Start = 0, // 虚拟起始地址 + Count = 20, // 寄存器数量 + //Group = "Group", // 执行组(同组设备串行操作) + Name = "TcpDevice1", + SlaveId = 1, // Modbus 从站ID + ModbusStart = 0 // 物理设备起始地址 +}); +await plcBridge.AddDriveAsync(plcDrive1); + +// 映射第二个寄存器区段 (50-70) +var plcDrive2 = new MyModbusHoldingRegistersDrive(modbusTcpMaster, new ModbusDriveOption() +{ + Start = 20, // 下一个虚拟起始地址 + Count = 20, + //Group = "Group", + Name = "TcpDevice2", + SlaveId = 1, + ModbusStart = 50 // 物理设备偏移地址 +}); +await plcBridge.AddDriveAsync(plcDrive2); +``` + +### 4.3 配置 Modbus UDP 设备桥接 + +```csharp +var modbusUdpMaster = new ModbusUdpMaster(); +await modbusUdpMaster.SetupAsync(new TouchSocketConfig() + .UseUdpReceive() + .SetRemoteIPHost("127.0.0.1:503")); +await modbusUdpMaster.StartAsync(); + +var plcDrive3 = new MyModbusHoldingRegistersDrive(modbusUdpMaster, new ModbusDriveOption() +{ + Start = 40, // 虚拟地址偏移 + Count = 20, + //Group = "Group", + Name = "UdpDevice1", + SlaveId = 1, + ModbusStart = 10 // 物理设备起始地址 +}); +await plcBridge.AddDriveAsync(plcDrive3); +``` + +### 4.4 配置 Modbus 串口设备桥接 + +```csharp +var modbusRtuMaster = new ModbusRtuMaster(); +await modbusRtuMaster.SetupAsync(new TouchSocketConfig() + .SetSerialPortOption(new SerialPortOption() + { + BaudRate = 9600, + DataBits = 8, + Parity = System.IO.Ports.Parity.Even, + PortName = "COM2", + StopBits = System.IO.Ports.StopBits.One + })); +await modbusRtuMaster.ConnectAsync(); + +var plcDrive4 = new MyModbusHoldingRegistersDrive(modbusRtuMaster, new ModbusDriveOption() +{ + Start = 60, // 虚拟地址偏移 + Count = 20, + //Group = "Group", + Name = "SerialDevice1", + SlaveId = 1, + ModbusStart = 20 // 物理设备起始地址 +}); +await plcBridge.AddDriveAsync(plcDrive4); +``` + +### 4.5 启动桥接服务 + +```csharp +await plcBridge.StartAsync(); +``` + +## 五、创建统一访问接口 + +### 5.1 定义 PLC 数据对象 + +```csharp +partial class MyPlcObject : PlcObject +{ + public MyPlcObject(IPlcBridgeService bridgeService) : base(bridgeService) + { + } + + // 以 short 类型访问所有寄存器 (0-79) + [PlcField(Start = 0, Quantity = 80)] + private ReadOnlyMemory m_allInt16Data; + + // 以 long 类型访问寄存器 (每4个寄存器合并为1个long) + [PlcField(Start = 0, Quantity = 20)] + private ReadOnlyMemory m_allInt64Data; + + // 访问特定 long 数据 (地址59-62) + [PlcField(Start = 59)] + private long m_int64Data; +} +``` + +### 5.2 使用 PLC 数据对象 + +```csharp +// 创建PLC数据访问对象 +MyPlcObject myPlcObject = new MyPlcObject(plcBridge); + +// 写入long数据 +var setInt64Result = await myPlcObject.SetInt64DataAsync(1000); +Console.WriteLine($"写入Int64结果: {setInt64Result}"); + +// 读取long数据 +var readInt64Result = await myPlcObject.GetInt64DataAsync(); +Console.WriteLine($"读取Int64结果: {readInt64Result}"); + +// 批量写入short数据 +var data = Enumerable.Range(1, 80).Select(i => (short)i).ToArray(); +var setAllInt16Result = await myPlcObject.SetAllInt16DataAsync(data); + +// 批量读取short数据 +var readAllInt16Result = await myPlcObject.GetAllInt16DataAsync(); + +// 批量读取long数据 +var readAllInt64Result = await myPlcObject.GetAllInt64DataAsync(); +``` + +## 六、自定义驱动器实现 + +```csharp +class MyModbusHoldingRegistersDrive : ModbusHoldingRegistersDrive +{ + public MyModbusHoldingRegistersDrive(IModbusMaster master, ModbusDriveOption option) + : base(master, option) + { + } + + // 自定义读取操作(添加日志等) + protected override async Task ExecuteReadAsync( + ExecuteReadableValue readableValue, + CancellationToken token) + { + var result = await base.ExecuteReadAsync(readableValue, token); + Console.WriteLine($"设备类型={this.Master.GetType().Name},读取地址={readableValue.Start + this.ModbusStart},长度={readableValue.Count},结果:{result.ToJsonString()}"); + return result; + } + + // 自定义写入操作 + protected override async Task ExecuteWriteAsync( + WritableValue writableValue, + CancellationToken token) + { + var result = await base.ExecuteWriteAsync(writableValue, token); + Console.WriteLine($"设备类型={this.Master.GetType().Name},写入地址={writableValue.Start + this.ModbusStart},长度={writableValue.Count},结果:{result.ToJsonString()}"); + return result; + } +} +``` + +## 七、执行流程 + +### 7.1 初始化阶段 + +- 创建 PLC Bridge 服务 +- 配置并添加各设备驱动器 +- 启动桥接服务 + +### 7.2 数据访问阶段 + +- 通过统一接口读写数据 +- PLC Bridge 自动处理: + * 地址映射转换 + * 请求合并优化 + * 数据类型转换 + * 多设备协同 + +### 7.3 资源释放 + +```csharp +await plcBridge.StopAsync(); +await modbusTcpMaster.CloseAsync(); +await modbusUdpMaster.StopAsync(); +await modbusRtuMaster.CloseAsync(); +// ...释放其他资源 +``` + +## 八、典型应用场景 + +1. **跨设备数据采集**:同时从不同协议的设备读取数据 +2. **集中控制**:通过单一接口控制多个设备 +3. **数据聚合**:将不同设备的数据合并处理 +4. **协议转换**:将不同协议统一为标准接口 +5. **设备热插拔**:动态添加/移除设备不影响系统运行 + +## 九、性能优化建议 + +1. **分组策略**:对实时性要求高的设备使用独立组 +2. **批量处理**:使用`ReadOnlyMemory`进行批量读写 +3. **地址规划**:将相邻地址分配到同一设备驱动器 +4. **缓存机制**:对低频变化数据实现读取缓存 +5. **连接复用**:同协议设备共享Master连接 + +## 十、总结 + +TouchSocket PLC Bridge 通过: +1. **统一访问层** 抽象底层设备差异 +2. **智能地址映射** 简化多设备协同 +3. **自动优化** 提升通信效率 +4. **强类型接口** 保证数据一致性 +5. **可扩展架构** 支持灵活定制 + +解决了工业自动化中多协议设备集成的核心挑战,是构建复杂工业控制系统的理想选择。 + +## 十一、本文示例 + + \ No newline at end of file diff --git a/handbook/versioned_docs/version-3.1/plcbridgeservice.mdx b/handbook/versioned_docs/version-3.1/plcbridgeservice.mdx new file mode 100644 index 000000000..d04252f4f --- /dev/null +++ b/handbook/versioned_docs/version-3.1/plcbridgeservice.mdx @@ -0,0 +1,250 @@ +--- +id: plcbridgeservice +title: 历史更新 +--- + +import Pro from "@site/src/components/Pro.js"; +import CardLink from "@site/src/components/CardLink.js"; +import { TouchSocketProPlcBridgesDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 +`TouchSocketPro.PlcBridges` 是一个高效、灵活的PLC数据桥接库,专为.NET开发者设计,用于简化与可编程逻辑控制器(PLC)的数据交互流程。该库提供了强大的数据读写管理能力,支持多种PLC协议和数据类型,使工业自动化开发更加便捷高效。 + + + +## 二、支持的目标框架 + +- .NET Framework 4.5+ +- .NET Standard 2.0+ +- .NET Core 3.1+ +- .NET 5/6/8/9 + + +## 三、快速开始 + +### 3.1 安装NuGet包 + +```bash +Install-Package TouchSocketPro.PlcBridges +``` + +### 3.2 基本使用示例 + +```csharp +using TouchSocket.PlcBridges; + +// 1. 初始化PLC桥接服务 +var plcBridge = new PlcBridgeService(); + +// 2. 简单配置 +await plcBridge.SetupAsync(new TouchSocketConfig()); + +// 3. 添加内存PLC驱动器 +await plcBridge.AddDriveAsync(new MemoryPlcDrive( + new PlcDriveOption() + { + Name = "DeviceA", + Start = 0, + Count = 10 + })); + +// 4. 启动PLC桥接服务 +await plcBridge.StartAsync(); + +// 5. 创建操作器 +var plcOperator = plcBridge.CreateOperator(); + +// 6. 写入数据 +var writeResult = await plcOperator.WriteAsync( + new WritableValueCollection( + new WritableValue(0, new short[] {1,2,3,4,5}) + )); + +// 7. 读取数据 +var readableValues = new ReadableValueCollection( + new ReadableValue(0, 5)); +var readResult = await plcOperator.ReadAsync(readableValues); + +// 8. 停止服务 +await plcBridge.StopAsync(); +``` + +## 四、配置详解 + +### 4.1 PlcDriveOption 配置属性 + +| 属性 | 类型 | 说明 | 默认值 | +|------|------|------|--------| +| `Name` | `string` | 驱动器名称标识符,用于唯一标识当前驱动实例 | `null` | +| `Start` | `int` | 映射到PLC桥接服务的**起始地址偏移量** | 必须自定义赋值 | +| `Count` | `int` | 映射到PLC桥接服务的**数据单元数量** | 必须自定义赋值 | +| `EndianType` | `EndianType` | PLC数据的**字节序类型**(Big-Endian) | `EndianType.Big` | +| `MaxReadGap` | `int` | 读取地址范围间的最大间隙阈值(单位:地址偏移量):
- 当连续地址块之间的间隙 ≤ 该值时,会被合并为一次性读取操作
- 示例:地址块 `0-1`, `2-3`, `4-7` (间隙≤10) → 合并读取 `0-7` | `10` | +| `MaxWriteGap` | `int` | 写入地址范围间的最大间隙阈值(默认值):
- `0` 表示每次写入操作单独处理(不合并)
- 若设置为有效值需配合 `WriteGapValidityWindow` | `0` | +| `WriteGapValidityWindow` | `TimeSpan` | 写入间隙有效时间窗口(需与 `MaxWriteGap` 配合使用):
- 当写入操作存在间隙(≤`MaxWriteGap`)时,若该间隙值在窗口时间内被读取过
- 系统会将读取值作为补丁数据,与写入操作合并批量提交
- **作用**:避免间隙地址被意外覆盖(如默认0值填充) | `TimeSpan.Zero` | +| `Group` | `string` | 驱动器分组名称:
- 相同分组名称的驱动器使用**同一个Task执行队列**(串行化执行) | `null` | +| `DelayTime` | `TimeSpan` | 驱动器轮询延迟时间:
- 值越大,批量处理合并的可能性越高(提升吞吐量)
- 值过大会**降低实时性**,需根据业务场景权衡 | `TimeSpan.Zero` | + + +### 4.2 示例配置 + +```csharp +var driveOption = new PlcDriveOption +{ + Name = "DeviceA", + Start = 0, + Count = 10, + EndianType = EndianType.Little, + MaxReadGap = 5, + MaxWriteGap = 1, + WriteGapValidityWindow = TimeSpan.FromMilliseconds(500), + Group = "GroupA", + DelayTime = TimeSpan.FromMilliseconds(100) +}; +``` + +## 五、核心类与方法 + +### 5.1 PlcBridgeService 类 + +PLC桥接服务的主入口点,负责管理驱动器和操作请求。 + +#### 5.1.1 主要方法 + +| 方法 | 说明 | +|------|------| +| `SetupAsync(TouchSocketConfig)` | 配置服务 | +| `AddDriveAsync(IPlcDrive)` | 添加PLC驱动器 | +| `StartAsync()` | 启动服务 | +| `StopAsync()` | 停止服务 | +| `CreateOperator()` | 创建数据操作器 | + +### 5.2 IPlcOperator 接口 + +提供读写PLC数据的操作接口。 + +#### 5.2.1 主要方法 + +| 方法 | 说明 | +|------|------| +| `ReadAsync(ReadableValueCollection)` | 读取数据 | +| `WriteAsync(WritableValueCollection)` | 写入数据 | + +### 5.3 数据集合类 + +| 类 | 说明 | +|------|------| +| `ReadableValue` | 定义可读值(起始地址+长度) | +| `ReadableValueCollection` | 可读值集合 | +| `WritableValue` | 定义可写值(起始地址+数据) | +| `WritableValueCollection` | 可写值集合 | + +## 六、高级特性 + +### 6.1 请求合并优化 + +通过配置`MaxWriteGap`和`WriteGapValidityWindow`实现相邻请求的智能合并: + +```csharp +// 配置写入间隙为1,有效窗口500ms +var driveOption = new PlcDriveOption +{ + MaxWriteGap = 1, + WriteGapValidityWindow = TimeSpan.FromMilliseconds(500) +}; + +// 写入操作会自动合并相邻请求 +var writeResult = await plcOperator.WriteAsync( + new WritableValueCollection( + new WritableValue(0, new byte[] {0,1,2,3}), + new WritableValue(5, new byte[] {5,6}) + )); +``` + +### 6.2 使用PlcObject简化访问 + +通过定义PLC对象映射简化数据访问: + +```csharp +partial class MyPlcObject : PlcObject +{ + public MyPlcObject(IPlcBridgeService bridgeService) + : base(bridgeService) { } + + [PlcField(Start = 0)] + private short m_shortValue; + + [PlcField(Start = 1, Quantity = 3)] + private ReadOnlyMemory m_shortValues; +} + +// 使用 +var myPlcObject = new MyPlcObject(plcBridge); +var resultSet = await myPlcObject.SetShortValueAsync(1); +var resultGet = await myPlcObject.GetShortValueAsync(); +``` + +### 6.3 多驱动器协同工作 + + +支持多个驱动器协同工作,自动处理地址映射: + +```csharp +// 添加两个驱动器 +var memoryPlcDrive_1 = new MemoryPlcDrive( + new PlcDriveOption() { Start = 0, Count = 5 }); +var memoryPlcDrive_2 = new MemoryPlcDrive( + new PlcDriveOption() { Start = 5, Count = 5 }); + +plcBridge.AddDriveAsync(memoryPlcDrive_1); +plcBridge.AddDriveAsync(memoryPlcDrive_2); + +// 写入跨越两个驱动器的数据 +var writeResult = await plcOperator.WriteAsync( + new WritableValueCollection( + new WritableValue(0, new short[] {0,1,2,3,4,5,6,7,8,9}) + )); +``` + +## 七、性能优化建议 + +1. **合理设置MaxGap参数**: + +```csharp +// 增大间隙值可提高合并率 +driveOption.MaxReadGap = 20; +driveOption.MaxWriteGap = 5; +``` + +2. **使用分组控制执行顺序**: + +```csharp +// 相同分组的驱动器串行执行 +driveOption.Group = "CriticalGroup"; +``` + +3. **调整延迟时间平衡实时性与性能**: + +```csharp +// 适当增加延迟时间提高合并率 +driveOption.DelayTime = TimeSpan.FromMilliseconds(100); +``` + +4. **利用内存池减少分配开销**: + +```csharp +// 使用ByteBlock减少内存分配 +using (var byteBlock = new ByteBlock(1024)) +{ + // 处理数据 +} +``` + +## 八、本文示例 + + diff --git a/handbook/versioned_docs/version-3.1/pluginsmanager.mdx b/handbook/versioned_docs/version-3.1/pluginsmanager.mdx new file mode 100644 index 000000000..6bcc55419 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/pluginsmanager.mdx @@ -0,0 +1,332 @@ +--- +id: pluginsmanager +title: 插件系统 +--- + +import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +插件系统是一组能实现多播订阅的,可中断的触发器,其主要功能就是实现类似事件、委托的通知消息。其设计核心来自于`AspNetCore`的中间件,它有着和中间件一样的使用体验,同时也有着更高的灵活性和自由度。 + +## 二、产品特点 + +- 简单易用。 +- 易扩展。 + +## 三、产品应用场景 + +- 所有可以使用事件。委托的场景。 + +## 四、与事件、委托相比 + +1. 订阅的时候可以不用知道被订阅方是谁,只需要知道要订阅什么通知即可。 +2. 订阅可以随时中断。例如:当事件多播的时候,即使其中一个订阅方已经处理,触发也不会停止,这样会造成资源浪费。 +3. 订阅回调。例如:第一个订阅方,想知道本次触发的最终结果是否已被处理时,委托则做不到。而插件则可以。 +4. 插件可以被继承,可以被扩展。 +5. 插件可以被注入。 +6. 插件可以独立负责相关功能,实现功能独立。可模块化功能。 + + +## 五、创建插件 + +#### 5.1 新建插件接口及事件类 + +```csharp showLineNumbers +public class MyPluginEventArgs : PluginEventArgs +{ + public string Words { get; set; } +} + +/// +/// 定义一个插件接口,使其继承 +/// +public interface ISayPlugin : IPlugin +{ + /// + /// Say。定义一个插件方法,必须遵循: + /// 1.必须是两个参数,第一个参数可以是任意类型,一般表示触发源。第二个参数必须继承 + /// 2.返回值必须是Task。 + /// + /// 触发主体 + /// 传递参数 + /// + Task Say(object sender, MyPluginEventArgs e); +} +``` + +:::caution 注意事项 + +确定插件唯一的是插件中的`类型`,所以要求一个插件中只允许有一个插件方法。 + +::: + +#### 5.2 实现插件接口 + +新建一个类,实现`ISayPlugin`接口即可。不过为了方便,也可先继承`PluginBase`基类,然后实现所需的插件接口。 + +```csharp showLineNumbers +public class SayHelloPlugin : PluginBase, ISayPlugin +{ + public async Task Say(object sender, MyPluginEventArgs e) + { + Console.WriteLine($"{this.GetType().Name}------Enter"); + if (e.Words == "hello") + { + Console.WriteLine($"{this.GetType().Name}------Say"); + //当满足的时候输出,且不在调用下一个插件。 + + //亦或者设置e.Handled = true,即使调用下一个插件,也会无效 + + return; + } + await e.InvokeNext(); + Console.WriteLine($"{this.GetType().Name}------Leave"); + } +} + +public class SayHiPlugin : PluginBase, ISayPlugin +{ + public async Task Say(object sender, MyPluginEventArgs e) + { + Console.WriteLine($"{this.GetType().Name}------Enter"); + if (e.Words == "hi") + { + Console.WriteLine($"{this.GetType().Name}------Say"); + //当满足的时候输出,且不在调用下一个插件。 + + //亦或者设置e.Handled = true,即使调用下一个插件,也会无效 + + return; + } + + await e.InvokeNext(); + Console.WriteLine($"{this.GetType().Name}------Leave"); + } +} + +internal class LastSayPlugin : PluginBase, ISayPlugin +{ + public async Task Say(object sender, MyPluginEventArgs e) + { + Console.WriteLine($"{this.GetType().Name}------Enter"); + Console.WriteLine($"您输入的{e.Words}似乎不被任何插件处理"); + await e.InvokeNext(); + Console.WriteLine($"{this.GetType().Name}------Leave"); + } +} +``` + +:::tip 提示 + +实现的插件,建议继承`PluginBase`,然后实现所需的插件接口,这样能简化实现过程。但是如果该类型已经拥有基类,则直接实现所需插件接口的全部内容即可。 + +::: + +## 六、订阅插件 + +在插件触发前,需要先订阅插件。这样才知道哪些插件可以处理该数据。 + +### 6.1 创建插件管理器 + +```csharp showLineNumbers +IPluginManager pluginManager = new PluginManager(new Container()) +{ + Enable = true//必须启用 +}; +``` + +### 6.2 添加订阅插件 + +#### 6.2.1 按类型添加 + +```csharp showLineNumbers +//添加订阅插件 +pluginManager.Add(); +pluginManager.Add(); +pluginManager.Add(); +``` + +#### 6.2.2 按实例添加 + +```csharp showLineNumbers +pluginManager.Add(new SayHelloPlugin()); +pluginManager.Add(new SayHiPlugin()); +pluginManager.Add(new LastSayPlugin()); +``` + +#### 6.2.3 按委托添加 + +委托添加插件有多个重载,下面一一为例: + +```csharp showLineNumbers +pluginManager.Add(typeof(ISayPlugin), () => +{ + //无参委托,一般做通知 + Console.WriteLine("在Action1中获得"); +}); + +pluginManager.Add(typeof(ISayPlugin), async (MyPluginEventArgs e) => +{ + //只1个指定参数,当参数是事件参数时,需要主动InvokeNext + Console.WriteLine("在Action2中获得"); + await e.InvokeNext(); +}); + +pluginManager.Add(typeof(ISayPlugin), async (client, e) => +{ + //2个不指定参数,需要主动InvokeNext + Console.WriteLine("在Action3中获得"); + await e.InvokeNext(); +}); + +pluginManager.Add(typeof(ISayPlugin), async (object client, MyPluginEventArgs e) => +{ + //2个指定参数,需要主动InvokeNext + Console.WriteLine("在Action3中获得"); + await e.InvokeNext(); +}); +``` + +:::tip 提示 + +需不需要`InvokeNext`,只需要记住一点,委托中是否接收了`PluginEventArgs`派生的事件参数,如果是,则需要主动调用。 + +::: + +## 七、触发插件 + +直接使用`RaiseAsync`方法即可触发插件。 + +在触发时,`sender`参数和`PluginEventArgs`参数必须和插件中定义的方法参数一致,不然会抛出异常。 + +```csharp {1} +await pluginManager.RaiseAsync(typeof(ISayPlugin), new object(), new MyPluginEventArgs() +{ + Words = Console.ReadLine() +}); +``` + +### 7.1 执行结果 + +按照上述代码代码逻辑,我们声明了一个名为`ISayPlugin`的插件接口,里面包含一个`Say`的方法。然后分别创建了`SayHelloPlugin`、`SayHiPlugin`、`LastSayPlugin`三个类去实现`ISayPlugin`接口。然后将该三个类都添加至插件管理器中,然后触发`Say`方法。同时传入不同的参数。 + +当Words=test时,`SayHelloPlugin`和`SayHiPlugin`均不满足处理条件,所以会将数据转至下一个插件,直到`LastSayPlugin`插件。然后当`LastSayPlugin`处理结束以后,处理结果又按照`LastSayPlugin`、`SayHiPlugin`、`SayHelloPlugin`的顺序退出插件。这样,即使`SayHelloPlugin`无法处理该数据,也能得知该数据最终有没有被处理。 + +当Words=hello时,`SayHelloPlugin`满足处理条件,并且终止插件的继续传递。 + +``` +请输入hello,或者hi +test +SayHelloPlugin------Enter +SayHiPlugin------Enter +LastSayPlugin------Enter +您输入的test似乎不被任何插件处理 +LastSayPlugin------Leave +SayHiPlugin------Leave +SayHelloPlugin------Leave + +请输入hello,或者hi +hello +SayHelloPlugin------Enter +SayHelloPlugin------Say + +请输入hello,或者hi +hi +SayHelloPlugin------Enter +SayHiPlugin------Enter +SayHiPlugin------Say +SayHelloPlugin------Leave +请输入hello,或者hi +``` + +## 八、插件特性 + +### 8.1 中断传递 + +当某个插件在响应时,如果设置`e.Handled=true`,或者没有调用下一个插件`e.InvokeNext`,则该数据将**不会**再触发后续的插件。 + + +## 九、提升插件性能 + +### 9.1 插件性能测试 + +如下图所示,添加10个插件,并且调用10000次。即:单个插件方法被调用10w次。 + +分别在net6.0,netcore3.1,net4.6.1上进行测试。测试项依次为: + +1. DirectRun(直接调用) +2. ActionRun(委托调用) +3. MethodInfoRun(反射调用) +4. ExpressionRun(表达式树调用) +5. PluginRun(插件调用) +6. PluginActionRun(插件委托调用) + +实际上插件内部使用的是IL调用,所以即使调用上有迭代,性能上也和直接调用的表达式树差不多(这里插件是采用递归的方式迭代,而表达式树测试则是直接for迭代)。但是总体而言性能上是有缺失的。 + +但是当使用插件委托调用时,性能上会有所提升。和直接调用相比,虽然性能降低了20%。但是这已经是委托调用的极限,且没有任何动态代码的生成。这意味着在unity调用也是完全可行的。 + + + +### 9.2 注册委托 + +注册委托,使用委托,可以提升插件性能。 + +```csharp showLineNumbers +//订阅插件,不仅可以使用声明插件的方式,还可以使用委托。 +pluginsManager.Add(typeof(ISayPlugin), () => +{ + Console.WriteLine("在Action1中获得"); +}); +``` + +其次,不仅可以直接使用委托,还可以在插件里面注册方法为委托。 + +```csharp showLineNumbers +public class SayHelloAction: PluginBase +{ + protected override void Loaded(IPluginsManager pluginsManager) + { + base.Loaded(pluginsManager); + + //注册本地方法为委托 + pluginsManager.Add(typeof(ISayPlugin),this.Say); + } + + public async Task Say(object sender, MyPluginEventArgs e) + { + Console.WriteLine($"{this.GetType().Name}------Enter"); + if (e.Words == "helloaction") + { + Console.WriteLine($"{this.GetType().Name}------Say"); + //当满足的时候输出,且不在调用下一个插件。 + + //亦或者设置e.Handled = true,即使调用下一个插件,也会无效 + + return; + } + await e.InvokeNext(); + Console.WriteLine($"{this.GetType().Name}------Leave"); + } +} +``` + +:::caution 注意事项 + +注册方法为委托时,不要再实现接口,避免重复调用。 + +::: + +### 9.3 源生成插件 + +使用源生成器,直接可以生成委托调用,但是这要求你的编译器支持,一般(vs2022以上、或者Rider等)。 + +一般的,这不需要你的关系,因为当源生成可用时,会自动使用源生成器。 + + +[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Core/PluginConsoleApp) \ No newline at end of file diff --git a/handbook/docs/reconnection.mdx b/handbook/versioned_docs/version-3.1/reconnection.mdx similarity index 100% rename from handbook/docs/reconnection.mdx rename to handbook/versioned_docs/version-3.1/reconnection.mdx diff --git a/handbook/docs/remotemonitoring.mdx b/handbook/versioned_docs/version-3.1/remotemonitoring.mdx similarity index 100% rename from handbook/docs/remotemonitoring.mdx rename to handbook/versioned_docs/version-3.1/remotemonitoring.mdx diff --git a/handbook/docs/resetid.mdx b/handbook/versioned_docs/version-3.1/resetid.mdx similarity index 100% rename from handbook/docs/resetid.mdx rename to handbook/versioned_docs/version-3.1/resetid.mdx diff --git a/handbook/versioned_docs/version-3.1/rpcactionfilter.mdx b/handbook/versioned_docs/version-3.1/rpcactionfilter.mdx new file mode 100644 index 000000000..b35f3c0ce --- /dev/null +++ b/handbook/versioned_docs/version-3.1/rpcactionfilter.mdx @@ -0,0 +1,165 @@ +--- +id: rpcactionfilter +title: Rpc服务AOP +--- + +import { TouchSocketRpcDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +Rpc服务在被调用时,会触发一系列的Rpc筛选器AOP**IRpcActionFilter**的**特性(Attribute)**,进行相关AOP操作。所以可以利用该特性做很多有关Rpc的AOP操作。 + +## 二、支持的特性方法 + +| 方法名 | 触发时机 |功能| +| ---- | ---- |---- | +| ExecutingAsync | 在执行Rpc之前 |当invokeResult的InvokeStatus不为InvokeStatus.Ready。则不会执行Rpc。同时,当InvokeStatus为Success。会直接返回结果| +| ExecutedAsync | 在执行Rpc后 |如果修改invokeResult的InvokeStatus,或Result。则会影响Rpc最终结果| + +## 三、使用 + +### 3.1 定义RpcActionFilterAttribute特性 + +```csharp showLineNumbers +public class MyRpcActionFilterAttribute : RpcActionFilterAttribute +{ + public override async Task ExecutingAsync(ICallContext callContext, object[] parameters, InvokeResult invokeResult) + { + if (callContext.Caller is ITcpSessionClient client) + { + client.Logger.Info($"即将执行Rpc-{callContext.RpcMethod.Name}"); + } + return await Task.FromResult(invokeResult); + } + + public override async Task ExecutedAsync(ICallContext callContext, object[] parameters, InvokeResult invokeResult, Exception exception) + { + if (callContext.Caller is ITcpSessionClient client) + { + client.Logger.Info($"执行RPC-{callContext.RpcMethod.Name}结束,状态={invokeResult.Status}"); + } + + return await base.ExecutedAsync(callContext, parameters, invokeResult, exception); + } +} +``` + +:::tip 提示 + +使用RpcActionFilterAttribute特性,不仅可以实现日志记录,还可以实现访问权限限制、全局异常捕捉等。 + +::: + + +### 3.2 使用 + +RpcActionFilterAttribute 特性可以应用到任何方法上,也可以应用到类上。 + +```csharp {1,19} +[MyRpcActionFilter] +class MyRpcServer : SingletonRpcServer +{ + private readonly ILog m_logger; + + public MyRpcServer(ILog logger) + { + this.m_logger = logger; + } + + /// + /// 将两个数相加 + /// + /// + /// + /// + [DmtpRpc(MethodInvoke = true)]//使用函数名直接调用 + [Description("将两个数相加")]//其作用是生成代理时,作为注释。 + [MyRpcActionFilter] + public int Add(int a, int b) + { + this.m_logger.Info("调用Add"); + var sum = a + b; + return sum; + } +} +``` + +### 3.3 规则 + +1. 标签添加在`方法`、`注册接口`或`注册服务`上均会生效。 +2. 标签生效顺序为`接口方法(如果有)`、`服务方法`、`注册接口(如果有`)、`服务`。 +3. 同一类型的标签仅生效一次。 +4. 继承的特性,可以通过`MutexAccessTypes`属性来标识以哪个类型作为同一特性标识。 + +第1、2条规则,以下代码为例,执行顺序依次为`MyRpcActionFilter1`、`MyRpcActionFilter3`、`MyRpcActionFilter2`、`MyRpcActionFilter4`。 + +```csharp showLineNumbers +[MyRpcActionFilter2] +interface IMyRpcServer : ISingletonRpcServer +{ + [MyRpcActionFilter1] + int Add(int a, int b); +} + +[MyRpcActionFilter4] +class MyRpcServer :SingletonRpcServer, IMyRpcServer +{ + [MyRpcActionFilter3] + public int Add(int a, int b) + { + return a + b; + } +} +``` + +第3条规则,以下代码为例,只会将`MyRpcActionFilter`执行一次。 + +```csharp showLineNumbers +[MyRpcActionFilter] +interface IMyRpcServer : ISingletonRpcServer +{ + [MyRpcActionFilter] + int Add(int a, int b); +} + +[MyRpcActionFilter] +class MyRpcServer :SingletonRpcServer, IMyRpcServer +{ + [MyRpcActionFilter] + public int Add(int a, int b) + { + return a + b; + } +} +``` + +第4条规则,以下代码为例。在`MyBaseAttribute`中指定`MutexAccessTypes`为`MyBaseAttribute`,则`MyAttribute`和`My2Attribute`都继承`MyBaseAttribute`时,即为互斥,在生效时会按照优先等级有且只有一个生效。 + +```csharp showLineNumbers +interface IInterface +{ + [My] + [My2] + void Test(); +} + +class MyBaseAttribute:RpcActionFilterAttribute +{ + public override Type[] MutexAccessTypes => new Type[] {typeof(MyBaseAttribute) }; +} + +class MyAttribute: MyBaseAttribute +{ + +} + +class My2Attribute : MyBaseAttribute +{ + +} +``` diff --git a/handbook/versioned_docs/version-3.1/rpcallcontext.mdx b/handbook/versioned_docs/version-3.1/rpcallcontext.mdx new file mode 100644 index 000000000..a1566857e --- /dev/null +++ b/handbook/versioned_docs/version-3.1/rpcallcontext.mdx @@ -0,0 +1,201 @@ +--- +id: rpcallcontext +title: 调用上下文 +--- + +import { TouchSocketRpcDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +> Rpc服务的调用是无状态的,即只知道当前服务被调用,但无法得知是被谁调用,这个问题给日志记录、Rpc回调等带来了很多麻烦事。所以我们在设计Rpc时,也设计了调用上下文获取。 + +在上下文中可以获得调用者`Caller`等信息,可以获得调用的IP或其他信息。 + +:::tip 说明 + +调用上下文(`ICallContext`)实例每次请求都会创建,所以,不要在上下文中存放一些需要长期使用的数据。 + +::: + + +## 二、使用 + +### 2.1 通过传参获得 + +当服务是单例注册时,服务方法可能会被并发调用,所以,调用上下文必须从参数获得传入。 + +**步骤:** + +定义的服务方法的`第一个参数`使用`ICallContext`或其派生类(例如:`DmtpRpc`可以使用`IDmtpRpcCallContext`)。 + +```csharp showLineNumbers +public class MyRpcServer : SingletonRpcServer +{ + [Description("登录")] + [DmtpRpc] + public bool Login(ICallContext callContext,string account,string password) + { + if (callContext.Caller is TcpDmtpSessionClient) + { + Console.WriteLine("TcpDmtpRpc请求"); + } + if (account=="123"&&password=="abc") + { + return true; + } + + return false; + } +} +``` + +### 2.2 通过瞬时生命周期获取 + +当服务是瞬态注册时,每次调用服务会创建新的实例,所以当前方法只会被当前调用者拥有,所以,调用上下文会从属性直接注入。 + +步骤: + +1. 继承TransientRpcServer或者实现ITransientRpcServer接口。 + +```csharp showLineNumbers +public class MyRpcServer : TransientRpcServer +{ + [Description("登录")] + [DmtpRpc] + public bool Login(string account,string password) + { + if ( this.CallContext.Caller is TcpDmtpSessionClient) + { + Console.WriteLine("TcpDmtpRpc请求"); + } + if (account=="123"&&password=="abc") + { + return true; + } + + return false; + } +} + +//或使用泛型上下文 +public class MyRpcServer : TransientRpcServer +{ + [Description("登录")] + [DmtpRpc] + public bool Login(string account, string password) + { + if (this.CallContext.Caller is TcpDmtpSessionClient) + { + Console.WriteLine("TcpDmtpRpc请求"); + } + if (account == "123" && password == "abc") + { + return true; + } + + return false; + } +} +``` + +### 2.3 通过IRpcCallContextAccessor服务获取 + +不管在单例,还是瞬态的Rpc服务,都可以通过`IRpcCallContextAccessor`服务获取到当前调用的上下文。 + +首先,需要在容器中注册`IRpcCallContextAccessor`服务。 + +```csharp {3} showLineNumbers +.ConfigureContainer(a => +{ + a.AddRpcCallContextAccessor(); +}); +``` + +然后,在Rpc服务中,通过`IRpcCallContextAccessor`服务获取到当前调用的上下文。 + +```csharp {5,7,16} showLineNumbers +public partial class MyRpcServer : SingletonRpcServer +{ + private readonly IRpcCallContextAccessor m_rpcCallContextAccessor; + + public MyRpcServer(IRpcCallContextAccessor rpcCallContextAccessor) + { + this.m_rpcCallContextAccessor = rpcCallContextAccessor; + } + + [Description("测试从CallContextAccessor中获取当前关联的CallContext")] + [DmtpRpc] + public async Task TestGetCallContextFromCallContextAccessor() + { + //通过CallContextAccessor获取当前关联的CallContext + //此处即使m_rpcCallContextAccessor与当前RpcServer均为单例,也能获取到正确的CallContext + var callContext = this.m_rpcCallContextAccessor.CallContext; + await Task.CompletedTask; + } +} +``` + +:::tip 提示 + +该方式是通过`AsyncLocal`的方式实现的,所以,即使把`IRpcCallContextAccessor`保存到静态变量中,也能正常获取到正确的`CallContext`。但前提是,获取流程在调用上下文生命周期内,否则,可能会获取到错误的`CallContext`。 + +::: + +## 三、取消任务 + +一般的,Rpc在执行时,如遇到异常,或主动操作,会自动取消任务。所以我们在调用上下文`ICallContext`接口中也规范了取消任务的方法。 + +下列将以`DmtpRpc`为例,介绍如何取消任务。 + +```csharp showLineNumbers +/// +/// 测试取消调用 +/// +/// +/// +[Description("测试取消调用")] +[DmtpRpc] +public async Task TestCancellationToken(ICallContext callContext) +{ + //模拟一个耗时操作 + for (var i = 0; i < 10; i++) + { + //判断任务是否已被取消 + if (callContext.Token.IsCancellationRequested) + { + Console.WriteLine("执行已取消"); + return i; + } + Console.WriteLine($"执行{i}次"); + await Task.Delay(1000); + } + + return -1; +} +``` + +:::tip 提示 + +由于Rpc框架仅约束了取消任务的接口,并无实际实现。所以取消任务的时机,完全取决于具体Rpc框架的实现者。例如:对于DmtpRpc,当连接断开或主动取消任务时,均会触发取消任务。 + +同时,如果想自己手动取消任务,也可以在调用上下文生命周期内的任意地方,使用`callContext.Cancel()`来取消任务。 + +::: + +## 四、调用上下文属性 + +调用上下文是所有Rpc均支持的(包括[DmtpRpc](./dmtprpc.mdx)、[JsonRpc](./jsonrpc.mdx)、[XmlRpc](./xmlrpc.mdx)、[WebApi](./webapi.mdx))。 + +但是由于不同的Rpc实现方式,其调用上下文参数的类型也不同。 + +一般的: + +- `Caller`属性就是调用的触发终端。例如:使用DmtpRpc-Tcp,由客户端调用服务器时,`Caller`就是`TcpDmtpSessionClient`。 +- `RpcMethod`属性就是调用的触发方法。 +- 其他参数可以参阅注释帮助理解。 + diff --git a/handbook/versioned_docs/version-3.1/rpcauthorization.mdx b/handbook/versioned_docs/version-3.1/rpcauthorization.mdx new file mode 100644 index 000000000..30777d679 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/rpcauthorization.mdx @@ -0,0 +1,167 @@ +--- +id: rpcauthorization +title: Rpc鉴权授权策略 +--- + +import BilibiliCard from '@site/src/components/BilibiliCard.js'; + +## 一、说明 + +鉴权授权策略,用于控制RPC请求的访问权限。是现代api-server的系统最重要的功能之一。 + +一般的鉴权授权策略可以分为2个大类: + +1. 基于会话 +2. 基于Token + +而基于Token的鉴权授权策略又分为2大类: + +1. 基于随机字符串的Token +2. 基于结构化的Token(例如:JWT) + + + +## 二、基于会话的鉴权授权策略 + +基于会话的鉴权授权策略,一般是只应用于面向连接的通信。一般的这种通信具有持久化的特性,并且是有状态的。 + + + +## 三、基于Token的鉴权授权策略 + +基于Token的鉴权授权策略,一般用于面向消息的通信。 + +### 3.1 基于随机字符串的Token + + + +### 3.2 基于结构化的Token(例如:JWT) + + + +## 四、鉴权授权策略的比较 + +好的,我们来详细比较一下随机字符串(通常指无结构会话令牌)和 JWT(JSON Web Token)的优缺点以及适用场景。 + +**核心区别:** + +* **随机字符串 (无结构令牌):** 本质上就是一个不可预测的长字符串(如 `f8a3d7c0b1e9`)。它本身**不包含任何有意义的信息**,只是一个指向服务器端存储(通常是数据库或缓存)中会话数据的**引用**。 +* **JWT (结构化令牌):** 是一个经过数字签名或加密的、**自包含**的结构化字符串。它由三部分组成(Header.Payload.Signature),其中 `Payload` 部分包含了**声明**(通常是关于用户身份和权限的 JSON 对象)。服务器可以验证其签名/加密,并直接从中提取信息,无需查询后端存储(至少在有效期内)。 + +--- + +### 4.1 随机字符串 (无结构会话令牌) + +* **优点:** + 1. **简单轻量:** 生成和验证极其简单快速(生成随机数 -> 存储;收到请求 -> 查存储)。 + 2. **完全控制:** 服务器拥有绝对控制权。可以即时撤销(删除存储条目)、修改关联数据(更新存储条目)或强制过期。 + 3. **无信息泄露:** 令牌本身不包含任何用户或会话信息,即使被截获,攻击者也无法从中直接获取有效内容(前提是字符串本身足够随机)。 + 4. **存储开销可控:** 服务器端存储的大小和内容完全由应用决定。 + 5. **天然防篡改:** 篡改令牌会导致在服务器端存储中查找失败,验证直接不通过。 + +* **缺点:** + 1. **状态性:** 服务器**必须**维护一个存储(数据库、Redis 等)来关联令牌和实际的会话数据。这引入了状态。 + 2. **数据库/缓存依赖:** 每次验证令牌都需要查询后端存储。**在高并发场景下,这可能成为性能瓶颈和单点故障。** + 3. **扩展性挑战:** 在分布式系统或微服务架构中,需要共享会话存储(例如集中式 Redis 集群)或实现粘性会话(Session Affinity),增加了复杂性和潜在瓶颈。 + 4. **无内置信息:** 令牌本身不携带任何信息,所有数据都需要从存储中获取。 + +* **典型使用场景:** + 1. **传统的 Web 应用会话管理:** 这是最经典的场景。用户登录后,服务器创建会话数据存储在服务端(内存、数据库、Redis),生成一个随机的 Session ID(通常存储在 Cookie 中)返回给浏览器。后续请求携带此 ID,服务器查询存储获取会话状态。 + 2. **一次性令牌:** 如密码重置令牌、邮箱验证令牌。它们通常是随机字符串,存储在数据库并关联特定操作和过期时间,使用一次即失效。 + 3. **简单的 API 认证(较少见):** 如果 API 调用频率不高或架构简单,也可以使用类似机制(API Key + Secret,但 Secret 通常也需要存储和验证)。 + 4. **需要即时撤销能力的场景:** 如用户登出、管理员踢人下线等,需要立即使令牌失效的场景。 + +--- + +### 4.2 JWT (JSON Web Token) + +* **优点:** + 1. **无状态:** 这是 JWT 最大的优势。服务器不需要在本地或共享存储中保存会话状态。验证仅依赖于签名(和可选的加密)以及预定义的密钥/证书。**极大简化了服务器架构,消除了数据库查询瓶颈。** + 2. **自包含:** Payload 中可以携带有用的声明(用户名、用户ID、角色、权限、过期时间等)。验证通过后,服务器可以直接使用这些信息,无需额外查询。 + 3. **扩展性极佳:** 完美适应分布式系统和微服务架构。任何服务实例只要拥有验证签名/解密的密钥,都可以独立验证令牌并提取所需信息。**天然支持跨域/跨服务认证。** + 4. **性能(特定场景):** 避免了每次请求的存储查询开销(验证签名的计算开销通常远小于网络 I/O 和数据库查询)。在分布式系统中,优势尤其明显。 + 5. **标准化:** 是 IETF 标准 (RFC 7519),有成熟的库支持多种语言,互操作性好。 + +* **缺点:** + 1. **体积较大:** 由于是 Base64 编码的 JSON,通常比随机字符串长很多。在带宽敏感或需频繁传输的场景(如放在 HTTP Header 中)可能成为负担。 + 2. **难以主动撤销/失效:** 一旦签发,在自然过期前,很难强制使其失效(因为服务端无状态)。实现主动撤销需要额外机制(如黑名单、短期有效期 + Refresh Token),增加了复杂性。 + 3. **安全风险:** + * **令牌泄露:** 如果 JWT 被盗(如 XSS、中间人攻击),攻击者可以在有效期内冒充用户(“持票攻击”)。缩短有效期和使用 HTTPS 是必须的。 + * **签名算法安全:** 如果使用弱算法(如 `HS256` 密钥太弱)或实现不当(如未验证签名),可能被伪造。必须使用强算法(如 `RS256`,`ES256`)并妥善保管私钥/密钥。 + * **敏感数据泄露:** Payload 默认是 Base64 编码(可逆),**不应在其中存放密码、信用卡号等绝对敏感信息**。如需保密,应使用 JWE(JSON Web Encryption)进行加密。 + 4. **客户端存储责任:** 令牌由客户端(浏览器、移动 App)存储和管理,需要防范 XSS、CSRF 等攻击。 + 5. **实现复杂度:** 需要正确理解和实现签名/验证、密钥管理、声明处理、过期处理等,比生成随机字符串更复杂。 + +* **典型使用场景:** + 1. **跨域/单点登录:** 用户在一个域登录后,可以获取一个 JWT,用于访问其他信任该 JWT 签发方的应用或服务。OAuth 2.0 / OpenID Connect 的核心令牌之一就是 JWT。 + 2. **API 认证与授权:** 现代 RESTful / GraphQL API 的**首选认证机制**。客户端在登录后获取 JWT,后续请求在 `Authorization: Bearer ` 头中携带。API 服务器验证签名和声明即可判断身份和权限。 + 3. **无状态微服务间通信:** 微服务 A 可以将包含用户上下文的 JWT 传递给微服务 B,B 能独立验证并使用其中的信息,无需调用中心化的认证服务或共享会话存储。 + 4. **信息交换:** 作为在双方之间安全传递声明信息的一种方式(需签名/加密确保完整性和保密性),例如在 OAuth 流程中传递用户信息(ID Token)。 + +--- + +### 4.3 总结与选择建议 + +| 特性 | 随机字符串 (无结构令牌) | JWT (JSON Web Token) | +| :----------- | :------------------------------------------------ | :-------------------------------------------------- | +| **核心** | **引用** (指向服务器存储的数据) | **自包含** (数据在令牌内) | +| **状态** | **有状态** (服务器需存储会话) | **无状态** (服务器无需存储会话) | +| **信息携带** | 无 | 有 (Payload 中的声明) | +| **验证方式** | 查数据库/缓存 | 验证签名/解密 | +| **性能** | 每次请求需查存储 (可能成瓶颈) | 无存储查询 (签名验证计算开销小) | +| **扩展性** | 差 (需共享存储或粘性会话) | **极佳** (天然支持分布式) | +| **撤销** | **容易** (直接删除存储条目) | **困难** (需额外机制如黑名单、短有效期+Refresh) | +| **大小** | 小 | 较大 | +| **安全性** | 依赖存储安全、传输安全;令牌本身无信息 | 依赖算法强度、密钥管理、传输安全;需防泄露、篡改 | +| **复杂度** | 低 (生成随机数、存、查) | 中高 (签名/验证、密钥管理、声明处理、安全最佳实践) | +| **典型场景** | **传统Web会话**
**一次性令牌**
需即时撤销 | **API 认证**
**单点登录 / SSO**
**微服务通信** | + +**如何选择?** + +1. **选择随机字符串 (无结构令牌) 当:** + * 你正在构建一个传统的、服务器端渲染的 Web 应用。 + * 你需要**立即且可靠地撤销令牌**的能力(如用户登出)。 + * 应用是单体或规模较小,维护共享会话存储不是问题。 + * 令牌本身不需要携带额外的声明信息。 + * 对性能要求极高且请求非常密集,且能承受存储查询开销(或有强大缓存)。 + +2. **选择 JWT 当:** + * 你在构建 **API**(尤其是 RESTful / GraphQL)。 + * 你需要 **SSO** 或 **跨域/跨服务认证**。 + * 你的架构是 **分布式、微服务**,需要服务间传递用户上下文。 + * **可扩展性** 和 **消除数据库依赖** 是主要目标。 + * 令牌需要携带一些**非绝对敏感**的声明(用户ID、角色、权限),并让服务端能直接使用。 + * 你愿意并能够处理 JWT 的安全复杂性(签名、密钥管理、短有效期、HTTPS)和潜在的撤销难题。 + +**重要安全提示:** + +* **无论使用哪种方式,都必须通过 HTTPS 传输令牌!** +* **JWT 的 Payload 不是加密的(除非使用 JWE),不要存敏感数据!** +* **使用强随机数生成器创建随机字符串或 JWT 的 `jti` (JWT ID)。** +* **仔细选择并安全配置 JWT 的签名算法和密钥。** +* **始终验证 JWT 的签名、有效期 (`exp`)、受众 (`aud`) 等关键声明。** +* **考虑令牌泄露的风险,使用较短的过期时间,并为 JWT 实现合理的撤销机制(如黑名单)或结合使用 Refresh Token。** + +在实践中,两者也并非完全互斥。例如,一个系统可能使用基于随机字符串的传统会话管理 Web 界面,而其后台 API 则使用 JWT 进行交互。或者,在 JWT 的流程中,Refresh Token 本身可能是一个存储在数据库中的随机字符串。理解各自的优缺点有助于你为系统的不同部分选择最合适的工具。 + +## 五、客户端传递Token + +### 5.1 客户端使用HttpClient请求授权 + + + +### 5.2 客户端使用WebApiClient请求授权 + + + +### 5.3 客户端使用DmtpRpc请求授权 + + + +### 5.4 客户端使用通用Rpc请求授权 + + + +### 5.5 基于角色的授权 + + diff --git a/handbook/versioned_docs/version-3.1/rpcdescription.mdx b/handbook/versioned_docs/version-3.1/rpcdescription.mdx new file mode 100644 index 000000000..4807f2528 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/rpcdescription.mdx @@ -0,0 +1,128 @@ +--- +id: rpcdescription +title: Rpc描述 +--- + +## 一、Rpc描述 + +Rpc(远程过程调用)​​ 是一种允许计算机程序调用另一台计算机(或同一台计算机的不同进程)上的子程序(服务)的技术,​​无需程序员显式处理网络通信细节​​。 + +简单来说,它的目标是让开发者像调用本地函数一样调用远程服务。 + + +## 二、核心思想 + +- **​隐藏复杂性​** ​:RPC 抽象了底层的网络传输、数据序列化、错误处理等细节。 + +- **​像本地调用一样简单**​ ​:开发者只需关注业务逻辑,无需编写网络通信代码。 + +### 2.1 像本地调用一样使用远程服务 + +想象一下打电话点外卖: +1. **你(客户端)**:拿起电话,告诉餐厅(服务器)你要什么(调用参数)。 +2. **电话网络(网络)**:你的请求被传输到餐厅。 +3. **餐厅(服务器)**:收到订单,厨房(服务端函数)开始制作。 +4. **电话网络(网络)**:做好的外卖(结果)被送回来。 +5. **你(客户端)**:收到外卖(结果),继续做自己的事。 + +```mermaid +sequenceDiagram + participant 客户端 as 客户端 (Client) + participant 网络 as 电话网络 (Network) + participant 服务器 as 餐厅服务器 (Server) + participant 服务 as 厨房 (Service Function) + + activate 客户端 + 客户端->>网络: 1. 发送请求:订单参数
(调用参数) + activate 网络 + 网络->>服务器: 2. 传输请求 + deactivate 网络 + activate 服务器 + + 服务器->>服务: 3. 处理订单
(调用服务端函数) + activate 服务 + 服务-->>服务器: 4. 返回结果
(制作完成的外卖) + deactivate 服务 + + 服务器->>网络: 5. 返回响应 + activate 网络 + 网络->>客户端: 6. 传输结果 + deactivate 网络 + + 客户端-->>客户端: 7. 处理结果
(继续其他任务) + deactivate 客户端 + deactivate 服务器 +``` + +RPC 的目标就是让你感觉“点外卖”这个动作(调用远程函数)就像从自己冰箱里拿东西(调用本地函数)一样简单直接,不用操心电话怎么接通、外卖员怎么送这些底层细节。 + +### 2.2 RPC 的工作原理(简化版) + +1. **客户端调用**:客户端程序像调用本地函数一样,调用一个看起来是本地函数的“存根”(Stub)。 +2. **参数打包**:客户端存根负责将调用的**函数名**、**参数**等信息打包(这个过程叫 **Marshalling** 或 **Serialization**),转换成适合网络传输的格式(如 JSON, XML, Protocol Buffers, Thrift 等)。 +3. **网络传输**:打包好的数据通过底层网络协议(通常是 TCP/IP)发送到远程服务器。 +4. **服务器接收**:服务器端的“骨架”(Skeleton)接收到网络请求。 +5. **参数解包**:服务器骨架将接收到的数据解包(**Unmarshalling** 或 **Deserialization**),还原成服务器程序能理解的形式(函数名、参数)。 +6. **实际执行**:服务器定位到真正的目标函数或方法,并用解包出来的参数执行它。 +7. **结果打包**:服务器将函数的执行**结果**(或错误信息)打包。 +8. **结果返回**:打包好的结果通过网络发送回客户端。 +9. **客户端接收**:客户端存根接收网络返回的数据。 +10. **结果解包**:客户端存根解包数据,得到真正的结果(或错误)。 +11. **返回结果**:客户端存根将结果返回给最初发起调用的客户端程序代码。对客户端代码来说,它感觉就像是本地函数返回了结果一样。 + +```mermaid +sequenceDiagram + participant Client as 客户端程序 + participant Stub as 客户端存根 + participant Network as 网络传输 + participant Skeleton as 服务器骨架 + participant Service as 实际服务 + + Client->>Stub: 1. 发起调用 (像调用本地函数) + activate Stub + + Stub->>Stub: 2. 参数打包 (Marshalling/Serialization) + Stub->>Network: 3. 发送请求 (函数名+参数) + activate Network + + Network->>Skeleton: 4. 传输请求 + deactivate Network + activate Skeleton + + Skeleton->>Skeleton: 5. 参数解包 (Unmarshalling/Deserialization) + Skeleton->>Service: 6. 调用实际服务 + activate Service + + Service->>Service: 执行目标函数 + Service-->>Skeleton: 7. 返回结果 + deactivate Service + + Skeleton->>Skeleton: 8. 结果打包 (Marshalling) + Skeleton->>Network: 9. 返回结果 + activate Network + + Network->>Stub: 10. 传输响应 + deactivate Network + + Stub->>Stub: 11. 结果解包 (Unmarshalling) + Stub-->>Client: 12. 返回最终结果 + deactivate Stub + + Client->>Client: 像本地函数返回一样处理结果 +``` + +## 三、RPC 的关键特性/优点 + +1. **透明性**:这是最大的优点!开发者主要关注业务逻辑(调什么函数、传什么参数、拿什么结果),无需深入处理网络连接、数据传输、序列化/反序列化等底层细节。这些由 RPC 框架自动处理。 +2. **抽象性**:将分布式系统中的服务调用抽象为简单的函数调用,简化了分布式程序的开发。 +3. **效率**:现代 RPC 框架(如 gRPC、DmtpRpc)通常使用高效的二进制序列化协议(如 Protobuf)和基于 HTTP/2 的传输,性能较高。 +4. **促进微服务架构**:RPC 是微服务之间进行通信的一种非常主流和自然的方式。 + +## 四、为什么使用TouchSocket的RPC框架? + +`TouchSocket`的`Rpc`组件,是一个轻量级的`Rpc`平台,它提供了一套完整的`Rpc服务注册`、`调度`、`执行`以及`调用`规范。 + +目前已有4个`Rpc`框架:`DmtpRpc`、`WebApi`、`JsonRpc`、`XmlRpc`是基于`TouchSocket`的`Rpc`框架开发的。 + +一般来说,你可以基于此,开发出自己的`Rpc`框架。你只需要简单遵循几个规范,即可使用全部`Rpc`功能。 + diff --git a/handbook/versioned_docs/version-3.1/rpcdispatcher.mdx b/handbook/versioned_docs/version-3.1/rpcdispatcher.mdx new file mode 100644 index 000000000..a79c5ade7 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/rpcdispatcher.mdx @@ -0,0 +1,57 @@ +--- +id: rpcdispatcher +title: Rpc执行调度器 +--- + +## 一、说明 + +Rpc执行调度器(`RpcDispatcher`)是`TouchSocket.Rpc`框架中的核心组件之一,用于实现远程过程调用(`Rpc`)的服务执行方式。 + +目前,内置了`ConcurrencyDispatcher`、`QueueRpcDispatcher`、`ImmediateRpcDispatcher`三种调度器,分别用于实现不同场景下的服务执行方式。 + +## 二、调度器说明 + +`RpcDispatcher`是直接应用于`Rpc Caller`的。所以它在被调用方可能是以多个实例的形式存在。 + +例如`DmtpRpc`,对于服务端而言,如果有多个`Rpc`客户端连接,那么每个连接都会拥有一个`RpcDispatcher`实例。 + +### 2.1 并发调度器(ConcurrencyDispatcher) + +并发调度器(`ConcurrencyDispatcher`)是在收到`Rpc`请求后,直接使用线程池(`Task.Run`)直接执行`Rpc`。所以它是完全并发的。 + +### 2.2 队列调度器(QueueRpcDispatcher) + +队列调度器(`QueueRpcDispatcher`)是在收到`Rpc`请求后,先将请求放入队列中,再通过线程(`Task.Run`)执行Rpc。所以它在当前实例中(可以简单理解为一个连接)是有顺序的。 + + +### 2.3 立即调度器(ImmediateRpcDispatcher) + +立即调度器(`ImmediateRpcDispatcher`)是在收到`Rpc`请求后,使用`Rpc`接收线程,直接执行`Rpc`。所以它的同步性是依赖接收线程的。 + +## 三、使用 + +`Rpc`调度器并非强制要求,具体还得看`Rpc`框架本身支不支持多样的调度方式。 + +例如,`DmtpRpc`框架就支持所有调度方式,但是对于WebApi框架,它只支持`ImmediateRpcDispatcher`。 + +所以实际使用还得看具体框架的支持情况。 + +## 四、可重入性 + +可重入性也表示的是在一个`Caller`的`Rpc`调用中的并发性。不过可以使用`[Reenterable]`特性来控制方法、服务级别的并发。 + +可重入性会受调度器的完全影响。如果调度器本身不支持可重入性,那么它将不能支持可重入性。 + +例如,对于并发调度器(`ConcurrencyDispatcher`),它默认支持可重入性。 + +但是对于有的`Rpc`方法,我们希望仅这个方法不支持可重入性,那么我们可以使用`[Reenterable]`特性来控制。 + +```csharp {3} showLineNumbers +interface IMyRpcServer : ISingletonRpcServer +{ + [Reenterable(false)] + [DmtpRpc(MethodInvoke = true)]//服务注册的函数键,此处为显式指定。默认不传参的时候,为该函数类全名+方法名的全小写。 + Task Output(int value); +} +``` + diff --git a/handbook/versioned_docs/version-3.1/rpcgenerateproxy.mdx b/handbook/versioned_docs/version-3.1/rpcgenerateproxy.mdx new file mode 100644 index 000000000..ffdf5923f --- /dev/null +++ b/handbook/versioned_docs/version-3.1/rpcgenerateproxy.mdx @@ -0,0 +1,657 @@ +--- +id: rpcgenerateproxy +title: 生成、获取代理 +--- + +import { TouchSocketRpcDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、为什么要生成代理 + +使用Rpc的原则就是像使用本地方法一样,让开发者感觉不到任何的不同。 + +但是我们的Rpc预留的接口都是`Invoke`函数。也就是每次调用都必须手动传入参数和返回值类型,即使有`InvokeT`的扩展调用,也很容易出错。因为参数类型都是`object`,所以可以无法进行良好的**代码提示**。 + +同时,当服务端更新时,例如:新增一个参数,或者修改了返回值类型,那么调用代码必须同步修改,不然就非常容易报错。 + +诸如此类的情况,会大大降低开发效率。 + +所以就必须把服务代理到本地,即生成调用接口代码,或者扩展调用代码。要实现此方式,常见的有三种,**动态代理接口**,**静态织入**,**静态编译**。三种方式殊途同归,最终都是构建本地数据结构,然后和远程通信。三种方式各有优缺,具体如下: + +| **优缺点** | **动态代理接口** | **静态织入(源代码生成)** | **静态编译** | +| --- | --- | --- | --- | +| **优点** | 动态构建类,灵活、适应性强。 | 静态代码生成,自定义类参数自动生成,修改较灵活,调用效率高 | 自定义类参数自动生成,密封性强,安全性高,调用效率高。 | +| **缺点** | 调用效率较低,自定义类参数须自行构建,实现须IL支持,对调用平台有要求,例如:IOS不允许动态类生成,则不可使用。 | 项目代码管理难统一,强迫症猝死 | 服务一旦有破坏性升级,则必须重新替换dll,灵活性几乎为0。 | + +:::tip 提示 + +该内容,对[DmtpRpc](./dmtprpc.mdx)、[JsonRpc](./jsonrpc.mdx)、[XmlRpc](./xmlrpc.mdx)、[WebApi](./webapi.mdx)均适用。 + +::: + + +## 二、从服务端生成代理 + +### 2.1 生成代理代码 + +在开发过程中,如果服务器和客户端,都是我们自己开发的话(在同一个电脑),则使用本地代理生成非常方便。如果不在一起,也没关系,可以直接把生成的cs文件直接复制到客户端项目。 + +使用的基本步骤如下: + +1. 在服务端生成.cs的代理文件。 +2. 将生成的.cs文件,复制到客户端项目(如果是同一电脑开发,则可以使用添加链接的方式编译)。 +3. 在客户端调用代理。 + +【示例1】 +将代理字符串,写成.cs文件,然后通过链接的形式,将代码添加到客户端项目。 + +服务器代码,在服务器`启动`后,会在运行路径下,生成一个**RpcProxy.cs**的文件。 + +```csharp {16-17} + var service = new TcpDmtpService(); + var config = new TouchSocketConfig()//配置 + .SetListenIPHosts(7789) + .ConfigureContainer(a => + { + a.AddDmtpRouteService(); + a.AddConsoleLogger(); + }) + .ConfigurePlugins(a => + { + a.UseDmtpRpc() + .ConfigureRpcStore(store => + { + store.RegisterServer(); +#if DEBUG + File.WriteAllText("RpcProxy.cs", store.GetProxyCodes("RpcProxy", new Type[] { typeof(DmtpRpcAttribute) })); + ConsoleLogger.Default.Info("成功生成代理"); +#endif + }); + + a.Add(); + }) + .SetDmtpOption(new DmtpOption() + { + VerifyToken = "Rpc"//连接验证口令。 + }); + + await service.SetupAsync(config); + await service.StartAsync(); +``` + +:::tip + +`RpcProxy.cs`字符串是代理文件路径,可以传入相对路径,也可以传入绝对路径。`RpcProxy`是生成的代理代码的命名空间。`typeof(DmtpRpcAttribute)`是需要生成的代理的服务类型。此处是以`DmtpRpcAttribute`为标记的Rpc服务。如果是其他标记,请替换为对应标记的类型。 + +最后,生成代理的操作,最好使用`DEBUG`预编译,因为这个功能仅在DEBUG模式我们才用得上。 + +::: + +然后打开需要引入的**客户端**解决方案。选择需要添加代理的项目,依次执行: + +**右击项目=>添加=>现有项** + +**然后选择服务器生成的.cs文件,选择“添加”的下拉框,选择“添加为连接”。** + + + + + +最后确认文件被正确添加为链接。 + + +**这样,每次当服务有更新的时候,只需要启动一下服务器,代理就会自动刷新。** + +:::caution 注意 + +上述操作仅对**客户端**与**服务器**都在同一电脑上开发时才有效。 + +::: + +:::tip 提示 + +当不在同一个电脑上时,可将代理信息写成文件,直接发给客户端开发电脑。亦或者,为防止篡改生成的代码,不想把代理代码直接投入使用,那可以考虑将代码单独编译成dll,然后将编译的程序集发送。 + +::: + +:::tip 提示 + +上述行为,均是导出所有已注册的服务,当需要直接生成多个不同代理的源码时,可通过CodeGenerator静态类的相关方法直接生成。例如: + +```csharp {1} +string codes=CodeGenerator.GetProxyCodes("Namespace",new Type[]{typeof(MyRpcServer) },new Type[] { typeof(DmtpRpcAttribute)}); +``` + +::: + + +### 2.2 代理类型添加 + +通过之前的学习,大家可能大概明白了,在Rpc中,客户端与服务器在进行交互时,所需的数据结构不要求是同一类型,。所以在声明了服务以后,服务中所包含的自定义类型,会被复刻成结构相同的类型,但是这也仅仅局限于参数与服务`相同程序集`的时候。如果服务中引入了其他程序集的数据结构,则**不会**复刻。 + +但是,往往在服务端开发中,会引入其他程序集,例如,我们习惯在项目中建立一个Models程序集,用于存放所有的实体模型,那是不是意味着客户端也必须引入这个程序集才能调用呢?没别的方法了? + +**答案,当然不是!!!** + +Rpc规范了两种方式来添加实体模型的复刻。 + +#### 2.2.1 直接添加代理类型 + +在服务注册之前,任意时刻,可调用CodeGenerator.AddProxyType静态方法,添加代理类型,同时可传入一个bool值,表明是否深度搜索,比如,假如ProxyClass1中还有其他类型,则参数为True时,依然会代理。 + +```csharp showLineNumbers +CodeGenerator.AddProxyType(); +CodeGenerator.AddProxyType(deepSearch:true); +``` + +或者直接按程序集添加 + +```csharp showLineNumbers +CodeGenerator.AddProxyAssembly(typeof(Program).Assembly); +``` + +#### 2.2.2 通过特性标记添加 + +在需要代理的类上面声明RpcProxy标签,然后也可以重新指定代理类名。 + +```csharp showLineNumbers +[RpcProxy("MyArgs")] +public class Args +{ +} +``` + +:::tip 提示 + +该场景可用于代理其他dll的自定义类型。 + +::: + +### 2.3 代理类型排除 + +默认情况下,与声明服务相同的自定义类型,会被代理复刻成结构相同的类型。但是有时候,我们希望服务端与客户端公用一个dll,所以就不需要复刻,那么可以排除代理类型。 + +```csharp showLineNumbers +CodeGenerator.AddIgnoreProxyType(typeof(Program)); +``` + +或者直接按程序集排除 + +```csharp showLineNumbers +CodeGenerator.AddIgnoreProxyAssembly(typeof(Program).Assembly); +``` + +:::tip 提示 + +该场景可用于服务端与客户端公用一个实体dll,例如:当使用**MemoryPack**序列化的场景。 + +::: + + +### 2.4 代理生成配置 + +代理生成配置,可以配置生成的代理。具体操作都是声明自定义特性,然后重写,或者属性配置等。 + +#### 2.4.1 重写GetGenericConstraintTypes + +泛型约束类型。用于约束生成代理的泛型类型,从而让生成的扩展方法只能让特定的类型执行。默认情况下只会约束**IRpcClient**接口。 + +例如: + +```csharp showLineNumbers +class MyRpcAttribute : RpcAttribute +{ + public override Type[] GetGenericConstraintTypes() + { + return new Type[] { typeof(IRpcClient) }; + } +} +``` + +结果: + +```csharp {2} +public static LoginResponse Login(this TClient client,LoginRequest request,IInvokeOption invokeOption = default) +where TClient:IRpcClient +{ + object[] parameters = new object[]{request}; + RpcClassLibrary.Models.LoginResponse returnData=client.InvokeT("Login",invokeOption, parameters); + return returnData; +} +``` + +:::caution 注意 + +泛型约束的总和,必须直接或间接实现**IRpcClient**接口。 + +::: + +#### 2.4.2 属性GeneratorFlag + +生成标识,可表示是否生成同步代码,或异步,或不生成接口等等。 + +例如:下列示例,只会生成**异步扩展**调用,和**异步接口**代码。 + +```csharp showLineNumbers +class MyRpcAttribute : RpcAttribute +{ + public MyRpcAttribute() + { + this.GeneratorFlag = CodeGeneratorFlag.ExtensionAsync | CodeGeneratorFlag.InstanceAsync; + } +} +``` + +#### 2.4.3 重写GetDescription + +获取生成方法的注释。 + +#### 2.4.4 其他 + +其他配置请在代码中自行探索。 + + +## 三、从源生成器生成代理 + +对于源代码生成代理来说,他可以仅凭一个接口,自己生成代理服务代码,然后再编译到当前程序集中。 + +:::tip 提示 + +源生成器也支持.net framework等,但是只能在支持的IDE中使用,例如:vs2019高版本,vs2022,Rider,vs code等。 + +::: + +### 3.1 生成代理代码 + +例如:对于下列服务 + +```csharp showLineNumbers +public partial class MyRpcServer : SingletonRpcServer +{ + [DmtpRpc] + public bool Login(string account, string password) + { + if (account == "123" && password == "abc") + { + return true; + } + + return false; + } +} +``` + +```csharp showLineNumbers +public interface IMyRpcServer +{ + public bool Login(string account, string password); +} +``` + +我们需要设置接口,如下: + +```csharp showLineNumbers +/// +/// GeneratorRpcProxy的标识,表明这个接口应该被生成其他源代码。 +/// ConsoleApp2.MyRpcServer参数是整个rpc调用的前缀,即:除方法名的所有,包括服务的类名。 +/// +[GeneratorRpcProxy(Prefix = "GeneratorRpcProxyConsoleApp.MyRpcServer")]//此处还可以设置其他参数,例如:生成代理的命名空间,是否生成接口等。具体f12查看。 +interface IMyRpcServer +{ + [Description("这是登录方法")]//该作用是生成注释 + [DmtpRpc] + public bool Login(string account, string password); +} +``` + +这时候,神奇的一幕发生了,凡是实现**IRpcClient**的接口的实例,都增加了扩展方法。而这功能,和服务器生成的扩展Rpc方法的功能是一致的。 + +![](@site/static/img/docs/generateproxy-1.png) + +:::info 说明 + +生成的扩展方法的类名,就是**接口名+Extensions**,命名空间默认在**TouchSocket.Rpc.Generators**下,所以可能需要提前using。 + +::: + + +:::tip 提示 + +大家可能会疑问,源代码生成代理,和服务端生成代理,有什么区别?或者说有什么优点? +实际上没有区别,优点最后会对比。之所以设计这个,是因为之前有人提过需求,想要完全分离前、后端。即:后端写好服务后,前端自由定义服务接口,和调用参数,仅此而已。 + +所以,生成代理的方式,按照大家的习惯需求选择就可以。 + +::: + +[源代码生成代理示例代码](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Dmtp/GeneratorRpcProxyConsoleApp) + +### 3.2 生成配置 + +#### 3.2.1 GeneratorRpcProxyAttribute配置 + +GeneratorRpcProxyAttribute的配置,是对整个接口的总体配置,通过**特性名称**直接配置即可。 + +```csharp {1} +[GeneratorRpcProxy(Prefix = "RpcClassLibrary")] +public interface IUserServer:ISingletonRpcServer +{ + [DmtpRpc] + LoginResponse Login(LoginRequest request); +} +``` + +可配置项: + +** (1)Prefix** + +调用前缀。用于配置接口方法的调用键前缀,应包括**命名空间**和**类名**,方法名会自动组合,不区分大小写。 + +**(2)GenericConstraintTypes** + +泛型约束类型。用于约束生成代理的泛型类型,从而让生成的扩展方法只能让特定的类型执行。默认情况下只会约束**IRpcClient**接口。 + +例如: + +```csharp {2} +public static LoginResponse Login(this TClient client,LoginRequest request,IInvokeOption invokeOption = default) +where TClient:IRpcClient +{ + if (client.TryCanInvoke?.Invoke(client)==false) + { + throw new RpcException("Rpc无法执行。"); + } + object[] parameters = new object[]{request}; + RpcClassLibrary.Models.LoginResponse returnData=client.Invoke("rpcclasslibrary.login",invokeOption, parameters); + return returnData; +} +``` + +:::caution 注意 + +泛型约束的总和,必须直接或间接实现**IRpcClient**接口。 + +::: + +**(3)MethodInvoke** + +表示接口的所有方法,均仅通过方法名调用,也就是直接会将方法名设置为调用键,区别大小写。 + +**(4) Namespace** + +表示生成接口,扩展类的命名空间。默认是**TouchSocket.Rpc.Generators**。 + +**(5)ClassName** + +表示生成接口,扩展类的基础名称,例如设为A,则生成的接口是IA,扩展类是AExtensions。默认是**声明接口的名称**(除去“I”)。 + +**(6)GeneratorFlag** + +生成标识,可表示是否生成同步代码,或异步,或不生成接口等等。 + +例如:下列示例,只会生成**异步扩展**调用,和**异步接口**代码。 + +```csharp {1} +[GeneratorRpcProxy(GeneratorFlag = CodeGeneratorFlag.ExtensionAsync| CodeGeneratorFlag.InterfaceAsync)] +public interface IUserServer:ISingletonRpcServer +{ + [DmtpRpc] + LoginResponse Login(LoginRequest request); +} +``` + +**(7) MethodFlags** + +函数标识,可以声明该函数支持调用上下文,即在生成代理时,会忽略第一个参数项。 + +```csharp {1,6} +[GeneratorRpcProxy] +public interface IUserServer:ISingletonRpcServer +{ + [DmtpRpc] + LoginResponse Login(ICallContext callContext,LoginRequest request); +} +``` + +:::tip 提示 + +该场景的使用,一般是,该接口会作为**服务实现**接口。 + +::: + +**(8) InheritedInterface** + +继承接口,标识生成接口代理时,是否依然保持其他接口的继承实现。 + +例如:下列示例中,A接口继承了IRpcServer(外部接口),而B和接口又继承了A,所以全部设置为true时,在生成接口中,依然会保持整个继承链。 + +```csharp {1,7} +[GeneratorRpcProxy(InheritedInterface =true)] +public interface IA:ISingletonRpcServer +{ + +} + +[GeneratorRpcProxy(InheritedInterface =true)] +public interface IB:IA +{ + +} + +``` + +## 四、从DispatchProxy生成代理 + +使用DispatchProxy生成代理,是对源代码生成代理的一个补充,他也是仅凭一个接口,自己生成代理服务,并且隐藏连接客户端。 + + +例如:对于下列服务 + +```csharp showLineNumbers +public partial class MyRpcServer : SingletonRpcServer +{ + /// + /// 将两个数相加 + /// + /// + /// + /// + [DmtpRpc(MethodInvoke = true)]//使用函数名直接调用 + [Description("将两个数相加")]//其作用是生成代理时,作为注释。 + public int Add(int a, int b) + { + this.m_logger.Info("调用Add"); + var sum = a + b; + return sum; + } +} +``` + +我们需要设置接口,如下: + +```csharp showLineNumbers +interface IMyRpcServer +{ + /// + /// 将两个数相加 + /// + /// + /// + /// + [DmtpRpc(MethodInvoke = true)]//使用函数名直接调用 + int Add(int a, int b); +} +``` + +并且需要构建基类: + +```csharp showLineNumbers +/// +/// 新建一个类,按照需要,继承DmtpRpcDispatchProxy,亦或者预设的JsonRpcDispatchProxy,亦或者RpcDispatchProxy基类。 +/// 然后实现抽象方法,主要是能获取到调用的IRpcClient派生接口。 +/// +class MyDmtpRpcDispatchProxy : DmtpRpcDispatchProxy +{ + private readonly TcpDmtpClient m_client; + + public MyDmtpRpcDispatchProxy() + { + this.m_client = GetTcpDmtpClient(); + } + + private static TcpDmtpClient GetTcpDmtpClient() + { + var client = new TcpDmtpClient(); + await client.SetupAsync(new TouchSocketConfig() + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .ConfigurePlugins(a => + { + a.UseDmtpRpc(); + }) + .SetRemoteIPHost("127.0.0.1:7789") + .SetDmtpOption(new DmtpOption() + { + VerifyToken = "Dmtp"//连接验证口令。 + })); + await client.ConnectAsync(); + client.Logger.Info($"连接成功,Id={client.Id}"); + return client; + } + + public override IDmtpRpcActor GetClient() + { + return m_client.GetDmtpRpcActor(); + } +} +``` + +然后生成代理,并直接调用 + +```csharp showLineNumbers +IMyRpcServer myRpcServer = DmtpRpcDispatchProxy.Create(); + +var result = myRpcServer.Add(10, 20); +Console.WriteLine(result); +``` + +:::caution 注意 + +该功能仅在net6以上才可以使用,并使用该方案,无法在限制IL的场景使用,例如:unity-ilcpp,native-aot等。 + +::: + +[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Dmtp/DispatchProxyDmtpRpcConsoleApp) + + +## 五、从RealityProxy生成透明代理 + +使用RealityProxy生成代理,是对源代码生成代理的一个补充,他也是仅凭一个接口,自己生成代理服务,并且隐藏连接客户端。 + + +例如:对于下列服务 + +```csharp showLineNumbers +public partial class MyRpcServer : SingletonRpcServer +{ + /// + /// 将两个数相加 + /// + /// + /// + /// + [DmtpRpc(MethodInvoke = true)]//使用函数名直接调用 + [Description("将两个数相加")]//其作用是生成代理时,作为注释。 + public int Add(int a, int b) + { + this.m_logger.Info("调用Add"); + var sum = a + b; + return sum; + } +} +``` + +我们需要设置接口,如下: + +```csharp showLineNumbers +interface IMyRpcServer +{ + /// + /// 将两个数相加 + /// + /// + /// + /// + [DmtpRpc(MethodInvoke = true)]//使用函数名直接调用 + int Add(int a, int b); +} +``` + +并且需要构建基类: + +```csharp showLineNumbers +/// +/// 新建一个类,按照需要,继承DmtpRpcRealityProxy,亦或者RpcRealityProxy基类。 +/// 然后实现抽象方法,主要是能获取到调用的IRpcClient派生接口。 +/// +class MyDmtpRpcRealityProxy : DmtpRpcRealityProxy +{ + private readonly TcpDmtpClient m_client; + + public MyDmtpRpcRealityProxy() + { + this.m_client = GetTcpDmtpClient(); + } + + private static TcpDmtpClient GetTcpDmtpClient() + { + var client = new TcpDmtpClient(); + await client.SetupAsync(new TouchSocketConfig() + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .ConfigurePlugins(a => + { + a.UseDmtpRpc(); + }) + .SetRemoteIPHost("127.0.0.1:7789") + .SetDmtpOption(new DmtpOption() + { + VerifyToken = "Dmtp" + })); + await client.ConnectAsync(); + client.Logger.Info($"连接成功,Id={client.Id}"); + return client; + } + + public override IDmtpRpcActor GetClient() + { + return m_client.GetDmtpRpcActor(); + } +} +``` + +然后生成代理,并直接调用 + +```csharp showLineNumbers +var myDmtpRpcRealityProxy = new MyDmtpRpcRealityProxy(); + +var myRpcServer = myDmtpRpcRealityProxy.GetTransparentProxy(); + +var result = myRpcServer.Add(10, 20); +``` + +:::caution 注意 + +该功能仅在net45以上,至net481才可以使用,并使用该方案,无法在限制IL的场景使用,例如:unity-ilcpp等。 + +::: + +[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Dmtp/RealityProxyDmtpRpcConsoleApp) \ No newline at end of file diff --git a/handbook/docs/rpcoption.mdx b/handbook/versioned_docs/version-3.1/rpcoption.mdx similarity index 100% rename from handbook/docs/rpcoption.mdx rename to handbook/versioned_docs/version-3.1/rpcoption.mdx diff --git a/handbook/versioned_docs/version-3.1/rpcratelimiting.mdx b/handbook/versioned_docs/version-3.1/rpcratelimiting.mdx new file mode 100644 index 000000000..63fe81f04 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/rpcratelimiting.mdx @@ -0,0 +1,219 @@ +--- +id: rpcratelimiting +title: Rpc访问速率限制 +--- + +import { TouchSocketRpcRateLimitingDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +速率限制是指限制一个资源的访问量的概念。例如,你知道你的应用程序访问的数据库可以安全地处理每分钟1000个请求,但你不相信它可以处理比这多得多的请求。你可以在你的应用程序中放置一个速率限制器,允许每分钟有1000个请求,并在访问数据库之前拒绝任何更多的请求。因此,速率限制你的数据库,允许你的应用程序处理安全数量的请求,而不可能有来自你的数据库的不良故障。 + +有多种不同的速率限制算法来控制请求的流量。我们将讨论其中的4种,他们分别为: + +- 固定窗口 +- 滑动窗口 +- 令牌桶 +- 并发 + +## 二、使用 + +### 2.1 安装 + +nuget安装TouchSocket.Rpc.RateLimiting。 + +```csharp showLineNumbers +Install-Package TouchSocket.Rpc.RateLimiting +``` + +### 2.2 固定窗口限制器 + +AddFixedWindowLimiter 方法使用固定的时间窗口来限制请求。 当时间窗口过期时,会启动一个新的时间窗口,并重置请求限制。 + +```csharp {3} showLineNumbers +.ConfigureContainer(a => +{ + a.AddRateLimiter(p => + { + p.AddFixedWindowLimiter("FixedWindow", options => + { + options.PermitLimit = 10; + options.Window = TimeSpan.FromSeconds(10); + }); + }); +}) +``` + +### 2.3 滑动窗口限制器 + +AddSlidingWindowLimiter 方法使用滑动的时间窗口来限制请求。 当时间窗口过期时,会启动一个新的时间窗口,与固定窗口限制器类似,但为每个窗口添加了段。 窗口在每个段间隔滑动一段。 段间隔的计算方式是:(窗口时间)/(每个窗口的段数)。 + +```csharp {1,4-6,11} showLineNumbers +.ConfigureContainer(a => +{ + a.AddRateLimiter(p => + { + //添加一个名称为SlidingWindow的滑动窗口的限流策略 + p.AddSlidingWindowLimiter("SlidingWindow", options => + { + options.PermitLimit = 10; + options.Window = TimeSpan.FromSeconds(10); + options.SegmentsPerWindow = 5; + }); + }); +}) +``` + +### 2.4 令牌桶限制器 + +AddTokenBucketLimiter 方法使用令牌桶来限制请求。 令牌桶限制器将根据指定的令牌生成速率向桶中添加令牌。 如果桶中有足够的令牌,则允许请求,否则拒绝请求。 + +```csharp showLineNumbers +.ConfigureContainer(a => +{ + a.AddRateLimiter(p => + { + //添加一个名称为TokenBucket的令牌桶的限流策略 + p.AddTokenBucketLimiter("TokenBucket", options => + { + options.TokenLimit = 100; + options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; + options.QueueLimit = 10; + options.ReplenishmentPeriod = TimeSpan.FromSeconds(10); + options.TokensPerPeriod = 10; + options.AutoReplenishment = true; + }); + }); +}) +``` + +### 2.5 并发限制器 + +并发限制器会限制并发请求数。 每添加一个请求,在并发限制中减去 1。 一个请求完成时,在限制中增加 1。 其他请求限制器限制的是指定时间段的请求总数,而与它们不同,并发限制器仅限制并发请求数,不对一段时间内的请求数设置上限。 + +```csharp showLineNumbers +.ConfigureContainer(a => +{ + a.AddRateLimiter(p => + { + //添加一个名称为Concurrency的并发的限流策略 + p.AddConcurrencyLimiter("Concurrency", options => + { + options.PermitLimit = 10; + options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; + options.QueueLimit = 10; + }); + }); +}) +``` + +### 2.6 使用 + +限流器使用很简单,只需要在需要限流的函数、服务或接口上添加特性即可。 + +```csharp {3} showLineNumbers +public partial class MyRpcServer : SingletonRpcServer +{ + [EnableRateLimiting("FixedWindow")] + [Description("登录")]//服务描述,在生成代理时,会变成注释。 + [DmtpRpc(InvokeKey ="Login")]//服务注册的函数键,此处为显式指定。默认不传参的时候,为该函数类全名+方法名的全小写。 + public bool Login(string account, string password) + { + if (account == "123" && password == "abc") + { + return true; + } + + return false; + } +} +``` + +## 三、算法 + +TouchSocket的限流算法,是完全引用`System.Threading.RateLimiting.dll`。所以算是比较通用的限流算法。 + +同时,算法的使用方法,也是完全借鉴Aspnetcore的限流算法使用方法。 + +所以,具体算法可以参考[ASP.NET Core 中的速率限制](https://learn.microsoft.com/zh-cn/aspnet/core/performance/rate-limit) + + +## 四、自定义限流分区键 + +上述限流器的默认工作分区键都是基于函数的。例如,对于`MyRpcServer`的`Login`函数,分区键为`Login`函数本身,这也就意味着,限流是对`Login`函数限制的。即使调用方来自不同用户,不同地区,只要他们都需要调用`Login`函数,都会被限流器影响。 + +但是有时候,我们希望能自定义实现分区键。 + +例如:实现IP限流,那么分区键就是`IP`地址。那么我们可以按下列防止做。 + +首先新建一个类`RpcFixedWindowLimiter`,继承`RateLimiterPolicy`,并指定泛型为`string`,因为我们是基于`IP`限流。 + +```csharp showLineNumbers +// 内部类 RpcFixedWindowLimiter 继承自 RateLimiterPolicy, +// 用于实现基于固定窗口的限流策略,特别适用于远程过程调用 (RPC) 场景。 +internal class RpcFixedWindowLimiter : RateLimiterPolicy +{ + // 私有成员变量,存储了创建限流器时使用的配置选项。 + private readonly FixedWindowRateLimiterOptions m_options; + + // 构造函数接受一个 FixedWindowRateLimiterOptions 类型的参数,并将其赋值给 m_options 成员变量。 + public RpcFixedWindowLimiter(FixedWindowRateLimiterOptions options) + { + this.m_options = options; + } + + // 重写基类的方法以返回一个字符串类型的分区键。 + // 根据传入的 ICallContext 对象,尝试从其中获取调用者的 TCP 会话信息。 + // 如果 callContext.Caller 是 ITcpSession 类型,则返回该会话的 IP 地址作为分区键。 + // 如果不是基于 TCP 协议的调用,则返回字符串 "any" 作为分区键。 + protected override string GetPartitionKey(ICallContext callContext) + { + if (callContext.Caller is ITcpSession tcpSession) + { + return tcpSession.IP; + } + + // 如果是基于 tcp 协议的调用,理论上不会执行到这里。 + return "any"; + } + + // 重写基类的方法以返回一个新的 RateLimiter 实例。 + // 使用 m_options 创建一个 FixedWindowRateLimiter 实例,并返回它。 + protected override RateLimiter NewRateLimiter(string partitionKey) + { + return new FixedWindowRateLimiter(this.m_options); + } +} +``` + +然后使用`AddPolicy`,直接添加自定义限流器。 + +```csharp showLineNumbers +a.AddRateLimiter(p => +{ + p.AddPolicy("FixedWindow", new RpcFixedWindowLimiter(new System.Threading.RateLimiting.FixedWindowRateLimiterOptions() + { + PermitLimit = 10, + Window = TimeSpan.FromSeconds(10) + })); + +}); +``` + +最后依然使用`EnableRateLimiting`特性即可。 + +```csharp {3} showLineNumbers +public partial class MyRpcServer : SingletonRpcServer +{ + [EnableRateLimiting("FixedWindow")] + ... +} +``` + +[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Rpc/RpcRateLimitingConsoleApp) + diff --git a/handbook/versioned_docs/version-3.1/rpcregister.mdx b/handbook/versioned_docs/version-3.1/rpcregister.mdx new file mode 100644 index 000000000..e01348d85 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/rpcregister.mdx @@ -0,0 +1,336 @@ +--- +id: rpcregister +title: 注册服务 +--- + +import Tag from "@site/src/components/Tag.js"; +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import { TouchSocketRpcDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、直接注册 + +### 1.1 注册实例服务 + +当服务仅是一个实例类,则可以在`AddRpcStore`时,可通过`RpcStore`实例,直接注册服务。 + +```csharp showLineNumbers +public partial class MyRpcServer : SingletonRpcServer +{ + /// + /// 将两个数相加 + /// + /// + /// + /// + [DmtpRpc(MethodInvoke = true)]//使用函数名直接调用 + [Description("将两个数相加")]//其作用是生成代理时,作为注释。 + public int Add(int a, int b) + { + var sum = a + b; + return sum; + } +} +``` + +```csharp {5,8} showLineNumbers +.ConfigureContainer(a => +{ + a.AddRpcStore(store => + { + store.RegisterServer(); + + //或者按照类型注册 + //store.RegisterServer(typeof(MyRpcServer)); + }); +}) +``` + +### 1.2 注册接口服务 + +当服务是一个接口时,则可以在`AddRpcStore`时,通过`RpcStore`实例,按注册接口与实例服务。 + +```csharp showLineNumbers +public interface IMyRpcServer2 : ISingletonRpcServer +{ + [DmtpRpc(MethodInvoke = true)]//使用函数名直接调用 + int Add(int a, int b); +} + +public partial class MyRpcServer2 : SingletonRpcServer, IMyRpcServer2 +{ + public int Add(int a, int b) + { + var sum = a + b; + return sum; + } +} +``` + +```csharp {5} showLineNumbers +.ConfigureContainer(a => +{ + a.AddRpcStore(store => + { + store.RegisterServer(); + }); +}) +``` + +:::tip 提示 + +使用接口注册服务时,标识`Rpc`(例如:`DmtpRpc`)特性**必须放在接口方法**中。否则不会生效。 + +::: + +### 1.3 注册其他生命周期服务 + +默认情况下,`Rpc`服务是单例注册,即多个请求调用同一个`Rpc`服务实例。当服务实例继承`TransientRpcServer`(或实现`ITransientRpcServer`接口),或者继承`ScopedRpcServer`(或实现`IScopedRpcServer`接口)时,则服务将会以瞬态,或者区域(此时`IOC`容器必须使用`IServiceCollection`)被创建。 + +- 注册为瞬态的服务,在每次调用时,都会创建一个**新的服务实例**。瞬态服务可以直接通过`this.CallContext`获取调用上下文。 +- 注册为区域的服务,在每次调用时,也会创建一个**新的服务实例**,并且在区域可用时单例。区域服务也可以直接通过`this.CallContext`获取调用上下文。 + +【瞬态服务】 + + +```csharp {7} showLineNumbers +public partial class MyRpcServer : TransientRpcServer +{ + [DmtpRpc(MethodInvoke = true)]//使用函数名直接调用 + [Description("将两个数相加")]//其作用是生成代理时,作为注释。 + public int Add(int a, int b) + { + var callContext= this.CallContext; + var sum = a + b; + return sum; + } +} +``` + + +```csharp {1,7} showLineNumbers +public partial class MyRpcServer : TransientRpcServer +{ + [DmtpRpc(MethodInvoke = true)]//使用函数名直接调用 + [Description("将两个数相加")]//其作用是生成代理时,作为注释。 + public int Add(int a, int b) + { + var callContext= this.CallContext; + var sum = a + b; + return sum; + } +} +``` + + + + +【区域服务】 + + +```csharp {7} showLineNumbers +public partial class MyRpcServer : ScopedRpcServer +{ + [DmtpRpc(MethodInvoke = true)]//使用函数名直接调用 + [Description("将两个数相加")]//其作用是生成代理时,作为注释。 + public int Add(int a, int b) + { + var callContext= this.CallContext; + var sum = a + b; + return sum; + } +} +``` + + +```csharp {1,7} showLineNumbers +public partial class MyRpcServer : ScopedRpcServer +{ + [DmtpRpc(MethodInvoke = true)]//使用函数名直接调用 + [Description("将两个数相加")]//其作用是生成代理时,作为注释。 + public int Add(int a, int b) + { + var callContext= this.CallContext; + var sum = a + b; + return sum; + } +} +``` + + + +## 二、注册所有服务 + +### 2.1 注册指定程序集的服务 + +```csharp {5} showLineNumbers +.ConfigureContainer(a => +{ + a.AddRpcStore(store => + { + store.RegisterAllServer(typeof(MyRpcServer).Assembly); + }); +}) +``` + +### 2.2 注册已加载程序集中的所有服务 + +```csharp {5} showLineNumbers +.ConfigureContainer(a => +{ + a.AddRpcStore(store => + { + store.RegisterAllServer(); + }); +}) +``` + +:::info 信息 + +`RegisterAllServer`会搜索已加载的所有类型,所以在AOT时可能无法使用,并且会影响**启动**性能。 + +::: + +:::caution 警告 + +`RegisterAllServer`无法实现接口注册,所以如果您的服务是接口,则不会生效。 + +::: + +## 三、源生成注册 + +当源生成可用时(一般指vs2019高版本或vs2022、rider、vs code等),TouchSocket.Rpc会自动搜索所有实现了`ISingletonRpcServer`接口的类,并生成统一注册的源代码。 + +### 3.1 启用源生成 + +首先请先确定是在声明Rpc**实例服务**的程序集中。一般的,如果您只声明了实例服务,或者实例服务和接口在同一程序集中,您将不用关心这个。如果您的声明接口与实现在不同的程序集,那么请您确定下列操作均在**实例服务**的程序集中。 + + +为程序集添加`[GeneratorRpcServerRegister]`特性。 + +一般的您需要新建个类文件,建议文件名为“AssemblyInfo.cs”(如果是Framework项目,则在Properties文件夹中有此文件,所以不必新建。) + +然后在该文件中添加新行: + +```csharp showLineNumbers +[assembly:GeneratorRpcServerRegister] +``` + +或者指定一些生成属性,不过一般建议默认即可。 + +```csharp showLineNumbers +[assembly:GeneratorRpcServerRegister(ClassName = "ClassName", MethodName = "MethodName",Accessibility = Accessibility.Both)] +``` + +### 3.2 注册指定程序集的服务 + +```csharp {6,9} showLineNumbers +.ConfigureContainer(a => +{ + a.AddRpcStore(store => + { + //该方法是由源生成提供,可以注册DmtpRpcServerConsoleApp程序集的所有公共Rpc服务 + store.RegisterAllFromDmtpRpcServerConsoleApp(); + + //该方法是由源生成提供,可以注册DmtpRpcServerConsoleApp程序集的所有Rpc服务(包含非公共服务) + store.InternalRegisterAllFromDmtpRpcServerConsoleApp(); + }); +}) +``` + +:::tip 提示 + +源生成的注册方法名并不固定,而是与**程序集名称**有关,或者如果直接指定的话,则是指定的方法名。例如上程序,默认情况下,程序集名称为DmtpRpcServerConsoleApp,即生成`RegisterAllFromDmtpRpcServerConsoleApp`方法与`InternalRegisterAllFromDmtpRpcServerConsoleApp`方法。 + +::: + +源生成的注册,支持接口与实例注册。例如下列服务: + +```csharp showLineNumbers +public interface IMyRpcServer : ISingletonRpcServer +{ + [DmtpRpc(MethodInvoke = true)]//使用函数名直接调用 + int Add(int a, int b); +} + +public partial class MyRpcServer : SingletonRpcServer,IMyRpcServer +{ + public int Add(int a, int b) + { + var sum = a + b; + return sum; + } +} +``` + +在生成源代码注册时,会以`IMyRpcServer`接口为注册,以`MyRpcServer`为实现。生成以下注册代码: + +```csharp showLineNumbers +/* +此代码由Rpc工具直接生成,非必要请不要修改此处代码 +*/ +#pragma warning disable +namespace TouchSocket.Rpc +{ + /// + /// RegisterRpcServerFromDmtpRpcServerConsoleAppExtension + /// + public static class RegisterRpcServerFromDmtpRpcServerConsoleAppExtension + { + /// + /// 注册程序集DmtpRpcServerConsoleApp中的所有公共Rpc服务。包括: + /// + /// : + /// + /// + /// + public static void RegisterAllFromDmtpRpcServerConsoleApp(this RpcStore rpcStore) + { + rpcStore.RegisterServer(); + } + /// + /// 注册程序集DmtpRpcServerConsoleApp中的所有Rpc服务。包括: + /// + /// : + /// + /// + /// + internal static void InternalRegisterAllFromDmtpRpcServerConsoleApp(this RpcStore rpcStore) + { + rpcStore.RegisterServer(); + } + } +} + +``` + +在生成源代码注册时,也会生成注释,来表明注册了哪些类型。 + + + + +:::tip 提示 + +统一生成的源代码注册**并非**是搜索反射注册,所以并不影响性能,且支持AOT,并且也支持接口注册。 + +::: \ No newline at end of file diff --git a/handbook/versioned_docs/version-3.1/serialportclient.mdx b/handbook/versioned_docs/version-3.1/serialportclient.mdx new file mode 100644 index 000000000..96dbedd0f --- /dev/null +++ b/handbook/versioned_docs/version-3.1/serialportclient.mdx @@ -0,0 +1,305 @@ +--- +id: serialportclient +title: 串口客户端 +--- + +import Tag from "@site/src/components/Tag.js"; +import { TouchSocketSerialPortsDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +`SerialPortClient`是串口系客户端,他直接参与串口的连接、发送、接收、处理、断开等。 + +## 二、特点 + +- 简单易用。 +- 内存池支持 +- 高性能 +- 适配器预处理,一键式解决**分包**、**粘包**、对象解析(modbus)等。 +- 超简单的同步发送、异步发送、接收等操作。 +- 基于委托、插件驱动,让每一步都能执行AOP。 + +## 三、产品应用场景 + +- 所有串口基础使用场景:可跨平台、跨语言使用。 +- 自定义协议解析场景:可解析任意数据格式的串口数据报文。 + +## 四、可配置项 + +
+可配置项 +
+ +#### SetMaxPackageSize + +数据包最大值(单位:byte),默认1024×1024×10。该值会在适当时间,直接作用DataHandlingAdapter.MaxPackageSize。 + +#### SetSerialPortOption + +配置串口相关。例如:端口、波特率、数据位、校验位、停止位等。 + +
+
+ +## 五、支持插件 + +| 插件方法| 功能 | +| --- | --- | +| ISerialConnectingPlugin | 在串口连接之前触发。 | +| ISerialConnectedPlugin | 同意连接,且成功启动接收后触发 | +| ISerialClosingPlugin | 当客户端主动调用`Close`时触发 | +| ISerialClosedPlugin | 当客户端断开连接后触发 | +| ISerialReceivingPlugin | 在收到原始数据时触发,所有的数据均在`ByteBlock`里面。 | +| ISerialReceivedPlugin | 在收到适配器数据时触发,根据适配器类型,数据可能在`ByteBlock`或者`IRequestInfo`里面。 | +| ISerialSendingPlugin | 当即将发送数据时,调用该方法在适配器之后,接下来即会发送数据。 | + +## 六、创建SerialPortClient + +#### 6.1 简单创建 + +简单的处理逻辑可通过**Connecting**、**Connected**、**Received**等委托直接实现。 + +代码如下: + +```csharp showLineNumbers +var client = new SerialPortClient(); +client.Connecting = (client, e) => { return EasyTask.CompletedTask; };//即将连接到端口 +client.Connected = (client, e) => { return EasyTask.CompletedTask; };//成功连接到端口 +client.Closing = (client, e) => { return EasyTask.CompletedTask; };//即将从端口断开连接。此处仅主动断开才有效。 +client.Closed = (client, e) => { return EasyTask.CompletedTask; };//从端口断开连接,当连接不成功时不会触发。 +client.Received = async (c, e) => +{ + await Console.Out.WriteLineAsync(e.ByteBlock.Span.ToString(Encoding.UTF8)); +}; + +await client.SetupAsync(new TouchSocketConfig() + .SetSerialPortOption(new SerialPortOption() + { + BaudRate = 9600,//波特率 + DataBits = 8,//数据位 + Parity = System.IO.Ports.Parity.None,//校验位 + PortName = "COM1",//COM + StopBits = System.IO.Ports.StopBits.One//停止位 + }) + .SetSerialDataHandlingAdapter(() => new PeriodPackageAdapter(){ CacheTimeout = TimeSpan.FromMilliseconds(100) }) + .ConfigurePlugins(a => + { + a.Add(); + })); + +await client.ConnectAsync(); +Console.WriteLine("连接成功"); +``` + +:::info 信息 + +上述示例中使用了`PeriodPackageAdapter`适配器,具体作用是确保把`100ms`内收到数据,作为一个包解析,在实际使用时,可以根据业务情况自由修改时间值。详情: [PeriodPackageAdapter](packageadapter.mdx) + +::: + +#### 6.2 继承实现 + +一般继承实现的话,可以从`SerialPortClientBase`继承。 + +```csharp showLineNumbers +class MySerialClient : SerialPortClientBase +{ + protected override async Task OnSerialReceived(ReceivedDataEventArgs e) + { + //此处逻辑单线程处理。 + + //此处处理数据,功能相当于Received委托。 + string mes = e.ByteBlock.Span.ToString(Encoding.UTF8); + Console.WriteLine($"已接收到信息:{mes}"); + + await base.OnSerialReceived(e); + } +} +``` + +```csharp showLineNumbers +var client = new MySerialClient(); +await client.SetupAsync(new TouchSocket.Core.TouchSocketConfig() + .SetSerialPortOption(new SerialPortOption() + { + BaudRate = 9600,//波特率 + DataBits = 8,//数据位 + Parity = System.IO.Ports.Parity.None,//校验位 + PortName = "COM1",//COM + StopBits = System.IO.Ports.StopBits.One//停止位 + })); +await client.ConnectAsync();//调用连接,当连接不成功时,会抛出异常。 +``` + +## 七、接收数据 + +在串口中,接收数据的方式有很多种。多种方式可以组合使用。 + +### 7.1 Received委托处理 + +当使用`SerialPortClient`创建客户端时,内部已经定义好了一个外置委托`Received`,可以通过该委托直接接收数据。 + +```csharp showLineNumbers +var client = new SerialPortClient(); +client.Received = (client, e) => +{ + //收到信息 + string mes = e.ByteBlock.Span.ToString(Encoding.UTF8); + Console.WriteLine($"接收到信息:{mes}"); + return EasyTask.CompletedTask; +}; + +``` + +### 7.2 ReadAsync异步读取 + +在使用串口时,可能更需要在当前代码上下文读取的情况。所以此处提供了异步读取方法。 + +```csharp showLineNumbers +await client.ConnectAsync(); + +using (var receiver = client.CreateReceiver()) +{ + while (true) + { + using (CancellationTokenSource tokenSource=new CancellationTokenSource(TimeSpan.FromSeconds(10))) + { + using (var receiverResult = await receiver.ReadAsync(tokenSource.Token)) + { + if (receiverResult.IsCompleted) + { + //断开 + } + + //按照适配器类型。此处可以获取receiverResult.ByteBlock或者receiverResult.RequestInfo + await Console.Out.WriteLineAsync(receiverResult.ByteBlock.Span.ToString(Encoding.UTF8)); + } + } + } +} +``` + +:::tip 提示 + +`receiver`在被`Create`之后,其优先级最高,即:`Received`委托和插件都不再被调用。 + +::: + +### 7.3 插件处理 推荐 + +按照`TouchSocket`的设计理念,使用插件处理数据,是一项非常简单,且高度解耦的方式。步骤如下: + +(1)声明插件 + +插件可以先继承`PluginBase`,然后再实现需要的功能插件接口,可以按需选择泛型或者非泛型实现。 + +如果已经有继承类,直接实现`IPlugin`接口即可。 + +```csharp showLineNumbers +public class MyPlugin : PluginBase, ISerialReceivedPlugin +{ + public async Task OnSerialReceived(ISerialPortSession client, ReceivedDataEventArgs e) + { + //这里处理数据接收 + //根据适配器类型,e.ByteBlock与e.RequestInfo会呈现不同的值,具体看文档=》适配器部分。 + var byteBlock = e.ByteBlock; + var requestInfo = e.RequestInfo; + + ////表示该数据已经被本插件处理,无需再投递到其他插件。 + + await e.InvokeNext(); + } +} +``` + +(2)创建使用插件处理的客户端 + +```csharp {13} showLineNumbers +var client = new SerialPortClient(); +await client.SetupAsync(new TouchSocketConfig() + .SetSerialPortOption(new SerialPortOption() + { + BaudRate = 9600,//波特率 + DataBits = 8,//数据位 + Parity = System.IO.Ports.Parity.None,//校验位 + PortName = "COM1",//COM + StopBits = System.IO.Ports.StopBits.One//停止位 + }) + .ConfigurePlugins(a => + { + a.Add(); + })); + +await client.ConnectAsync(); +``` + +## 八、发送数据 + +### 8.1 发送 + +`SerialPortClient`已经内置了多种发送方法,直接调用就可以发送。 + +但需要注意的是,通过该方法发送的数据,会经过**适配器**,如果想要直接发送,请继承以后,使用**DefaultSendAsync**。如果发送失败,则会立即抛出异常。 + +```csharp showLineNumbers +Task SendAsync(ReadOnlyMemory memory); +``` + +:::tip 提示 + +框架不仅内置了`SendAsync`字节的发送,也扩展了字符串等常见数据的发送。而且还包括了`TrySend`等不会抛出异常的发送方法。 + +::: + +### 8.2 同步发送等待 + +`WaitingClient`是`TouchSocket`提供的一个等待客户端发送数据的客户端。例如:串口客户端发送一个"Hello",等待对方回信"Hi",此时即可使用`WaitingClient`。 + +```csharp showLineNumbers +await client.ConnectAsync(); + +//调用CreateWaitingClient获取到IWaitingClient的对象。 +var waitClient = client.CreateWaitingClient(new WaitingOptions() +{ + FilterFunc = response => //设置用于筛选的fun委托,当返回为true时,才会响应返回 + { + return true; + + //if (response.Data.Length == 1) + //{ + // return true; + //} + //return false; + } +}); + +//然后使用SendThenReturn。 +byte[] returnData = waitClient.SendThenReturn(Encoding.UTF8.GetBytes("Hello")); +client.Logger.Info($"收到回应消息:{Encoding.UTF8.GetString(returnData)}"); + +//同时,如果适配器收到数据后,返回的并不是字节,而是IRequestInfo对象时,可以使用SendThenResponse. +ResponsedData responsedData = waitClient.SendThenResponse(Encoding.UTF8.GetBytes("Hello")); +IRequestInfo requestInfo = responsedData.RequestInfo;//同步收到的RequestInfo +``` + +:::tip 提示 + +在`SendThenReturn`时,通过其他参数,还可以设置`Timeout`,以及可取消的等待`Token`。 + +::: + +:::danger 注意事项 + +1. 发送完数据,在等待时,如果收到其他返回数据,则可能得到错误结果。 +2. 发送采用同步锁,一个事务没结束,另一个请求也发不出去。 +3. 在**Net461及以下**版本中,`SendThenReturn`与`SendThenReturnAsync`不能混合使用。即:要么全同步,要么全异步。 + +::: + +[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Serial) + diff --git a/handbook/docs/socketioclient.mdx b/handbook/versioned_docs/version-3.1/socketioclient.mdx similarity index 100% rename from handbook/docs/socketioclient.mdx rename to handbook/versioned_docs/version-3.1/socketioclient.mdx diff --git a/handbook/versioned_docs/version-3.1/startguide.mdx b/handbook/versioned_docs/version-3.1/startguide.mdx new file mode 100644 index 000000000..a1a724365 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/startguide.mdx @@ -0,0 +1,227 @@ +--- +id: startguide +title: 入门指南 +--- + +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +## 一、说明 + +**TouchSocket(Pro)** 系是基于`.Net`发布的程序集系列,所以它可以被用于对应`.Net`版本的`C#`、`F#`、`VB.net`等语言项目。 + +它支持您的项目是以下类型: +- 控制台 +- Winform +- Wpf +- AspNetCore +- Unity3d +- 其他项目 + +## 二、发布平台说明 + +`TouchSocket(Pro)`系是基于`net45`、`net462`、`net472`、`net481`、`netstandard2.0`、`netstandard2.1`平台作为**长期发布**平台。 + +`net6.0`作为`C#`稳定版支持平台。net8.0(目前)作为最新发布平台。 + +其中细节如下: + +- net45、net462、net472:保证了在.Net Framework上的最低支持版本。基本上支持全系99%的功能。 +- net481:这是在net45的依赖基础之上,额外添加了一些微软官方库,以支持达到net6.0一样的功能(例如:System.Text.Json等)。 +- netstandard2.0:这是保证了在一些通用平台上的最低依赖。基本上支持全系99%的功能。 +- net6.0:这是目前最新的稳定版发布平台,它在最低依赖的前提下,还保证了全部功能。 +- net8.0:这是目前最新的发布平台,我们会在该平台上开发最能尝鲜的功能(例如:AOT等)。 + +:::tip 提示 + +一般来说,这些版本的发布平台,您不需要关心如何选择。因为如果你是使用`Nuget`包,那么它将自动选择最合适的版本。如果您是使用`dll`,或者源码编译,那么您需要根据您的项目选择对应的最低发布平台。 + +对于Unity3d的用户朋友,我们建议您使用`net472`或者`netstandard2.0`作为发布平台。当然如果您能自行解决较高版本的依赖问题,那么您也可以使用`net481`或者`netstandard2.1`。 + +::: + + +## 三、创建项目 + +下面我们将以vs2022作为示例: + +以最简单的`C# 控制台`程序作为入门,让您最直观的感受`TouchSocket`的强大。 + +:::info 说明 + +如果您选择`vs code`等其他的编译工具,那么我相信您已不是新手。那么您只需要安装`TouchSocket(Pro)`的最新`nuget`包即可。 + +::: + +### 2.1 创建新项目 + + + +### 2.2 选择项目类型 + + + +### 2.3 配置项目名称和路径 + + + +### 2.4 选择Net版本 + + + + + + + +## 四、安装依赖库 + +### 4.1 使用Nuget安装 + +右击项目=》点击“管理Nuget程序包”。 + + + +点击“浏览”,然后在搜索框输入`TouchSocket`,然后在搜索结果中选择。最后点击安装。 + + + +### 4.2 直接编辑PackageReference + +直接编辑PackageReference,其原理等同于Nuget。只不过直接编辑较简单,并且新项目支持比较好。 + +单击项目,打开项目编辑,然后在空白根内部引用。 + +```xml + + + +``` + +:::info 信息 + +该操作仅适用于新项目模版,如果您使用的是vs2019以下的项目模版,则无法直接编辑且使用该方式。 + +::: + +:::tip 提示 + +实际使用时,可能需要安装的是其他组件,例如`TouchSocket.Dmtp`。所以请直接安装所需组件库。同时Version="1.2.3"可能不是最新的版本,请前往[Nuget官网](https://www.nuget.org/packages?q=TouchSocket)查看。 + +::: + + + + + +### 4.3 下载dll并直接引用 + +在[Nuget官网](https://www.nuget.org/packages?q=TouchSocket)中,选择需要的,然后选择“Download package”,即可下载一个后缀为`.nupkg`文件。 + + + +:::tip 提示 + +如果下载速度较慢,可以考虑使用迅雷等工具加速。同时不同的包可能有不同的依赖,所以依赖包也需要下载。 + +::: + +当然你可以通过`nuget.exe`直接下载,这样的好处就是依赖库它会一起下载。首先你需要到[nuget](https://www.nuget.org/downloads)下载可运行程序。一般选择`Windows x86 Commandline`的最新的即可。本示例使用`v6.7.0`. + +然后把下载好的`nuget.exe`放到任意目录下。 + +然后使用`cmd`、或者`powershell`等工具进入该目录(比较简单的操作是,先打开该目录,然后按住`shift`键,然后右击鼠标,会有`在此处打开命令行`或`powershell`等选项)。然后执行下列命令。 + +```bash + .\nuget.exe install TouchSocket -SolutionDirectory . -PackageSaveMode nupkg +``` + +:::tip 提示 + +实际使用时,可能需要安装的是其他组件,例如`TouchSocket.Dmtp`。所以请直接安装所需组件库。同时Version="1.2.3"可能不是最新的版本,请前往[Nuget官网](https://www.nuget.org/packages?q=TouchSocket)查看。 + +::: + +然后会在当前目录下,生成一个名为“packages”的文件夹。在这个文件夹就有所有依赖的库。 + +然后将下载的后缀为`.nupkg`的文件,通过压缩工具`解压`(如果是通过nuget.exe下载的话,不用解压)。得到如下目录结构。 + + + +然后进入“lib”的文件夹。选择对应平台。 + +:::tip 提示 + +一般来说,所有基于.NET Framework的项目,都最好引用net45的库。然后其他版本的,选择最近的,较低的库即可。在本示例中,选择netcoreapp3.1的库。 + +::: + +把下列文件引用到项目即可。 + + + + + + + +## 五、在Unity3d中使用 + +由于Unity默认不支持直接安装NuGet包,所以一般会选择使用`Dll`的方式。直接添加到资产中。 + +下面我们将介绍2种比较常见的方式。 + +### 5.1 下载Dll并导入 + +首先,请按照4.3中,使用Nuget.exe下载dll。这样下载后的文件夹中,应该有`TouchSocket`运行所依赖的所有dll。 + +以`TouchSocket-v2.1`为例,下载后,应该有如下目录结构。 + +其中除了`TouchSocket`和`TouchSocket.Core`,其他都是依赖库。并且对于不同的平台,依赖库也有所不同。所以我们需要摘出我们需要的dll,并添加到Unity资源中。 + +```csharp showLineNumbers +|-- packages + |-- Microsoft.Bcl.AsyncInterfaces 8.0.0 + |-- Newtonsoft.Json 13.0.3 + |-- System.Buffers 4.5.1 + |-- System.Memory 4.5.5 + |-- System.Numerics.Vectors 4.5.0 + |-- System.Runtime.CompilerServices.Unsafe 6.0.0 + |-- System.Text.Encodings.Web 8.0.0 + |-- System.Text.Json 8.0.4 + |-- System.Threading.Tasks.Extensions 4.5.4 + |-- System.ValueTuple 4.5.0 + |-- TouchSocket 2.1.2 + |-- TouchSocket.Core 2.1.2 +``` + +然后,我们需要明确,在`Unity`中,我希望使用哪个平台的dll。 + +一般来说`Unity`支持`.Net Framework 4.x`或者`.netstandard2.0`(高版本为:`.netstandard2.1`) 。 + +这里推荐如果是`.Net Framework 4.x`,那么就选择`Net472`的dll。如果是`.netstandard2.0`或者`.netstandard2.1`,那么就选择`.netstandard2.0`的dll。 + +因为`.netstandard2.1`的TouchSocket,会额外引入了`System.Text.Json`,所以如果不需要的话使用`.netstandard2.0`低依赖即可。 + +### 5.2 使用Unity包导入 + +我们在[TouchSocket-UnityPackage](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Unity3d/UnityPackage)中,已经提供了Unity包。 + +其中包含一个测试场景和测试脚本。直接导入到Unity即可使用。 + +如果已提供的包中,没有你需要的版本,那么可以到[适用于Unity3d的TouchSocket包Issue](https://gitee.com/RRQM_Home/TouchSocket/issues/IASO8P)中留言,我们会尽快添加。 + +:::info 备注 + +一般我们只会添加新版本,所以,如果你需要旧版本,请到自行下载解决。 + +同时,我们打包使用的是Unity2022,如果是较低版本,也可能无法导入。 + +::: + + + +## 六、毕业 + +恭喜你,到这里,你就完成了入门的教学,你可能会好奇,做了这么多,还是什么都没有做啊。这是因为TouchSocket并不是一个单功能的,而是一个多功能程序库。所以,你必须在其他模块中查看你所需要的内容。 + +祝你好运!!! diff --git a/handbook/versioned_docs/version-3.1/stategridtransmission.mdx b/handbook/versioned_docs/version-3.1/stategridtransmission.mdx new file mode 100644 index 000000000..d9d8c7714 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/stategridtransmission.mdx @@ -0,0 +1,166 @@ +--- +id: stategridtransmission +title: 国网输电i1标准版 +--- + +## 说明 + +本代码仅适用以下协议。 +协议中,由Packet\_Length表示序号7-11的长度。也就是Packet\_Length=N+2+2+1+1。 +但是在设计时,会将序号1-10,视为固定包头。序号11-13为Body。 + +## 版权 + +该代码所有版权归若汝棋茗所有,使用时请务必注明。 + +## 协议类型 + + +## 代码 + +```csharp showLineNumbers +using TouchSocket.Core; +using TouchSocket.Sockets; + +namespace AdapterConsoleApp +{ + /// + /// 国网输电i1标准版 + /// + internal class SGCCCustomDataHandlingAdapter : CustomFixedHeaderDataHandlingAdapter + { + public override int HeaderLength => 30; + + public override bool CanSendRequestInfo => false; + + protected override SGCCRequestInfo GetInstance() + { + return new SGCCRequestInfo(); + } + + protected override void PreviewSend(IRequestInfo requestInfo) + { + throw new System.NotImplementedException(); + } + } + + public class SGCCRequestInfo : IFixedHeaderRequestInfo + { + private byte[] m_sync; + private int m_bodyLength; + private byte[] m_cMDID; + private byte[] m_sample; + private byte[] m_cRC16; + + public int BodyLength { get => m_bodyLength; } + + /// + /// 报文头:5AA5 + /// + public byte[] Sync { get => m_sync; set => m_sync = value; } + + /// + /// 报文长度 + /// + public ushort PacketLength { get => (ushort)(this.m_bodyLength - 3); } + + /// + /// 状态监测装置ID(17位编码) + /// + public byte[] CMDID { get => m_cMDID; set => m_cMDID = value; } + + /// + /// 帧类型—参考附表C8-1相关含义 + /// + public byte FrameType { get; set; } + + /// + /// 报文类型—参考附表C8-2相关含义 + /// + public byte PacketType { get; set; } + + /// + /// 帧序列号(无符号整数) + /// + public byte FrameNo { get; set; } + + /// + /// 通道号—表示采集装置上的摄像机编号。如:一个装连接⒉部摄像机,则分别标号为1、2 + /// + public byte ChannelNo { get; set; } + + /// + /// 预置位号—即云台摄像所设置的预置位号,不带云台摄像机,预置位号为255 + /// + public byte PresettingNo { get; set; } + + /// + /// 总包数(无符号整数,取值范围:大于等于0) + /// + public ushort PacketNo { get; set; } + + /// + /// 子包包号(无符号整数,取值范围:大于等于0> + /// + public ushort SubpacketNo { get; set; } + + /// + /// 数据区 + /// + public byte[] Sample { get => m_sample; set => m_sample = value; } + + /// + /// 校验位 + /// + public byte[] CRC16 { get => m_cRC16; } + + /// + /// 报文尾:0x96 + /// + public byte End { get; set; } + + public bool OnParsingHeader(byte[] header) + { + if (header.Length == 30) + { + using (ByteBlock byteBlock = new ByteBlock(header)) + { + byteBlock.Pos = 0; + byteBlock.Read(out m_sync, 2); + + byte[] lenBuffer; + byteBlock.Read(out lenBuffer, 2); + + this.m_bodyLength = TouchSocketBitConverter.LittleEndian.ToUInt16(lenBuffer, 0) + 3 - 6;//先把crc校验和end都获取。 + byteBlock.Read(out m_cMDID, 17); + this.FrameType = (byte)byteBlock.ReadByte(); + this.PacketType = (byte)byteBlock.ReadByte(); + this.FrameNo = (byte)byteBlock.ReadByte(); + this.ChannelNo = (byte)byteBlock.ReadByte(); + this.PresettingNo = (byte)byteBlock.ReadByte(); + this.PacketNo = byteBlock.ReadUInt16(); + this.SubpacketNo = byteBlock.ReadUInt16(); + + return true; + } + } + return false; + } + + public bool OnParsingBody(byte[] body) + { + if (body.Length == this.BodyLength && body[^1] == 150) + { + using (ByteBlock byteBlock = new ByteBlock(body)) + { + byteBlock.Read(out this.m_sample, this.m_bodyLength - 3); + byteBlock.Read(out this.m_cRC16, 2); + this.End = (byte)byteBlock.ReadByte(); + } + return true; + } + return false; + } + } +} +``` diff --git a/handbook/docs/swagger.mdx b/handbook/versioned_docs/version-3.1/swagger.mdx similarity index 100% rename from handbook/docs/swagger.mdx rename to handbook/versioned_docs/version-3.1/swagger.mdx diff --git a/handbook/docs/tcpaot.mdx b/handbook/versioned_docs/version-3.1/tcpaot.mdx similarity index 100% rename from handbook/docs/tcpaot.mdx rename to handbook/versioned_docs/version-3.1/tcpaot.mdx diff --git a/handbook/versioned_docs/version-3.1/tcpclient.mdx b/handbook/versioned_docs/version-3.1/tcpclient.mdx new file mode 100644 index 000000000..1a57d117a --- /dev/null +++ b/handbook/versioned_docs/version-3.1/tcpclient.mdx @@ -0,0 +1,460 @@ +--- +id: tcpclient +title: 创建TcpClient +--- + +import Tag from "@site/src/components/Tag.js"; +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import { TouchSocketDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +`TcpClient`是Tcp系客户端基类,他直接参与tcp的连接、发送、接收、处理、断开等,他的业务与服务器的`TcpSessionClient`是一一对应的。 + +## 二、特点 + +- 简单易用。 +- IOCP多线程。 +- 内存池支持 +- 高性能 +- 适配器预处理,一键式解决**分包**、**粘包**、对象解析(如HTTP,Json)等。 +- 超简单的同步发送、异步发送、接收等操作。 +- 基于委托、插件驱动,让每一步都能执行AOP。 + +## 三、产品应用场景 + +- 所有Tcp基础使用场景:可跨平台、跨语言使用。 +- 自定义协议解析场景:可解析任意数据格式的TCP数据报文。 + +## 四、可配置项 + +
+可配置项 +
+ +#### SetMaxPackageSize +数据包最大值(单位:byte),默认1024×1024×10。该值会在适当时间,直接作用DataHandlingAdapter.MaxPackageSize。 + +#### SetRemoteIPHost +链接到的远程IPHost,支持域名。支持类型: +1. 使用IPv4,传入形如:127.0.0.1:7789的字符串即可。 +2. 使用IPv6,传入形如:[\*::\*]:7789的字符串即可。 +3. 使用域名,必须包含协议类型,形如:http://baidu.com或者https://baidu.com:80 +3. 使用IPv6域名,必须包含协议类型,形如:http://[\*::\*]:80 + +#### SetClientSslOption +客户端Ssl配置,为Null时则不启用。 +注意,当RemoteIPHost使用https、wss的域名时,该配置会使用系统默认配置生效。 + + + +#### SetKeepAliveValue +为Socket设置的属性。 +注意:该配置仅在window平台生效。 + + + +#### SetBindIPHost +绑定端口。 +- 在UdpSessionBase中表示本地监听地址 +- 在TcpClient中表示固定客户端端口号。 + + + + +#### UseNoDelay +设置Socket的NoDelay属性,默认false。 + + +#### UseReuseAddress +启用端口复用。该配置可在客户端在监听端口时,可以一定程度缓解端口来不及释放的问题。 + + + +#### SetMaxBufferSize +设置最大缓存容量,默认自动。 + +#### SetMinBufferSize +设置最小缓存容量,默认自动。 + + + +
+
+ +## 五、支持插件 + +| 插件方法| 功能 | +| --- | --- | +| ITcpConnectingPlugin | 此时Socket实际上已经完成连接,但是并没有启动接收,然后触发。 | +| ITcpConnectedPlugin | 同意连接,且成功启动接收后触发 | +| ITcpClosingPlugin | 当客户端主动调用Close时触发 | +| ITcpClosedPlugin | 当客户端断开连接后触发 | +| ITcpReceivingPlugin | 在收到原始数据时触发,所有的数据均在ByteBlock里面。 | +| ITcpReceivedPlugin | 在收到适配器数据时触发,根据适配器类型,数据可能在ByteBlock或者IRequestInfo里面。 | +| ITcpSendingPlugin | 当即将发送数据时,调用该方法在适配器之后,接下来即会发送数据。 | + +## 六、创建TcpClient + +#### 6.1 简单创建 + +简单的处理逻辑可通过**Connected**、**Closed**、**Received**等委托直接实现。 + +代码如下: + +```csharp showLineNumbers +var tcpClient = new TcpClient(); +tcpClient.Connecting = (client, e) => { return EasyTask.CompletedTask; };//即将连接到服务器,此时已经创建socket,但是还未建立tcp +tcpClient.Connected = (client, e) => { return EasyTask.CompletedTask; };//成功连接到服务器 +tcpClient.Closing = (client, e) => { return EasyTask.CompletedTask; };//即将从服务器断开连接。此处仅主动断开才有效。 +tcpClient.Closed = (client, e) => { return EasyTask.CompletedTask; };//从服务器断开连接,当连接不成功时不会触发。 +tcpClient.Received = (client, e) => +{ + //从服务器收到信息。但是一般byteBlock和requestInfo会根据适配器呈现不同的值。 + var mes = e.ByteBlock.Span.ToString(Encoding.UTF8); + tcpClient.Logger.Info($"客户端接收到信息:{mes}"); + return EasyTask.CompletedTask; +}; + +//载入配置 +await tcpClient.SetupAsync(new TouchSocketConfig() + .SetRemoteIPHost("127.0.0.1:7789") + .ConfigureContainer(a => + { + a.AddConsoleLogger();//添加一个日志注入 + })); + +await tcpClient.ConnectAsync();//调用连接,当连接不成功时,会抛出异常。 + +Result result = await tcpClient.TryConnectAsync();//或者可以调用TryConnectAsync +if (result.IsSuccess()) +{ + +} + +tcpClient.Logger.Info("客户端成功连接"); +``` + + + +#### 6.2 继承实现 + +一般继承实现的话,可以从`TcpClient`继承。如果有特殊需求,也可以从`TcpClientBase`继承。 + +```csharp showLineNumbers +class MyTcpClient : TcpClient +{ + protected override async Task OnTcpReceived(ReceivedDataEventArgs e) + { + //此处逻辑单线程处理。 + + //此处处理数据,功能相当于Received委托。 + string mes =e.ByteBlock.Span.ToString(Encoding.UTF8); + Console.WriteLine($"已接收到信息:{mes}"); + await base.OnTcpReceived(e); + } +} +``` + +```csharp showLineNumbers +var tcpClient = new MyTcpClient(); +//载入配置 +await tcpClient.SetupAsync(new TouchSocketConfig() + .SetRemoteIPHost("127.0.0.1:7789") + .ConfigureContainer(a => + { + a.AddConsoleLogger();//添加一个日志注入 + })); + +await tcpClient.ConnectAsync();//调用连接,当连接不成功时,会抛出异常。 +``` + + +## 七、接收数据 + +在`TcpClient`中,接收数据的方式有很多种。多种方式可以组合使用。 + +### 7.1 Received委托处理 + +当使用TcpClient创建客户端时,内部已经定义好了一个外置委托Received,可以通过该委托直接接收数据。 + +```csharp showLineNumbers +var tcpClient = new TcpClient(); +tcpClient.Received = (client, e) => +{ + //从服务器收到信息 + string mes = e.ByteBlock.Span.ToString(Encoding.UTF8); + Console.WriteLine($"接收到信息:{mes}"); + return EasyTask.CompletedTask; +}; + +await tcpClient.ConnectAsync("127.0.0.1:7789"); +``` + + +### 7.2 插件处理 推荐 + + + + + +按照TouchSocket的设计理念,使用插件处理数据,是一项非常简单,且高度解耦的方式。步骤如下: + +(1)声明插件 + +插件可以先继承`PluginBase`,然后再实现需要的功能插件接口,可以按需选择泛型或者非泛型实现。 + +如果已经有继承类,直接实现`IPlugin`接口即可。 + +```csharp showLineNumbers +public class MyPlugin : PluginBase, ITcpReceivedPlugin +{ + public async Task OnTcpReceived(ITcpSession client, ReceivedDataEventArgs e) + { + //这里处理数据接收 + //根据适配器类型,e.ByteBlock与e.RequestInfo会呈现不同的值,具体看文档=》适配器部分。 + ByteBlock byteBlock = e.ByteBlock; + IRequestInfo requestInfo = e.RequestInfo; + + ////表示该数据已经被本插件处理,无需再投递到其他插件。 + + await e.InvokeNext(); + } +} +``` + +(2)创建使用插件处理的客户端 + +```csharp showLineNumbers +var client = new TcpClient(); +await client.SetupAsync(new TouchSocketConfig() + .SetRemoteIPHost("127.0.0.1:7789") + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .ConfigurePlugins(a => + { + a.Add(); + })); + +await client.ConnectAsync(); +``` + +:::danger 注意 + +当接收数据时,ByteBlock与RequestInfo的值会根据适配器类型不同而不同。 + +::: + + + + + + + + + + + + +### 7.3 异步阻塞接收 推荐 + +异步阻塞接收,即使用await的方式接收数据。其特点是能在代码上下文中,直接获取到收到的数据。例如: + +```csharp showLineNumbers +var client = new TcpClient(); +await client.ConnectAsync("127.0.0.1:7789");//连接 + +//receiver可以复用,不需要每次接收都新建 +using (var receiver = client.CreateReceiver()) +{ + while (true) + { + //receiverResult必须释放 + using (var receiverResult = await receiver.ReadAsync(CancellationToken.None)) + { + if (receiverResult.IsCompleted) + { + Console.WriteLine($"客户端已断开,信息:{receiverResult.Message}"); + //断开连接了 + return; + } + + //如果是适配器信息,则可以直接获取receiverResult.RequestInfo; + var requestInfo = receiverResult.RequestInfo; + var byteBlock = receiverResult.ByteBlock; + + //从服务器收到信息。 + var mes = byteBlock.Span.ToString(Encoding.UTF8); + client.Logger.Info($"客户端接收到信息:{mes}"); + } + } +} +``` + +在异步阻塞接收时,当接收的数据不满足解析条件时,还可以缓存起来,下次一起处理。 + +例如:下列将演示接收字符串,当没有发现“\r\n”时,将缓存数据,直到发现重要字符。 + +其中,`CacheMode`与`MaxCacheSize`是启用缓存的重要属性。`byteBlock.Seek`则是将已读取的数据游标移动至指定位置。 + +```csharp {7-8,45} showLineNumbers +var client = new TcpClient(); +await client.ConnectAsync("127.0.0.1:7789");//连接 + +//receiver可以复用,不需要每次接收都新建 +using (var receiver = client.CreateReceiver()) +{ + receiver.CacheMode = true; + receiver.MaxCacheSize = 1024 * 1024; + + var rn = Encoding.UTF8.GetBytes("\r\n"); + while (true) + { + //receiverResult每次接收完必须释放 + using (var receiverResult = await receiver.ReadAsync(CancellationToken.None)) + { + //收到的数据,此处的数据会根据适配器投递不同的数据。 + var byteBlock = receiverResult.ByteBlock; + var requestInfo = receiverResult.RequestInfo; + + if (receiverResult.IsCompleted) + { + //断开连接了 + Console.WriteLine($"断开信息:{receiverResult.Message}"); + return; + } + + //在CacheMode下,byteBlock将不可能为null + + var index = 0; + while (true) + { + var r = byteBlock.Span.Slice(index).IndexOf(rn); + if (r < 0) + { + break; + } + + var str = byteBlock.Span.Slice(index, r).ToString(Encoding.UTF8); + Console.WriteLine(str); + + index += rn.Length; + index += r; + } + + byteBlock.Seek(index); + } + } +} +``` + +:::tip 提示 + +异步阻塞接收,在等待接收数据时,不会阻塞线程资源,所以即使大量使用,也不会影响性能。 + +::: + + +## 八、发送数据 + +TcpClient已经内置了发送方法,直接调用就可以发送,如果发送失败,则会立即抛出异常。 + +```csharp showLineNumbers +//原生 +public Task SendAsync(string id, ReadOnlyMemory memory); +public Task SendAsync(string id, IRequestInfo requestInfo); +``` + +:::tip 提示 + +框架不仅内置了字节的发送,也扩展了字符串等常见数据的发送。而且还包括了`TrySend`等不会抛出异常的发送方法。 + +::: + +:::caution 注意 + +所有的发送,框架内部实际上**只实现了异步发送**,但是为了兼容性,仍然保留了同步发送的扩展。但是强烈建议如有可能,请**务必使用异步发送来提高效率**。 + +::: + + +## 九、断线重连 + +断线重连,即tcp客户端在断开服务器后,主动发起的再次连接请求。 + +### 9.1 触发型重连 + +触发型重连,依靠的是Tcp断开事件(Closed)发生时,再次尝试连接。所以,这就要求客户端在初始时,至少完成一次连接。 + +```csharp {8,11} showLineNumbers +var tcpClient = new TcpClient(); + +//载入配置 +await tcpClient.SetupAsync(new TouchSocketConfig() + .SetRemoteIPHost("127.0.0.1:7789") + .ConfigurePlugins(a => + { + a.UseTcpReconnection(); + })); + +await tcpClient.ConnectAsync();//调用连接 +``` +:::caution 注意 + +**触发重连,必须满足以下几个要求:** + +1. 必须完成第一次连接。 +2. 必须是被动断开,如果是客户端主动调用Close、Disposed等方法主动断开的话,一般不会生效。 +3. 必须有显式的断开信息,也就是说,直接拔网线的话,不会立即生效,会等tcp保活到期后再生效。 + +::: + + + + +### 9.2 使用Polling轮询连接插件 + +使用Polling断线重连,是一种无人值守的连接方式,它不要求首次连接。 + +```csharp showLineNumbers +.ConfigurePlugins(a => +{ + a.UseTcpReconnection() + .UsePolling(TimeSpan.FromSeconds(1)); +}) +``` + +:::caution 注意 + +**Polling重连,必须满足以下几个要求:** + +1. 必须有显式的断开信息,也就是说,直接拔网线的话,也不会立即生效,会等tcp保活到期后再生效。 + +::: + +:::tip 提示 + +UseReconnection插件,可以通过设置SetActionForCheck,自己规定检查活性的方法。默认情况下,只会检验Online属性,所以无法检验出断网等情况。如果自己控制,则可以发送心跳包,以保证在线状态。 + +::: + + + +[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Tcp) + diff --git a/handbook/versioned_docs/version-3.1/tcpcommandlineplugin.mdx b/handbook/versioned_docs/version-3.1/tcpcommandlineplugin.mdx new file mode 100644 index 000000000..8d65e1a09 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/tcpcommandlineplugin.mdx @@ -0,0 +1,117 @@ +--- +id: tcpcommandlineplugin +title: 命令行执行插件 +--- + +import { TouchSocketDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +**TcpCommandLinePlugin**命令行执行插件,是用于TCP的快捷事务实现。该类是抽象类,必须通过继承,在继承类中,声明的具的**公共的**且名称以**Command**结尾的方法,均可被快捷执行。 + +## 二、创建快捷执行插件 + +```csharp showLineNumbers +/// +/// 命令执行插件。方法必须以Command结尾。 +/// +class MyCommandLinePlugin : TcpCommandLinePlugin +{ + private readonly ILog logger; + + public MyCommandLinePlugin(ILog logger) : base(logger) + { + this.ReturnException = true;//表示执行异常的时候,是否返回异常信息 + this.logger = logger; + } + + /// + /// 加法 + /// + /// + /// + /// + public int AddCommand(int a, int b) + { + this.logger.Info($"执行{nameof(AddCommand)}"); + return a + b; + } + + /// + /// 乘法,并且获取调用者信息 + /// + /// + /// + /// + /// + public int MULCommand(ITcpSessionClient SessionClient,int a, int b) + { + this.logger.Info($"{SessionClient.IP}:{SessionClient.Port}执行{nameof(MULCommand)}"); + return a * b; + } + + /// + /// 测试异常 + /// + /// + public void ExcCommand() + { + throw new Exception("我异常了"); + } +} +``` + +## 三、创建服务器 + +```csharp showLineNumbers +TcpService service = new TcpService(); + +var config = new TouchSocketConfig(); +config.SetListenIPHosts(new IPHost[] { new IPHost("127.0.0.1:7789"), new IPHost(7790) }) //同时监听两个地址 + .SetDataHandlingAdapter(() => + { + //return new TerminatorPackageAdapter(1024, "\r\n");//命令行中使用\r\n结尾 + return new NormalDataHandlingAdapter();//亦或者省略\r\n,但此时调用方不能高速调用,会粘包 + }) + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .ConfigurePlugins(a => + { + a.Add(); + }); + +//载入配置 +await service.SetupAsync(config); + +//启动 +await service.StartAsync(); + +service.Logger.Info("服务器成功启动。"); +service.Logger.Info("使用:“Add 10 20”测试"); +service.Logger.Info("使用:“MUL 10 20”测试"); +service.Logger.Info("使用:“Exc”测试异常"); +``` + +## 四、调用 + +上述快捷执行插件,即可被普通tcp客户端,或cmd/telnet等便捷调用。 + +调用数据格式: + +`Add 10 20 /r/n` **/r/n**非必须,但是当适配器选为[终止字符分割适配器](./packageadapter.mdx)时,则必须。不然,则不可连续调用,会粘包。 + +:::tip 提示 + +调用的参数也支持自定义实体类,届时参数使用Json数据格式即可。 + +::: + + +[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Tcp/TcpCommandLineConsoleApp) diff --git a/handbook/versioned_docs/version-3.1/tcpcommonplugins.mdx b/handbook/versioned_docs/version-3.1/tcpcommonplugins.mdx new file mode 100644 index 000000000..d748ade90 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/tcpcommonplugins.mdx @@ -0,0 +1,55 @@ +--- +id: tcpcommonplugins +title: 常用插件 +--- + +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import { TouchSocketDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、健康活性检验插件 + +健康活性检验插件(CheckClearPlugin)用于检测当前连接是否有正常的数据交流,如果没有,则主动断开连接。 + +该插件可以用于客户端或服务器,但是一般建议仅用于服务器即可。 + +工作原理非常简单。当连接建立时,会自动启动一个定时器,每隔一段时间(默认60秒),就会检验在该连接上,是否有数据交流。如果没有,则判定该客户端静默,或者已经无效。 + +例如:在Tcp场景中,经常有客户端因为断网等原因,已经断开连接,但是又无法主动发送断开连接报文。则此后,如果服务器不主动发送数据,则会非常久的时间无法得知该客户端已经断开。 + +所以需要使用该插件进行健康活性检验,以便及时断开无效连接。 + +代码示例: + +只需要在插件中添加UseCheckClear即可。其中,可配置: + +- CheckClearType:健康检验类型,默认是所有类型都进行健康检验,即同时开启发送、接收健康检验,只要有一方有数据交互,即认为连接有效。 +- Tick:健康检验的间隔时间,默认是60秒。 +- OnClose:健康检验失败时的回调函数,默认是直接关闭连接。 + +```csharp {3} showLineNumbers +.ConfigurePlugins(a => +{ + a.UseCheckClear() + .SetCheckClearType( CheckClearType.All) + .SetTick(TimeSpan.FromSeconds(60)) + .SetOnClose((c,t) => + { + await c.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both); + await c.SafeCloseAsync("超时无数据"); + }); +}) +``` + +:::info 温馨提示 + +健康检验插件,可能因为操作系定时器原因和检验周期原因,导致检验时间间隔比配置的间隔时间要长。一般来说,最坏情况不会超过设置周期的10%。例如:设置周期为100秒,则最坏情况可能到110秒才会执行清理。 + +::: + + + \ No newline at end of file diff --git a/handbook/versioned_docs/version-3.1/tcpheartbeat.mdx b/handbook/versioned_docs/version-3.1/tcpheartbeat.mdx new file mode 100644 index 000000000..b090412e6 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/tcpheartbeat.mdx @@ -0,0 +1,292 @@ +--- +id: tcpheartbeat +title: 心跳设计 +--- + +## 一、说明 + +### 1.1 为什么要设置心跳? + +心跳机制一般是**客户端**向**服务器**定时发送一个特定的数据包,让服务器知道自己还在线,以确保连接的有效性的机制。 网络中的接收和发送数据都是使用操作系统中的 SOCKET 进行实现。 但是如果此 套接字 已经断开,那发送数据和接收数据的时候就一定会有问题。 可是如何判断这个套接字是否还可以使用呢? 这个就需要在系统中创建心跳机制。 + +其实TCP中已经为我们实现了一个[内置心跳机制(SetKeepAliveValue)](./tcpclient.mdx)。但是该机制受限于操作系统,而且很容易误报。所以很少被大家使用。 + +大家使用最多的,就是自己设计数据包,然后预留心跳格式,当对方收到心跳包时,直接返回响应包即可。 + +那么,按这个思路,让我们使用优雅的实现吧。 + +## 二、设计数据格式 + +使用心跳之前,必须要明确数据格式,绝对不能混淆业务数据。一般在适配Plc等现成模块时,他们是有固定的数据格式,这时候你可以参阅[数据处理适配器](./adapterdemodescription.mdx),快速的解析数据。 + +但是在本文中,并没有规定的格式,所以我们需要先设计一种简单高效的数据格式。 + +如下: + +| **数据长度** | **数据类型** | **载荷数据** | +| --- | --- | --- | +| 2字节(Ushort) | 1字节(Byte) | n字节(65535) | + +### 2.1 解析数据格式 + +下列代码主要实现对上述数据格式的解析 + +```csharp showLineNumbers + +internal class MyFixedHeaderDataHandlingAdapter : CustomFixedHeaderDataHandlingAdapter +{ + public override int HeaderLength => 3; + + public override bool CanSendRequestInfo => false; + + protected override MyRequestInfo GetInstance() + { + return new MyRequestInfo(); + } + + protected override void PreviewSend(IRequestInfo requestInfo) + { + throw new NotImplementedException(); + } +} + +internal class MyRequestInfo : IFixedHeaderRequestInfo +{ + public DataType DataType { get; set; } + public byte[] Data { get; set; } + + public int BodyLength { get; private set; } + + public bool OnParsingBody(byte[] body) + { + if (body.Length == this.BodyLength) + { + this.Data = body; + return true; + } + return false; + } + + public bool OnParsingHeader(byte[] header) + { + if (header.Length == 3) + { + this.BodyLength = TouchSocketBitConverter.Default.ToUInt16(header, 0) - 1; + this.DataType = (DataType)header[2]; + return true; + } + return false; + } + + public void Package(ByteBlock byteBlock) + { + byteBlock.Write((ushort)((this.Data == null ? 0 : this.Data.Length) + 1)); + byteBlock.Write((byte)this.DataType); + if (this.Data != null) + { + byteBlock.Write(this.Data); + } + } + + public byte[] PackageAsBytes() + { + using var byteBlock = new ByteBlock(1024*64); + this.Package(byteBlock); + return byteBlock.ToArray(); + } + + public override string ToString() + { + return $"数据类型={this.DataType},数据={(this.Data == null ? "null" : Encoding.UTF8.GetString(this.Data))}"; + } +} + +internal enum DataType : byte +{ + Ping, + Pong, + Data +} +``` + +## 三、创建扩展类 + +下列代码可选,主要实现对Client增加Ping的扩展方法。方便调用。 + +```csharp showLineNumbers +/// +/// 一个心跳计数器扩展。 +/// +internal static class DependencyExtensions +{ + public static readonly DependencyProperty HeartbeatTimerProperty = + DependencyProperty.Register("HeartbeatTimer", null); + + public static bool Ping(this TClient client) where TClient : ITcpSession + { + try + { + client.SendAsync(new MyRequestInfo() { DataType = DataType.Ping }.PackageAsBytes()); + return true; + } + catch (Exception ex) + { + client.Logger.Exception(ex); + } + + return false; + } + + public static bool Pong(this TClient client) where TClient : ITcpSession + { + try + { + client.SendAsync(new MyRequestInfo() { DataType = DataType.Pong }.PackageAsBytes()); + return true; + } + catch (Exception ex) + { + client.Logger.Exception(ex); + } + + return false; + } +} +``` + +## 四、创建心跳插件类 + +下列代码主要实现心跳插件的功能。默认每五秒自动触发一次。且接收方收到Ping后,直接会回复Pong。 + +```csharp showLineNumbers +internal class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugin, ITcpDisconnectedPlugin, ITcpReceivedPlugin +{ + private readonly int m_timeTick; + private readonly ILog logger; + + [DependencyInject(1000 * 5)] + public HeartbeatAndReceivePlugin(int timeTick, ILog logger) + { + this.m_timeTick = timeTick; + this.logger = logger; + } + + + public async Task OnTcpConnected(ITcpSession client, ConnectedEventArgs e) + { + if (client is ITcpSessionClient) + { + return;//此处可判断,如果为服务器,则不用使用心跳。 + } + + if (client.GetValue(DependencyExtensions.HeartbeatTimerProperty) is Timer timer) + { + timer.Dispose(); + } + + client.SetValue(DependencyExtensions.HeartbeatTimerProperty, new Timer((o) => + { + client.Ping(); + }, null, 0, this.m_timeTick)); + await e.InvokeNext(); + } + + public async Task OnTcpDisconnected(ITcpSession client, DisconnectEventArgs e) + { + if (client.GetValue(DependencyExtensions.HeartbeatTimerProperty) is Timer timer) + { + timer.Dispose(); + client.SetValue(DependencyExtensions.HeartbeatTimerProperty, null); + } + + await e.InvokeNext(); + } + + public async Task OnTcpReceived(ITcpSession client, ReceivedDataEventArgs e) + { + if (e.RequestInfo is MyRequestInfo myRequest) + { + this.logger.Info(myRequest.ToString()); + if (myRequest.DataType == DataType.Ping) + { + client.Pong(); + } + } + await e.InvokeNext(); + } +} +``` + +## 五、测试、启动 + +```csharp showLineNumbers +/// +/// 示例心跳。 +/// 博客地址 +/// +/// +private static void Main(string[] args) +{ + var consoleAction = new ConsoleAction(); + + //服务器 + var service = new TcpService(); + await service.SetupAsync(new TouchSocketConfig()//载入配置 + .SetListenIPHosts(new IPHost[] { new IPHost("127.0.0.1:7789"), new IPHost(7790) })//同时监听两个地址 + .SetTcpDataHandlingAdapter(() => new MyFixedHeaderDataHandlingAdapter()) + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .ConfigurePlugins(a => + { + a.Add(); + })); + + await service.StartAsync();//启动 + service.Logger.Info("服务器成功启动"); + + //客户端 + var tcpClient = new TcpClient(); + tcpClient.Setup(new TouchSocketConfig() + .SetRemoteIPHost(new IPHost("127.0.0.1:7789")) + .SetTcpDataHandlingAdapter(() => new MyFixedHeaderDataHandlingAdapter()) + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .ConfigurePlugins(a => + { + a.Add(); + })); + tcpClient.Connect(); + tcpClient.Logger.Info("客户端成功连接"); + + consoleAction.OnException += ConsoleAction_OnException; + consoleAction.Add("1", "发送心跳", () => + { + tcpClient.Ping(); + }); + consoleAction.Add("2", "发送数据", () => + { + tcpClient.SendAsync(new MyRequestInfo() + { + DataType = DataType.Data, + Data = Encoding.UTF8.GetBytes(Console.ReadLine()) + } + .PackageAsBytes()); + }); + consoleAction.ShowAll(); + while (true) + { + consoleAction.Run(Console.ReadLine()); + } +} + +private static void ConsoleAction_OnException(Exception obj) +{ + Console.WriteLine(obj); +} +``` + +[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/BlogsDemos/HeartbeatConsoleApp) diff --git a/handbook/versioned_docs/version-3.1/tcpintroduction.mdx b/handbook/versioned_docs/version-3.1/tcpintroduction.mdx new file mode 100644 index 000000000..aef03b317 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/tcpintroduction.mdx @@ -0,0 +1,93 @@ +--- +id: tcpintroduction +title: Tcp入门基础 +--- + +import BilibiliCard from '@site/src/components/BilibiliCard.js'; + +## 一、说明 + +传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的`传输层`通信协议。 + +**特点:** + +1. 基于流的方式; +2. 面向连接; +3. 可靠通信方式; +4. 在网络状况不佳的时候尽量降低系统由于重传带来的带宽开销; +5. 通信连接维护是面向通信的两个端点的,而不考虑中间网段和节点。 + + + +## 二、建立连接 + +**TCP三次握手的过程如下:** + +1. 客户端发送SYN(SEQ=x)报文给服务器端,进入SYN_SEND状态。 +2. 服务器端收到SYN报文,回应一个SYN (SEQ=y)ACK(ACK=x+1)报文,进入SYN_RECV状态。 +3. 客户端收到服务器端的SYN报文,回应一个ACK(ACK=y+1)报文,进入Established状态。 + +三次握手完成,TCP客户端和服务器端成功地建立连接,可以开始传输数据了。 + + + + + +## 三、断开连接 + +建立一个连接需要三次握手,而终止一个连接要经过四次握手,这是由TCP的半关闭(half-close)造成的。具体过程如下图所示。 + +1. 某个应用进程首先调用close,称该端执行“主动关闭”(active close)。该端的TCP于是发送一个FIN分节,表示数据发送完毕。 +2. 接收到这个FIN的对端执行 “被动关闭”(passive close),这个FIN由TCP确认。 +3. 一段时间后,接收到这个文件结束符的应用进程将调用close关闭它的套接字。这导致它的TCP也发送一个FIN。 +4. 接收这个最终FIN的原发送端TCP(即执行主动关闭的那一端)确认这个FIN。 + +:::info 注意 + +FIN的接收也作为一个文件结束符(end-of-file)传递给接收端应用进程,放在已排队等候该应用进程接收的任何其他数据之后,因为,FIN的接收意味着接收端应用进程在相应连接上再无额外数据可接收。 + +::: + +既然每个方向都需要一个FIN和一个ACK,因此通常需要4个分节。 + +:::caution 注意 + +1. “通常”是指,某些情况下,步骤1的FIN随数据一起发送,另外,步骤2和步骤3发送的分节都出自执行被动关闭那一端,有可能被合并成一个分节。 +2. 在步骤2与步骤3之间,从执行被动关闭一端到执行主动关闭一端流动数据是可能的,这称为“半关闭”(half-close)。 +3. 当一个Unix进程无论自愿地(调用exit或从main函数返回)还是非自愿地(收到一个终止本进程的信号)终止时,所有打开的描述符都被关闭,这也导致仍然打开的任何TCP连接上也发出一个FIN。 + +无论是客户还是服务器,任何一端都可以执行主动关闭。通常情况是,客户执行主动关闭。 + +::: + + + + + +## 四、发送 + +当调用Send函数时,实际上在Socket内部会做以下事项: + +1. 检查Send数据的有效性。例如是否为null,长度是否为0等。 +2. 检查发送缓存区是否有空闲,如果有,将需要发送的数据`复制`到缓存区,并且返回已复制的字节数。如果没有空闲,则会一直等待。 + +通过上述两个步骤,我们会明白,Send函数的返回,仅仅是保证数据被复制到了发送缓存区,而不是已被接收方收到。 + +:::tip 提示 + +由于Tcp协议的可靠性设计,如果成功将数据复制到缓存区,且在`没有断开`的情况下,数据就一定会被成功发送。 + +::: + +:::tip 提示 + +调用完Send后,返回的字节数可能会小于需要发送的字节数,所以一般需要循环发送。但是在TouchSocket中,已经做了封装,所有调用Send的字节,都是完整数据发送。 + +::: + + + + + + + diff --git a/handbook/versioned_docs/version-3.1/tcpservice.mdx b/handbook/versioned_docs/version-3.1/tcpservice.mdx new file mode 100644 index 000000000..81efa4d4e --- /dev/null +++ b/handbook/versioned_docs/version-3.1/tcpservice.mdx @@ -0,0 +1,650 @@ +--- +id: tcpservice +title: 创建TcpService +--- + +import Tag from "@site/src/components/Tag.js"; +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import { TouchSocketDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +TcpService是Tcp系服务器基类,它不参与实际的数据交互,只是配置、激活、管理、注销、重建**SessionClient**类实例。而**SessionClient**是当**TcpClient(客户端)**成功连接服务器以后,由服务器新建的一个实例类,后续的所有通信,也都是通过该实例完成的。 + +## 二、特点 + +- 简单易用。 +- IOCP多线程。 +- 内存池支持 +- 高性能(实测服务器单客户端单线程,每秒可接收200w条8字节的信息,接收数据流量可达3GB/s)。 +- **多地址监听**(可以一次性监听多个IP及端口) +- 适配器预处理,一键式解决**分包**、**粘包**、对象解析(如HTTP,Json)等。 +- 超简单的同步发送、异步发送、接收等操作。 +- 基于委托、[插件](./pluginsmanager.mdx)驱动,让每一步都能执行AOP。 + +### 2.1 吞吐量性能测试 + +如下图所示,使用最简单数据接收,不做任何处理。数据吞吐量可达3Gb。 [测试Demo示例](https://gitee.com/RRQM_Home/TouchSocket/tree/master/performancetest/Tcp/TcpFlowStressTestingConsoleApp) + + + +### 2.2 连接性能测试 + +如下图所示,使用最简单连接测试,不做任何处理。建立1000本地连接仅需0.1秒。 [测试Demo示例](https://gitee.com/RRQM_Home/TouchSocket/tree/master/performancetest/Tcp/TcpConnectStressTestingConsoleApp) + + + +## 三、产品应用场景 + +- 所有Tcp基础使用场景:可跨平台、跨语言使用。 +- 自定义协议解析场景:可解析任意数据格式的TCP数据报文。 + +## 四、服务器架构 + +### 4.1 连接架构 + +服务器在收到**新客户端连接**时,会创建一个SessionClient的派生类实例,与客户端TcpClient一一对应,后续的数据通信均由此实例负责。 + +SessionClient在Service里面以字典映射。ID为键,SessionClient本身为值。 + +```mermaid +flowchart TD; + Service-->TcpSessionClient-1; + Service-->TcpSessionClient-2; + Service-->TcpSessionClient-3; + TcpSessionClient-1-->TcpClient-1; + TcpSessionClient-2-->TcpClient-2; + TcpSessionClient-3-->TcpClient-3; + +``` + +### 4.2 Scoped 生命周期 + +`TcpService`在[支持Scoped](ioc.mdx)的`IOC`容器中工作时,也是支持`Scoped`区域划分的。 + +一般情况下,`TcpService`在`Setup`时,首先会创建一个`Scoped`区域,用于整个`TcpService`的生命周期。在`TcpService`释放(`Dispose`)时释放。 + +然后,当有新客户端连接后,会为每个`SessionClient`的派生类实例也创建一个`Scoped`区域,用于`SessionClient`的生命周期。当连接断开时,会释放此区域。 + + + +## 五、可配置项 + +
+可配置项 +
+ +#### SetMaxPackageSize +数据包最大值(单位:byte),默认1024×1024×10。该值会在适当时间,直接作用DataHandlingAdapter.MaxPackageSize。 + +#### SetGetDefaultNewId +配置初始Id的分配策略。 + + + +#### SetListenIPHosts +设置统一的监听IP和端口号组,可以一次性设置多个地址。 + +#### SetListenOptions +设置独立的监听IP和端口号,可以独立控制当前地址监听的个性化配置。 + +#### SetServerName +服务器标识名称,无实际使用意义。 + +#### SetBacklogProperty +Tcp半连接挂起连接队列的最大长度。默认为30 + + + +#### SetMaxCount +最大可连接数,默认为10000 + + + +#### SetServiceSslOption +Ssl配置,为Null时则不启用。 + + + +#### UseNoDelay +设置Socket的NoDelay属性,默认false。 + + + +#### UseReuseAddress +启用端口复用。该配置可在服务器、或客户端在监听端口时,运行监听同一个端口。可以一定程度缓解端口来不及释放的问题。 + + + + +#### SetMaxBufferSize +设置最大缓存容量,默认自动。 + +#### SetMinBufferSize +设置最小缓存容量,默认自动。 + + + +
+
+ + +## 六、支持插件 + +| 插件方法| 功能 | +| --- | --- | +| ITcpConnectingPlugin | 此时Socket实际上已经完成连接,但是并没有启动接收,然后触发。 | +| ITcpConnectedPlugin | 同意连接,且成功启动接收后触发 | +| ITcpClosingPlugin | 当客户端主动调用Close时触发 | +| ITcpClosedPlugin | 当客户端断开连接后触发 | +| ITcpReceivingPlugin | 在收到原始数据时触发,所有的数据均在ByteBlock里面。 | +| ITcpReceivedPlugin | 在收到适配器数据时触发,根据适配器类型,数据可能在ByteBlock或者IRequestInfo里面。 | +| ITcpSendingPlugin | 当即将发送数据时,调用该方法在适配器之后,接下来即会发送数据。 | +| IIdChangedPlugin | 当SessionClient的Id发生改变时触发。 | + +## 七、创建TcpService + +### 7.1 简单创建 + +直接初始化TcpService,会使用默认的**SessionClient**。 +简单的处理逻辑可通过**Connecting**、**Connected**、**Received**等委托直接实现。 + +代码如下: + +```csharp showLineNumbers +var service = new TcpService(); +service.Connecting = (client, e) => { return EasyTask.CompletedTask; };//有客户端正在连接 +service.Connected = (client, e) => { return EasyTask.CompletedTask; };//有客户端成功连接 +service.Closing = (client, e) => { return EasyTask.CompletedTask; };//有客户端正在断开连接,只有当主动断开时才有效。 +service.Closed = (client, e) => { return EasyTask.CompletedTask; };//有客户端断开连接 +service.Received = async (client, e) => +{ + //从客户端收到信息 + var mes = e.ByteBlock.Span.ToString(Encoding.UTF8); + client.Logger.Info($"已从{client.Id}接收到信息:{mes}"); +}; + +await service.SetupAsync(new TouchSocketConfig()//载入配置 + .SetListenIPHosts("tcp://127.0.0.1:7789", 7790)//可以同时监听两个地址 + .ConfigureContainer(a =>//容器的配置顺序应该在最前面 + { + a.AddConsoleLogger();//添加一个控制台日志注入(注意:在maui中控制台日志不可用) + }) + .ConfigurePlugins(a => + { + //a.Add();//此处可以添加插件 + })); + +await service.StartAsync();//启动 +``` + +:::info 温馨提示 + +Service.StartAsync()方法并不会阻塞当前运行,所以当在控制台运行时,可能需要使用Console.ReadKey()等操作进行阻塞。 + +::: + + + +### 7.2 泛型创建 + +通过泛型创建服务器,可以实现很多有意思,且能**重写**一些有用的功能。下面就演示,如何通过泛型创建服务器。 + +代码如下: + +(1)建立SessionClient继承类。 + +```csharp showLineNumbers +public sealed class MySessionClient : TcpSessionClient +{ + protected override async Task OnTcpReceived(ReceivedDataEventArgs e) + { + //此处逻辑单线程处理。 + + //此处处理数据,功能相当于Received委托。 + var mes = e.ByteBlock.Span.ToString(Encoding.UTF8); + Console.WriteLine($"已接收到信息:{mes}"); + + await base.OnTcpReceived(e); + } +} +``` + +(2)建立TcpService继承类。实际上如果业务不涉及服务器配置的话,可以省略该步骤,使用**TcpService的泛型**直接创建。 + +```csharp showLineNumbers +public class MyService : TcpService +{ + protected override void LoadConfig(TouchSocketConfig config) + { + //此处加载配置,用户可以从配置中获取配置项。 + base.LoadConfig(config); + } + + protected override MySessionClient NewClient() + { + return new MySessionClient(); + } + + protected override async Task OnTcpConnecting(MySessionClient socketClient, ConnectingEventArgs e) + { + //此处逻辑会多线程处理。 + + //e.Id:对新连接的客户端进行ID初始化,默认情况下是按照设定的规则随机分配的。 + //但是按照需求,您可以自定义设置,例如设置为其IP地址。但是需要注意的是id必须在生命周期内唯一。 + + //e.IsPermitOperation:指示是否允许该客户端链接。 + + await base.OnTcpConnecting(socketClient, e); + } +} +``` + +(3)创建服务器。 + +```csharp showLineNumbers +MyService service = new MyService(); + +await service.SetupAsync(new TouchSocketConfig()//载入配置 + .SetListenIPHosts("tcp://127.0.0.1:7789") + .ConfigureContainer(a =>//容器的配置顺序应该在最前面 + { + a.AddConsoleLogger();//添加一个控制台日志注入(注意:在maui中控制台日志不可用) + }) + .ConfigurePlugins(a => + { + //a.Add();//此处可以添加插件 + })); + +await service.StartAsync();//启动 +``` + +:::tip 建议 + +由上述代码可以看出,通过继承,可以更加灵活的实现扩展。但实际上,很多业务我们希望大家能通过插件完成。 + +::: + +## 八、配置监听 + +所有配置监听的项,都是从`IPHost`类创建而来。 + +`IPHost`支持以下格式创建: + +- 端口:直接按`int`入参,该操作一般在监听`Ipv4`时使用。 +- IPv4:按"127.0.0.1:7789"入参。 +- IPv6:按"[\*::\*]:7789"入参。 + + + +### 8.1 Config直接配置 + +服务器在配置监听时,有多种方式实现。其中最简单、最常见的配置方式就是通过Config直接配置。 + +```csharp showLineNumbers +TcpService service = new TcpService(); + +await service.SetupAsync(new TouchSocketConfig()//载入配置 + .SetListenIPHosts("tcp://127.0.0.1:7789", 7790)); + +await service.StartAsync();//启动 +``` + +### 8.2 直接添加监听配置 + +直接添加监听配置是更加个性化的监听配置,它可以单独控制指定监听地址的具体配置,例如:是否启用Ssl加密、使用何种适配器等。 + +```csharp showLineNumbers +var service = new TcpService(); + +await service.SetupAsync(new TouchSocketConfig()//载入配置 + .SetListenOptions(option => + { + option.Add(new TcpListenOption() + { + IpHost = "127.0.0.1:7789", + Name = "server1",//名称用于区分监听 + ServiceSslOption = null,//可以针对当前监听,单独启用ssl加密 + Adapter = () => new NormalDataHandlingAdapter(),//可以单独对当前地址监听,配置适配器 + //还有其他可配置项,都是单独对当前地址有效。 + }); + + option.Add(new TcpListenOption() + { + IpHost = 7790, + Name = "server2",//名称用于区分监听 + ServiceSslOption = null,//可以针对当前监听,单独启用ssl加密 + Adapter = () => new FixedHeaderPackageAdapter(),//可以单独对当前地址监听,配置适配器 + //还有其他可配置项,都是单独对当前地址有效。 + }); + })); + +await service.StartAsync();//启动 +``` + +:::info 温馨提示 + +`SetListenIPHosts`可以和`SetListenOptions`可以同时使用,但是需要注意的是,Config的全局配置仅会对`SetListenIPHosts`单独生效的。`SetListenOptions`的地址配置均是单独配置的。 + +::: + + +### 8.3 动态添加、移除监听配置 + +服务器支持在运行时,动态添加,和移除监听配置,这极大的为灵活监听提供了方便,并且还不影响现有连接。可以轻量级的实现Stop操作。 + +```csharp {5,16} +TcpService service = new TcpService(); +await service.SetupAsync(new TouchSocketConfig()); +await service.StartAsync();//启动 + +service.AddListen(new TcpListenOption()//在Service运行时,可以调用,直接添加监听 +{ + IpHost = 7791, + Name = "server3",//名称用于区分监听 + ServiceSslOption = null,//可以针对当前监听,单独启用ssl加密 + Adapter = () => new FixedHeaderPackageAdapter(),//可以单独对当前地址监听,配置适配器 + //还有其他可配置项,都是单独对当前地址有效。 +}); + +foreach (var item in service.Monitors) +{ + service.RemoveListen(item);//在Service运行时,可以调用,直接移除现有监听 +} +``` + + + +## 九、接收数据 + +在TcpService中,接收数据的方式有很多种。多种方式可以组合使用。 + +### 9.1 Received委托处理 + + + +当使用TcpService(非泛型)创建服务器时,内部已经定义好了一个外置委托Received,可以通过该委托直接接收数据。 + +```csharp showLineNumbers +var service = new TcpService(); +service.Received = (client, e) => +{ + //从客户端收到信息 + var mes = e.ByteBlock.Span.ToString(Encoding.UTF8); + client.Logger.Info($"已从{client.Id}接收到信息:{mes}"); + return EasyTask.CompletedTask; +}; + +await service.SetupAsync(new TouchSocketConfig()//载入配置 + .SetListenIPHosts("tcp://127.0.0.1:7789", 7790)//同时监听两个地址 + .ConfigureContainer(a =>//容器的配置顺序应该在最前面 + { + a.AddConsoleLogger();//添加一个控制台日志注入(注意:在maui中控制台日志不可用) + })); +await service.StartAsync();//启动 +``` + +### 9.2 重写TcpSessionClient处理 + +正如6.2所示,可以直接在MySessionClient的重写**OnTcpReceived**中直接处理数据。 + +### 9.3 插件处理 推荐 + + + +按照TouchSocket的设计理念,使用插件处理数据,是一项非常简单,且高度解耦的方式。步骤如下: + +(1)声明插件 + +插件可以先继承`PluginBase`,然后再实现需要的功能插件接口,可以按需选择泛型或者非泛型实现。 + +如果已经有继承类,直接实现`IPlugin`接口即可。 + +```csharp showLineNumbers +public class MyPlugin : PluginBase, ITcpReceivedPlugin +{ + public async Task OnTcpReceived(ITcpSession client, ReceivedDataEventArgs e) + { + //这里处理数据接收 + //根据适配器类型,e.ByteBlock与e.RequestInfo会呈现不同的值,具体看文档=》适配器部分。 + ByteBlock byteBlock = e.ByteBlock; + IRequestInfo requestInfo = e.RequestInfo; + + ////表示该数据已经被本插件处理,无需再投递到其他插件。 + + await e.InvokeNext();//如果本插件无法处理当前数据,请将数据转至下一个插件。 + } +} +``` + +(2)创建使用插件处理的服务器 + +```csharp {10} +var service = new TcpService(); +await service.SetupAsync(new TouchSocketConfig() + .SetListenIPHosts(7789) + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .ConfigurePlugins(a => + { + a.Add(); + })); +await service.StartAsync(); +``` + +:::danger 注意 + +当接收数据时,ByteBlock与RequestInfo的值会根据适配器类型不同而不同。并且,当数据存于ByteBlock时,其实际的数据长度是ByteBlock.Length(Length)。而不是ByteBlock.Buffer.Length + +::: + + + + + + + + + + + + + + + + + +### 9.4 异步阻塞接收 推荐 + +异步阻塞接收,即使用`await`的方式接收数据。其特点是能在代码上下文中,直接获取到收到的数据。 + +只是在服务器使用异步阻塞时,建议直接在`Connected`触发时相关使用。 + +下列将以插件为例: + +```csharp showLineNumbers +class TcpServiceReceiveAsyncPlugin : PluginBase, ITcpConnectedPlugin +{ + public async Task OnTcpConnected(ITcpSession client, ConnectedEventArgs e) + { + if (client is ITcpSessionClient sessionClient) + { + //receiver可以复用,不需要每次接收都新建 + using (var receiver = sessionClient.CreateReceiver()) + { + while (true) + { + //receiverResult每次接收完必须释放 + using (var receiverResult = await receiver.ReadAsync(CancellationToken.None)) + { + //收到的数据,此处的数据会根据适配器投递不同的数据。 + var byteBlock = receiverResult.ByteBlock; + var requestInfo = receiverResult.RequestInfo; + + if (receiverResult.IsCompleted) + { + //断开连接了 + Console.WriteLine($"断开信息:{receiverResult.Message}"); + return; + } + + //如果数据是从ByteBlock投递 + if (byteBlock != null) + { + Console.WriteLine(byteBlock.Span.ToString(Encoding.UTF8)); + } + + //如果是适配器信息,则可以直接处理requestInfo; + } + } + } + } + + await e.InvokeNext(); + } +} +``` + +在异步阻塞接收时,当接收的数据不满足解析条件时,还可以缓存起来,下次一起处理。 + +例如:下列将演示接收字符串,当没有发现“\r\n”时,将缓存数据,直到发现重要字符。 + +其中,`CacheMode`与`MaxCacheSize`是启用缓存的重要属性。`byteBlock.Seek`则是将已读取的数据游标移动至指定位置。 + +```csharp {10-11,48} showLineNumbers +class TcpServiceReceiveAsyncPlugin : PluginBase, ITcpConnectedPlugin +{ + public async Task OnTcpConnected(ITcpSession client, ConnectedEventArgs e) + { + if (client is ITcpSessionClient sessionClient) + { + //receiver可以复用,不需要每次接收都新建 + using (var receiver = sessionClient.CreateReceiver()) + { + receiver.CacheMode = true; + receiver.MaxCacheSize = 1024 * 1024; + + var rn = Encoding.UTF8.GetBytes("\r\n"); + while (true) + { + //receiverResult每次接收完必须释放 + using (var receiverResult = await receiver.ReadAsync(CancellationToken.None)) + { + //收到的数据,此处的数据会根据适配器投递不同的数据。 + var byteBlock = receiverResult.ByteBlock; + var requestInfo = receiverResult.RequestInfo; + + if (receiverResult.IsCompleted) + { + //断开连接了 + Console.WriteLine($"断开信息:{receiverResult.Message}"); + return; + } + + //在CacheMode下,byteBlock将不可能为null + + var index = 0; + while (true) + { + var r = byteBlock.Span.Slice(index).IndexOf(rn); + if (r < 0) + { + break; + } + + var str = byteBlock.Span.Slice(index, r).ToString(Encoding.UTF8); + Console.WriteLine(str); + + index += rn.Length; + index += r; + } + + byteBlock.Seek(index); + } + } + } + } + + await e.InvokeNext(); + } +} +``` + +:::tip 提示 + +异步阻塞接收,在等待接收数据时,不会阻塞线程资源,所以即使大量使用,也不会影响性能。 + +::: + +## 十、发送数据 + +按照架构图,每个客户端成功连接后,**服务器**都会创建一个派生自**TcpSessionClient**的实例,并将其存以生成的Id为键,存在一个字典中。 + +所以,service提供了一下原生方法,可以通过id直接将数据发送至指定客户端。 + +```csharp showLineNumbers +//原生 +public Task SendAsync(string id, ReadOnlyMemory memory); +public Task SendAsync(string id, IRequestInfo requestInfo); +``` + +例如: + +```csharp showLineNumbers +await service.SendAsync("id",Encoding.UTF8.GetBytes("hello")); +``` + +亦或者,可以先用id查到对应的TcpSessionClient,然后用其提供的方法直接发送。 + +例如: + +```csharp showLineNumbers +//尝试性获取 +if (service.TryGetClient("id", out var sessionClient)) +{ + await sessionClient.SendAsync("hello"); +} +``` + +```csharp showLineNumbers +//直接获取,如果id不存在,则会抛出异常 +var sessionClient = service.GetClient("id"); +await sessionClient.SendAsync("hello"); +``` + +:::caution 注意 + +由于`TcpSessionClient`的生命周期是由框架控制的,所以最好尽量不要直接引用该实例,可以引用`TcpSessionClient.Id`,然后再通过服务器查找。 + +::: + +:::caution 注意 + +所有的发送,框架内部实际上**只实现了异步发送**,但是为了兼容性,仍然保留了同步发送的扩展。但是强烈建议如有可能,请**务必使用异步发送来提高效率**。 + +::: + +:::tip 提示 + +框架不仅内置了字节的发送,也扩展了**字符串**等常见数据的发送。而且还包括了`TrySend`等不会抛出异常的发送方法。 + +::: + +[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Tcp) + diff --git a/handbook/versioned_docs/version-3.1/tcpwaitingclient.mdx b/handbook/versioned_docs/version-3.1/tcpwaitingclient.mdx new file mode 100644 index 000000000..54efaf6be --- /dev/null +++ b/handbook/versioned_docs/version-3.1/tcpwaitingclient.mdx @@ -0,0 +1,110 @@ +--- +id: tcpwaitingclient +title: 同步请求 +--- + +import { TouchSocketDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +有很多小伙伴一直有一些需求: + +1. 客户端发送一个数据,然后等待服务器回应。 +2. 服务器向客户端发送一个数据,然后等待客户端回应。 + +那针对这些需求,可以使用**WaitingClient。**其内部实现了**IWaitSender**接口,能够在发送完成后,等待返回。 + +## 二、创建及使用 + +### 2.1 以TcpClient为例 + +```csharp showLineNumbers +var client = new TcpClient(); +await client.ConnectAsync("tcp://127.0.0.1:7789"); + +//调用CreateWaitingClient获取到IWaitingClient的对象。 +var waitClient = client.CreateWaitingClient(new WaitingOptions() +{ + FilterFunc = response => //设置用于筛选的fun委托,当返回为true时,才会响应返回 + { + return true; + + //if (response.Data.Length == 1) + //{ + // return true; + //} + //return false; + } +}); + +//然后使用SendThenReturn。 +byte[] returnData = await waitClient.SendThenReturnAsync(Encoding.UTF8.GetBytes("RRQM")); +Console.WriteLine($"收到回应消息:{Encoding.UTF8.GetString(returnData)}"); + +//同时,如果适配器收到数据后,返回的并不是字节,而是IRequestInfo对象时,可以使用SendThenResponse. +ResponsedData responsedData = await waitClient.SendThenResponseAsync(Encoding.UTF8.GetBytes("RRQM")); +IRequestInfo requestInfo = responsedData.RequestInfo;//同步收到的RequestInfo +``` + +### 2.2 以TcpService为例 + +```csharp showLineNumbers +var service = new TcpService(); +await service.StartAsync(7789);//启动服务器 + +//在服务器中,找到指定Id的会话客户端 +if (service.TryGetClient("targetId", out var tcpSessionClient)) +{ + //调用CreateWaitingClient获取到IWaitingClient的对象。 + var waitClient = tcpSessionClient.CreateWaitingClient(new WaitingOptions() + { + FilterFunc = response => //设置用于筛选的fun委托,当返回为true时,才会响应返回 + { + return true; + + //if (response.Data.Length == 1) + //{ + // return true; + //} + //return false; + } + }); + + //然后使用SendThenReturn。 + byte[] returnData = await waitClient.SendThenReturnAsync(Encoding.UTF8.GetBytes("RRQM")); + Console.WriteLine($"收到回应消息:{Encoding.UTF8.GetString(returnData)}"); + + //同时,如果适配器收到数据后,返回的并不是字节,而是IRequestInfo对象时,可以使用SendThenResponse. + ResponsedData responsedData = await waitClient.SendThenResponseAsync(Encoding.UTF8.GetBytes("RRQM")); + IRequestInfo responseRequestInfo = responsedData.RequestInfo;//同步收到的RequestInfo +} +``` + +:::tip 提示 + +在SendThenReturnAsync时,通过其他参数,还可以设置Timeout,以及可取消的等待Token。 + +::: + +:::caution 注意 + +所有的发送,框架内部实际上**只实现了异步发送**,但是为了兼容性,仍然保留了同步发送的扩展。但是强烈建议如有可能,请**务必使用异步发送来提高效率**。 + +::: + + +:::danger 注意事项 + +1. 发送完数据,在等待时,如果收到其他返回数据,则可能得到错误结果。 +2. 发送采用同步锁,一个事务没结束,另一个请求也发不出去。 +3. waitClient的使用**不可以**直接在`Received`相关触发中使用,因为必然会导致死锁,详见:[#I9GCGT](https://gitee.com/RRQM_Home/TouchSocket/issues/I9GCGT) 。 +4. 在**Net461及以下**版本中,SendThenReturn与SendThenReturnAsync不能混合使用。即:要么全同步,要么全异步(这可能是.net bug)。 + +::: + +[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Tcp/TcpWaitingClientWinFormsApp) \ No newline at end of file diff --git a/handbook/versioned_docs/version-3.1/thingsgateway.mdx b/handbook/versioned_docs/version-3.1/thingsgateway.mdx new file mode 100644 index 000000000..1878404c3 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/thingsgateway.mdx @@ -0,0 +1,26 @@ +--- +id: thingsgateway +title: 工业协议采集网关 +--- + +[开源地址](https://gitee.com/diego2098/ThingsGateway) + +# ThingsGateway + +## 介绍 + + **NetCore** 跨平台边缘采集网关(工业设备采集) + + **ThingsGateway** 存储库同时提供 [**设备采集驱动**](https://www.nuget.org/packages?q=Tags%3A%22ThingsGateway%22) + + **ThingsGateway** 存储库同时提供 **基于Blazor的权限框架** 查看 [**ThingsGateway.Admin**](https://gitee.com/dotnetchina/ThingsGateway/blob/master/framework/ThingsGateway.Admin.sln) + + +## 文档 + +[ThingsGateway](https://diego2098.gitee.io/thingsgateway-docs/) 文档。 + +## 协议 + +[ThingsGateway](https://gitee.com/diego2098/ThingsGateway) 采用 [Apache-2.0](https://gitee.com/diego2098/ThingsGateway/blob/master/LICENSE.zh) 开源协议。 + diff --git a/handbook/docs/tlvdatahandlingadapter.mdx b/handbook/versioned_docs/version-3.1/tlvdatahandlingadapter.mdx similarity index 100% rename from handbook/docs/tlvdatahandlingadapter.mdx rename to handbook/versioned_docs/version-3.1/tlvdatahandlingadapter.mdx diff --git a/handbook/versioned_docs/version-3.1/touchsocketbitconverter.mdx b/handbook/versioned_docs/version-3.1/touchsocketbitconverter.mdx new file mode 100644 index 000000000..19296a3e9 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/touchsocketbitconverter.mdx @@ -0,0 +1,194 @@ +--- +id: touchsocketbitconverter +title: 大小端转换器 +--- + +import { TouchSocketCoreDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、基本概念 + +### 1. 大小端模式 +- **大端模式 (Big Endian)** + 高位字节存储在内存低地址,低位字节存储在高地址。 + *示例:整数 `10` 的4字节大端表示为 `{0, 0, 0, 10}*` + +- **小端模式 (Little Endian)** + 低位字节存储在内存低地址,高位字节存储在高地址。 + *示例:整数 `10` 的4字节小端表示为 `{10, 0, 0, 0}` + +### 2. 端序类型枚举 +```csharp +public enum EndianType { + Big, // 标准大端 + Little, // 标准小端 + BigSwap, // 交换大端(特殊场景) + LittleSwap // 交换小端(特殊场景) +} +``` + +--- + + +## 二、核心 API 使用 + +### 1. 直接指定端序转换器 +```csharp +// 预定义静态实例 +TouchSocketBitConverter.BigEndian // 绝对大端 +TouchSocketBitConverter.LittleEndian // 绝对小端 +TouchSocketBitConverter.BigSwapEndian // 交换大端 +TouchSocketBitConverter.LittleSwapEndian // 交换小端 +``` + +### 2. 默认端序配置 +```csharp +// 默认小端模式(可全局修改) +TouchSocketBitConverter.DefaultEndianType = EndianType.Big; + +// 获取当前默认转换器 +TouchSocketBitConverter.Default.GetBytes(123); +``` + +--- + +## 三、基础数据类型转换 + +### 1. 数值类型转换 +#### 转换整型 (`int`) +```csharp +// int -> byte[] +byte[] data = TouchSocketBitConverter.BigEndian.GetBytes(0x12345678); + +// byte[] -> int +int value = TouchSocketBitConverter.BigEndian.ToInt32(data, 0); +``` + +#### 转换浮点型 (`float`) +```csharp +// float -> byte[] +float num = 3.14f; +byte[] bytes = TouchSocketBitConverter.LittleEndian.GetBytes(num); + +// byte[] -> float +float result = TouchSocketBitConverter.LittleEndian.ToSingle(bytes, 0); +``` + +### 2. 长数据类型处理 +#### 转换长整型 (`long`) +```csharp +// long -> byte[] +long bigValue = 0x123456789ABCDEF0; +byte[] data = TouchSocketBitConverter.Default.GetBytes(bigValue); + +// byte[] -> long +long restored = TouchSocketBitConverter.Default.ToInt64(data, 0); +``` + +#### 转换高精度小数 (`decimal`) +```csharp +// decimal -> byte[] +decimal money = 123456.78m; +byte[] decimalBytes = TouchSocketBitConverter.BigEndian.GetBytes(money); + +// byte[] -> decimal +decimal result = TouchSocketBitConverter.BigEndian.ToDecimal(decimalBytes, 0); +``` + +--- + +## 四、高级功能 + +### 1. 不安全内存操作 +```csharp +unsafe { + byte[] buffer = new byte[8]; + long value = 0x1122334455667788; + + // 直接内存写入 + fixed (byte* ptr = buffer) { + TouchSocketBitConverter.LittleEndian.UnsafeWriteBytes(ref *ptr, value); + } + + // 直接内存读取 + long readValue = TouchSocketBitConverter.LittleEndian.UnsafeTo(ref buffer[0]); +} +``` + +### 2. 布尔数组转换 +```csharp +// bool[] -> byte[] +bool[] flags = { true, false, true, true, false, true, false, true }; +byte[] boolBytes = TouchSocketBitConverter.Default.GetBytes(flags); + +// byte[] -> bool[] +bool[] decodedFlags = TouchSocketBitConverter.Default.ToBooleans(boolBytes, length: 1); +``` + +--- + +## 五、特殊场景处理 + +### 1. 字节序验证 +```csharp +// 检查当前配置是否与系统字节序一致 +bool isCompatible = TouchSocketBitConverter.Default.IsSameOfSet(); +``` + +### 2. 交换端序模式 +```csharp +// 使用 BigSwap 模式处理网络数据包 +byte[] swappedData = TouchSocketBitConverter.BigSwapEndian.GetBytes(0xAABBCCDD); +``` + +--- + +## 六、注意事项 + +1. **字节数组长度验证** + 所有转换方法会检查数组长度,不足时抛出 `ArgumentOutOfRangeException` + +2. **跨平台兼容性** + 建议通过 `IsSameOfSet()` 验证端序一致性 + +3. **性能优化** + 对性能敏感场景推荐使用 `UnsafeWriteBytes` 和 `UnsafeTo` 方法 + +--- + +## 七、完整示例 + +### 网络数据包处理 +```csharp +// 配置全局大端模式 +TouchSocketBitConverter.DefaultEndianType = EndianType.Big; + +// 构造数据包 +var packet = new byte[16]; +int header = 0x4D534754; // "MSGT" +float payload = 1024.5f; + +// 写入数据 +TouchSocketBitConverter.Default.WriteBytes(packet.AsSpan(0, 4), header); +TouchSocketBitConverter.Default.WriteBytes(packet.AsSpan(4, 4), payload); + +// 解析数据 +int parsedHeader = TouchSocketBitConverter.Default.ToInt32(packet, 0); +float parsedPayload = TouchSocketBitConverter.Default.ToSingle(packet, 4); +``` + +--- + +## 八、API 参考 + +| 方法 | 说明 | +|------|------| +| `GetBytes(T value)` | 将值转换为字节数组 | +| `To(ReadOnlySpan)` | 从字节跨度转换回值 | +| `WriteBytes(Span, T)` | 将值写入字节跨度 | +| `UnsafeWriteBytes(ref byte, T)` | 不安全内存写入 | +| `UnsafeTo(ref byte)` | 不安全内存读取 | diff --git a/handbook/versioned_docs/version-3.1/troubleshootissue.mdx b/handbook/versioned_docs/version-3.1/troubleshootissue.mdx new file mode 100644 index 000000000..666decb13 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/troubleshootissue.mdx @@ -0,0 +1,67 @@ +--- +id: troubleshootissue +title: Issue +--- + +## 一、什么是Issue + +Issue是GitHub、Gitee等上用于记录问题、bug、任务、需求等的地方。 + +## 二、为什么使用Issue + +Issue实际上是一系列与仓库有关的讨论集合,它可以用来记录问题、bug、任务、需求等。他和QQ、微信等不同,它是一个公开的讨论区,任何人都可以参与讨论。同时,还可以搜索到前人的讨论,方便我们学习。 + +同时,他也是像邮件一样,具有低频交流的特性,这就使得不管是提问还是解答,都得描述的相较仔细。且最好图文并茂。大大减少无效交流。 + +## 三、如何使用Issue + +首先,目前我们只支持Gitee的Issue功能,所以请先注册一个Gitee账号。 + +然后在[TouchSocket Issues](https://gitee.com/RRQM_Home/TouchSocket/issues) 连接下,新建Issue即可。 + + + +:::tip 提示 + +在填写时,请尽可能的描述清楚现状,及其他一切有关信息。最好能提供可运行测试的demo。 + +::: + +:::caution 警告 + +未按格式提供的Issue,会被直接关闭。一般建议使用电脑端编写。因为我们已经为大家写好了相关模版。 + +::: + + +## 四、提供Issue有关的Demo + +在Issue中,我们强烈建议提供可运行的demo,这样可以帮助我们更快的定位问题。 + +提供的方式**仅限下列git仓库**,其余途径均不受理。 + +- [TouchSocket Demo](https://gitee.com/RRQM_Home/publicworkbook) + +### 4.1 Fork仓库 + +首先,点击Fork,将仓库克隆到自己的名下。 + +然后,在仓库中,新建一个文件夹,名称按照序号,依次递增,要求不重复。然后在此文件夹中新建一个demo解决方案,就是能直接编译运行的。 + + + +### 4.2 提交 + +当建立好demo后,将demo提交到自己的仓库中。请确定此Demo可以脱离环境编译并运行。 + +### 4.3 合并PR + +在提交以后,在gitee上,点开自己Fork的仓库,然后点击`Pull Request`,`新建Pull Request`。 + + + +然后注意,选择目标分支为`RRQM_Home/publicworkbook=>master`。同时,请填写相关信息。 + + + +最后,所提的Issue中,提供此demo的链接。 diff --git a/handbook/versioned_docs/version-3.1/troubleshootsourcecode.mdx b/handbook/versioned_docs/version-3.1/troubleshootsourcecode.mdx new file mode 100644 index 000000000..d388e26ba --- /dev/null +++ b/handbook/versioned_docs/version-3.1/troubleshootsourcecode.mdx @@ -0,0 +1,65 @@ +--- +id: troubleshootsourcecode +title: 疑难解答 +--- + +## 一、源码编译 + +TouchSocket是基于Apache License 2.0免费开源的,源码托管在Gitee、Github上,如果需要编译源码,请参考以下步骤: + +### 1.1 下载源码 + +下载源码的方式有多种。 + +1. 直接下载源码包,下载地址:[TouchSocket最新源码](https://gitee.com/RRQM_Home/TouchSocket/repository/archive/master.zip) +2. 使用Fork,Clone到本地。这个的优点是,可以随时提交PR,随时更新源码。 + +### 1.2 编译源码 + +编译工具我们使用VS,一般来说vs2017以上版本只需要简单配置就可以编译。如果是再低版本,则需要新建个项目,把源码复制进去,然后编译即可。 + +下列我们只讲说vs2017以上版本编译源码。 + +首先,把下载好的源码解压(如果是clone的则不用),一般目录如下: + +``` +├─.gitee +├─benchmark +├─Build +├─examples +├─examples_aot +├─handbook +├─images +├─performancetest +├─src +├─xunit +``` + +我们需要找到src目录,然后打开TouchSocket.sln的解决方案。 + +然后,先试下右键解决方案,选择“重新生成解决方案”。看下能否直接生成。一般的,如果你的vs是2022版本,且安装了net45,net6.0,net7.0的sdk,则可以直接生成。 + +如果生成成功,则生成的所有dll在Build文件夹下。 + +如果不能生成,则需要进行修改。 + +例如: + +编译电脑直接使用的vs2022及以上版本,且没有安装net45的sdk所导致的。 + + + +解决方法也很简单: + +1. 安装net45,详情自行网络。 +2. 或者取消net45的编译(删除“net45;”包括分号)。如下图: + +如果要取消net45的编译,则双击项目,打开项目编辑。然后删除`net45;` + + + +同理,如果你的vs是2017或者2019,那就需要删除net6.0,net7.0的编译。方法同上。 + +至此,基本上就能解决TouchSocket编译的问题了。 + +不过需要注意的是,即使您自己编译TouchSocket,也不能删除任何作者信息及项目名称。因为一来代码里面可能有判断程序集名称及命名空间的代码。二来,作者信息及项目名称是作者的标识,所以我们也希望您能尊重作者。 \ No newline at end of file diff --git a/handbook/versioned_docs/version-3.1/troubleshootunity3d.mdx b/handbook/versioned_docs/version-3.1/troubleshootunity3d.mdx new file mode 100644 index 000000000..74844be31 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/troubleshootunity3d.mdx @@ -0,0 +1,108 @@ +--- +id: troubleshootunity3d +title: Unity3D相关 +--- + +## 一、【问】TouchSocket系列是否能用于Unity3D? + +TouchSocket系列是否能用于Unity3D + +### 【解决】 + +可以的,TouchSocket是基于Net45和.netstandard2.0的,且没有任何其他运行时,是100%基于C#开发的,所以可以直接用于unity。 + +但是由于TouchSocket全系都是基于Socket构建的,所以目前无法用于WebGL。除此之外,支持window、android、ios、linux等平台。 + +目前**实测**支持Il2cpp编译,支持HybridCLR热更新。 + +TouchSocket提供一个简单的示例demo,包含一个服务器和unity包。以供大家下载试用。 [TouchSocket For Unity](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Unity3d) + +## 二、【问】用于Unity 3D时,显示有dll不兼容,或者找不到? + +【描述】 + +unity提示:由于以下错误,将不会加载程序集“Assets/Plugins/TouchSocket.dl”无法解析引用“TouchSocket.Core”。程序集是否丢失或与当前平台不兼容?可以在插件检查器中禁用引用验证.无法解析引用“TouchSocket”。程序集是否丢失或与当前平台不兼容?可以在插件检查器中禁用引用验证 + +### 【解决】 + +这是因为TouchSocket系列程序集有依赖关系。例如:TouchSocket依赖于TouchSocket.Core。所以在下载TouchSocket.dll时,应该也下载TouchSocket.Core.dll. + +当然有时候我们对具体依赖不了解时,可以直接在vs看依赖关系。 + + +## 三、【问】程序集用于Unity 3D时,Json序列化有问题? + +### 【解决】 +这是因为unity中不支持代码生成的Json序列化,所以必须用IL2cpp版的json。下面提供参考博客。 + +在git上下载Newtonsoft.Json-for-Unity-master的压缩文件(.zip),解压之后,复制到unity3D工程的Asset/Plugins文件夹下就可以用了, + +[Unity版Newtonsoft.Json](https://github.com/jilleJr/Newtonsoft.Json-for-Unity) + +## 四、【问】程序集用于Unity 3D时,显示操作不被支持? + +Operation is not supported on this platform.或者我将TouchSocket程序集引入到U3D中后,使用了相关功能,或者其他组件功能,在编辑器界面正常,但是发布到PC、Android等平台时无法使用? + + + + +### 【解决1】 + +首先查看项目是否设置了`IL2CPP`,如果设置了的话,可以考虑是否能设置为`Mono`,如果能,则OK。 + + + + +### 【解决2】 + +这是因为unity中不支持IL生成,所以必须把所有的动态调用转换为反射。 + +即:在任意地方,最前部调用下列代码即可。同时可选的值有`DynamicBuilderType.Reflect(反射)`和`DynamicBuilderType.Expression(表达式树)`。 + +其中表达式树可能需要你的unity是较高版本,且使用.netstandard2.1的SDK。 + +```csharp showLineNumbers +GlobalEnvironment.DynamicBuilderType = DynamicBuilderType.Reflect; +``` + +但是上述方法并不完美,因为反射调用需要消耗一部分性能,所以可以根据实际情况做一些优化。 + +例如:使用动态调用的地方,一般为序列化和插件调用。 + +所以序列化则可以考虑使用其他序列化作为替代(因为目前Fast序列化暂不支持生成器)。 + +而对于插件,则最好使用委托接收,详情请看 [插件系统](./pluginsmanager.mdx) + +例如: + +```csharp showLineNumbers +class MyPluginClass : PluginBase +{ + protected override void Loaded(IPluginManager pluginManager) + { + pluginManager.Add(typeof(ITcpReceivedPlugin), OnTcpReceived); + base.Loaded(pluginManager); + } + + private async Task OnTcpReceived(ITcpSession client, ReceivedDataEventArgs e) + { + await e.InvokeNext(); + } +} +``` + +其次,Il2cpp会对程序进行裁剪,所以需要unity内link.xml设置(放置在Assets文件夹内)。 [unity官方文档 托管代码剥离](https://docs.unity3d.com/cn/current/Manual/ManagedCodeStripping.html#LinkXML) + + +```csharp showLineNumbers xml + + + + +``` + +:::info 备注 + +上述仅示例部分,如果是其他组件库,则添加相应**程序集名称**。 + +::: \ No newline at end of file diff --git a/handbook/docs/udpbroadcast.mdx b/handbook/versioned_docs/version-3.1/udpbroadcast.mdx similarity index 100% rename from handbook/docs/udpbroadcast.mdx rename to handbook/versioned_docs/version-3.1/udpbroadcast.mdx diff --git a/handbook/versioned_docs/version-3.1/udpdatahandlingadapter.mdx b/handbook/versioned_docs/version-3.1/udpdatahandlingadapter.mdx new file mode 100644 index 000000000..d9ad1cf93 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/udpdatahandlingadapter.mdx @@ -0,0 +1,49 @@ +--- +id: udpdatahandlingadapter +sidebar_position: 1 +title: 原始自定义适配器 +sidebar_label: a.原始自定义适配器 +--- + +import { TouchSocketDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 说明 + +Udp的适配器,主要承担组包和解析数据。其基本逻辑和Tcp相似。但是需要注意的是,Udp适配器是**多线程**操作。在解析数据时,应当充分考虑并发问题。 + +```csharp showLineNumbers +class MyUdpAdatper : UdpDataHandlingAdapter +{ + public override bool CanSplicingSend => false; + + protected override void PreviewReceived(EndPoint remoteEndPoint, ByteBlock byteBlock) + { + + } + + protected override void PreviewSend(EndPoint endPoint, byte[] buffer, int offset, int length, bool isAsync) + { + + } + + protected override void PreviewSend(EndPoint endPoint, IList transferBytes, bool isAsync) + { + + } + + protected override void Reset() + { + + } +} +``` + + +## 单元测试 + +使用**UdpDataAdapterTester**即可测试。 diff --git a/handbook/versioned_docs/version-3.1/udpsession.mdx b/handbook/versioned_docs/version-3.1/udpsession.mdx new file mode 100644 index 000000000..19c279417 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/udpsession.mdx @@ -0,0 +1,186 @@ +--- +id: udpsession +title: 创建UdpSession +--- + +import Tag from "@site/src/components/Tag.js"; +import CardLink from "@site/src/components/CardLink.js"; +import { TouchSocketDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +UDP组件是基于UDP协议的最基础组件,其功能简单,易用。它既能充当服务器,又能够作为客户端。 + +## 二、产品特点 + +- 简单易用。 +- 多线程重叠IO。 +- 内存池 +- 高性能 +- 支持组播、广播 +- 支持插件扩展 +- 支持跨平台 + +## 三、产品应用场景 + +- UDP基础使用场景:可跨平台、跨语言使用。 + +## 四、支持插件接口 + +| 插件方法| 功能 | +| --- | --- | +| IUdpReceivedPlugin | 在收到数据时触发 | + +## 五、使用UdpSession + +### 5.1 作为服务器使用 + +```csharp showLineNumbers +var udpService = new UdpSession(); +udpService.Received = (c,e) => +{ + Console.WriteLine(e.ByteBlock.ToString()); + return EasyTask.CompletedTask; +}; +udpService.Setup(new TouchSocketConfig() + .SetBindIPHost(new IPHost(7789))); +udpService.Start(); +Console.WriteLine("等待接收"); +``` + +### 5.2 作为客户端使用 + +```csharp showLineNumbers +var udpClient = new UdpSession(); +udpClient.Setup(new TouchSocketConfig() + //.UseUdpReceive()//作为客户端时,如果需要接收数据,那么需要绑定端口。要么使用SetBindIPHost指定端口,要么调用UseUdpReceive绑定随机端口。 + .SetBindIPHost(new IPHost(7788))); +udpClient.Start(); +``` + +:::caution 注意 + +1. 即使不监听地址,Setup和Start都是必须要的。 +2. 当udp作为客户端时,Config如果不设置SetBindIPHost,将不会接收,如果不知道绑定那个端口,可以直接绑定0端口(或者使用UseUdpReceive),这样,就会使用系统空闲的一个端口了。 + +::: + +## 六、发送数据 + +框架内置很多发送发送。但是大致可分为两种。一种是带参数`EndPoint`,和一种不带的。 + +### 6.1 发送到指定地址 + +带`EndPoint`,即表示可以发送到任意终结点。 + +```csharp showLineNumbers +public virtual void SendAsync(EndPoint remoteEP, byte[] buffer, int offset, int length) +``` + +### 6.2 发送到接收数据的地址 + +一般的,当udp收到数据时,都能获取到`UdpReceivedDataEventArgs`参数。然后通过该参数,能获取到接收数据的`EndPoint`。然后向这个终结点发送数据。发送方就能收到数据。 + +例如: + +```csharp {5} showLineNumbers +class MyPluginClass : PluginBase, IUdpReceivedPlugin +{ + public async Task OnUdpReceived(IUdpSession client, UdpReceivedDataEventArgs e) + { + await client.SendAsync(e.EndPoint, "RRQM"); + await e.InvokeNext(); + } +} +``` + +### 6.3 发送到默认地址 + +不带`EndPoint`,即表示直接发送到默认(通过`SetRemoteIPHost`设置)的终结点。这一般在Udp作为客户端时是有用的。 + +```csharp showLineNumbers +public virtual void SendAsync(byte[] buffer, int offset, int length) +``` + + + +## 七、接收数据 + +### 7.1 委托接收 + +委托接收,则是直接订阅Received即可。 + +```csharp showLineNumbers +var udpService = new UdpSession(); +udpService.Received = (c, e) => +{ + Console.WriteLine(e.ByteBlock.ToString()); + return EasyTask.CompletedTask; +}; +udpService.Start(7789); +``` + +### 7.2 插件接收 推荐 + +使用插件接收,可以很好的分离业务。 + +声明插件 + +```csharp showLineNumbers +class MyPluginClass1 : PluginBase, IUdpReceivedPlugin +{ + public async Task OnUdpReceived(IUdpSession client, UdpReceivedDataEventArgs e) + { + var msg = e.ByteBlock.ToString(); + if (msg == "hello") + { + Console.WriteLine("已处理Hello"); + } + else + { + //如果判断逻辑发现此处无法处理,即可转到下一个插件 + await e.InvokeNext(); + } + } +} + +class MyPluginClass2 : PluginBase, IUdpReceivedPlugin +{ + public async Task OnUdpReceived(IUdpSession client, UdpReceivedDataEventArgs e) + { + var msg = e.ByteBlock.ToString(); + if (msg == "hi") + { + Console.WriteLine("已处理Hi"); + } + else + { + //如果判断逻辑发现此处无法处理,即可转到下一个插件 + await e.InvokeNext(); + } + } +} +``` + +使用插件 + +```csharp showLineNumbers {6-7} +var udpService = new UdpSession(); +udpService.Setup(new TouchSocketConfig() + .SetBindIPHost(new IPHost(7789)) + .ConfigurePlugins(a => + { + a.Add(); + a.Add(); + })); +udpService.Start(); +``` + +## 八、本文示例Demo + + diff --git a/handbook/docs/udptransmitbigdata.mdx b/handbook/versioned_docs/version-3.1/udptransmitbigdata.mdx similarity index 100% rename from handbook/docs/udptransmitbigdata.mdx rename to handbook/versioned_docs/version-3.1/udptransmitbigdata.mdx diff --git a/handbook/docs/udpwaitingclient.mdx b/handbook/versioned_docs/version-3.1/udpwaitingclient.mdx similarity index 100% rename from handbook/docs/udpwaitingclient.mdx rename to handbook/versioned_docs/version-3.1/udpwaitingclient.mdx diff --git a/handbook/versioned_docs/version-3.1/video.mdx b/handbook/versioned_docs/version-3.1/video.mdx new file mode 100644 index 000000000..697849e06 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/video.mdx @@ -0,0 +1,96 @@ +--- +id: video +title: 视频教学 +--- + +## 🚀【零基础逆袭】B站爆款课程《C# TouchSocket!零基础网络通信入门》重磅来袭!助你成为领域大神!🎉 + +✨ **为什么选择这门课?看这里!** ✨ + +🔥 **课程5大核心优势** 🔥 + +1️⃣ 🎁 **保姆级教学**:从"青铜"到"王者"的全路径拆解,小白也能轻松上手! + +2️⃣ 💻 **实战为王**:真实案例拆解 + 即学即用模板,学完就能做🐂🐎! + +3️⃣ 🧠 **思维升级**:独创「三维知识地图」教学法,打通你的任督二脉! + +4️⃣ 🎧 **沉浸体验**:4K超清录制 + 趣味动画演示,学习就像追番一样爽! + +--- + +🎯 **适合这样的你** 👇 + +▫️ 想转行却找不到突破口的职场新人 🧑💼 + +▫️ 渴望发展第二收入的斜杠青年 💸 + +▫️ 在校想提前储备硬技能的大学生 🎓 + +▫️ 任何不甘平庸的终身学习者 🌟 + +--- + +🌈 **学习大礼包** 🎁 + +✅ 价值999元的行业资源工具箱 🔧 + +✅ 独家学员交流社区VIP权限 👑 + +✅ QQ群答疑 + 直播答疑 📝 + +--- +⏳ **限时福利(以下活动截止2025.6.22)** 🚨 + +✨TouchSocket VIP感恩回馈活动✨ + +🎁【已授权客户专属】 + +截至**2025.6.22**凡购买过Pro版(个人/企业)的新老朋友 +🎉立享课程5折(实际为100¥)返现优惠! + +💎【打赏达人专属】 + +**2025.5.1**前打赏过的朋友看这里! +📸凭打赏截图联系若汝棋茗(QQ505554090) +💰双倍金额抵扣!上限100元! +(例:累计打赏10元→购课减20元) + +🚀【开源贡献者专属】 + +**2025.5.1**前提交过PR的开发者大大请注意! +📎已合并的代码/文档PR均可 +✨每次贡献兑换20元优惠!上限100元! +(例:合并2次→购课减40元) + +🐛【Bug猎手专属】 + +**2025.5.1**前提交过框架Bug的小伙伴看过来! +🔗已证实的Bug类Issue均可(必须提交过issue,不限git平台) +💥每次Issue兑换10元优惠!上限100元! +(例:提交2次→购课减20元) + + +📢【活动规则】 + + 1. 所有优惠可叠加使用,总上限100元 + 2. 优惠凭证仅限本人使用,禁止转让 + + +💸【返现/优惠操作指南】 + + 1. 先到B站课堂下单:https://www.bilibili.com/cheese/play/ss489296905 + 2. 保留购课截图+对应凭证(打赏/PR/Issue) + 3. 添加QQ505554090提交材料 + 4. **次日**18点后核对订单,无误后立即返现 + 5. 支持微信/支付宝收款码 + +💖感谢每一位伙伴的支持与陪伴! +如有疑问可随时联系客服哦~ (๑•̀ㅂ•́)و✧ + +--- + +📱 **马上行动** 👇 +点击课程 👉 [立即抢位](https://www.bilibili.com/cheese/play/ss489296905) + +🎯 **今日投资自己,明日惊艳所有人!** 💪 \ No newline at end of file diff --git a/handbook/versioned_docs/version-3.1/waitingclient.mdx b/handbook/versioned_docs/version-3.1/waitingclient.mdx new file mode 100644 index 000000000..16e5e4e0d --- /dev/null +++ b/handbook/versioned_docs/version-3.1/waitingclient.mdx @@ -0,0 +1,313 @@ +--- +id: waitingclient +title: 同步请求 +--- + +import CardLink from "@site/src/components/CardLink.js"; + +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import { TouchSocketDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +有很多小伙伴一直有一些需求: + +1. 客户端发送一个数据,然后等待服务器回应。 +2. 服务器向客户端发送一个数据,然后等待客户端回应。 + +那针对这些需求,可以使用`WaitingClient`其内部实现了`IWaitSender`接口,能够在发送完成后,直接等待返回。 + +:::tip 提示 + +`WaitingClient`是一种发送-响应机制,其原理是`IReceiverClient`,只要实现该接口的组件均可以使用。 + +例如:`TcpClient`、`TcpService`、`NamedPipeClient`、`NamedPipeService`、`SerialPortClient`等。 + + + +## 二、在客户端使用 + +在客户端工作时,支持很多组件,例如:`TcpClient`、`NamedPipeClient`、`SerialPortClient`,下面仅以`TcpClient`为例。 + +```csharp showLineNumbers +var client = new TcpClient(); +await client.ConnectAsync("tcp://127.0.0.1:7789"); + +//调用CreateWaitingClient获取到IWaitingClient的对象。 +var waitClient = client.CreateWaitingClient(new WaitingOptions() +{ + FilterFunc = response => //设置用于筛选的fun委托,当返回为true时,才会响应返回 + { + return true; + } +}); + +//然后使用SendThenReturn。 +byte[] returnData = await waitClient.SendThenReturnAsync(Encoding.UTF8.GetBytes("RRQM")); +Console.WriteLine($"收到回应消息:{Encoding.UTF8.GetString(returnData)}"); + +//同时,如果适配器收到数据后,返回的并不是字节,而是IRequestInfo对象时,可以使用SendThenResponse. +ResponsedData responsedData = await waitClient.SendThenResponseAsync(Encoding.UTF8.GetBytes("RRQM")); +IRequestInfo requestInfo = responsedData.RequestInfo;//同步收到的RequestInfo +``` + +## 三、在服务器使用 + +同理,在客户端工作时,支持很多组件,例如:`TcpService`、`NamedPipeService`,下面仅以`TcpService`为例。 + +```csharp showLineNumbers +var service = new TcpService(); +await service.StartAsync(7789);//启动服务器 + +//在服务器中,找到指定Id的会话客户端 +if (service.TryGetClient("targetId", out var tcpSessionClient)) +{ + //调用CreateWaitingClient获取到IWaitingClient的对象。 + var waitClient = tcpSessionClient.CreateWaitingClient(new WaitingOptions() + { + FilterFunc = response => //设置用于筛选的fun委托,当返回为true时,才会响应返回 + { + return true; + } + }); + + //然后使用SendThenReturn。 + byte[] returnData = await waitClient.SendThenReturnAsync(Encoding.UTF8.GetBytes("RRQM")); + Console.WriteLine($"收到回应消息:{Encoding.UTF8.GetString(returnData)}"); + + //同时,如果适配器收到数据后,返回的并不是字节,而是IRequestInfo对象时,可以使用SendThenResponse. + ResponsedData responsedData = await waitClient.SendThenResponseAsync(Encoding.UTF8.GetBytes("RRQM")); + IRequestInfo responseRequestInfo = responsedData.RequestInfo;//同步收到的RequestInfo +} +``` + +:::tip 提示 + +`WaitingClient`在创建以后,可以长久使用,直到原始的组件被`Dispose`释放。所以即使是断线重连后,也会是有效的。所以没必要在使用时每次都创建。 + +::: + +## 四、配置 + +### 4.1 超时配置 + +在默认情况下,超时时间是5秒。 + +```csharp showLineNumbers +await waitingClient.SendThenResponseAsync("hello");//默认5秒超时 +``` + +所以可以直接传参,设置超时时间。 + +```csharp showLineNumbers +await waitingClient.SendThenResponseAsync("hello", 1000*10);//设置10秒超时 +``` + +但是有时候,我们希望**不设置超时时间**,而是由用户自己控制超时时间,也就是能有**取消**的等待。 + + +```csharp showLineNumbers +var cts = new CancellationTokenSource(); + +_=Task.Run(async () => +{ + await Task.Delay(5000); + cts.Cancel();//5秒后取消等待,不再等待服务端的消息。这里模拟的是客户端主动取消等待 +}); + +await waitingClient.SendThenResponseAsync("hello", cts.Token); +``` + +### 4.2 筛选配置 + +筛选函数,用于筛选符合要求的数据。因为`WaitingClient`的响应机制是建立在**一问一答**的基础之上实现的,所以,在发送完数据后,可能收到之前的过期响应数据,那么这时候,会根据用户设置的筛选函数,判断是否响应。 + +在默认情况下,**筛选函数**为空,即**不筛选**。 + +```csharp {3} showLineNumbers +var waitingClient = client.CreateWaitingClient(new WaitingOptions() +{ + FilterFunc=default +}); +``` + +所以可以设置筛选函数。 + +```csharp {3-10} showLineNumbers +var waitingClient = client.CreateWaitingClient(new WaitingOptions() +{ + FilterFunc = response => //设置用于筛选的fun委托,当返回为true时,才会响应返回 + { + var requestInfo=response.RequestInfo; + var byteBlock=response.ByteBlock; + + //这里可以根据服务端返回的信息,判断是否响应 + return true; + } +}); +``` + +:::tip 异步筛选函数 + +可以设置异步筛选函数,可以返回一个`Task`,用于异步筛选。 + +```csharp +FilterFuncAsync=async response=>await Task.FromResult(true) +``` + +::: + +:::info 备注 + +筛选函数的参数是`Response`,它包含两个属性,分别为`RequestInfo`和`ByteBlock`,具体使用哪个属性,看[适配器类型](./adapterdescription.mdx) + +::: + +## 五、使用注意事项 + + + +### 5.1 线程安全 + +`WaitingClient`是线程安全的,可以多线程使用。但是实际上内部有`SemaphoreSlim`锁,所以,如果使用在多线程中,可能会造成大量锁竞争,从而降低效率。 + +### 5.2 关于ReadAsync + +`WaitingClient`的原理实际上是使用了`IReceiverClient`接口,这也就意味着它不能和`ReadAsync`同时使用。 + +### 5.3 关于筛选函数 + +在筛选函数`FilterFunc(Async)`中,不管返回`true`还是`false`,数据都不再向下传递。这也就意味着一旦使用`WaitingClient`,在其**等待返回的时段**中,即使收到了无效数据,也不会再触发`Receive`事件(或者相关插件)。 + +如果不在等待时段,则不受影响。 + +如果想要实现,当筛选函数返回`false`时,将数据从插件再次传递,那么就需要在筛选函数中,自行触发插件。 + +例如: + +```csharp {18} showLineNumbers +var waitingClient = this.m_tcpClient.CreateWaitingClient(new WaitingOptions() +{ + FilterFuncAsync = async (response) => + { + var byteBlock = response.ByteBlock; + var requestInfo = response.RequestInfo; + + if (true)//如果满足某个条件,则响应WaitingClient + { + return true; + } + else + { + //否则 + //数据不符合要求,waitingClient继续等待 + //如果需要在插件中继续处理,在此处触发插件 + + await this.m_tcpClient.PluginManager.RaiseAsync(typeof(ITcpReceivedPlugin), this.m_tcpClient, new ReceivedDataEventArgs(byteBlock, requestInfo)); + + return false; + } + } +}); +``` + +### 5.4 关于SendThenReturn与SendThenReturnAsync + +在`WaitingClient`中,所有的同步方法,其实都是异步转换来的,所以,功能一致。但是强烈建议如有可能,请**务必使用异步发送来提高效率**。 + +:::caution 注意 + +在主线程是`GUI线程`(例如:`winform`、`wpf`等),如果在主线程中调用同步代码,也可能会导致死锁。所以请务必使用异步代码。 + +::: + +### 5.5 关于使用时机 + +`WaitingClient`的机制是发送一个数据,然后等待响应,所以,使用时机,绝对不可以在`Received`事件(或者插件)中调用。这将导致死锁。 + +例如: + +```csharp {8} showLineNumbers +var tcpClient = new TcpClient(); + +var tcpClient.Received =async (client,e) => +{ + var waitingClient = client.CreateWaitingClient(new WaitingOptions()); + + //这里将导致死锁 + var bytes = await waitingClient.SendThenReturnAsync("hello"); +}; + +... +``` + +如果确实需要使用,请使用`Task.Run`来异步处理。 + +例如: + +```csharp {4-9} showLineNumbers +this.m_tcpClient.Received =async (client,e) => +{ + //此处不能await,否则也会导致死锁 + _ = Task.Run(async () => + { + var waitingClient = client.CreateWaitingClient(new WaitingOptions()); + + var bytes = await waitingClient.SendThenReturnAsync("hello"); + }); +}; + +... +``` + + +### 5.6 其他 + +:::danger 注意事项 + +1. 发送完数据,在等待时,如果收到其他返回数据,则可能得到错误结果。 +2. 发送采用同步锁,一个事务没结束,另一个请求也发不出去。 +3. waitClient的使用**不可以**直接在`Received`相关触发中使用,因为必然会导致死锁,详见:[#I9GCGT](https://gitee.com/RRQM_Home/TouchSocket/issues/I9GCGT) 。 +4. 在**Net461及以下**版本中,SendThenReturn与SendThenReturnAsync不能混合使用。即:要么全同步,要么全异步(这可能是.net bug)。 + +::: + +## 六、高级实现方式 + +使用`WaitingClient`的方式虽然简单通用,但是其实质并不建议在要求较高的环境中使用,尤其是对“请求-响应”要求较高的时候。 + +一般来说,使用`WaitingClient`的不可靠性来源于2个方面: +1. 响应机制是依赖`Reveiver`来实现的,会在请求时创建`Reveiver`,在响应时,会销毁`Reveiver`。这其中可能导致数据丢失。 +2. 响应数据没办法和请求进行关联,导致数据可能被其他数据打断。 + + + +那么基于此,如果您有更高要求的需求,我们建议使用以下方法实现: + +### 6.1 设计通信协议,以支持响应数据关联 + +1. 在请求时,请求数据中添加一个`requestId`,用于标识请求。 +2. 在响应时,响应数据中添加一个`requestId`,用于标识响应。 +3. 在响应时,将响应数据发送给对应的请求方。 +4. 在请求方收到响应数据时,将响应数据与对应的请求进行关联。 +5. 在响应方收到请求数据时,将请求数据发送给对应的响应方。 +6. 在响应方收到响应数据时,将响应数据发送给对应的请求方。 + + + +### 6.2 长期持有Reveiver,以建立相较稳固的接收 + + + +### 6.3 使用继承的方式实现 + + + +## 七、示例代码 + + \ No newline at end of file diff --git a/handbook/versioned_docs/version-3.1/webapi.mdx b/handbook/versioned_docs/version-3.1/webapi.mdx new file mode 100644 index 000000000..4d83b7d46 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/webapi.mdx @@ -0,0 +1,765 @@ +--- +id: webapi +title: 产品及架构介绍 +--- + +import CardLink from "@site/src/components/CardLink.js"; +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import { TouchSocketWebApiDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +WebApi是**通用**的Rpc调用,与**编程语言无关**,与**操作系统无关**。其路由机制模仿AspNetCore,可实现很多路由机制。但是因为http兼容性错综复杂,所以目前TouchSocket的WebApi仅支持**GET**、**POST**函数。使用体验接近于AspNetCore。 + + + +## 二、特点 + +- 高性能,100个客户端,10w次调用,仅用时17s。 +- **全异常反馈** 。 +- 支持大部分路由规则。 +- 支持js、Android等调用。 + + +## 三、定义服务 + +在**服务器**端中新建一个类,继承于**SingletonRpcServer**类(或实现IRpcServer),然后在该类中写**公共方法**,并用**WebApi**属性标签标记。 + +```csharp showLineNumbers +public partial class ApiServer : SingletonRpcServer +{ + private readonly ILog m_logger; + + public ApiServer(ILog logger) + { + this.m_logger = logger; + } + + [WebApi(Method = HttpMethodType.Get)] + public int Sum(int a, int b) + { + return a + b; + } +} +``` + +## 四、启动服务器 + +更多注册Rpc的方法请看[注册Rpc服务](./rpcregister.mdx) + +```csharp showLineNumbers +var service = new HttpService(); +await service.SetupAsync(new TouchSocketConfig() + .SetListenIPHosts(7789) + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + a.AddRpcStore(store => + { + store.RegisterServer();//注册服务 + }); + }) + .ConfigurePlugins(a => + { + a.UseCheckClear(); + + a.UseWebApi(); + + //此插件是http的兜底插件,应该最后添加。作用是当所有路由不匹配时返回404.且内部也会处理Option请求。可以更好的处理来自浏览器的跨域探测。 + a.UseDefaultHttpServicePlugin(); + })); +await service.StartAsync(); + +Console.WriteLine("以下连接用于测试webApi"); +Console.WriteLine($"使用:http://127.0.0.1:7789/ApiServer/Sum?a=10&b=20"); +``` + + + + + + +## 五、参数规则 + +### 5.1 Get规则 + +使用`Get`进行请求时,服务方法可以声明多个参数,但是每个参数都必须是**简单类型**(例如:int、string、DateTime等)。 + +```csharp showLineNumbers +[WebApi(Method = HttpMethodType.Get)] +public int Get(int a) +{ + return a; +} + +[WebApi(Method = HttpMethodType.Get)] +public int Sum(int a, int b) +{ + return a + b; +} +``` + +### 5.2 Post规则 + +使用`Post`进行请求时,服务方法可以声明多个参数,但是当参数是基础类型或者字符串类型时,它也会来源于`Query`参数。 +同时,有且只有当最后一个参数为**其他类型**时,才会从`Body`解析。 + +例如: + +以下参数依然来自`Query`,`Body`为空也可以。 + +```csharp showLineNumbers +[WebApi(Method = HttpMethodType.Post)] +public int Sum(int a, int b) +{ + return a + b; +} +``` + +当最后一个参数为**其他类型**时,它将会从`Body`解析。 + +例如: + +以下参数,前两个来自`Query`,`MyClass`将从`Body`解析。 + +```csharp showLineNumbers +[WebApi(Method = HttpMethodType.Post)] +public int Sum(int a, int b, MyClass myClass) +{ + return a + b; +} +``` + + + +### 5.3 特性规则 + +`WebApi`支持使用特性来定制参数规则。目前支持`[FromQuery]`、`[FromHeader]`、`[FromForm]`、`[FromBody]`等。 + +例如: + +[FromQuery] + +```csharp showLineNumbers +[WebApi(Method = HttpMethodType.Get)] +public int SumFromQuery([FromQuery] int a, [FromQuery] int b) +{ + return a + b; +} +``` + +[FromHeader] + +```csharp showLineNumbers +[WebApi(Method = HttpMethodType.Get)] +public int SumFromHeader([FromHeader] int a, [FromHeader] int b) +{ + return a + b; +} +``` + +[FromForm] + +```csharp showLineNumbers +[WebApi(Method = HttpMethodType.Post)] +public int SumFromForm([FromForm] int a, [FromForm] int b) +{ + return a + b; +} +``` + +[FromBody] + +```csharp showLineNumbers +[WebApi(Method = HttpMethodType.Post)] +public int SumFromBody([FromBody] MyClass myClass) +{ + return myClass.A + myClass.B; +} +``` + + + +:::tip 提示 + +当使用特性时,可以重新指定参数名,例如: + +```csharp showLineNumbers +[WebApi(Method = HttpMethodType.Get)] +public int SumFromQuery([FromQuery(Name ="aa")] int a, [FromQuery] int b) +{ + return a + b; +} +``` + +::: + +## 六、路由规则 + +框架的路由规则比较简单,默认情况下,以服务的名称+方法名称作为路由。 + +例如下列: + +将会以`/ApiServer/Sum`为请求url(不区分大小写)。 + +```csharp showLineNumbers +public class ApiServer : SingletonRpcServer +{ + [WebApi(Method = HttpMethodType.Get)] + public int Sum(int a, int b) + { + return a + b; + } +} +``` + +当需要定制路由消息时,可用`[api]`替代服务名,`[action]`替代方法名。 + +例如下列: + +将会以`user/ApiServer/test/Sum`为请求url(不区分大小写)。 + +```csharp showLineNumbers +[Router("/user/[api]/test/[action]")] +public class ApiServer : SingletonRpcServer +{ + [WebApi(Method = HttpMethodType.Get)] + public int Sum(int a, int b) + { + return a + b; + } +} +``` + + + +:::tip 提示 + +`Router`特性不仅可以用于服务,也可以用于方法。而且可以多个使用。 + +::: + +## 七、调用上下文 + +框架中,每个请求都会产生一个**调用上下文(ICallContext)**,这个上下文可以获取到当前请求的**客户端**、**服务**、**方法**、**参数**等。 + +通用调用上下文可以参阅[通用Rpc调用上下文](./rpcallcontext.mdx)。 + +下列将介绍`WebApi`的专用调用上下文。 + +首先,在服务方法中,只要有参数类型为`IWebApiCallContext`(或者`ICallContext`),框架会自动注入当前调用上下文。 + +例如: + +```csharp {2} showLineNumbers +[WebApi(Method = HttpMethodType.Get)] +public int SumCallContext(IWebApiCallContext callContext, int a, int b) +{ + return a + b; +} +``` + +:::tip 提示 + +调用上下文的位置没有限制,但是建议在方法参数的**最前面**,这样即使对`Post`,也不会有额外的判断压力。 + +::: + + + + + +### 7.1 获取当前客户端信息 + +一般来说,调用上下文的`Caller`就是实际通信的客户端,也就是`HttpSessionClient`。 + +```csharp showLineNumbers +if (callContext.Caller is IHttpSessionClient httpSessionClient) +{ + Console.WriteLine($"IP:{httpSessionClient.IP}"); + Console.WriteLine($"Port:{httpSessionClient.Port}"); + Console.WriteLine($"Id:{httpSessionClient.Id}"); +} +``` + +### 7.2 获取HttpContext + +`WebApi`的调用上下文,除了`Caller`,还有`HttpContext`。 + +```csharp showLineNumbers +//http内容 +var httpContext = callContext.HttpContext; + +//http请求 +var request = httpContext.Request; +//http响应 +var response = httpContext.Response; +``` + +:::tip 提示 + +当获取到`HttpContext`后,就可以做`HttpService`的所有功能。例如:读取请求头,修改响应头,修改响应内容,修改响应状态码、响应WebSocket连接等。具体可以参考[创建HttpService](./httpservice.mdx)和[WebSocketService](./websocketservice.mdx)。 + +::: + +## 八、调用服务 + +### 8.1 直接调用 + +直接调用,则是不使用**任何代理**,直接Call Rpc,使用比较简单,**浏览器**也能直接调用实现。 + +【Url请求】 + +```scheme +http://127.0.0.1:7789/ApiServer/Sum?a=10&b=20 +``` + +### 8.2 框架HttpClient调用 + +框架内置的`HttpClient`,可以参考[HttpClient](./httpclient.mdx)的使用,此处做一个简单使用示例。 + +```csharp {4} showLineNumbers +var client = new HttpClient(); +await client.ConnectAsync("127.0.0.1:7789"); + +string responseString = await client.GetStringAsync("/ApiServer/Sum?a=10&b=20"); +``` + +### 8.3 内置WebApiClient调用 + +内置`WebApi`的客户端和`HttpClient`基本一致,但是封装了一些`Rpc`的调用接口,可以更加方便的执行一些操作。 + +【创建WebApi客户端】 + +```csharp {8} showLineNumbers +var client = new WebApiClient(); +await client.SetupAsync(new TouchSocketConfig() + .SetRemoteIPHost("127.0.0.1:7789") + .ConfigurePlugins(a => + { + a.Add(); + })); +await client.ConnectAsync(); +``` + +【GET调用】 + +在使用`GET`进行调用时,其`InvokeKey`直接使用Url即可,然后还需要构建一个`WebApiRequest`,然后使用`Invoke`(或者`InvokeT`)进行调用。 + +例如: + +```csharp showLineNumbers +var request = new WebApiRequest(); +request.Method = HttpMethodType.Get; +request.Querys = new KeyValuePair[] { new KeyValuePair("a", "10"), new KeyValuePair("b", "20") }; + +var sum1 = client.InvokeT("/ApiServer/Sum", invokeOption_30s, request); +Console.WriteLine($"Get调用成功,结果:{sum1}"); +``` + +【POST调用】 + +例如: + +```csharp showLineNumbers +var requestForPost = new WebApiRequest(); +requestForPost.Method = HttpMethodType.Post; +requestForPost.Body = new MyClass() { A = 10, B = 20 }; + +var sum2 = client.InvokeT("/ApiServer/TestPost", invokeOption_30s, requestForPost); +Console.WriteLine($"Post调用成功,结果:{sum2}"); +``` + +### 8.4 Dotnet自带HttpClient调用 + +`Dotnet`自带`HttpClient`则是通过连接池的方式访问。详情看[HttpClient](https://learn.microsoft.com/zh-cn/dotnet/api/system.net.http.httpclient) + +【创建客户端】 + +```csharp showLineNumbers +var client = new WebApiClientSlim(new System.Net.Http.HttpClient()); +await client.SetupAsync(new TouchSocketConfig() + .SetRemoteIPHost("http://127.0.0.1:7789")); +``` + +调用方法同上。 + +:::info 备注 + +按照微软建议,`HttpClient`应该保证整个程序中单实例使用,所以可以在`WebApiClientSlim`构造函数中传入已存在的对象。 + +::: + +:::caution 注意 + +`WebApiClientSlim`仅在net6.0+,net481可用。 + +::: + + +### 8.5 生成代理调用 + +使用`WebApi`客户端进行调用时,其使用规则相较比较复杂的。但是在实际使用时,基本上是不需要手动书写调用代码的。下面将介绍代理生成调用。 + +在服务器端,注册完服务后,就可以生成客户端调用代码了。详细的操作可以查看[服务端代理生成](./rpcgenerateproxy.mdx) + +```csharp {8-9} +a.UseWebApi() +.ConfigureRpcStore(store => +{ + store.RegisterServer();//注册服务 + +#if DEBUG + //下列代码,会生成客户端的调用代码。 + var codeString = store.GetProxyCodes("WebApiProxy", typeof(WebApiAttribute)); + File.WriteAllText("../../../WebApiProxy.cs", codeString); +#endif +}); +``` + +然后把生成的.cs文件复制(或链接)到客户端项目。然后客户端直接使用同名`扩展方法`即可调用。 + +```csharp showLineNumbers +var sum3 =await client.SumAsync(10,20); +``` + + + +### 8.6 使用DispatchProxy代理调用 + +使用DispatchProxy代理调用,可以实现动态代理,详情请看[DispatchProxy代理生成](./rpcgenerateproxy.mdx) + +首先,需要声明一个基类,用于通讯基础。 + +```csharp showLineNumbers +/// +/// 新建一个类,继承WebApiDispatchProxy,亦或者RpcDispatchProxy基类。 +/// 然后实现抽象方法,主要是能获取到调用的IRpcClient派生接口。 +/// +class MyWebApiDispatchProxy : WebApiDispatchProxy +{ + private readonly WebApiClient m_client; + + public MyWebApiDispatchProxy() + { + this.m_client = CreateWebApiClient(); + } + + private static WebApiClient CreateWebApiClient() + { + var client = new WebApiClient(); + await client.SetupAsync(new TouchSocketConfig() + .SetRemoteIPHost("127.0.0.1:7789") + .ConfigurePlugins(a => + { + a.UseReconnection(); + })); + await client.ConnectAsync(); + Console.WriteLine("连接成功"); + return client; + } + + public override IWebApiClientBase GetClient() + { + return m_client; + } +} +``` + +然后按照服务,定义一个相同的代理接口。 + +```csharp showLineNumbers +interface IApiServer +{ + [Router("ApiServer/[action]")] + [WebApi(Method = HttpMethodType.Get)] + int Sum(int a, int b); +} +``` + +:::tip 提示 + +路由规则和服务端相同。 + +::: + +最后生成代理,并按照接口调用。 + +```csharp {1} +IApiServer api = MyWebApiDispatchProxy.Create(); +while (true) +{ + Console.WriteLine("请输入两个数,中间用空格隔开,回车确认"); + string str = Console.ReadLine(); + var strs = str.Split(' '); + int a = int.Parse(strs[0]); + int b = int.Parse(strs[1]); + + var sum = api.Sum(a, b); + Console.WriteLine(sum); +} +``` + +:::tip + +`WebApi`的调用,除了上述,还支持源生成调用,更多请看[源生成调用](./rpcgenerateproxy.mdx)。 + +::: + + +## 九、数据格式化 + +数据格式化,就是对`WebApi`执行前后的数据进行序列化和反序列化。一般来说,常用的格式化类型有两种,一种是`JSON`,另一种是`XML`。具体的,应该根据`Accept`请求头中的数据格式来选择。默认情况下: + +- 如果Accept请求头中包含`application/json`、`text/json`,则使用JSON格式化。 +- 如果Accept请求头中包含`application/xml`、`text/xml`,则使用XML格式化。 +- 如果Accept请求头中包含`text/plain`,则使用文本格式化(如果是复杂类型,则依然会按照Json或则Xml)。 + +### 9.1 格式化配置 + +在添加`WebApi`插件时,可以通过`ConfigureConverter`方法来配置数据格式化。 + +```csharp {4} showLineNumbers +.ConfigurePlugins(a => +{ + a.UseWebApi() + .ConfigureConverter(converter => + { + //配置转换器 + + //converter.Clear();//可以选择性的清空现有所有格式化器 + + //添加Json格式化器,可以自定义Json的一些设置 + converter.AddJsonSerializerFormatter(new Newtonsoft.Json.JsonSerializerSettings() {Formatting= Newtonsoft.Json.Formatting.None } ); + + //添加Xml格式化器 + converter.AddXmlSerializerFormatter(); + }); +}) +``` + +### 9.2 自定义格式化器 + +TouchSocket的`WebApi`插件支持自定义格式化器。 + +新建一个类,实现`ISerializerFormatter`接口,并实现相关方法。 + +```csharp showLineNumbers +class MySerializerFormatter : ISerializerFormatter +{ + public int Order { get; set; } + + public bool TryDeserialize(HttpContext state, in string source, Type targetType, out object target) + { + //反序列化 + throw new NotImplementedException(); + } + + public bool TrySerialize(HttpContext state, in object target, out string source) + { + //序列化 + throw new NotImplementedException(); + } +} +``` + +添加格式化器 + +```csharp {6} showLineNumbers +.ConfigurePlugins(a => +{ + a.UseWebApi() + .ConfigureConverter(converter => + { + converter.Add(new MySerializerFormatter()); + }); +}) +``` + +## 十、鉴权、授权 + +### 10.1 请求插件实现 + +在AspNetCore中,鉴权与授权是通过中间件实现的。而TouchSocket的WebApi(HttpService)在设计时也可以使用类似方式实现该功能。下列就以伪代码jwt鉴权示例。 + +首先声明一个鉴权插件。用于判断当前请求header中是否包含授权header。 + +```csharp showLineNumbers +/// +/// 鉴权插件 +/// +class AuthenticationPlugin : PluginBase, IHttpPlugin +{ + public async Task OnHttpRequest(IHttpSessionClient client, HttpContextEventArgs e) + { + string aut = e.Context.Request.Headers["Authorization"]; + if (aut.IsNullOrEmpty())//授权header为空 + { + await e.Context.Response + .SetStatus(401, "授权失败") + .AnswerAsync(); + return; + } + + //伪代码,假设使用jwt解码成功。那就执行下一个插件。 + //if (jwt.Encode(aut)) + //{ + // 此处可以做一些授权相关的。 + //} + await e.InvokeNext(); + } +} +``` + +然后添加使用插件即可。 + +```csharp showLineNumbers +.ConfigurePlugins(a => +{ + a.UseCheckClear(); + + a.Add(); + + a.UseWebApi(); + + ... +}) +``` + +:::caution 注意 + +鉴权插件的添加,应该在UseWebApi之前。这样才能保证api的安全性。 + +::: + + +### 10.2 Rpc Aop实现 + +WebApi也属于Rpc的行列,所以在执行时,也可以在Rpc的Aop中实现鉴权。具体请看[Rpc服务AOP](./rpcactionfilter.mdx) + + +## 十一、跨域 + +在`WebApi`中的跨域,除了[Cors跨域](./cors.mdx)全局设置之外,还支持特性设置,进行更细粒度的控制。 + +所以,首先添加跨域服务是必须的。 + +```csharp {4} showLineNumbers +.ConfigureContainer(a => +{ + //添加跨域服务 + a.AddCors(corsOption => + { + //添加跨域策略,后续使用policyName即可应用跨域策略。 + corsOption.Add("cors", corsBuilder => + { + corsBuilder.AllowAnyMethod() + .AllowAnyOrigin(); + }); + }); +}) +``` + +然后,在WebApi中使用特性进行跨域设置。 + +```csharp {3} showLineNumbers +public partial class ApiServer : SingletonRpcServer +{ + [EnableCors("cors")]//使用跨域 + [WebApi(Method = HttpMethodType.Get)] + public int Sum(int a, int b) + { + return a + b; + } +} +``` + +:::tip 提示 + +`EnableCors`特性,不仅可以用于方法,还支持服务类,接口直接使用。 + +::: + +## 十二、AOT相关 + +`WebApi`组件目前已**完全支持**使用AOT进行编译。具体使用步骤如下: + +首先,使用通用主机模型进行新建项目。详情查看[通用主机模型](./generichost.mdx)。 + +然后基本代码如下: + +```csharp showLineNumbers +var builder = Host.CreateApplicationBuilder(args); + +builder.Services.ConfigureContainer(a => +{ + a.AddRpcStore(store => + { + store.RegisterServer();//注册服务 + }); + + a.AddAspNetCoreLogger(); +}); + +builder.Services.AddServiceHostedService(config => +{ + config.SetListenIPHosts(7789) + .ConfigurePlugins(a => + { + a.UseCheckClear(); + + a.UseWebApi(); + }); +}); + +var host = builder.Build(); +host.Run(); +``` + +然后需要解决支持Aot的序列化问题。目前仅支持`System.Text.Json`。所以需要新建一个序列化上下文。然后把需要序列化的类型添加到`JsonSerializable`中。 + +```csharp showLineNumbers +[JsonSerializable(typeof(MyClass))]//实际类型1 +[JsonSerializable(typeof(MySum))]//实际类型2 +internal partial class AppJsonSerializerContext : JsonSerializerContext +{ + +} +``` + +:::info 信息 + +`JsonSerializable`中,可以添加多个类型。具体要添加哪些类型要看你服务中端需要序列化的类型。**一般来说需要包含服务方法中的所有参数类型和返回值类型**。 + +::: + +然后使用序列化上下文。 + +```csharp {5-12} showLineNumbers +.ConfigurePlugins(a => +{ + ... + a.UseWebApi() + .ConfigureConverter(converter => + { + converter.Clear(); + converter.AddSystemTextJsonSerializerFormatter(options => + { + options.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default); + }); + }); +}) +``` + + + +## 十三、本文示例Demo + + + + diff --git a/handbook/docs/webdataforwarding.mdx b/handbook/versioned_docs/version-3.1/webdataforwarding.mdx similarity index 100% rename from handbook/docs/webdataforwarding.mdx rename to handbook/versioned_docs/version-3.1/webdataforwarding.mdx diff --git a/handbook/versioned_docs/version-3.1/websocketclient.mdx b/handbook/versioned_docs/version-3.1/websocketclient.mdx new file mode 100644 index 000000000..05772290c --- /dev/null +++ b/handbook/versioned_docs/version-3.1/websocketclient.mdx @@ -0,0 +1,676 @@ +--- +id: websocketclient +title: 创建WebSocket客户端 +--- + +import Tag from "@site/src/components/Tag.js"; +import CardLink from "@site/src/components/CardLink.js"; +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import { TouchSocketHttpDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、可配置项 + +继承[HttpClient](./httpclient.mdx) + +## 二、支持插件接口 + +| 插件方法| 功能 | +| --- | --- | +| IWebSocketHandshakingPlugin | 当收到握手请求之前,可以进行连接验证等 | +| IWebSocketHandshakedPlugin | 当成功握手响应之后 | +| IWebSocketReceivedPlugin | 当收到Websocket的数据报文 | +| IWebSocketClosingPlugin | 当收到关闭请求时,如果对方直接断开连接,此方法则不会触发。 | +| IWebSocketClosedPlugin | 当WebSocket连接断开时触发,无论是否正常断开。但如果是断网等操作,可能不会立即执行,需要结合心跳操作和`UseCheckClear`插件来进行清理。 | + +## 三、创建客户端 + +### 3.1 创建常规客户端 + +```csharp showLineNumbers +var client = new WebSocketClient(); +await client.SetupAsync(new TouchSocketConfig() + .SetRemoteIPHost("ws://127.0.0.1:7789/ws") + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + })); +await client.ConnectAsync(); +client.Logger.Info("连接成功"); +``` + +### 3.2 创建WSs客户端 + +**当需要连接到由证书机构颁发的网址(例如:小程序、物联网等)时,仅需要设置带有`wss`的url即可。** + +```csharp showLineNumbers +wss://127.0.0.1:7789/ws +``` + +**当连接自定义证书的Ssl:wss://127.0.0.1:7789/ws** + +```csharp showLineNumbers +var client = new WebSocketClient(); + +await client.SetupAsync(new TouchSocketConfig() + .SetRemoteIPHost(new IPHost("wss://127.0.0.1:7789/ws")) + .SetClientSslOption( + new ClientSslOption() + { + ClientCertificates = new X509CertificateCollection() { new X509Certificate2("RRQMSocket.pfx", "RRQMSocket") }, + SslProtocols = SslProtocols.Tls12, + TargetHost = "127.0.0.1", + CertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => { return true; } + })); + +await client.ConnectAsync(); + +Console.WriteLine("连接成功"); +``` + +:::caution 注意 + +当使用域名连接时,`TargetHost`为域名,例如连接到`IPHost("wss://baidu.com")`时,`TargetHost`应当填写:`baidu.com` + +::: + +## 四、连接服务器 + +`WebSocketClient`可以使用默认配置直接连接到服务器,同时也支持使用多种方法定义连接。 + +### 4.1 直接连接 + +使用`url`直接建立连接,这一般是服务器也只是普通的`ws`服务器的情况下。 + +```csharp showLineNumbers +var client = new WebSocketClient(); +await client.SetupAsync(new TouchSocketConfig() + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .SetRemoteIPHost("ws://127.0.0.1:7789/ws")); +await client.ConnectAsync(); + +client.Logger.Info("通过ws://127.0.0.1:7789/ws连接成功"); +``` + +### 4.2 带Query参数连接 + +带`Query`参数连接,实际上还是通过`url`直接连接。 + +```csharp showLineNumbers +var client = new WebSocketClient(); +await client.SetupAsync(new TouchSocketConfig() + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .SetRemoteIPHost("ws://127.0.0.1:7789/wsquery?token=123456")); +await client.ConnectAsync(); + +client.Logger.Info("通过ws://127.0.0.1:7789/wsquery?token=123456连接成功"); + +``` + +### 4.3 使用特定Header连接 + +一般的,当某些服务器安全级别较高时,可能会定制特定的`header`用于验证连接。 + +```csharp showLineNumbers +var client = new WebSocketClient(); +await client.SetupAsync(new TouchSocketConfig() + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .ConfigurePlugins(a => + { + a.Add(typeof(IWebSocketHandshakingPlugin), async (IWebSocket client, HttpContextEventArgs e) => + { + e.Context.Request.Headers.Add("token", "123456"); + await e.InvokeNext(); + }); + }) + .SetRemoteIPHost("ws://127.0.0.1:7789/wsheader")); +await client.ConnectAsync(); + +client.Logger.Info("通过ws://127.0.0.1:7789/wsheader连接成功"); +``` + +:::tip 提示 + +实际上`OnWebSocketHandshaking`就是插件委托,也可以自己封装到插件使用。 + +::: + +### 4.4 使用Post方式连接 + +`WebSocket`默认情况下是基于`Get`方式连接的,但是在一些更特殊的情况下,需要以`Post`,甚至其他方式连接,那么可以使用以下方式实现。 + +```csharp showLineNumbers +using var client = new WebSocketClient(); +await client.SetupAsync(new TouchSocketConfig() + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .ConfigurePlugins(a => + { + a.Add(typeof(IWebSocketHandshakingPlugin), async (IWebSocket client, HttpContextEventArgs e) => + { + e.Context.Request.Method = HttpMethod.Post;//将请求方法改为Post + await e.InvokeNext(); + }); + }) + .SetRemoteIPHost("ws://127.0.0.1:7789/postws")); +await client.ConnectAsync(); + +client.Logger.Info("通过ws://127.0.0.1:7789/postws连接成功"); +``` + +:::tip 提示 + +使用此方式时,基本上就能完全定制请求连接了。比如一些`Cookie`等。 + +::: + + +## 五、发送数据 + +客户端定义了一些发送方法,方便开发者快速发送数据。 + + + +### 5.1 发送文本类消息 + +```csharp showLineNumbers +await client.SendAsync("Text"); +``` + +### 5.2 发送二进制消息 + +```csharp showLineNumbers +await client.SendAsync(new byte[10]); +``` + +### 5.3 直接发送自定义构建的数据帧 +```csharp showLineNumbers +using (var frame = new WSDataFrame()) +{ + frame.Opcode = WSDataType.Text; + frame.FIN = true; + frame.RSV1 = true; + frame.RSV2 = true; + frame.RSV3 = true; + frame.AppendText("I"); + frame.AppendText("Love"); + frame.AppendText("U"); + await client.SendAsync(frame); +} +``` +:::info 备注 + +此部分功能就需要你对`WebSocket`有充分了解才可以操作。 + +::: + +## 六、接收数据 + +### 6.1 订阅Received事件实现 + +```csharp showLineNumbers +client.Received = async (c, e) => +{ + switch (e.DataFrame.Opcode) + { + case WSDataType.Cont: + break; + case WSDataType.Text: + break; + case WSDataType.Binary: + break; + case WSDataType.Close: + break; + case WSDataType.Ping: + break; + case WSDataType.Pong: + break; + default: + break; + } + + await e.InvokeNext(); +}; +``` + +### 6.2 使用插件实现 推荐 + +【定义插件】 +```csharp showLineNumbers +public class MyWebSocketPlugin : PluginBase, IWebSocketReceivedPlugin +{ + private readonly ILog m_logger; + + public MyWebSocketPlugin(ILog logger) + { + this.m_logger = logger; + } + public async Task OnWebSocketReceived(IWebSocket client, WSDataFrameEventArgs e) + { + switch (e.DataFrame.Opcode) + { + case WSDataType.Cont: + m_logger.Info($"收到中间数据,长度为:{e.DataFrame.PayloadLength}"); + + return; + + case WSDataType.Text: + m_logger.Info(e.DataFrame.ToText()); + + if (!client.Client.IsClient) + { + client.SendAsync("我已收到"); + } + return; + + case WSDataType.Binary: + if (e.DataFrame.FIN) + { + m_logger.Info($"收到二进制数据,长度为:{e.DataFrame.PayloadLength}"); + } + else + { + m_logger.Info($"收到未结束的二进制数据,长度为:{e.DataFrame.PayloadLength}"); + } + return; + + case WSDataType.Close: + { + m_logger.Info("远程请求断开"); + client.Close("断开"); + } + return; + + case WSDataType.Ping: + break; + + case WSDataType.Pong: + break; + + default: + break; + } + + await e.InvokeNext(); + } +} +``` + +【使用】 + +```csharp {10} +var client = new WebSocketClient(); +await client.SetupAsync(new TouchSocketConfig() + .SetRemoteIPHost("ws://127.0.0.1:7789/ws") + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .ConfigurePlugins(a => + { + a.Add(); + })); +await client.ConnectAsync(); +``` + +### 6.3 使用WebSocket显式ReadAsync + +```csharp showLineNumbers +using (var client = GetClient()) +{ + //当WebSocket想要使用ReadAsync时,需要设置此值为true + client.AllowAsyncRead = true; + + while (true) + { + using (var receiveResult = await client.ReadAsync(CancellationToken.None)) + { + if (receiveResult.IsClosed) + { + //断开连接了 + break; + } + + //判断是否为最后数据 + //例如发送方发送了一个10Mb的数据,接收时可能会多次接收,所以需要此属性判断。 + if (receiveResult.DataFrame.FIN) + { + if (receiveResult.DataFrame.IsText) + { + Console.WriteLine($"WebSocket文本:{receiveResult.DataFrame.ToText()}"); + } + } + } + } +} +``` + +:::info 信息 + +`ReadAsync`的方式是属于**同步不阻塞**的接收方式。他不会单独占用线程,只会阻塞当前`Task`。所以可以大量使用,不需要考虑性能问题。同时,`ReadAsync`的好处就是单线程访问上下文,这样在处理ws分包时是非常方便的。 + +::: + +:::caution 注意 + +使用该方式,会阻塞`IWebSocketHandshakedPlugin`的插件传递。在收到`WebSocket`消息的时候,不会再触发插件。 + +::: + + + + +### 6.4 接收中继数据 + +`WebSocket`协议本身是支持超大数据包的,但是这些包不会一次性接收,而是分多次接收的,同时会通过`Opcode`来表明其为中继数据。 + + + +下面将演示接收文本数据。 + +【方法1】 + +```csharp {22-46} showLineNumbers +//当WebSocket想要使用ReadAsync时,需要设置此值为true +client.AllowAsyncRead = true; + +//此处即表明websocket已连接 + +MemoryStream stream = default;//中继包缓存 +var isText = false;//标识是否为文本 +while (true) +{ + using (var receiveResult = await client.ReadAsync(CancellationToken.None)) + { + if (receiveResult.IsCompleted) + { + break; + } + + var dataFrame = receiveResult.DataFrame; + var data = receiveResult.DataFrame.PayloadData; + + switch (dataFrame.Opcode) + { + case WSDataType.Cont: + { + //如果是非net6.0即以上,即:NetFramework平台使用。原因是stream不支持span写入 + //var segment = data.AsSegment(); + //stream.Write(segment.Array, segment.Offset, segment.Count); + + //如果是net6.0以上,直接写入span即可 + stream.Write(data.Span); + + //收到的是中继包 + if (dataFrame.FIN)//判断是否为最终包 + { + //是 + + if (isText)//判断是否为文本 + { + this.m_logger.Info($"WebSocket文本:{Encoding.UTF8.GetString(stream.ToArray())}"); + } + else + { + this.m_logger.Info($"WebSocket二进制:{stream.Length}长度"); + } + } + } + break; + case WSDataType.Text: + { + if (dataFrame.FIN)//判断是不是最后的包 + { + //是,则直接输出 + //说明上次并没有中继数据缓存,直接输出本次内容即可 + this.m_logger.Info($"WebSocket文本:{dataFrame.ToText()}"); + } + else + { + isText = true; + + //否,则说明数据太大了,分中继包了。 + //则,初始化缓存容器 + stream ??= new MemoryStream(); + + //下面则是缓存逻辑 + + //如果是非net6.0即以上,即:NetFramework平台使用。原因是stream不支持span写入 + //var segment = data.AsSegment(); + //stream.Write(segment.Array, segment.Offset, segment.Count); + + //如果是net6.0以上,直接写入span即可 + stream.Write(data.Span); + } + } + break; + case WSDataType.Binary: + { + if (dataFrame.FIN)//判断是不是最后的包 + { + //是,则直接输出 + //说明上次并没有中继数据缓存,直接输出本次内容即可 + this.m_logger.Info($"WebSocket二进制:{data.Length}长度"); + } + else + { + isText = false; + + //否,则说明数据太大了,分中继包了。 + //则,初始化缓存容器 + stream ??= new MemoryStream(); + + //下面则是缓存逻辑 + + //如果是非net6.0即以上,即:NetFramework平台使用。原因是stream不支持span写入 + //var segment = data.AsSegment(); + //stream.Write(segment.Array, segment.Offset, segment.Count); + + //如果是net6.0以上,直接写入span即可 + stream.Write(data.Span); + } + } + break; + case WSDataType.Close: + break; + case WSDataType.Ping: + break; + case WSDataType.Pong: + break; + default: + break; + } + } +} +``` + +或者可以使用内置的扩展方法来进行一次性接收。 + +例如一次性接收文本: + +```csharp showLineNumbers + var str = await client.ReadStringAsync(); +``` + +一次性接收二进制数据: + +```csharp showLineNumbers +using (MemoryStream stream=new MemoryStream()) +{ + var str = await client.ReadBinaryAsync(stream); +} +``` + +:::caution 注意 + +`ReadStringAsync`或者`ReadBinaryAsync`,都只接收对应的数据类型,如果收到非匹配数据则会抛出异常。 + +::: + +【方法2】 + +使用消息组合器。如果想在`OnWebSocketReceived`进行合并消息,则可以使用该方法。 + +```csharp {30-74} showLineNumbers +public class MyWebSocketPlugin : PluginBase, IWebSocketReceivedPlugin +{ + public MyWebSocketPlugin(ILog logger) + { + this.m_logger = logger; + } + + private readonly ILog m_logger; + + public async Task OnWebSocketReceived(IWebSocket client, WSDataFrameEventArgs e) + { + switch (e.DataFrame.Opcode) + { + case WSDataType.Close: + { + this.m_logger.Info("远程请求断开"); + await client.CloseAsync("断开"); + } + return; + + case WSDataType.Ping: + this.m_logger.Info("Ping"); + await client.PongAsync();//收到ping时,一般需要响应pong + break; + + case WSDataType.Pong: + this.m_logger.Info("Pong"); + break; + + default: + { + + //其他报文,需要考虑中继包的情况。所以需要手动合并 WSDataType.Cont类型的包。 + //或者使用消息合并器 + + //获取消息组合器 + var messageCombinator = client.GetMessageCombinator(); + + try + { + //尝试组合 + if (messageCombinator.TryCombine(e.DataFrame, out var webSocketMessage)) + { + //组合成功,必须using释放模式 + using (webSocketMessage) + { + //合并后的消息 + var dataType = webSocketMessage.Opcode; + + //合并后的完整消息 + var data = webSocketMessage.PayloadData; + + if (dataType == WSDataType.Text) + { + //按文本处理 + } + else if (dataType == WSDataType.Binary) + { + //按字节处理 + } + else + { + //可能是其他自定义协议 + } + } + } + } + catch (Exception ex) + { + this.m_logger.Exception(ex); + messageCombinator.Clear();//当组合发生异常时,应该清空组合器数据 + } + + } + break; + } + + await e.InvokeNext(); + } +} +``` + +:::caution 注意 + +使用消息组合器,实际上是由框架缓存了数据,所以,整体数据不要太大,不然可能会有爆内存的风险。 + +::: + + +## 七、其他操作 + +### 7.1 握手机制 + +`WebSocket`拥有独立的握手机制,直接获取`Online`属性即可。 + +### 7.2 Ping机制 + +`WebSocket`有自己的`Ping`、`Pong`机制。所以直接调用已有方法即可。 + +```csharp showLineNumbers +await client.PingAsync(); +await client.PongAsync(); +``` + + + + +:::tip 建议 + +`WebSocket`是双向通讯,所以支持客户端和服务器双向操作`Ping`和`Pong`报文。但是一般来说都是客户端执行`Ping`,服务器回应`Pong`。 + +::: + +### 7.3 断线重连 + +`WebSocket`断线重连,可以直接使用插件。 + +```csharp showLineNumbers +.ConfigurePlugins(a => +{ + a.UseWebSocketReconnection(); +}) +``` + + + +## 八、关闭连接 + +在使用`WebSocket`时,如果想主动关闭连接,可以使用`CloseAsync`方法,同时可以携带一个关闭原因。 + +默认关闭状态码为1000。意为:正常关闭。 + +```csharp showLineNumbers +await webSocket.CloseAsync("关闭"); +``` + +如果你想使用其他状态码,可以参考如下代码。 + +```csharp showLineNumbers +await webSocket.CloseAsync(WebSocketCloseStatus.EndpointUnavailable,"关闭");//状态码为1001,意为:服务端不可用。 +``` + + + +## 九、本文示例Demo + + diff --git a/handbook/versioned_docs/version-3.1/websocketdescription.mdx b/handbook/versioned_docs/version-3.1/websocketdescription.mdx new file mode 100644 index 000000000..86cd2e4e6 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/websocketdescription.mdx @@ -0,0 +1,36 @@ +--- +id: websocketdescription +title: 产品及架构介绍 +--- + +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import { TouchSocketHttpDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 产品介绍 + +- WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。 + + + +## 产品特点 + +- 简单易用。 +- 多线程。 +- 内存池 +- 高性能 +- **多种数据接收模式**(IOCP,BIO,Select)。 +- **多地址监听**(可以一次性监听多个IP及端口) + +## 产品应用场景 + +- WebSocket基础使用场景:可跨平台、跨语言使用。 +- 自定义协议解析场景:可解析任意数据格式的WebSocket数据报文。 + +## 服务器架构 + +服务器运行挂载在HttpService上,所以架构和HttpService一致。 diff --git a/handbook/docs/websocketheartbeat.mdx b/handbook/versioned_docs/version-3.1/websocketheartbeat.mdx similarity index 100% rename from handbook/docs/websocketheartbeat.mdx rename to handbook/versioned_docs/version-3.1/websocketheartbeat.mdx diff --git a/handbook/versioned_docs/version-3.1/websocketservice.mdx b/handbook/versioned_docs/version-3.1/websocketservice.mdx new file mode 100644 index 000000000..9607d25e0 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/websocketservice.mdx @@ -0,0 +1,921 @@ +--- +id: websocketservice +title: 创建WebSocket服务器 +--- + +import CardLink from "@site/src/components/CardLink.js"; +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import { TouchSocketHttpDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +`WebSocket`是基于`Http`协议的升级协议,所以应当挂载在`http`服务器执行。 + + +## 二、可配置项 + +继承[HttpService](./httpservice.mdx) + + +## 三、支持插件接口 + +| 插件方法| 功能 | +| --- | --- | +| IWebSocketHandshakingPlugin | 当收到握手请求之前,可以进行连接验证等 | +| IWebSocketHandshakedPlugin | 当成功握手响应之后 | +| IWebSocketReceivedPlugin | 当收到Websocket的数据报文 | +| IWebSocketClosingPlugin | 当收到关闭请求时触发。如果对方直接断开连接,则此方法则不会触发。 | +| IWebSocketClosedPlugin | 当WebSocket连接断开时触发,无论是否正常断开。但如果是断网等操作,可能不会立即执行,需要结合心跳操作和CheckClear插件来进行清理。 | + +## 四、创建WebSocket服务 + +### 4.1 简单直接创建 + +可以使用WebSocket插件,直接指定一个特殊`url`路由,来完全接收WebSocket连接。 + +然后通过插件来接收数据。(例:下列接收数据) + +```csharp showLineNumbers +var service = new HttpService(); +await service.SetupAsync(new TouchSocketConfig()//加载配置 + .SetListenIPHosts(7789) + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .ConfigurePlugins(a => + { + a.UseWebSocket()//添加WebSocket功能 + .SetWSUrl("/ws")//设置url直接可以连接。 + .UseAutoPong();//当收到ping报文时自动回应pong + })); + +await service.StartAsync(); + +service.Logger.Info("服务器已启动"); +``` + + + +### 4.2 验证连接 + +可以对连接的`Url`、`Query`、`Header`等参数进行验证,然后决定是否执行`WebSocket`连接。 + +```csharp showLineNumbers +var service = new HttpService(); +await service.SetupAsync(new TouchSocketConfig()//加载配置 + .SetListenIPHosts(7789) + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .ConfigurePlugins(a => + { + a.UseWebSocket()//添加WebSocket功能 + .SetVerifyConnection(VerifyConnection) + .UseAutoPong();//当收到ping报文时自动回应pong + })); + +await service.StartAsync(); + +service.Logger.Info("服务器已启动"); +``` + +```csharp showLineNumbers +/// +/// 验证websocket的连接 +/// +/// +/// +/// +private static Task VerifyConnection(IHttpSessionClient client, HttpContext context) +{ + if (!context.Request.IsUpgrade())//如果不包含升级协议的header,就直接返回false。 + { + return false; + } + if (context.Request.UrlEquals("/ws"))//以此连接,则直接可以连接 + { + return true; + } + else if (context.Request.UrlEquals("/wsquery"))//以此连接,则需要传入token才可以连接 + { + if (context.Request.Query.Get("token") == "123456") + { + return true; + } + else + { + await context.Response + .SetStatus(403, "token不正确") + .AnswerAsync(); + } + } + else if (context.Request.UrlEquals("/wsheader"))//以此连接,则需要从header传入token才可以连接 + { + if (context.Request.Headers.Get("token") == "123456") + { + return true; + } + else + { + await context.Response + .SetStatus(403, "token不正确") + .AnswerAsync(); + } + } + return false; +} +``` + +### 4.3 通过WebApi创建 + +通过WebApi的方式会更加灵活,也能很方便的获得Http相关参数。还能实现多个Url的连接路由。 + +实现步骤: + +1. 配置`WebApi`相关,详情请看[WebApi](./webapi.mdx) +2. 在插件中接收`WebSocket`连接。 + +```csharp {8-11,15} showLineNumbers +var service = new HttpService(); +await service.SetupAsync(new TouchSocketConfig()//加载配置 + .SetListenIPHosts(7789) + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + + a.AddRpcStore(store => + { + store.RegisterServer();//注册服务 + }); + }) + .ConfigurePlugins(a => + { + a.UseWebApi(); + })); + +await service.StartAsync(); + +service.Logger.Info("服务器已启动"); +``` + + + +【接收连接】 + +```csharp {16} showLineNumbers +public class MyApiServer : SingletonRpcServer +{ + private readonly ILog m_logger; + + public MyApiServer(ILog logger) + { + this.m_logger = logger; + } + + [Router("/[api]/[action]")] + [WebApi(Method = HttpMethodType.Get)] + public async Task ConnectWS(IWebApiCallContext callContext) + { + if (callContext.Caller is HttpSessionClient sessionClient) + { + var result=await sessionClient.SwitchProtocolToWebSocketAsync(callContext.HttpContext); + if (result.IsSuccess) + { + m_logger.Info("WS通过WebApi连接"); + var webSocket = sessionClient.WebSocket; + } + } + } +} +``` + +或者接收到连接后,直接使用`WebSocket`接收。这样如果想直接在`WebApi`返回数据也比较方便。 + +```csharp {16-43} showLineNumbers +public class MyApiServer : SingletonRpcServer +{ + private readonly ILog m_logger; + + public MyApiServer(ILog logger) + { + this.m_logger = logger; + } + + [Router("/[api]/[action]")] + [WebApi(Method = HttpMethodType.Get)] + public async Task ConnectWS(IWebApiCallContext callContext) + { + if (callContext.Caller is HttpSessionClient sessionClient) + { + if (await sessionClient.SwitchProtocolToWebSocketAsync(callContext.HttpContext)) + { + m_logger.Info("WS通过WebApi连接"); + var webSocket = sessionClient.WebSocket; + + webSocket.AllowAsyncRead = true; + + while (true) + { + using (var tokenSource=new CancellationTokenSource(TimeSpan.FromSeconds(30))) + { + using (var receiveResult = await webSocket.ReadAsync(tokenSource.Token)) + { + if (receiveResult.IsCompleted) + { + //webSocket已断开 + return; + } + + //webSocket数据帧 + var dataFrame = receiveResult.DataFrame; + + //此处可以处理数据 + } + } + + } + } + } + } +} +``` + +### 4.4 通过Http上下文直接创建 + +使用上下文直接创建的优点在于能更加个性化的实现`WebSocket`的连接。 + +```csharp showLineNumbers +class MyHttpPlugin : PluginBase, IHttpPlugin +{ + public async Task OnHttpRequest(IHttpSessionClient client, HttpContextEventArgs e) + { + if (e.Context.Request.UrlEquals("/GetSwitchToWebSocket")) + { + var result = await client.SwitchProtocolToWebSocketAsync(e.Context); + return; + } + await e.InvokeNext(); + } +} +``` + +### 4.5 创建基于Ssl的WebSocket服务 + +创建`WSs`服务器时,其他配置不变,只需要在`config`中配置`SslOption`代码即可,放置了一个自制`Ssl`证书,密码为“RRQMSocket”以供测试。使用配置非常方便。 + +```csharp showLineNumbers +var config = new TouchSocketConfig(); +config.SetServiceSslOption(new ServiceSslOption() //Ssl配置,当为null的时候,相当于创建了ws服务器,当赋值的时候,相当于wss服务器。 + { + Certificate = new X509Certificate2("RRQMSocket.pfx", "RRQMSocket"), + SslProtocols = SslProtocols.Tls12 + }); +``` + + +## 五、接收消息 + +WebSocket服务器接收消息,目前有两种方式。第一种就是通过订阅`IWebSocketReceivedPlugin`插件完全异步的接收消息。第二种就是调用`WebSocket`,然后调用`ReadAsync`方法异步阻塞式读取。 + + + +### 5.1 简单接收消息 + +【定义插件】 +```csharp showLineNumbers +public class MyWebSocketPlugin : PluginBase, IWebSocketReceivedPlugin +{ + private readonly ILog m_logger; + + public MyWebSocketPlugin(ILog logger) + { + this.m_logger = logger; + } + public async Task OnWebSocketReceived(IWebSocket client, WSDataFrameEventArgs e) + { + switch (e.DataFrame.Opcode) + { + case WSDataType.Cont: + m_logger.Info($"收到中间数据,长度为:{e.DataFrame.PayloadLength}"); + + return; + + case WSDataType.Text: + m_logger.Info(e.DataFrame.ToText()); + + if (!client.Client.IsClient) + { + client.SendAsync("我已收到"); + } + return; + + case WSDataType.Binary: + if (e.DataFrame.FIN) + { + m_logger.Info($"收到二进制数据,长度为:{e.DataFrame.PayloadLength}"); + } + else + { + m_logger.Info($"收到未结束的二进制数据,长度为:{e.DataFrame.PayloadLength}"); + } + return; + + case WSDataType.Close: + { + m_logger.Info("远程请求断开"); + client.Close("断开"); + } + return; + + case WSDataType.Ping: + break; + + case WSDataType.Pong: + break; + + default: + break; + } + + await e.InvokeNext(); + } +} +``` + +【使用】 + +```csharp {12} +var service = new HttpService(); +await service.SetupAsync(new TouchSocketConfig()//加载配置 + .SetListenIPHosts(7789) + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .ConfigurePlugins(a => + { + a.UseWebSocket()//添加WebSocket功能 + .SetWSUrl("/ws"); + a.Add();//自定义插件。 + })); + +await service.StartAsync(); +``` + +:::tip 提示 + +插件的所有函数,都是可能被并发执行的,所以应当做好线程安全。 + +::: + +### 5.2 WebSocket显式ReadAsync + +`WebSocket`显式`ReadAsync`数据,实际上是直接通过`WebSocket`直接循环读取数据。 + +一般的,如果`WebSocket`是常规连接,则可能需要使用`IWebSocketHandshakedPlugin`插件,在`握手成功`的后直接读取数据。 + +如果连接是通过`WebApi`、或者`Http上下文`创建的连接,则可以直接使用`ReadAsync`。 + +总之要能拿到`WebSocket`对象,则可以直接使用。 + +和`简单接收消息`相比,使用`ReadAsync`接收消息可以直接访问代码上下文资源,这可能在处理`中继数据`时是方便的。 + +```csharp showLineNumbers +class MyReadWebSocketPlugin : PluginBase, IWebSocketHandshakedPlugin +{ + private readonly ILog m_logger; + + public MyReadWebSocketPlugin(ILog logger) + { + this.m_logger = logger; + } + public async Task OnWebSocketHandshaked(IWebSocket client, HttpContextEventArgs e) + { + //当WebSocket想要使用ReadAsync时,需要设置此值为true + client.AllowAsyncRead = true; + + //此处即表明websocket已连接 + while (true) + { + using (var receiveResult = await client.ReadAsync(CancellationToken.None)) + { + + if (receiveResult.IsCompleted) + { + break; + } + + //判断是否为最后数据 + //例如发送方发送了一个10Mb的数据,接收时可能会多次接收,所以需要此属性判断。 + if (receiveResult.DataFrame.FIN) + { + if (receiveResult.DataFrame.IsText) + { + m_logger.Info($"WebSocket文本:{receiveResult.DataFrame.ToText()}"); + } + } + + } + } + + //此处即表明websocket已断开连接 + m_logger.Info("WebSocket断开连接"); + await e.InvokeNext(); + } +} +``` + + + +【使用】 + +```csharp {16} +private static HttpService CreateHttpService() +{ + var service = new HttpService(); + await service.SetupAsync(new TouchSocketConfig()//加载配置 + .SetListenIPHosts(7789) + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .ConfigurePlugins(a => + { + a.UseWebSocket()//添加WebSocket功能 + .SetWSUrl("/ws")//设置url直接可以连接。 + .UseAutoPong();//当收到ping报文时自动回应pong + + a.Add(); + })); + + await service.StartAsync(); + + service.Logger.Info("服务器已启动"); + service.Logger.Info("直接连接地址=>ws://127.0.0.1:7789/ws"); + return service; +} +``` + +:::info 信息 + +`ReadAsync`的方式是属于**同步不阻塞**的接收方式(和当下Aspnetcore模式一样)。他不会单独占用线程,只会阻塞当前`Task`。所以可以大量使用,不需要考虑性能问题。同时,`ReadAsync`的好处就是单线程访问上下文,这样在处理ws分包时是非常方便的。 + +::: + +:::caution 注意 + +使用该方式,会阻塞`IWebSocketHandshakedPlugin`的插件传递。在收到`WebSocket`消息的时候,不会再触发插件。 + +::: + +### 5.3 接收中继数据 + +`WebSocket`协议本身是支持超大数据包的,但是这些包不会一次性接收,而是分多次接收的,同时会通过`Opcode`来表明其为中继数据。 + + + +下面将演示接收文本数据。 + +【方法1】 + +使用`ReadAsync`,在`OnWebSocketHandshaked`组合消息。 + +```csharp {33-52} showLineNumbers +internal class MyReadTextWebSocketPlugin : PluginBase, IWebSocketHandshakedPlugin +{ + private readonly ILog m_logger; + + public MyReadTextWebSocketPlugin(ILog logger) + { + this.m_logger = logger; + } + + public async Task OnWebSocketHandshaked(IWebSocket client, HttpContextEventArgs e) + { + //当WebSocket想要使用ReadAsync时,需要设置此值为true + client.AllowAsyncRead = true; + + //此处即表明websocket已连接 + + MemoryStream stream = default;//中继包缓存 + var isText = false;//标识是否为文本 + while (true) + { + using (var receiveResult = await client.ReadAsync(CancellationToken.None)) + { + if (receiveResult.IsCompleted) + { + break; + } + + var dataFrame = receiveResult.DataFrame; + var data = receiveResult.DataFrame.PayloadData; + + switch (dataFrame.Opcode) + { + case WSDataType.Cont: + { + //先缓存中继数据 + stream.Write(data.Span); + + //然后判断中继包是否为最终包 + if (dataFrame.FIN) + { + //是 + + if (isText)//判断是否为文本 + { + this.m_logger.Info($"WebSocket文本:{Encoding.UTF8.GetString(stream.ToArray())}"); + } + else + { + this.m_logger.Info($"WebSocket二进制:{stream.Length}长度"); + } + } + } + break; + case WSDataType.Text: + { + if (dataFrame.FIN)//判断是不是最后的包 + { + //是,则直接输出 + //说明上次并没有中继数据缓存,直接输出本次内容即可 + this.m_logger.Info($"WebSocket文本:{dataFrame.ToText()}"); + } + else + { + isText = true; + + //否,则说明数据太大了,分中继包了。 + //则,初始化缓存容器 + stream ??= new MemoryStream(); + + //下面则是缓存逻辑 + + //如果是非net6.0即以上,即:NetFramework平台使用。原因是stream不支持span写入 + //var segment = data.AsSegment(); + //stream.Write(segment.Array, segment.Offset, segment.Count); + + //如果是net6.0以上,直接写入span即可 + stream.Write(data.Span); + } + } + break; + case WSDataType.Binary: + { + if (dataFrame.FIN)//判断是不是最后的包 + { + //是,则直接输出 + //说明上次并没有中继数据缓存,直接输出本次内容即可 + this.m_logger.Info($"WebSocket二进制:{data.Length}长度"); + } + else + { + isText = false; + + //否,则说明数据太大了,分中继包了。 + //则,初始化缓存容器 + stream ??= new MemoryStream(); + + //下面则是缓存逻辑 + + //如果是非net6.0即以上,即:NetFramework平台使用。原因是stream不支持span写入 + //var segment = data.AsSegment(); + //stream.Write(segment.Array, segment.Offset, segment.Count); + + //如果是net6.0以上,直接写入span即可 + stream.Write(data.Span); + } + } + break; + case WSDataType.Close: + break; + case WSDataType.Ping: + break; + case WSDataType.Pong: + break; + default: + break; + } + } + } + + //此处即表明websocket已断开连接 + this.m_logger.Info("WebSocket断开连接"); + await e.InvokeNext(); + } +} +``` + +或者可以使用内置的扩展方法来进行一次性接收。 + +例如一次性接收文本: + +```csharp showLineNumbers + var str = await client.ReadStringAsync(); +``` + +一次性接收二进制数据: + +```csharp showLineNumbers +using (MemoryStream stream=new MemoryStream()) +{ + var str = await client.ReadBinaryAsync(stream); +} +``` + +:::caution 注意 + +`ReadStringAsync`或者`ReadBinaryAsync`,都只接收对应的数据类型,如果收到非匹配数据则会抛出异常。 + +::: + +【方法2】 + +使用消息组合器。如果想在`OnWebSocketReceived`进行合并消息,则可以使用该方法。 + +```csharp {30-74} showLineNumbers +public class MyWebSocketPlugin : PluginBase, IWebSocketReceivedPlugin +{ + public MyWebSocketPlugin(ILog logger) + { + this.m_logger = logger; + } + + private readonly ILog m_logger; + + public async Task OnWebSocketReceived(IWebSocket client, WSDataFrameEventArgs e) + { + switch (e.DataFrame.Opcode) + { + case WSDataType.Close: + { + this.m_logger.Info("远程请求断开"); + await client.CloseAsync("断开"); + } + return; + + case WSDataType.Ping: + this.m_logger.Info("Ping"); + await client.PongAsync();//收到ping时,一般需要响应pong + break; + + case WSDataType.Pong: + this.m_logger.Info("Pong"); + break; + + default: + { + + //其他报文,需要考虑中继包的情况。所以需要手动合并 WSDataType.Cont类型的包。 + //或者使用消息合并器 + + //获取消息组合器 + var messageCombinator = client.GetMessageCombinator(); + + try + { + //尝试组合 + if (messageCombinator.TryCombine(e.DataFrame, out var webSocketMessage)) + { + //组合成功,必须using释放模式 + using (webSocketMessage) + { + //合并后的消息 + var dataType = webSocketMessage.Opcode; + + //合并后的完整消息 + var data = webSocketMessage.PayloadData; + + if (dataType == WSDataType.Text) + { + //按文本处理 + } + else if (dataType == WSDataType.Binary) + { + //按字节处理 + } + else + { + //可能是其他自定义协议 + } + } + } + } + catch (Exception ex) + { + this.m_logger.Exception(ex); + messageCombinator.Clear();//当组合发生异常时,应该清空组合器数据 + } + + } + break; + } + + await e.InvokeNext(); + } +} +``` + +:::caution 注意 + +使用消息组合器,实际上是由框架缓存了数据,所以,整体数据不要太大,不然可能会有爆内存的风险。 + +::: + +## 六、回复、响应数据 + +要回复`Websocket`消息,必须使用**HttpSessionClient**对象。如果要获取到所有的`HttpSessionClient`对象,则必须通过`HttpService`对象获取。 + +所以,首先,得确保能访问到`HttpService`对象。 + +如果是在`HttpService`的创建过程中,你可以直接访问到`HttpService`对象。 + +但是如果在插件中,或者其他地方,你需要使用其他方法来实现。 + +### 6.1 在插件中获取`HttpService`对象 + +插件是支持依赖注入的,所以可以直接注入`HttpService`对象。然后在插件中获取。 + +例如: + +【注册服务】 + +```csharp {6} showLineNumbers +var service = new HttpService(); +await service.SetupAsync(new TouchSocketConfig()//加载配置 + ... + .ConfigureContainer(a => + { + a.RegisterSingleton(service); + ... + })); +await service.StartAsync(); +``` + +【获取服务】 + +```csharp {3,5,8} showLineNumbers +public class MyWebSocketPlugin : PluginBase,... +{ + private readonly IHttpService m_httpService; + + public MyWebSocketPlugin(ILog logger,IHttpService httpService) + { + this.m_logger = logger; + this.m_httpService = httpService; + } +} +``` + +除了使用容器,还可以在插件触发时,通过`WebSocket`获取`HttpService`对象。 + +```csharp {6-9} showLineNumbers +public class MyWebSocketPlugin : PluginBase, IWebSocketReceivedPlugin +{ + ... + public async Task OnWebSocketReceived(IWebSocket client, WSDataFrameEventArgs e) + { + if (client.Client is IHttpSessionClient httpSessionClient) + { + var service = httpSessionClient.Service as IHttpService + } + await e.InvokeNext(); + } +} +``` + + +### 6.2 获取SessionClient + +#### (1)直接获取所有在线客户端 + +通过`service.Clients`属性,获取当前在线的所有客户端。 + +```csharp showLineNumbers +var clients = service.Clients; +foreach (var client in clients) +{ + if (client.Protocol == Protocol.WebSocket)//先判断是不是websocket协议 + { + if (client.Id == "id")//再按指定id发送,或者直接广播发送 + { + await client.WebSocket.SendAsync("hello"); + } + } +} +``` + +:::caution 注意 + +由于`HttpSessionClient`的生命周期是由框架控制的,所以最好尽量不要直接引用该实例,可以引用`HttpSessionClient.Id`,然后再通过服务器查找。 + +::: + +#### (2)通过Id获取 + +先调用`service.GetIds`方法,获取当前在线的所有客户端的Id,然后选择需要的Id,通过`TryGetClient`方法,获取到想要的客户端。 + +```csharp showLineNumbers +string[] ids = service.GetIds(); +if (service.TryGetClient(ids[0], out HttpSessionClient sessionClient)) +{ + await sessionClient.WebSocket.SendAsync("hello"); +} +``` + +### 6.3 发送文本类消息 + +```csharp showLineNumbers +await webSocket.SendAsync("Text"); +``` + +### 6.4 发送二进制消息 + +```csharp showLineNumbers +await webSocket.SendAsync(new byte[10]); +``` + +### 6.5 直接发送自定义构建的数据帧 + +```csharp showLineNumbers +using (var frame = new WSDataFrame()) +{ + frame.Opcode = WSDataType.Text; + frame.FIN = true; + frame.RSV1 = true; + frame.RSV2 = true; + frame.RSV3 = true; + frame.AppendText("I"); + frame.AppendText("Love"); + frame.AppendText("U"); + await webSocket.SendAsync(frame); +} +``` +:::info 备注 + +此部分功能就需要你对`WebSocket`有充分了解才可以操作。 + +::: + +### 6.6 发送Ping + +```csharp showLineNumbers +await webSocket.PingAsync(); +``` + +### 6.7 发送Pong + +```csharp showLineNumbers +await webSocket.PongAsync(); +``` + + + +### 6.8 发送大数据 + +发送大数据时,需要分包发送,可以使用`SendAsync`的重载方法,设置`FIN`标志。 + +```csharp showLineNumbers +// 发送 "hello" 消息 100 次,最后一次设置 FIN 标志 +var i = 0; +while (true) +{ + if (i++ == 100) + { + await sessionClient.WebSocket.SendAsync("hello", true); + break; + } + await sessionClient.WebSocket.SendAsync("hello", false); +} +``` + + +## 七、关闭连接 + +在使用`WebSocket`时,如果想主动关闭连接,可以使用`CloseAsync`方法,同时可以携带一个关闭原因。 + +默认关闭状态码为1000。意为:正常关闭。 + +```csharp showLineNumbers +await webSocket.CloseAsync("关闭"); +``` + +如果你想使用其他状态码,可以参考如下代码。 + +```csharp showLineNumbers +await webSocket.CloseAsync(WebSocketCloseStatus.EndpointUnavailable,"关闭");//状态码为1001,意为:服务端不可用。 +``` + + + +## 八、本文示例Demo + + \ No newline at end of file diff --git a/handbook/docs/wpfuifiletransfer.mdx b/handbook/versioned_docs/version-3.1/wpfuifiletransfer.mdx similarity index 100% rename from handbook/docs/wpfuifiletransfer.mdx rename to handbook/versioned_docs/version-3.1/wpfuifiletransfer.mdx diff --git a/handbook/versioned_docs/version-3.1/wscommandlineplugin.mdx b/handbook/versioned_docs/version-3.1/wscommandlineplugin.mdx new file mode 100644 index 000000000..52990aa4f --- /dev/null +++ b/handbook/versioned_docs/version-3.1/wscommandlineplugin.mdx @@ -0,0 +1,117 @@ +--- +id: wscommandlineplugin +title: 快捷事务命令行 +--- + +import BilibiliCard from '@site/src/components/BilibiliCard.js'; +import { TouchSocketHttpDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +快捷事务命令行,是用于**WebSocket**的快捷事务实现,让WS在**Text**文本中,用最简单的文字消息即可完成相关事务的执行。 + + + +## 二、使用 + +### 2.1 声明事务插件 + +新建类型,继承自`WebSocketCommandLinePlugin`。然后在里面写一些需要的事务方法。 + +声明的事务方法必须满足:只能是实例方法、必须是公开方法、方法名以`Command`结尾。 + +```csharp showLineNumbers +/// +/// 命令行插件。 +/// 声明的方法必须为公开实例方法、以"Command"结尾,且支持json字符串,参数之间空格隔开。 +/// +public class MyWSCommandLinePlugin : WebSocketCommandLinePlugin +{ + public MyWSCommandLinePlugin(ILog logger) : base(logger) + { + } + + public int AddCommand(int a, int b) + { + return a + b; + } + + //当第一个参数,直接或间接实现ITcpSession接口时,会收集到当前请求的客户端,从而可以获取IP等。 + public SumClass SumCommand(IHttpClientBase client, SumClass sumClass) + { + sumClass.Sum = sumClass.A + sumClass.B; + return sumClass; + } +} +``` + +### 3.1 注册使用 + +直接通过插件添加即可。 + +```csharp {14} +var service = new HttpService(); +await service.SetupAsync(new TouchSocketConfig()//加载配置 + .SetListenIPHosts(7789) + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .ConfigurePlugins(a => + { + a.UseWebSocket()//添加WebSocket功能 + //.SetWSUrl("/ws")//设置url直接可以连接。 + .SetVerifyConnection(VerifyConnection) + .UseAutoPong();//当收到ping报文时自动回应pong + a.Add(); + })); + +await service.StartAsync(); +``` + +### 3.2 调用 + +调用数据格式: + +以事务方法的**方法名**为第一个参数(不包含Command),后续参数直接写,多个参数用**空格**隔开。例如:`Add 10 20` + +```csharp {18} +var client = new WebSocketClient(); +await client.SetupAsync(new TouchSocketConfig() + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + }) + .ConfigurePlugins(a => + { + a.Add(typeof(IWebSocketReceivedPlugin), async (IHttpClientBase c, WSDataFrameEventArgs e) => + { + client.Logger.Info($"收到Add的计算结果:{e.DataFrame.ToText()}"); + await e.InvokeNext(); + }); + }) + .SetRemoteIPHost("ws://127.0.0.1:7789/ws")); +await client.ConnectAsync(); + +client.SendWithWS("Add 10 20"); +``` + +:::tip 提示 + +快捷事务命令行不仅可以添加在服务器,客户端也可以添加使用。 + +::: + +:::tip 提示 + +快捷事务参数支持Json字符串,但是需要注意的是,调用的Json字符串也不能包含空格。 + +::: + + +[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/WebSocket/WebSocketConsoleApp) \ No newline at end of file diff --git a/handbook/versioned_docs/version-3.1/xmlrpc.mdx b/handbook/versioned_docs/version-3.1/xmlrpc.mdx new file mode 100644 index 000000000..d07f1cf83 --- /dev/null +++ b/handbook/versioned_docs/version-3.1/xmlrpc.mdx @@ -0,0 +1,230 @@ +--- +id: xmlrpc +title: 产品及架构介绍 +--- + +import { TouchSocketXmlRpcDefinition } from "@site/src/components/Definition.js"; + +### 定义 + + + + +## 一、说明 + +XmlRpc是**通用**的工作在**Internet**上的RPC。一个[XML-RPC](https://baike.baidu.com/item/XML-RPC/0) 消息就是一个请求体为xml的**http-post**请求,被调用的方法在服务器端执行并将执行结果以xml格式编码后返回。这与**编程语言无关**,与**操作系统无关**。在此处封装了**前后端**,使其使用更加方便、高效。 + + +## 二、特点: + +- **异常反馈** 。 +- 支持自定义类型。 +- 支持类型嵌套。 +- 支持Web等调用。 + +## 三、创建服务 + +### 3.1 定义服务 + +在**服务器**端中新建一个类,继承于**SingletonRpcServer**类(或实现IRpcServer),然后在该类中写**公共方法**,并用**XmlRpc**属性标签标记。 + +```csharp showLineNumbers +public partial class XmlServer : SingletonRpcServer +{ + [XmlRpc(MethodInvoke = true)] + public int Sum(int a, int b) + { + return a + b; + } + + [XmlRpc(MethodInvoke = true)] + public int TestClass(MyClass myClass) + { + return myClass.A + myClass.B; + } +} + +public class MyClass +{ + public int A { get; set; } + public int B { get; set; } +} + +``` + +### 3.2 启动服务 + +更多注册Rpc的方法请看[注册Rpc服务](./rpcregister.mdx) + +```csharp showLineNumbers +var service = new HttpService(); + +await service.SetupAsync(new TouchSocketConfig() + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + a.AddRpcStore(store => + { + store.RegisterServer(); + + //下列为生成客户端的代理代码 + File.WriteAllText("../../../RpcProxy.cs", store.GetProxyCodes("RpcProxy", new Type[] { typeof(XmlRpcAttribute) })); + ConsoleLogger.Default.Info("成功生成代理"); + + }); + }) + .ConfigurePlugins(a => + { + a.UseXmlRpc() + .SetXmlRpcUrl("/xmlRpc"); + }) + .SetListenIPHosts(7789)); +await service.StartAsync(); + +service.Logger.Info("服务器已启动"); +``` + +## 四、客户端调用 + +### 4.1 直接调用 + +因为XmlRpc是一套已经约定俗成的通讯协议。所以可以通过常规方法直接调用。 + +【Http访问】 + +使用Post方式,url=http://127.0.0.1:7789/xmlRpc + +然后使用XmlRpc的规范传参。 + +```xml + + + Sum + + + + 10 + + + + + 20 + + + + +``` + +### 4.2 使用客户端调用 + +使用客户端调用,则是使用RPC的范式进行调用,使用比较简单。 + +```csharp showLineNumbers +private static async Task Main(string[] args) +{ + var client =await GetXmlRpcClientAsync(); + + //直接调用 + var result1 = client.InvokeT("Sum", InvokeOption.WaitInvoke, 10, 20); + Console.WriteLine($"直接调用,返回结果:{result1}"); + + var result2 = client.Sum(10, 20);//此Sum方法是服务端生成的代理。 + Console.WriteLine($"代理调用,返回结果:{result2}"); + + Console.ReadKey(); +} + +private static async Task GetXmlRpcClientAsync() +{ + var jsonRpcClient = new XmlRpcClient(); + await jsonRpcClient.ConnectAsync("http://127.0.0.1:7789/xmlRpc"); + Console.WriteLine("连接成功"); + return jsonRpcClient; +} +``` + +### 4.3 生成代理调用 + +在服务器端,注册完服务后,就可以生成客户端调用代码了。详细的操作可以查看[服务端代理生成](./rpcgenerateproxy.mdx) + +```csharp {6-7} +a.AddRpcStore(store => +{ + store.RegisterServer(); + +#if DEBUG + File.WriteAllText("../../../RpcProxy.cs", store.GetProxyCodes("RpcProxy", new Type[] { typeof(XmlRpcAttribute) })); + ConsoleLogger.Default.Info("成功生成代理"); +#endif +}); +``` + +然后把生成的.cs文件复制(或链接)到客户端项目。然后客户端直接使用同名`扩展方法`即可调用。 + +```csharp showLineNumbers +var sum3 = client.Sum(10,20); +``` + +### 4.4 使用DispatchProxy代理调用 + +使用DispatchProxy代理调用,可以实现动态代理,详情请看[DispatchProxy代理生成](./rpcgenerateproxy.mdx) + +首先,需要声明一个基类,用于通讯基础。 + +```csharp showLineNumbers +/// +/// 新建一个类,继承XmlRpcDispatchProxy,亦或者RpcDispatchProxy基类。 +/// 然后实现抽象方法,主要是能获取到调用的IRpcClient派生接口。 +/// +internal class MyXmlRpcDispatchProxy : XmlRpcDispatchProxy +{ + private readonly IXmlRpcClient m_client; + + public MyXmlRpcDispatchProxy() + { + this.m_client = GetXmlRpcClientAsync().GetFalseAwaitResult(); + } + + public override IXmlRpcClient GetClient() + { + return this.m_client; + } + + private static async Task GetXmlRpcClientAsync() + { + var jsonRpcClient = new XmlRpcClient(); + await jsonRpcClient.ConnectAsync("http://127.0.0.1:7789/xmlRpc"); + Console.WriteLine("连接成功"); + return jsonRpcClient; + } +} +``` + +然后按照服务,定义一个相同的代理接口。 + +```csharp showLineNumbers +interface IXmlRpcServer +{ + [XmlRpc(MethodInvoke = true)] + int Sum(int a, int b); +} +``` + +最后生成代理,并按照接口调用。 + +```csharp {1} +var rpc = MyXmlRpcDispatchProxy.Create(); +while (true) +{ + Console.WriteLine("请输入两个数,中间用空格隔开,回车确认"); + string str = Console.ReadLine(); + var strs = str.Split(' '); + int a = int.Parse(strs[0]); + int b = int.Parse(strs[1]); + + var sum = rpc.Sum(a, b); + Console.WriteLine(sum); +} +``` + +[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/XmlRpc) \ No newline at end of file diff --git a/handbook/versioned_sidebars/version-1.3.9-sidebars.json b/handbook/versioned_sidebars/version-1.3.9-sidebars.json index 936d30e1d..ff9ef0e9d 100644 --- a/handbook/versioned_sidebars/version-1.3.9-sidebars.json +++ b/handbook/versioned_sidebars/version-1.3.9-sidebars.json @@ -6,9 +6,9 @@ "label": "01、说明(使用前必要阅读)" }, { - "type": "doc", - "id": "upgrade", - "label": "02、历史更新" + "type": "link", + "label": "02、历史更新", + "href": "/upgrade" }, { "type": "category", @@ -580,4 +580,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/handbook/versioned_sidebars/version-2.0-sidebars.json b/handbook/versioned_sidebars/version-2.0-sidebars.json index f7e825724..912c0c8ee 100644 --- a/handbook/versioned_sidebars/version-2.0-sidebars.json +++ b/handbook/versioned_sidebars/version-2.0-sidebars.json @@ -6,9 +6,9 @@ "label": "01、说明(使用前必要阅读)" }, { - "type": "doc", - "id": "upgrade", - "label": "02、历史更新" + "type": "link", + "label": "02、历史更新", + "href": "/upgrade" }, { "type": "category", @@ -585,4 +585,4 @@ "id": "generichost" } ] -} +} \ No newline at end of file diff --git a/handbook/versioned_sidebars/version-2.1-sidebars.json b/handbook/versioned_sidebars/version-2.1-sidebars.json index f962b4142..4365bef43 100644 --- a/handbook/versioned_sidebars/version-2.1-sidebars.json +++ b/handbook/versioned_sidebars/version-2.1-sidebars.json @@ -6,9 +6,9 @@ "label": "01、说明(使用前必要阅读)" }, { - "type": "doc", - "id": "upgrade", - "label": "02、历史更新" + "type": "link", + "label": "02、历史更新", + "href": "/upgrade" }, { "type": "category", @@ -591,4 +591,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/handbook/versioned_sidebars/version-3.0-sidebars.json b/handbook/versioned_sidebars/version-3.0-sidebars.json index 6785b4b09..8268a59df 100644 --- a/handbook/versioned_sidebars/version-3.0-sidebars.json +++ b/handbook/versioned_sidebars/version-3.0-sidebars.json @@ -6,9 +6,9 @@ "label": "01、说明(使用前必要阅读)" }, { - "type": "doc", - "id": "upgrade", - "label": "02、历史更新" + "type": "link", + "label": "02、历史更新", + "href": "/upgrade" }, { "type": "category", @@ -601,4 +601,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/handbook/versioned_sidebars/version-3.1-sidebars.json b/handbook/versioned_sidebars/version-3.1-sidebars.json new file mode 100644 index 000000000..cd0402768 --- /dev/null +++ b/handbook/versioned_sidebars/version-3.1-sidebars.json @@ -0,0 +1,640 @@ +{ + "docs": [ + { + "type": "doc", + "id": "description", + "label": "01、说明(使用前必要阅读)" + }, + { + "type": "link", + "label": "02、历史更新", + "href": "/upgrade" + }, + { + "type": "category", + "label": "03、支持作者及商业运营", + "items": [ + { + "type": "doc", + "id": "donate", + "label": "3.1 支持作者" + }, + { + "type": "doc", + "id": "enterprise", + "label": "3.2 Pro相关" + }, + {}, + { + "type": "category", + "label": "3.3 使用者项目", + "items": [ + { + "type": "doc", + "id": "fpsgame", + "label": "a.FPS实时游戏" + }, + { + "type": "doc", + "id": "engineertoolbox", + "label": "b.工程师软件工具箱" + }, + { + "type": "doc", + "id": "thingsgateway", + "label": "c.ThingsGateway" + } + ] + } + ] + }, + { + "type": "doc", + "id": "startguide", + "label": "04、入门指南" + }, + { + "type": "category", + "label": "05、疑难解答", + "items": [ + { + "type": "doc", + "id": "troubleshootsourcecode", + "label": "5.1 源码相关" + }, + { + "type": "doc", + "id": "troubleshootunity3d", + "label": "5.2 Unity3D相关" + }, + { + "type": "doc", + "id": "troubleshootissue", + "label": "5.3 Issue解答" + } + ] + }, + { + "type": "category", + "label": "06、Core", + "items": [ + { + "type": "doc", + "id": "bytepool", + "label": "6.1 内存池" + }, + { + "type": "doc", + "id": "consoleaction", + "label": "6.2 控制台行为" + }, + { + "type": "doc", + "id": "touchsocketbitconverter", + "label": "6.3 大小端转换器" + }, + { + "type": "doc", + "id": "datasecurity", + "label": "6.4 数据加密" + }, + { + "type": "doc", + "id": "ilog", + "label": "6.5 日志记录器" + }, + { + "type": "doc", + "id": "appmessenger", + "label": "6.6 应用信使" + }, + { + "type": "doc", + "id": "fastbinaryformatter", + "label": "6.7 高性能二进制序列化" + }, + { + "type": "doc", + "id": "jsonserialize", + "label": "6.8 Json序列化" + }, + { + "type": "doc", + "id": "ioc", + "label": "6.9 依赖注入容器(IOC)" + }, + { + "type": "doc", + "id": "dependencyproperty", + "label": "6.10 依赖属性" + }, + { + "type": "doc", + "id": "filepool", + "label": "6.11 文件流池" + }, + { + "type": "doc", + "id": "pluginsmanager", + "label": "6.12 插件系统" + }, + { + "type": "doc", + "id": "ipackage", + "label": "6.13 包序列化模式" + }, + { + "type": "doc", + "id": "dynamicmethod", + "label": "6.14 动态方法调用" + }, + { + "type": "doc", + "id": "othercore", + "label": "6.15 其他相关功能类" + } + ] + }, + { + "type": "category", + "label": "07、Tcp组件", + "items": [ + { + "type": "doc", + "id": "tcpintroduction", + "label": "7.1 Tcp入门基础" + }, + { + "type": "doc", + "id": "tcpservice", + "label": "7.2 创建TcpService" + }, + { + "type": "doc", + "id": "tcpclient", + "label": "7.3 创建TcpClient" + }, + { + "type": "doc", + "id": "natservice", + "label": "7.4 Tcp端口转发" + }, + { + "type": "doc", + "id": "resetid", + "label": "7.5 服务器重置Id" + }, + { + "type": "doc", + "id": "tcpcommandlineplugin", + "label": "7.6 命令行执行插件" + }, + { + "type": "doc", + "id": "tcpcommonplugins", + "label": "7.7 其他常用插件" + }, + { + "type": "doc", + "id": "tcpaot", + "label": "7.8 AOT模式" + } + ] + }, + { + "type": "category", + "label": "08、NamedPipe组件", + "items": [ + { + "type": "doc", + "id": "namedpipedescription", + "label": "8.1 命名管道描述" + }, + { + "type": "doc", + "id": "namedpipeservice", + "label": "8.2 创建NamedPipeService" + }, + { + "type": "doc", + "id": "namedpipeclient", + "label": "8.2 创建NamedPipeClient" + } + ] + }, + { + "type": "category", + "label": "09、Udp组件", + "items": [ + { + "type": "doc", + "id": "udpsession", + "label": "9.1 创建UdpSession" + }, + { + "type": "doc", + "id": "udpwaitingclient", + "label": "9.2 同步请求数据" + }, + { + "type": "doc", + "id": "udptransmitbigdata", + "label": "9.3 传输大于64K的数据" + }, + { + "type": "doc", + "id": "udpbroadcast", + "label": "9.4 组播、广播" + } + ] + }, + { + "type": "doc", + "id": "serialportclient", + "label": "10、串口组件" + }, + { + "type": "doc", + "id": "waitingclient", + "label": "11、等待响应组件" + }, + { + "type": "category", + "label": "12、数据处理适配器", + "items": [ + { + "type": "doc", + "id": "adapterdescription", + "label": "12.1 介绍及使用" + }, + { + "type": "category", + "label": "12.2 单线程流式适配器", + "items": [ + { + "type": "doc", + "id": "datahandleadapter", + "label": "a.原始适配器" + }, + { + "type": "doc", + "id": "packageadapter", + "label": "b.内置包适配器" + }, + { + "type": "doc", + "id": "customdatahandlingadapter", + "label": "c.用户自定义适配器" + }, + { + "type": "doc", + "id": "customfixedheaderdatahandlingadapter", + "label": "d.模板解析固定包头适配器" + }, + { + "type": "doc", + "id": "bigfixedheadercustomdatahandlingadapter", + "label": "e.模板解析大数据固定包头适配器" + }, + { + "type": "doc", + "id": "customunfixedheaderdatahandlingadapter", + "label": "f.模板解析非固定包头适配器" + }, + { + "type": "doc", + "id": "custombigunfixedheaderdatahandlingadapter", + "label": "g.模板解析大数据非固定包头适配器" + }, + { + "type": "doc", + "id": "custombetweenanddatahandlingadapter", + "label": "h.模板解析区间数据适配器" + }, + { + "type": "doc", + "id": "customjsondatahandlingadapter", + "label": "i.模板解析“Json”数据适配器" + }, + { + "type": "doc", + "id": "customcountspliterdatahandlingadapter", + "label": "j.模板解析“固定数量分隔符”数据适配器" + } + ] + }, + { + "type": "category", + "label": "12.3 多线程非流式适配器", + "items": [ + { + "type": "doc", + "id": "udpdatahandlingadapter", + "label": "a.原始自定义适配器" + } + ] + }, + { + "type": "category", + "label": "12.4 适配器案例赏析", + "items": [ + { + "type": "doc", + "id": "adapterdemodescription", + "label": "a.说明" + }, + { + "type": "doc", + "id": "stategridtransmission", + "label": "b.国网输电i1标准版" + }, + { + "type": "doc", + "id": "adaptersiemenss7", + "label": "d.西门子S7" + } + ] + }, + { + "type": "doc", + "id": "independentusedatahandlingadapter", + "label": "12.5 独立使用适配器" + }, + { + "type": "doc", + "id": "adaptererrorcorrection", + "label": "12.6 适配器纠错" + }, + { + "type": "doc", + "id": "dataadaptertester", + "label": "12.7 适配器完整性、性能测试" + }, + { + "type": "doc", + "id": "adapterbuilder", + "label": "12.8 适配器消息构建器" + } + ] + }, + { + "type": "category", + "label": "13、Http组件", + "items": [ + { + "type": "doc", + "id": "httpservice", + "label": "13.1 创建HttpService" + }, + { + "type": "doc", + "id": "httpclient", + "label": "13.2 创建HttpClient" + }, + { + "type": "doc", + "id": "httpstaticpageplugin", + "label": "13.3 静态页面插件" + }, + { + "type": "doc", + "id": "cors", + "label": "13.4 Cors跨域" + } + ] + }, + { + "type": "category", + "label": "14、WebSocket组件", + "items": [ + { + "type": "doc", + "id": "websocketdescription", + "label": "14.1 产品及架构介绍" + }, + { + "type": "doc", + "id": "websocketservice", + "label": "14.2 创建WebSocket服务器" + }, + { + "type": "doc", + "id": "websocketclient", + "label": "14.3 创建WebSocket客户端" + }, + { + "type": "doc", + "id": "websocketheartbeat", + "label": "14.4 心跳设置" + }, + { + "type": "doc", + "id": "wscommandlineplugin", + "label": "14.5 快捷事务命令行" + } + ] + }, + { + "type": "category", + "label": "15、Rpc组件", + "items": [ + { + "type": "doc", + "id": "rpcdescription", + "label": "15.1 Rpc描述" + }, + { + "type": "doc", + "id": "rpcregister", + "label": "15.2 注册服务" + }, + { + "type": "doc", + "id": "rpcgenerateproxy", + "label": "15.3 生成调用代理" + }, + { + "type": "doc", + "id": "generateproxysourcegeneratordemo", + "label": "15.4 源生成代理推荐写法" + }, + { + "type": "doc", + "id": "rpcactionfilter", + "label": "15.5 Rpc服务AOP" + }, + { + "type": "doc", + "id": "rpcallcontext", + "label": "15.6 调用上下文" + }, + { + "type": "doc", + "id": "rpcratelimiting", + "label": "15.7 Rpc访问速率限制" + }, + { + "type": "doc", + "id": "rpcdispatcher", + "label": "15.8 Rpc执行调度器" + }, + { + "type": "doc", + "id": "rpcauthorization", + "label": "15.9 Rpc鉴权授权策略" + } + ] + }, + { + "type": "category", + "label": "16、Dmtp组件", + "items": [ + { + "type": "doc", + "id": "dmtpdescription", + "label": "16.1 产品及架构介绍" + }, + { + "type": "doc", + "id": "dmtpservice", + "label": "16.2 创建Dmtp服务器" + }, + { + "type": "doc", + "id": "dmtplient", + "label": "16.3 创建Dmtp客户端" + }, + { + "type": "doc", + "label": "16.4 基础功能", + "id": "dmtpbase" + }, + { + "type": "category", + "label": "16.5 进阶功能", + "items": [ + { + "type": "doc", + "label": "a.自定义DmtpActor", + "id": "dmtpcustomactor" + } + ] + }, + { + "type": "doc", + "label": "16.6 Rpc功能", + "id": "dmtprpc" + }, + { + "type": "doc", + "label": "16.7 文件传输", + "id": "dmtptransferfile" + }, + { + "type": "doc", + "id": "dmtpremoteaccess", + "label": "16.8 远程文件系统" + }, + { + "type": "doc", + "id": "dmtpremotestream", + "label": "16.9 远程流映射" + }, + { + "type": "doc", + "id": "dmtprouterpackage", + "label": "16.10 路由包传输" + }, + { + "type": "doc", + "id": "dmtpredis", + "label": "16.11 Redis缓存" + } + ] + }, + { + "type": "category", + "label": "17、WebApi组件", + "items": [ + { + "type": "doc", + "label": "17.1 WebApi", + "id": "webapi" + }, + { + "type": "doc", + "label": "17.2 Swagger页面", + "id": "swagger" + } + ] + }, + { + "type": "doc", + "label": "18、JsonRpc组件", + "id": "jsonrpc" + }, + { + "type": "doc", + "label": "19、XmlRpc组件", + "id": "xmlrpc" + }, + { + "type": "category", + "label": "20、Modbus组件", + "items": [ + { + "type": "doc", + "label": "20.1 Modbus主站", + "id": "modbusmaster" + }, + { + "type": "doc", + "label": "20.2 Modbus从站", + "id": "modbusslave" + } + ] + }, + { + "type": "doc", + "label": "21、通用主机(Hosting)", + "id": "generichost" + }, + { + "type": "category", + "label": "22、Mqtt组件", + "items": [ + { + "type": "doc", + "label": "22.1 Mqtt服务端", + "id": "mqttservice" + }, + { + "type": "doc", + "label": "22.2 Mqtt客户端", + "id": "mqttclient" + } + ] + }, + { + "type": "category", + "label": "23、PlcBridge组件", + "items": [ + { + "type": "doc", + "label": "23.1 PlcBridge说明", + "id": "plcbridgedescription" + }, + { + "type": "doc", + "label": "23.2 PlcBridge服务", + "id": "plcbridgeservice" + }, + { + "type": "doc", + "label": "23.3 PlcBridgeModbus", + "id": "plcbridgemodbus" + } + ] + } + ] +} \ No newline at end of file diff --git a/handbook/versions.json b/handbook/versions.json index e2e915d47..488c98d75 100644 --- a/handbook/versions.json +++ b/handbook/versions.json @@ -1,4 +1,5 @@ [ + "3.1", "3.0", "2.1", "2.0", diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 52f05ecd9..9092753a0 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,12 +1,13 @@  + $(TouchSocketVersion) false true - latest + preview 若汝棋茗 Copyright © 2025 若汝棋茗 https://touchsocket.net/ @@ -15,22 +16,15 @@ true 若汝棋茗 ..\..\Build - IDE0290;IDE0090;IDE0305;IDE0028;IDE0300;IDE0130;IDE0057 true true $(SolutionDir)\Build\NugetPackages\Packages + logo.png + Readme.md - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - True @@ -39,8 +33,6 @@ True - - true @@ -51,45 +43,143 @@ - + + - ValueTask;Unsafe;SystemNetHttp;SystemMemory;AsyncLocal - - - ValueTask;Unsafe;SystemMemory - - - ValueTask;Unsafe;SystemMemory;AsyncLocal + - ValueTask;Unsafe;SystemMemory;AsyncLocal + - ValueTask;Unsafe;SystemMemory;AsyncLocal + - ValueTask;Unsafe;SystemNetHttp;SystemMemory;SystemTextJson;AsyncEnumerable;AsyncLocal + + + AsyncEnumerable; + - ValueTask;Unsafe;SystemNetHttp;SystemMemory;SystemTextJson;AsyncEnumerable;DefaultInterfaceMethods;AsyncLocal + + + AsyncEnumerable;DefaultInterfaceMethods; + - ValueTask;Unsafe;SystemNetHttp;SystemMemory;SystemTextJson;AsyncEnumerable;DisposeAsync;DefaultInterfaceMethods;AsyncLocal + + + AsyncEnumerable;DisposeAsync;DefaultInterfaceMethods;CollectionsMarshal + - - ValueTask;Unsafe;SystemNetHttp;SystemMemory;SystemTextJson;AsyncEnumerable;DisposeAsync;DefaultInterfaceMethods;AsyncLocal + + + + AsyncEnumerable;DisposeAsync;DefaultInterfaceMethods;CollectionsMarshal + - ValueTask;Unsafe;SystemNetHttp;SystemMemory;SystemTextJson;AsyncEnumerable;DisposeAsync;DefaultInterfaceMethods;AsyncLocal + + + AsyncEnumerable;DisposeAsync;DefaultInterfaceMethods;CollectionsMarshal + - + - Readme.md + $(ReadmeFile) - + + + + + LICENSEPRO.txt + LICENSE.txt + + + + + + + + + + + + + logo.png + true + true + + + + + true + $(LicenseFile) + + + + + True + Embedded + True + + + + + True + Embedded + + + + + + + + + True + Embedded + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/TouchSocket.AspNetCore/Dmtp/Components/WebSocketDmtpService.cs b/src/TouchSocket.AspNetCore/Dmtp/Components/WebSocketDmtpService.cs index cbd1257d3..4be4bd2c3 100644 --- a/src/TouchSocket.AspNetCore/Dmtp/Components/WebSocketDmtpService.cs +++ b/src/TouchSocket.AspNetCore/Dmtp/Components/WebSocketDmtpService.cs @@ -11,11 +11,6 @@ //------------------------------------------------------------------------------ using Microsoft.AspNetCore.Http; -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Resources; using TouchSocket.Sockets; @@ -43,7 +38,7 @@ public class WebSocketDmtpService : ConnectableService - public override async Task ResetIdAsync(string sourceId, string targetId) + public override async Task ResetIdAsync(string sourceId, string targetId, CancellationToken cancellationToken = default) { ThrowHelper.ThrowArgumentNullExceptionIfStringIsNullOrEmpty(sourceId, nameof(sourceId)); ThrowHelper.ThrowArgumentNullExceptionIfStringIsNullOrEmpty(targetId, nameof(targetId)); @@ -54,7 +49,7 @@ public class WebSocketDmtpService : ConnectableService> findDmtpActor = default; + var dmtpRouteService = this.Resolver.Resolve(); + if (dmtpRouteService != null) + { + allowRoute = true; + findDmtpActor = dmtpRouteService.FindDmtpActor; + } + + async Task FindDmtpActor(string id) + { + if (allowRoute) + { + if (findDmtpActor != null) + { + return await findDmtpActor.Invoke(id).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + if (this.Clients.TryGetClient(id, out var client) && client is IDmtpActorObject dmtpActorObject) + { + return dmtpActorObject.DmtpActor; + } + return null; + } + else + { + return null; + } + } + + client.InitDmtpActor(allowRoute, FindDmtpActor); await client.Start(webSocket, context).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } else @@ -129,34 +157,6 @@ public class WebSocketDmtpService : ConnectableService - /// 创建DmtpActor对象。 - /// - /// 关联的WebSocketDmtpSessionClient对象。 - /// SealedDmtpActor对象。 - private SealedDmtpActor CreateDmtpActor(WebSocketDmtpSessionClient client) - { - return new SealedDmtpActor(true) - { - FindDmtpActor = this.OnServiceFindDmtpActor, - Id = client.Id - }; - } - - /// - /// 服务查找DmtpActor的方法。 - /// - /// DmtpActor的标识符。 - /// DmtpActor对象或默认值。 - private Task OnServiceFindDmtpActor(string id) - { - if (this.TryGetClient(id, out var client)) - { - return Task.FromResult(client.DmtpActor); - } - return Task.FromResult(default); - } - #region override /// @@ -193,7 +193,7 @@ public class WebSocketDmtpService : ConnectableService /// 异步任务。 /// 抛出不支持异常。 - public override Task StopAsync(CancellationToken token = default) + public override Task StopAsync(CancellationToken cancellationToken = default) { throw new NotSupportedException("此服务的生命周期跟随主Host"); } diff --git a/src/TouchSocket.AspNetCore/Dmtp/Components/WebSocketDmtpSessionClient.cs b/src/TouchSocket.AspNetCore/Dmtp/Components/WebSocketDmtpSessionClient.cs index f1b359e14..bbb65f151 100644 --- a/src/TouchSocket.AspNetCore/Dmtp/Components/WebSocketDmtpSessionClient.cs +++ b/src/TouchSocket.AspNetCore/Dmtp/Components/WebSocketDmtpSessionClient.cs @@ -11,57 +11,47 @@ //------------------------------------------------------------------------------ using Microsoft.AspNetCore.Http; -using System; +using System.Buffers; using System.Net.WebSockets; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; +using TouchSocket.Http.WebSockets; using TouchSocket.Resources; using TouchSocket.Sockets; namespace TouchSocket.Dmtp.AspNetCore; /// -/// WebSocketDmtpSessionClient +/// WebSocket Dmtp会话客户端。 /// public class WebSocketDmtpSessionClient : ResolverConfigObject, IWebSocketDmtpSessionClient { /// - /// WebSocketDmtpSessionClient + /// 初始化WebSocket Dmtp会话客户端。 /// public WebSocketDmtpSessionClient() { - this.m_receiveCounter = new ValueCounter - { - Period = TimeSpan.FromSeconds(1), - OnPeriod = this.OnReceivePeriod - }; - this.m_sentCounter = new ValueCounter - { - Period = TimeSpan.FromSeconds(1), - OnPeriod = this.OnSendPeriod - }; + this.m_receiveCounter = new ValueCounter(TimeSpan.FromSeconds(1), this.OnReceivePeriod); + this.m_sentCounter = new ValueCounter(TimeSpan.FromSeconds(1), this.OnSendPeriod); } #region 字段 - private WebSocket m_client; + private ClosedEventArgs m_closedEventArgs; private TouchSocketConfig m_config; - private DmtpActor m_dmtpActor; + private SealedDmtpActor m_dmtpActor; private DmtpAdapter m_dmtpAdapter; - private IPluginManager m_pluginManager; - private int m_receiveBufferSize = 1024 * 10; - private ValueCounter m_receiveCounter; - private IResolver m_resolver; - private int m_sendBufferSize = 1024 * 10; - private ValueCounter m_sentCounter; - private WebSocketDmtpService m_service; private HttpContext m_httpContext; private string m_id; - private readonly Lock m_locker = new Lock(); - private CancellationTokenSource m_tokenSourceForReceive; + private IPluginManager m_pluginManager; + private ValueCounter m_receiveCounter; + private IResolver m_resolver; + private ValueCounter m_sentCounter; + private WebSocketDmtpService m_service; + #endregion 字段 + /// + public CancellationToken ClosedToken => this.m_httpContext?.RequestAborted ?? new CancellationToken(true); + /// public override TouchSocketConfig Config => this.m_config; @@ -77,15 +67,15 @@ public class WebSocketDmtpSessionClient : ResolverConfigObject, IWebSocketDmtpSe /// public bool IsClient => false; - /// - public bool Online => this.DmtpActor.Online; - /// public DateTimeOffset LastReceivedTime => this.m_receiveCounter.LastIncrement; /// public DateTimeOffset LastSentTime => this.m_sentCounter.LastIncrement; + /// + public bool Online => this.DmtpActor.Online; + /// public override IPluginManager PluginManager => this.m_pluginManager; @@ -105,19 +95,28 @@ public class WebSocketDmtpSessionClient : ResolverConfigObject, IWebSocketDmtpSe public string VerifyToken => this.Config.GetValue(DmtpConfigExtension.DmtpOptionProperty).VerifyToken; /// - public async Task CloseAsync(string msg, CancellationToken token = default) + public async Task CloseAsync(string msg, CancellationToken cancellationToken = default) { try { - if (this.m_dmtpActor != null) + if (!this.Online) { - await this.m_dmtpActor.CloseAsync(msg, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return Result.Success; } - if (this.m_client != null) + await this.OnDmtpClosing(new ClosingEventArgs(msg)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + var dmtpActor = this.m_dmtpActor; + if (dmtpActor != null) { - await this.m_client.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, msg, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + // 向IDmtpActor对象发送关闭消息 + await dmtpActor.SendCloseAsync(msg).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + // 关闭IDmtpActor对象 + await dmtpActor.CloseAsync(msg, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - this.Abort(true, msg); + + await this.m_client.SafeCloseClientAsync(msg, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return Result.Success; } catch (Exception ex) @@ -127,7 +126,7 @@ public class WebSocketDmtpSessionClient : ResolverConfigObject, IWebSocketDmtpSe } /// - public async Task ResetIdAsync(string newId) + public async Task ResetIdAsync(string newId, CancellationToken cancellationToken = default) { ThrowHelper.ThrowArgumentNullExceptionIfStringIsNullOrEmpty(newId, nameof(newId)); if (this.m_id == newId) @@ -135,7 +134,7 @@ public class WebSocketDmtpSessionClient : ResolverConfigObject, IWebSocketDmtpSe return; } - await this.DmtpActor.ResetIdAsync(newId).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.DmtpActor.ResetIdAsync(newId, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); await this.ProtectedResetIdAsync(newId).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } @@ -144,8 +143,7 @@ public class WebSocketDmtpSessionClient : ResolverConfigObject, IWebSocketDmtpSe this.m_config = config; this.m_dmtpAdapter = new DmtpAdapter() { - ReceivedAsyncCallBack = this.PrivateHandleReceivedData, - Logger = this.Logger + ReceivedAsyncCallBack = this.PrivateHandleReceivedData }; this.m_dmtpAdapter.Config(config); @@ -172,84 +170,41 @@ public class WebSocketDmtpSessionClient : ResolverConfigObject, IWebSocketDmtpSe this.m_service = service; } - internal void SetDmtpActor(DmtpActor actor) + internal void InitDmtpActor(bool allowRoute, Func> onServiceFindDmtpActor) { - actor.IdChanged = this.ThisOnResetId; - actor.OutputSendAsync = this.OnDmtpActorSendAsync; - //actor.OutputSend = this.OnDmtpActorSend; - actor.Client = this; - actor.Closing = this.OnDmtpActorClose; - actor.Routing = this.OnDmtpActorRouting; - actor.Handshaked = this.OnDmtpActorHandshaked; - actor.Handshaking = this.OnDmtpActorHandshaking; - actor.CreatedChannel = this.OnDmtpActorCreateChannel; - actor.Logger = this.Logger; + var actor = new SealedDmtpActor(allowRoute, true) + { + Id = this.m_id, + FindDmtpActor = onServiceFindDmtpActor, + IdChanged = this.ThisOnResetId, + OutputSendAsync = this.OnDmtpActorSendAsync, + Client = this, + Closing = this.OnDmtpActorClose, + Routing = this.OnDmtpActorRouting, + Connected = this.OnDmtpActorConnected, + Connecting = this.OnDmtpActorConnecting, + CreatedChannel = this.OnDmtpActorCreateChannel, + Logger = this.Logger + }; + this.m_dmtpActor = actor; } internal async Task Start(WebSocket client, HttpContext context) { - var tokenSourceForReceive = new CancellationTokenSource(); - this.m_tokenSourceForReceive = tokenSourceForReceive; - var token = tokenSourceForReceive.Token; - + var cancellationToken = context.RequestAborted; this.m_client = client; this.m_httpContext = context; - var msg = string.Empty; - try - { - while (true) - { - using (var byteBlock = new ByteBlock(this.m_receiveBufferSize)) - { - if (client.State != WebSocketState.Open) - { - msg = TouchSocketResource.ClientNotConnected; - break; - } - var result = await client.ReceiveAsync(byteBlock.TotalMemory, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - - if (result.MessageType == WebSocketMessageType.Close) - { - try - { - if (!token.IsCancellationRequested) - { - await client.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - } - catch - { - } - break; - } - if (result.Count == 0) - { - break; - } - byteBlock.SetLength(result.Count); - this.m_receiveCounter.Increment(result.Count); - - await this.m_dmtpAdapter.ReceivedInputAsync(byteBlock).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - } - } - catch (Exception ex) - { - msg = ex.Message; - } - finally - { - this.Abort(false, msg); - } + await this.ReceiveLoopAsync(client, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + this.m_service.TryRemove(this.m_id, out _); } /// /// 直接重置Id。 /// - /// - /// - /// + /// 目标Id + /// Id重复时抛出异常 + /// 找不到客户端时抛出异常 protected async Task ProtectedResetIdAsync(string targetId) { var sourceId = this.m_id; @@ -285,64 +240,118 @@ public class WebSocketDmtpSessionClient : ResolverConfigObject, IWebSocketDmtpSe } /// - protected override void Dispose(bool disposing) + protected override void SafetyDispose(bool disposing) { if (disposing) { - this.Abort(true, $"{nameof(Dispose)}断开链接"); - } - } - - private void CancelReceive() - { - var tokenSourceForReceive = this.m_tokenSourceForReceive; - if (tokenSourceForReceive != null) - { - tokenSourceForReceive.Cancel(); - tokenSourceForReceive.Dispose(); - } - this.m_tokenSourceForReceive = null; - } - - private void Abort(bool manual, string msg) - { - lock (this.m_locker) - { - if (this.DisposedValue) - { - return; - } - - base.Dispose(true); - this.m_client.SafeDispose(); - this.DmtpActor.SafeDispose(); - this.CancelReceive(); - if (this.m_service.TryRemove(this.m_id, out _)) - { - //if (this.PluginManager.Enable) - //{ - // this.PluginManager.RaiseAsync(nameof(ITcpDisconnectedPlugin.OnTcpClosed), this, new ClosedEventArgs(manual, msg)); - //} - } + _ = EasyTask.SafeRun(async () => await this.CloseAsync(TouchSocketResource.DisposeClose).ConfigureAwait(EasyTask.ContinueOnCapturedContext)); } } private void OnReceivePeriod(long value) { - this.m_receiveBufferSize = TouchSocketCoreUtility.HitBufferLength(value); + //this.m_receiveBufferSize = TouchSocketCoreUtility.HitBufferLength(value); } private void OnSendPeriod(long value) { - this.m_sendBufferSize = TouchSocketCoreUtility.HitBufferLength(value); + //this.m_sendBufferSize = TouchSocketCoreUtility.HitBufferLength(value); + } + + private async Task OnWebSocketReceived(WebSocketReceiveResult result, ReadOnlySequence sequence) + { + try + { + using (var buffer = new ContiguousMemoryBuffer(sequence)) + { + var message = DmtpMessage.CreateFrom(buffer.Memory); + if (!await this.m_dmtpActor.InputReceivedData(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) + { + await this.PluginManager.RaiseAsync(typeof(IDmtpReceivedPlugin), this.Resolver, this, new DmtpMessageEventArgs(message)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + } + } + catch (Exception ex) + { + this.Logger?.Debug(this, ex); + } + } + + private async Task PrivateHandleReceivedData(ReadOnlyMemory memory, IRequestInfo requestInfo) + { + var message = (DmtpMessage)requestInfo; + if (!await this.m_dmtpActor.InputReceivedData(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) + { + await this.PluginManager.RaiseAsync(typeof(IDmtpReceivedPlugin), this.Resolver, this, new DmtpMessageEventArgs(message)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + } + + private async Task ReceiveLoopAsync(WebSocket client, CancellationToken cancellationToken) + { + try + { + SegmentedBytesWriter writer = default; + while (!cancellationToken.IsCancellationRequested) + { + try + { + if (client == null || client.State != WebSocketState.Open) + { + break; + } + writer ??= new SegmentedBytesWriter(1024 * 64); + var segment = writer.GetMemory(1024 * 64).GetArray(); + var result = await client.ReceiveAsync(segment, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + if (result.MessageType == WebSocketMessageType.Close) + { + try + { + if (!cancellationToken.IsCancellationRequested) + { + await client.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + } + catch + { + } + break; + } + + if (result.Count == 0) + { + break; + } + this.m_receiveCounter.Increment(result.Count); + writer.Advance(result.Count); + if (result.EndOfMessage) + { + //处理数据 + await this.OnWebSocketReceived(result, writer.Sequence).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + writer.Dispose(); + writer = null; + } + } + catch (Exception ex) + { + this.Logger?.Debug(this, ex); + break; + } + } + this.m_closedEventArgs ??= new ClosedEventArgs(false, TouchSocketResource.RemoteDisconnects); + } + catch (Exception ex) + { + this.m_closedEventArgs = new ClosedEventArgs(false, ex.Message); + this.Logger?.Debug(this, ex); + } } #region 内部委托绑定 - private Task OnDmtpActorClose(DmtpActor actor, string msg) + private async Task OnDmtpActorClose(DmtpActor actor, string msg) { - this.Abort(true, msg); - return EasyTask.CompletedTask; + await this.CloseAsync(msg, CancellationToken.None); } private Task OnDmtpActorCreateChannel(DmtpActor actor, CreateChannelEventArgs e) @@ -350,12 +359,12 @@ public class WebSocketDmtpSessionClient : ResolverConfigObject, IWebSocketDmtpSe return this.OnCreateChannel(e); } - private Task OnDmtpActorHandshaked(DmtpActor actor, DmtpVerifyEventArgs e) + private Task OnDmtpActorConnected(DmtpActor actor, DmtpVerifyEventArgs e) { - return this.OnHandshaked(e); + return this.OnConnected(e); } - private async Task OnDmtpActorHandshaking(DmtpActor actor, DmtpVerifyEventArgs e) + private async Task OnDmtpActorConnecting(DmtpActor actor, DmtpVerifyEventArgs e) { if (e.Token == this.VerifyToken) { @@ -365,7 +374,7 @@ public class WebSocketDmtpSessionClient : ResolverConfigObject, IWebSocketDmtpSe { e.Message = "Token不受理"; } - await this.OnHandshaking(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.OnConnecting(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } private Task OnDmtpActorRouting(DmtpActor actor, PackageRouterEventArgs e) @@ -373,35 +382,9 @@ public class WebSocketDmtpSessionClient : ResolverConfigObject, IWebSocketDmtpSe return this.OnRouting(e); } - //private void OnDmtpActorSend(DmtpActor actor, ArraySegment[] transferBytes) - //{ - // try - // { - // this.m_semaphoreForSend.Wait(); - // for (var i = 0; i < transferBytes.Length; i++) - // { - // Task task; - // if (i == transferBytes.Length - 1) - // { - // task = this.m_client.SendAsync(transferBytes[i], WebSocketMessageType.Binary, true, CancellationToken.None); - // } - // else - // { - // task = this.m_client.SendAsync(transferBytes[i], WebSocketMessageType.Binary, false, CancellationToken.None); - // } - // task.GetFalseAwaitResult(); - // this.m_sendCounter.Increment(transferBytes[i].Count); - // } - // } - // finally - // { - // this.m_semaphoreForSend.Release(); - // } - //} - - private async Task OnDmtpActorSendAsync(DmtpActor actor, ReadOnlyMemory memory) + private async Task OnDmtpActorSendAsync(DmtpActor actor, ReadOnlyMemory memory, CancellationToken cancellationToken) { - await this.m_client.SendAsync(memory.GetArray(), WebSocketMessageType.Binary, true, CancellationToken.None).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.m_client.SendAsync(memory.GetArray(), WebSocketMessageType.Binary, true, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); this.m_sentCounter.Increment(memory.Length); } @@ -412,7 +395,7 @@ public class WebSocketDmtpSessionClient : ResolverConfigObject, IWebSocketDmtpSe /// /// 当创建通道 /// - /// + /// 通道事件参数 protected virtual async Task OnCreateChannel(CreateChannelEventArgs e) { if (e.Handled) @@ -426,33 +409,33 @@ public class WebSocketDmtpSessionClient : ResolverConfigObject, IWebSocketDmtpSe /// /// 在完成握手连接时 /// - /// - protected virtual async Task OnHandshaked(DmtpVerifyEventArgs e) + /// 验证事件参数 + protected virtual async Task OnConnected(DmtpVerifyEventArgs e) { if (e.Handled) { return; } - await this.PluginManager.RaiseAsync(typeof(IDmtpHandshakedPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.PluginManager.RaiseAsync(typeof(IDmtpConnectedPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// /// 在验证Token时 /// - /// 参数 - protected virtual async Task OnHandshaking(DmtpVerifyEventArgs e) + /// 验证事件参数 + protected virtual async Task OnConnecting(DmtpVerifyEventArgs e) { if (e.Handled) { return; } - await this.PluginManager.RaiseAsync(typeof(IDmtpHandshakingPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.PluginManager.RaiseAsync(typeof(IDmtpConnectingPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// /// 在需要转发路由包时。 /// - /// + /// 路由事件参数 protected virtual async Task OnRouting(PackageRouterEventArgs e) { if (e.Handled) @@ -462,17 +445,45 @@ public class WebSocketDmtpSessionClient : ResolverConfigObject, IWebSocketDmtpSe await this.PluginManager.RaiseAsync(typeof(IDmtpRoutingPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - #endregion 事件 - - private async Task PrivateHandleReceivedData(ByteBlock byteBlock, IRequestInfo requestInfo) + /// + /// 当Dmtp即将被关闭时触发。 + /// + /// 该触发条件有2种: + /// + /// 终端主动调用 + /// 终端收到的请求。 + /// + /// + /// + /// 关闭事件参数 + /// 异步操作任务 + protected virtual async Task OnDmtpClosing(ClosingEventArgs e) { - var message = (DmtpMessage)requestInfo; - if (!await this.m_dmtpActor.InputReceivedData(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) + // 如果关闭事件已经被处理,则直接返回,不再执行后续操作。 + if (e.Handled) { - await this.PluginManager.RaiseAsync(typeof(IDmtpReceivedPlugin), this.Resolver, this, new DmtpMessageEventArgs(message)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return; } + // 通知插件管理器,触发IDmtpClosingPlugin接口的事件处理程序,并传递相关参数。 + await this.PluginManager.RaiseAsync(typeof(IDmtpClosingPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } + /// + /// 已断开连接。 + /// + /// 断开事件参数 + protected virtual async Task OnDmtpClosed(ClosedEventArgs e) + { + // 如果事件已经被处理,则直接返回 + if (e.Handled) + { + return; + } + // 异步触发插件管理器中的 IDmtpClosedPlugin 接口的事件,并传递相关参数 + await this.PluginManager.RaiseAsync(typeof(IDmtpClosedPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + #endregion 事件 + private async Task ThisOnResetId(DmtpActor actor, IdChangedEventArgs e) { await this.ProtectedResetIdAsync(e.NewId).ConfigureAwait(EasyTask.ContinueOnCapturedContext); diff --git a/src/TouchSocket.AspNetCore/Dmtp/Extensions/WebSocketDmtpServiceExtensions.cs b/src/TouchSocket.AspNetCore/Dmtp/Extensions/WebSocketDmtpServiceExtensions.cs index d72c54337..61b83cab7 100644 --- a/src/TouchSocket.AspNetCore/Dmtp/Extensions/WebSocketDmtpServiceExtensions.cs +++ b/src/TouchSocket.AspNetCore/Dmtp/Extensions/WebSocketDmtpServiceExtensions.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using TouchSocket.Core; using TouchSocket.Dmtp.AspNetCore; namespace Microsoft.Extensions.DependencyInjection; diff --git a/src/TouchSocket.AspNetCore/Dmtp/Interfaces/IWebSocketDmtpService.cs b/src/TouchSocket.AspNetCore/Dmtp/Interfaces/IWebSocketDmtpService.cs index 03b45da37..28d431ed4 100644 --- a/src/TouchSocket.AspNetCore/Dmtp/Interfaces/IWebSocketDmtpService.cs +++ b/src/TouchSocket.AspNetCore/Dmtp/Interfaces/IWebSocketDmtpService.cs @@ -11,7 +11,6 @@ //------------------------------------------------------------------------------ using Microsoft.AspNetCore.Http; -using System.Threading.Tasks; using TouchSocket.Sockets; namespace TouchSocket.Dmtp.AspNetCore; diff --git a/src/TouchSocket.AspNetCore/Dmtp/Interfaces/IWebSocketDmtpSessionClient.cs b/src/TouchSocket.AspNetCore/Dmtp/Interfaces/IWebSocketDmtpSessionClient.cs index 5dc532a7e..7bc5087ce 100644 --- a/src/TouchSocket.AspNetCore/Dmtp/Interfaces/IWebSocketDmtpSessionClient.cs +++ b/src/TouchSocket.AspNetCore/Dmtp/Interfaces/IWebSocketDmtpSessionClient.cs @@ -11,7 +11,6 @@ //------------------------------------------------------------------------------ using Microsoft.AspNetCore.Http; -using TouchSocket.Core; using TouchSocket.Sockets; namespace TouchSocket.Dmtp.AspNetCore; diff --git a/src/TouchSocket.AspNetCore/Dmtp/Middlewares/WebSocketDmtpMiddleware.cs b/src/TouchSocket.AspNetCore/Dmtp/Middlewares/WebSocketDmtpMiddleware.cs index 4eec70f98..6bd426f0b 100644 --- a/src/TouchSocket.AspNetCore/Dmtp/Middlewares/WebSocketDmtpMiddleware.cs +++ b/src/TouchSocket.AspNetCore/Dmtp/Middlewares/WebSocketDmtpMiddleware.cs @@ -11,9 +11,6 @@ //------------------------------------------------------------------------------ using Microsoft.AspNetCore.Http; -using System; -using System.Threading.Tasks; -using TouchSocket.Core; namespace TouchSocket.Dmtp.AspNetCore; diff --git a/src/TouchSocket.AspNetCore/Extensions/AspNetCoreExtension.cs b/src/TouchSocket.AspNetCore/Extensions/AspNetCoreExtension.cs index bfe406cc5..f25b4f96d 100644 --- a/src/TouchSocket.AspNetCore/Extensions/AspNetCoreExtension.cs +++ b/src/TouchSocket.AspNetCore/Extensions/AspNetCoreExtension.cs @@ -10,8 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using TouchSocket.Core; +using System.Diagnostics.CodeAnalysis; using TouchSocket.Dmtp; using TouchSocket.Http; @@ -35,7 +34,7 @@ public static class AspNetCoreExtension /// 服务集合,用于存储应用程序中所有注册的服务。 /// 配置操作委托,用于定制TouchSocket的配置。 /// 返回扩展后的服务集合,允许方法链式调用。 - public static IServiceCollection AddTcpDmtpService(this IServiceCollection services, Action actionConfig) + public static IServiceCollection AddTcpDmtpService(this IServiceCollection services, Action actionConfig) where TService : class, ITcpDmtpServiceBase where TImpService : class, TService { @@ -68,7 +67,7 @@ public static class AspNetCoreExtension /// /// /// - public static IServiceCollection AddScopedTcpDmtpClient(this IServiceCollection services, Action actionConfig) + public static IServiceCollection AddScopedTcpDmtpClient(this IServiceCollection services, Action actionConfig) where TClient : class, ITcpDmtpClient where TImpClient : class, TClient { @@ -94,7 +93,7 @@ public static class AspNetCoreExtension /// /// /// - public static IServiceCollection AddSingletonTcpDmtpClient(this IServiceCollection services, Action actionConfig) + public static IServiceCollection AddSingletonTcpDmtpClient(this IServiceCollection services, Action actionConfig) where TClient : class, ITcpDmtpClient where TImpClient : class, TClient { @@ -120,7 +119,7 @@ public static class AspNetCoreExtension /// /// /// - public static IServiceCollection AddTransientTcpDmtpClient(this IServiceCollection services, Action actionConfig) + public static IServiceCollection AddTransientTcpDmtpClient(this IServiceCollection services, Action actionConfig) where TClient : class, ITcpDmtpClient where TImpClient : class, TClient { @@ -150,7 +149,7 @@ public static class AspNetCoreExtension /// /// /// - public static IServiceCollection AddHttpService(this IServiceCollection services, Action actionConfig) + public static IServiceCollection AddHttpService(this IServiceCollection services, Action actionConfig) where TService : class, IHttpServiceBase where TImpService : class, TService { @@ -180,7 +179,7 @@ public static class AspNetCoreExtension /// /// /// - public static IServiceCollection AddScopedHttpClient(this IServiceCollection services, Action actionConfig) + public static IServiceCollection AddScopedHttpClient(this IServiceCollection services, Action actionConfig) where TClient : class, IHttpClient where TImpClient : class, TClient { @@ -206,7 +205,7 @@ public static class AspNetCoreExtension /// /// /// - public static IServiceCollection AddSingletonHttpClient(this IServiceCollection services, Action actionConfig) + public static IServiceCollection AddSingletonHttpClient(this IServiceCollection services, Action actionConfig) where TClient : class, IHttpClient where TImpClient : class, TClient { @@ -232,7 +231,7 @@ public static class AspNetCoreExtension /// /// /// - public static IServiceCollection AddTransientHttpClient(this IServiceCollection services, Action actionConfig) + public static IServiceCollection AddTransientHttpClient(this IServiceCollection services, Action actionConfig) where TClient : class, IHttpClient where TImpClient : class, TClient { @@ -262,7 +261,7 @@ public static class AspNetCoreExtension /// /// /// - public static IServiceCollection AddHttpDmtpService(this IServiceCollection services, Action actionConfig) + public static IServiceCollection AddHttpDmtpService(this IServiceCollection services, Action actionConfig) where TService : class, IHttpDmtpServiceBase where TImpService : class, TService { @@ -292,7 +291,7 @@ public static class AspNetCoreExtension /// /// /// - public static IServiceCollection AddScopedHttpDmtpClient(this IServiceCollection services, Action actionConfig) + public static IServiceCollection AddScopedHttpDmtpClient(this IServiceCollection services, Action actionConfig) where TClient : class, IHttpDmtpClient where TImpClient : class, TClient { @@ -318,7 +317,7 @@ public static class AspNetCoreExtension /// /// /// - public static IServiceCollection AddSingletonHttpDmtpClient(this IServiceCollection services, Action actionConfig) + public static IServiceCollection AddSingletonHttpDmtpClient(this IServiceCollection services, Action actionConfig) where TClient : class, IHttpDmtpClient where TImpClient : class, TClient { @@ -344,7 +343,7 @@ public static class AspNetCoreExtension /// /// /// - public static IServiceCollection AddTransientHttpDmtpClient(this IServiceCollection services, Action actionConfig) + public static IServiceCollection AddTransientHttpDmtpClient(this IServiceCollection services, Action actionConfig) where TClient : class, IHttpDmtpClient where TImpClient : class, TClient { diff --git a/src/TouchSocket.AspNetCore/Readme.md b/src/TouchSocket.AspNetCore/Readme.md index 60c02d204..68d983129 100644 --- a/src/TouchSocket.AspNetCore/Readme.md +++ b/src/TouchSocket.AspNetCore/Readme.md @@ -12,7 +12,7 @@ TouchSocket.AspNetCore是TouchSocket的一个扩展包,提供了基于Asp.Net ## 支持的目标框架 - net6.0 - net8.0 -- net9.0 +- net10.0 ## 使用方法 diff --git a/src/TouchSocket.AspNetCore/TouchSocket.AspNetCore.csproj b/src/TouchSocket.AspNetCore/TouchSocket.AspNetCore.csproj index 45824c0a1..2a288eb06 100644 --- a/src/TouchSocket.AspNetCore/TouchSocket.AspNetCore.csproj +++ b/src/TouchSocket.AspNetCore/TouchSocket.AspNetCore.csproj @@ -1,6 +1,6 @@ - net6.0;net9.0;net8.0 + net6.0;net10.0;net8.0 Tcp;Udp;Ssl;Socket;Saea;AspNetCore;TouchSocket TouchSocket.AspNetCore是适用于AspNetCore的专属版本。 @@ -9,6 +9,7 @@ + @@ -23,6 +24,7 @@ + diff --git a/src/TouchSocket.AspNetCore/WebSockets/WebSocketService.cs b/src/TouchSocket.AspNetCore/WebSockets/WebSocketService.cs new file mode 100644 index 000000000..10b8a5a3d --- /dev/null +++ b/src/TouchSocket.AspNetCore/WebSockets/WebSocketService.cs @@ -0,0 +1,75 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; +//using System.Threading; +//using System.Threading.Tasks; +//using TouchSocket.Core; +//using TouchSocket.Sockets; + +//namespace TouchSocket.Http.WebSockets.AspNetCore; +//public class WebSocketService : ConnectableService +//{ +// #region 字段 +// private readonly InternalClientCollection m_clients = new (); +// #endregion + +// public override IClientCollection Clients => m_clients; + +// public override int Count => this.m_clients.Count; + +// public override ServerState ServerState => ServerState.Running; + +// public override async Task ClearAsync() +// { +// foreach (var id in this.GetIds()) +// { +// if (this.TryGetClient(id, out var client)) +// { +// await client.CloseAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); +// } +// } +// } + +// public override bool ClientExists(string id) +// { +// return this.m_clients.ClientExist(id); +// } + +// public override IEnumerable GetIds() +// { +// return this.m_clients.GetIds(); +// } + +// public override Task ResetIdAsync(string sourceId, string targetId) +// { +// throw new NotImplementedException(); +// } + +// public override Task StartAsync() +// { +// throw new NotImplementedException(); +// } + +// public override Task StopAsync(CancellationToken cancellationToken = default) +// { +// throw new NotImplementedException(); +// } + +// protected override WebSocketSessionClient NewClient() +// { +// throw new NotImplementedException(); +// } +//} diff --git a/src/TouchSocket.AspNetCore/WebSockets/WebSocketSessionClient.cs b/src/TouchSocket.AspNetCore/WebSockets/WebSocketSessionClient.cs new file mode 100644 index 000000000..b487fb8bb --- /dev/null +++ b/src/TouchSocket.AspNetCore/WebSockets/WebSocketSessionClient.cs @@ -0,0 +1,37 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; +//using System.Threading.Tasks; +//using TouchSocket.Core; +//using TouchSocket.Sockets; + +//namespace TouchSocket.Http.WebSockets.AspNetCore; +//public class WebSocketSessionClient : ResolverConfigObject, IWebSocketSessionClient +//{ +// private TouchSocketConfig m_config; +// private IPluginManager m_pluginManager; +// private IResolver m_resolver; +// public override IPluginManager PluginManager => m_pluginManager; + +// public override IResolver Resolver => m_resolver; + +// public override TouchSocketConfig Config => m_config; +//} + +//public interface IWebSocketSessionClient : IDependencyClient, IIdClient, IClosableClient, IResolverConfigObject, IOnlineClient +//{ + +//} diff --git a/src/TouchSocket.Core.Autofac/Config/AutofacConfigExtension.cs b/src/TouchSocket.Core.Autofac/Config/AutofacConfigExtension.cs index 5ec2dc1ae..3597cbcad 100644 --- a/src/TouchSocket.Core.Autofac/Config/AutofacConfigExtension.cs +++ b/src/TouchSocket.Core.Autofac/Config/AutofacConfigExtension.cs @@ -11,7 +11,6 @@ //------------------------------------------------------------------------------ using Autofac; -using System; namespace TouchSocket.Core; diff --git a/src/TouchSocket.Core.Autofac/Container/AutofacContainer.cs b/src/TouchSocket.Core.Autofac/Container/AutofacContainer.cs index dfc15767b..ca55fc4a4 100644 --- a/src/TouchSocket.Core.Autofac/Container/AutofacContainer.cs +++ b/src/TouchSocket.Core.Autofac/Container/AutofacContainer.cs @@ -11,8 +11,6 @@ //------------------------------------------------------------------------------ using Autofac; -using System; -using System.Collections.Generic; namespace TouchSocket.Core; @@ -59,7 +57,7 @@ public class AutofacContainer : IRegistrator, IResolver } /// - public bool IsRegistered(Type fromType, string key) + public bool IsRegistered(Type fromType, object key) { throw new NotSupportedException($"{this.GetType().Name}不支持包含Key的设定"); } @@ -79,7 +77,7 @@ public class AutofacContainer : IRegistrator, IResolver } /// - public void Register(DependencyDescriptor descriptor, string key) + public void Register(DependencyDescriptor descriptor, object key) { throw new NotSupportedException($"{this.GetType().Name}不支持包含Key的设定"); } @@ -108,7 +106,7 @@ public class AutofacContainer : IRegistrator, IResolver } /// - public void Unregister(DependencyDescriptor descriptor, string key) + public void Unregister(DependencyDescriptor descriptor, object key) { throw new NotSupportedException($"{this.GetType().Name}不支持包含Key的设定"); } @@ -132,7 +130,7 @@ public class AutofacContainer : IRegistrator, IResolver } /// - public object Resolve(Type fromType, string key) + public object Resolve(Type fromType, object key) { throw new NotImplementedException(); } diff --git a/src/TouchSocket.Core.Autofac/Readme.md b/src/TouchSocket.Core.Autofac/Readme.md index aa3e36045..d863e2f47 100644 --- a/src/TouchSocket.Core.Autofac/Readme.md +++ b/src/TouchSocket.Core.Autofac/Readme.md @@ -17,7 +17,7 @@ - netstandard2.0 - netstandard2.1 - net6.0 -- net9.0 +- net10.0 - net8.0 ## 使用方法 diff --git a/src/TouchSocket.Core.Autofac/TouchSocket.Core.Autofac.csproj b/src/TouchSocket.Core.Autofac/TouchSocket.Core.Autofac.csproj index 77a7dd4f0..4299b6ff2 100644 --- a/src/TouchSocket.Core.Autofac/TouchSocket.Core.Autofac.csproj +++ b/src/TouchSocket.Core.Autofac/TouchSocket.Core.Autofac.csproj @@ -1,6 +1,6 @@ - net462;net472;netstandard2.0;netstandard2.1;net6.0;net9.0;net8.0 + net462;netstandard2.0;netstandard2.1;net6.0;net10.0;net8.0 IOC;Autofac;DependencyInjection;TouchSocket 这是一个为Core中扩展Ioc容器为Autofac的库。 @@ -8,7 +8,7 @@ TouchSocket.Core.Autofac - + diff --git a/src/TouchSocket.Core.DependencyInjection/Config/AspNetCoreConfigExtension.cs b/src/TouchSocket.Core.DependencyInjection/Config/AspNetCoreConfigExtension.cs index 3d6627fda..d2e408038 100644 --- a/src/TouchSocket.Core.DependencyInjection/Config/AspNetCoreConfigExtension.cs +++ b/src/TouchSocket.Core.DependencyInjection/Config/AspNetCoreConfigExtension.cs @@ -11,7 +11,6 @@ //------------------------------------------------------------------------------ using Microsoft.Extensions.DependencyInjection; -using System; using TouchSocket.Core.AspNetCore; namespace TouchSocket.Core; diff --git a/src/TouchSocket.Core.DependencyInjection/Container/AspNetCoreContainer.cs b/src/TouchSocket.Core.DependencyInjection/Container/AspNetCoreContainer.cs index 44088dde4..3ec71146f 100644 --- a/src/TouchSocket.Core.DependencyInjection/Container/AspNetCoreContainer.cs +++ b/src/TouchSocket.Core.DependencyInjection/Container/AspNetCoreContainer.cs @@ -11,9 +11,6 @@ //------------------------------------------------------------------------------ using Microsoft.Extensions.DependencyInjection; -using System; -using System.Collections.Generic; -using System.Linq; namespace TouchSocket.Core.AspNetCore; @@ -74,7 +71,7 @@ public class AspNetCoreContainer : IRegistrator, IResolver, IKeyedServiceProvide } /// - public bool IsRegistered(Type fromType, string key) + public bool IsRegistered(Type fromType, object key) { return this.m_scopedResolver.IsRegistered(fromType, key); } @@ -86,7 +83,7 @@ public class AspNetCoreContainer : IRegistrator, IResolver, IKeyedServiceProvide } /// - public void Register(DependencyDescriptor descriptor, string key) + public void Register(DependencyDescriptor descriptor, object key) { switch (descriptor.Lifetime) { @@ -145,7 +142,7 @@ public class AspNetCoreContainer : IRegistrator, IResolver, IKeyedServiceProvide } /// - public void Unregister(DependencyDescriptor descriptor, string key) + public void Unregister(DependencyDescriptor descriptor, object key) { var array = this.m_services.ToArray(); foreach (var item in array) @@ -154,7 +151,7 @@ public class AspNetCoreContainer : IRegistrator, IResolver, IKeyedServiceProvide { continue; } - if (item.ServiceType == descriptor.FromType && item.ServiceKey?.ToString() == key) + if (item.ServiceType == descriptor.FromType && item.ServiceKey == key) { this.m_services.Remove(item); return; @@ -204,7 +201,7 @@ public class AspNetCoreContainer : IRegistrator, IResolver, IKeyedServiceProvide } /// - public object Resolve(Type fromType, string key) + public object Resolve(Type fromType, object key) { return this.m_scopedResolver.GetKeyedService(fromType, key); } diff --git a/src/TouchSocket.Core.DependencyInjection/Readme.md b/src/TouchSocket.Core.DependencyInjection/Readme.md index cdc541f49..69d9056bd 100644 --- a/src/TouchSocket.Core.DependencyInjection/Readme.md +++ b/src/TouchSocket.Core.DependencyInjection/Readme.md @@ -17,7 +17,7 @@ - netstandard2.0 - netstandard2.1 - net6.0 -- net9.0 +- net10.0 - net8.0 ## 使用方法 diff --git a/src/TouchSocket.Core.DependencyInjection/ScopedResolver.cs b/src/TouchSocket.Core.DependencyInjection/ScopedResolver.cs index fc0d3f4bd..5451de0d1 100644 --- a/src/TouchSocket.Core.DependencyInjection/ScopedResolver.cs +++ b/src/TouchSocket.Core.DependencyInjection/ScopedResolver.cs @@ -11,7 +11,6 @@ //------------------------------------------------------------------------------ using Microsoft.Extensions.DependencyInjection; -using System; namespace TouchSocket.Core.AspNetCore; @@ -46,7 +45,7 @@ internal class ScopedResolver : IResolver, IKeyedServiceProvider public IServiceProvider ServiceProvider { get => this.m_serviceProvider; set => this.m_serviceProvider = value; } /// - public bool IsRegistered(Type fromType, string key) + public bool IsRegistered(Type fromType, object key) { if (typeof(IResolver) == fromType) { @@ -58,7 +57,7 @@ internal class ScopedResolver : IResolver, IKeyedServiceProvider { continue; } - if (item.ServiceType == fromType && item.ServiceKey?.ToString() == key) + if (item.ServiceType == fromType && item.ServiceKey == key) { return true; } @@ -115,7 +114,7 @@ internal class ScopedResolver : IResolver, IKeyedServiceProvider } /// - public object Resolve(Type fromType, string key) + public object Resolve(Type fromType, object key) { return this.m_serviceProvider.GetRequiredKeyedService(fromType, key); } diff --git a/src/TouchSocket.Core.DependencyInjection/TouchSocket.Core.DependencyInjection.csproj b/src/TouchSocket.Core.DependencyInjection/TouchSocket.Core.DependencyInjection.csproj index 3665895fa..c0a36e7a7 100644 --- a/src/TouchSocket.Core.DependencyInjection/TouchSocket.Core.DependencyInjection.csproj +++ b/src/TouchSocket.Core.DependencyInjection/TouchSocket.Core.DependencyInjection.csproj @@ -1,6 +1,6 @@ - net462;net472;netstandard2.0;netstandard2.1;net6.0;net9.0;net8.0 + net462;netstandard2.0;netstandard2.1;net6.0;net10.0;net8.0 IOC;DependencyInjection;TouchSocket 这是一个为Core中扩展Ioc容器为IServiceCollection的库。 diff --git a/src/TouchSocket.Core.SourceGenerator/AnalyzerReleases.Shipped.md b/src/TouchSocket.Core.SourceGenerator/AnalyzerReleases.Shipped.md index 4e90c9eed..15d26bf81 100644 --- a/src/TouchSocket.Core.SourceGenerator/AnalyzerReleases.Shipped.md +++ b/src/TouchSocket.Core.SourceGenerator/AnalyzerReleases.Shipped.md @@ -13,4 +13,6 @@ | Plugin0002 | Plugin | Error | Plugin0002_AnalyzerName | | Plugin0003 | Plugin | Error | Plugin0003_AnalyzerName | | Plugin0004 | Plugin | Error | Plugin0004_AnalyzerName | -| FastSerialize0001 | FastSerialize | Error | FastSerialize0001_AnalyzerName | \ No newline at end of file +| FastSerialize0001 | FastSerialize | Error | FastSerialize0001_AnalyzerName | +| CodeAnalysis0001 | CodeAnalysis | Error | CodeAnalysis0001_AnalyzerName | +| CodeAnalysis0002 | CodeAnalysis | Error | CodeAnalysis0002_AnalyzerName | \ No newline at end of file diff --git a/src/TouchSocket.Core.SourceGenerator/Analyzers/CoreAnalyzer.cs b/src/TouchSocket.Core.SourceGenerator/Analyzers/CoreAnalyzer.cs index d98a7b6b1..87808294e 100644 --- a/src/TouchSocket.Core.SourceGenerator/Analyzers/CoreAnalyzer.cs +++ b/src/TouchSocket.Core.SourceGenerator/Analyzers/CoreAnalyzer.cs @@ -11,8 +11,13 @@ //------------------------------------------------------------------------------ using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; +using System; using System.Collections.Immutable; +using System.Globalization; using System.Linq; using System.Threading.Tasks; @@ -34,7 +39,10 @@ internal class CoreAnalyzer : DiagnosticAnalyzer DiagnosticDescriptors.m_rule_Plugin0002, DiagnosticDescriptors.m_rule_Plugin0003, DiagnosticDescriptors.m_rule_Plugin0004, - DiagnosticDescriptors.m_rule_FastSerialize0001 + DiagnosticDescriptors.m_rule_Plugin0005, + DiagnosticDescriptors.m_rule_FastSerialize0001, + DiagnosticDescriptors.m_rule_CodeAnalysis0001, + DiagnosticDescriptors.m_rule_CodeAnalysis0002 ); } } @@ -46,6 +54,140 @@ internal class CoreAnalyzer : DiagnosticAnalyzer //context.RegisterOperationAction(this.AnalyzeSymbol, OperationKind.PropertyReference); context.RegisterSymbolAction(this.AnalyzePluginSymbol, SymbolKind.NamedType); + context.RegisterOperationAction(this.AnalyzeAsyncToSyncMethodSymbol, OperationKind.Invocation); + + context.RegisterSyntaxNodeAction(this.AnalyzeMember, SyntaxKind.FieldDeclaration); + context.RegisterSyntaxNodeAction(this.AnalyzeMember, SyntaxKind.PropertyDeclaration); + } + + #region AnalyzeMember + private void AnalyzeMember(SyntaxNodeAnalysisContext context) + { + switch (context.Node) + { + case FieldDeclarationSyntax field: + this.AnalyzeField(field, context); + break; + case PropertyDeclarationSyntax property: + this.AnalyzeProperty(property, context); + break; + } + } + + private void AnalyzeField(FieldDeclarationSyntax field, SyntaxNodeAnalysisContext context) + { + foreach (var variable in field.Declaration.Variables) + { + var fieldSymbol = context.SemanticModel.GetDeclaredSymbol(variable) as IFieldSymbol; + if (!Utils.IsDependencyProperty(fieldSymbol?.Type)) + { + return; + } + + // 检查static修饰符 + if (field.Modifiers.Any(SyntaxKind.StaticKeyword) && field.Modifiers.Any(SyntaxKind.ReadOnlyKeyword)) + { + continue; + } + + this.ReportDiagnostic_CodeAnalysis0002(field.Declaration.Type.GetLocation(), fieldSymbol.Name, context); + } + } + + private void AnalyzeProperty(PropertyDeclarationSyntax property, SyntaxNodeAnalysisContext context) + { + var propertySymbol = context.SemanticModel.GetDeclaredSymbol(property); + if (!Utils.IsDependencyProperty(propertySymbol?.Type)) + { + return; + } + + // 检查static修饰符 + if (property.Modifiers.Any(SyntaxKind.StaticKeyword) && propertySymbol.IsReadOnly) + { + return; + } + this.ReportDiagnostic_CodeAnalysis0002(property.Type.GetLocation(), propertySymbol.Name, context); + } + + private void ReportDiagnostic_CodeAnalysis0002(Location location, string memberName, SyntaxNodeAnalysisContext context) + { + var diagnostic = Diagnostic.Create( + DiagnosticDescriptors.m_rule_CodeAnalysis0002, + location, + memberName); + + context.ReportDiagnostic(diagnostic); + } + #endregion + + private void AnalyzeAsyncToSyncMethodSymbol(OperationAnalysisContext context) + { + //Debugger.Launch(); + if (context.Operation is not IInvocationOperation invocationOperation) + { + return; + } + var methodSymbol = invocationOperation.TargetMethod; + + //取消任何框架的限制 + + //// 1. 检测目标框架 + //if (!context.Options.AnalyzerConfigOptionsProvider.GlobalOptions.TryGetValue("build_property.TargetFramework", out var targetFramework)) + //{ + // return; + //} + + //if (!IsTargetFrameworkValid(targetFramework)) + //{ + // return; + //} + + if (methodSymbol.HasAttribute(AsyncToSyncWarningAttribute)) + { + context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.m_rule_CodeAnalysis0001, invocationOperation.Syntax.GetLocation(), methodSymbol.Name)); + } + } + + public const string AsyncToSyncWarningAttribute = "TouchSocket.Core.AsyncToSyncWarningAttribute"; + + private static bool IsTargetFrameworkValid(string targetFramework) + { + return true;//在任何框架下都提示同步不推荐使用 + if (string.IsNullOrEmpty(targetFramework)) + { + return false; + } + + // 处理不同目标框架格式: + // - net6.0 + // - net7.0-windows + // - net8.0.1 + if (targetFramework.StartsWith("net", StringComparison.OrdinalIgnoreCase) && + targetFramework.Length > 3 && + char.IsDigit(targetFramework[3])) + { + var versionString = targetFramework.Substring(3); + var dashIndex = versionString.IndexOf('-'); + if (dashIndex > 0) + { + versionString = versionString.Substring(0, dashIndex); + } + + // 处理带小数点的版本号 + var decimalIndex = versionString.IndexOf('.'); + if (decimalIndex > 0) + { + versionString = versionString.Substring(0, decimalIndex + 1) + + versionString.Substring(decimalIndex + 1).Replace(".", ""); + } + + if (decimal.TryParse(versionString, NumberStyles.Any, CultureInfo.InvariantCulture, out var version)) + { + return version >= 6.0m; + } + } + return false; } public static bool IsPluginInterface(INamedTypeSymbol namedTypeSymbol) @@ -97,7 +239,7 @@ internal class CoreAnalyzer : DiagnosticAnalyzer return; } - if (!namedTypeSymbol.HasAttribute(MethodInvokeSyntaxReceiver.DynamicMethod)) + if (!namedTypeSymbol.HasAttribute(MethodInvokeSourceGenerator.DynamicMethod)) { context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.m_rule_Plugin0004, namedTypeSymbol.Locations[0])); return; diff --git a/src/TouchSocket.Core.SourceGenerator/Analyzers/DiagnosticDescriptors.cs b/src/TouchSocket.Core.SourceGenerator/Analyzers/DiagnosticDescriptors.cs index ac89ee618..7c17d51dd 100644 --- a/src/TouchSocket.Core.SourceGenerator/Analyzers/DiagnosticDescriptors.cs +++ b/src/TouchSocket.Core.SourceGenerator/Analyzers/DiagnosticDescriptors.cs @@ -60,6 +60,12 @@ internal static class DiagnosticDescriptors "Plugin0004", "判断声明的插件接口是否包含[DynamicMethod]特性", "声明的插件接口必须标识[DynamicMethod]特性。", +"Plugin", DiagnosticSeverity.Error, isEnabledByDefault: true); + + public static readonly DiagnosticDescriptor m_rule_Plugin0005 = new DiagnosticDescriptor( +"Plugin0005", +"判断声明的插件触发接口是否继承了IPlugin", +"类型{0}并不是有效的插件接口类", "Plugin", DiagnosticSeverity.Error, isEnabledByDefault: true); #endregion Plugin @@ -70,4 +76,18 @@ internal static class DiagnosticDescriptors "类型{0}并非IPackage,所以无法使用源生成Fast序列化。", "FastSerialize", DiagnosticSeverity.Error, isEnabledByDefault: true); #endregion + + #region CodeAnalysis + public static readonly DiagnosticDescriptor m_rule_CodeAnalysis0001 = new DiagnosticDescriptor( +"CodeAnalysis0001", +"判断该方法是不是由异步转为的同步", +"方法{0}是由异步转为的同步,这可能会导致死锁。请使用{0}Async代替。该方法将在正式发布时删除。", +"CodeAnalysis", DiagnosticSeverity.Error, isEnabledByDefault: true); + + public static readonly DiagnosticDescriptor m_rule_CodeAnalysis0002 = new DiagnosticDescriptor( +"CodeAnalysis0002", +"判断DependencyProperty属性是否为静态只读字段或者静态只读属性", +"依赖属性{0}应该使用静态只读字段(static readonly)或者静态只读属性(不包含set访问器)可能更加推荐。", +"CodeAnalysis", DiagnosticSeverity.Error, isEnabledByDefault: true); + #endregion } \ No newline at end of file diff --git a/src/TouchSocket.Core.SourceGenerator/Analyzers/FrameworkVersionAnalyzer.cs b/src/TouchSocket.Core.SourceGenerator/Analyzers/FrameworkVersionAnalyzer.cs new file mode 100644 index 000000000..7aaf0521c --- /dev/null +++ b/src/TouchSocket.Core.SourceGenerator/Analyzers/FrameworkVersionAnalyzer.cs @@ -0,0 +1,133 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +//namespace ClassLibrary1 +//{ +// [Generator] +// public class MethodUsageAnalyzer : IIncrementalGenerator +// { +// private const string SpecialAttributeName = "MyNamespace.SpecialAttribute"; + +// public void Initialize(IncrementalGeneratorInitializationContext context) +// { +// // 第一步:收集所有方法调用语法节点 +// var invocationNodes = context.SyntaxProvider +// .CreateSyntaxProvider( +// predicate: static (s, _) => s is InvocationExpressionSyntax, +// transform: static (ctx, _) => (Invocation: (InvocationExpressionSyntax)ctx.Node, ctx.SemanticModel) +// ); + +// // 第二步:获取项目配置 +// var configOptions = context.AnalyzerConfigOptionsProvider; + +// // 第三步:组合输入数据 +// var combined = invocationNodes.Combine(configOptions); + +// // 第四步:注册分析操作 +// context.RegisterSourceOutput(combined, (spc, tuple) => +// { +// var ((invocation, semanticModel), options) = tuple; + +// // 1. 检测目标框架 +// if (!options.GlobalOptions.TryGetValue("build_property.TargetFramework", out var targetFramework)) +// return; + +// if (!IsTargetFrameworkValid(targetFramework)) +// return; + +// // 2. 获取被调用方法符号 +// var symbolInfo = semanticModel.GetSymbolInfo(invocation); +// if (symbolInfo.Symbol is not IMethodSymbol methodSymbol) +// return; + +// // 3. 检查特性标记 +// if (HasSpecialAttribute(methodSymbol)) +// { +// var location = invocation.GetLocation(); +// var diagnostic = Diagnostic.Create( +// descriptor: Rules.RestrictedMethodWarning, +// location: location, +// methodSymbol.Name +// ); +// spc.ReportDiagnostic(diagnostic); +// } +// }); +// } + +// private static bool IsTargetFrameworkValid(string targetFramework) +// { +// if (string.IsNullOrEmpty(targetFramework)) return false; + +// // 处理不同目标框架格式: +// // - net6.0 +// // - net7.0-windows +// // - net8.0.1 +// if (targetFramework.StartsWith("net", StringComparison.OrdinalIgnoreCase) && +// targetFramework.Length > 3 && +// char.IsDigit(targetFramework[3])) +// { +// var versionString = targetFramework.Substring(3); +// var dashIndex = versionString.IndexOf('-'); +// if (dashIndex > 0) +// { +// versionString = versionString.Substring(0, dashIndex); +// } + +// // 处理带小数点的版本号 +// var decimalIndex = versionString.IndexOf('.'); +// if (decimalIndex > 0) +// { +// versionString = versionString.Substring(0, decimalIndex + 1) + +// versionString.Substring(decimalIndex + 1).Replace(".", ""); +// } + +// if (decimal.TryParse(versionString, NumberStyles.Any, CultureInfo.InvariantCulture, out var version)) +// { +// return version >= 6.0m; +// } +// } +// return false; +// } + +// private static bool HasSpecialAttribute(IMethodSymbol methodSymbol) +// { +// foreach (var attribute in methodSymbol.GetAttributes()) +// { +// if (attribute.AttributeClass?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == +// "global::MyNamespace.SpecialAttribute") +// { +// return true; +// } +// } + +// // 检查覆盖方法的情况 +// if (methodSymbol.OverriddenMethod != null) +// { +// return HasSpecialAttribute(methodSymbol.OverriddenMethod); +// } + +// return false; +// } +// } + +// public static class Rules +// { +// public static readonly DiagnosticDescriptor RestrictedMethodWarning = new( +// id: "SG2001", +// title: "Restricted method usage", +// messageFormat: "Method '{0}' is marked as restricted in .NET 6+ environment", +// category: "Usage", +// defaultSeverity: DiagnosticSeverity.Warning, +// isEnabledByDefault: true, +// description: "This method has special restrictions in modern .NET versions"); +// } +//} diff --git a/src/TouchSocket.Core.SourceGenerator/Container/ContainerAttribute.cs b/src/TouchSocket.Core.SourceGenerator/Container/ContainerAttribute.cs deleted file mode 100644 index bff27fa28..000000000 --- a/src/TouchSocket.Core.SourceGenerator/Container/ContainerAttribute.cs +++ /dev/null @@ -1,169 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -/* -此代码由SourceGenerator工具直接生成,非必要请不要修改此处代码 -*/ - -#pragma warning disable - -using System; -using System.Collections.Generic; -using System.Text; - -namespace TouchSocket.Core -{ - /// - /// 源生成容器特性 - /// - /*GeneratedCode*/ - internal class GeneratorContainerAttribute : Attribute - { - } - - /*GeneratedCode*/ - internal class BaseInjectAttribute : Attribute - { - /// - /// 注册类型 - /// - public Type FromType { get; set; } - - /// - /// 实例类型 - /// - public Type ToType { get; set; } - - /// - /// 注册额外键 - /// - public string Key { get; set; } - } - - /// - /// 自动注入为单例。 - /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true)] - /*GeneratedCode*/ - internal class AutoInjectForSingletonAttribute : BaseInjectAttribute - { - } - - /// - /// 自动注入为瞬时。 - /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true)] - /*GeneratedCode*/ - internal class AutoInjectForTransientAttribute : BaseInjectAttribute - { - } - - /// - /// 添加单例注入。 - /// - /// 该标签仅添加在继承的容器上时有用。 - /// - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - /*GeneratedCode*/ - internal class AddSingletonInjectAttribute : BaseInjectAttribute - { - /// - /// 添加单例注入。 - /// - /// 该标签仅添加在继承的容器上时有用。 - /// - /// - /// 注册类型 - /// 实例类型 - /// 注册额外键 - public AddSingletonInjectAttribute(Type fromType, Type toType, string key) - { - this.FromType = fromType; - this.ToType = toType; - this.Key = key; - } - - /// - /// 添加单例注入。 - /// - /// 该标签仅添加在继承的容器上时有用。 - /// - /// - /// 注册类型与实例类型一致 - public AddSingletonInjectAttribute(Type type) : this(type, type, null) - { - } - - /// - /// 添加单例注入。 - /// - /// 该标签仅添加在继承的容器上时有用。 - /// - /// - /// 注册类型 - /// 实例类型 - public AddSingletonInjectAttribute(Type fromType, Type toType) : this(fromType, toType, null) - { - } - } - - /// - /// 添加瞬态注入。 - /// - /// 该标签仅添加在继承的容器上时有用。 - /// - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - /*GeneratedCode*/ - internal class AddTransientInjectAttribute : BaseInjectAttribute - { - /// - /// 添加瞬态注入。 - /// - /// 该标签仅添加在继承的容器上时有用。 - /// - /// - /// 注册类型 - /// 实例类型 - /// 注册额外键 - public AddTransientInjectAttribute(Type fromType, Type toType, string key) - { - this.FromType = fromType; - this.ToType = toType; - this.Key = key; - } - - /// - /// 添加瞬态注入。 - /// - /// 该标签仅添加在继承的容器上时有用。 - /// - /// - /// 注册类型与实例类型一致 - public AddTransientInjectAttribute(Type type) : this(type, type, null) - { - } - - /// - /// 添加瞬态注入。 - /// - /// 该标签仅添加在继承的容器上时有用。 - /// - /// - /// 注册类型 - /// 实例类型 - public AddTransientInjectAttribute(Type fromType, Type toType) : this(fromType, toType, null) - { - } - } -} \ No newline at end of file diff --git a/src/TouchSocket.Core.SourceGenerator/Container/ContainerCodeBuilder.cs b/src/TouchSocket.Core.SourceGenerator/Container/ContainerCodeBuilder.cs deleted file mode 100644 index 7b6ed48b6..000000000 --- a/src/TouchSocket.Core.SourceGenerator/Container/ContainerCodeBuilder.cs +++ /dev/null @@ -1,597 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Text; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TouchSocket; - -internal sealed class ContainerCodeBuilder : CodeBuilder -{ - private const string AddSingletonInjectAttributeString = "TouchSocket.Core.AddSingletonInjectAttribute"; - private const string AddTransientInjectAttributeString = "TouchSocket.Core.AddTransientInjectAttribute"; - private const string DependencyInjectAttributeString = "TouchSocket.Core.DependencyInjectAttribute"; - private const string DependencyTypeAttributeString = "TouchSocket.Core.DependencyTypeAttribute"; - private readonly IEnumerable m_autoInjectForSingletonClassTypes; - private readonly IEnumerable m_autoInjectForTransientClassTypes; - private readonly INamedTypeSymbol m_containerClass; - - public ContainerCodeBuilder(INamedTypeSymbol containerClass, IEnumerable autoInjectForSingletonClassTypes, IEnumerable autoInjectForTransientClassTypes) - { - this.m_containerClass = containerClass; - this.m_autoInjectForSingletonClassTypes = autoInjectForSingletonClassTypes; - this.m_autoInjectForTransientClassTypes = autoInjectForTransientClassTypes; - } - - /// - /// 依赖注入类型。 - /// - [Flags] - private enum DependencyType - { - /// - /// 构造函数 - /// - - Constructor = 0, - - /// - /// 属性 - /// - Property = 1, - - /// - /// 方法 - /// - Method = 2 - } - - public string Prefix { get; set; } - - public IEnumerable Usings - { - get - { - yield return "using System;"; - yield return "using System.Diagnostics;"; - yield return "using TouchSocket.Core;"; - yield return "using System.Threading.Tasks;"; - } - } - - public override string Id => this.m_containerClass.ToDisplayString(); - - public override string GetFileName() - { - return this.m_containerClass.ToDisplayString() + "Generator"; - } - - public override string ToString() - { - var codeString = new StringBuilder(); - codeString.AppendLine("/*"); - codeString.AppendLine("此代码由SourceGenerator工具直接生成,非必要请不要修改此处代码"); - codeString.AppendLine("*/"); - codeString.AppendLine("#pragma warning disable"); - - foreach (var item in this.Usings) - { - codeString.AppendLine(item); - } - - //Debugger.Launch(); - if (!this.m_containerClass.ContainingNamespace.IsGlobalNamespace) - { - codeString.AppendLine($"namespace {this.m_containerClass.ContainingNamespace}"); - codeString.AppendLine("{"); - } - - - codeString.AppendLine($"partial class {this.m_containerClass.Name}"); - codeString.AppendLine("{"); - var singletonInjectDescriptions = this.FindSingletonInjects().ToList(); - singletonInjectDescriptions.AddRange(this.m_autoInjectForSingletonClassTypes); - singletonInjectDescriptions = singletonInjectDescriptions.Distinct(new InjectDescriptionCompare()).ToList(); - - var transientInjectDescriptions = this.FindTransientInjects().ToList(); - transientInjectDescriptions.AddRange(this.m_autoInjectForTransientClassTypes); - transientInjectDescriptions = transientInjectDescriptions.Distinct(new InjectDescriptionCompare()).ToList(); - this.BuildConstructor(codeString); - this.BuildContainerInit(codeString, singletonInjectDescriptions); - foreach (var item in singletonInjectDescriptions) - { - this.BuildSingletonInjectField(codeString, item); - this.BuildInject(codeString, item); - } - - foreach (var item in transientInjectDescriptions) - { - this.BuildInject(codeString, item); - } - - this.BuildPrivateTryResolve(codeString, singletonInjectDescriptions, transientInjectDescriptions, true); - this.BuildPrivateTryResolve(codeString, singletonInjectDescriptions, transientInjectDescriptions, false); - this.TryBuildInvokeTryResolve(codeString); - codeString.AppendLine("}"); - - if (!this.m_containerClass.ContainingNamespace.IsGlobalNamespace) - { - codeString.AppendLine("}"); - } - - // System.Diagnostics.Debugger.Launch(); - return codeString.ToString(); - } - - public bool TryToSourceText(out SourceText sourceText) - { - var code = this.ToString(); - if (string.IsNullOrEmpty(code)) - { - sourceText = null; - return false; - } - sourceText = SourceText.From(code, Encoding.UTF8); - return true; - } - - #region TryResolve - - private void BuildPrivateTryResolve(StringBuilder codeString, List singletonDescriptions, List transientInjectDescriptions, bool isKey) - { - if (isKey) - { - codeString.AppendLine($"private bool PrivateTryResolve(Type fromType, out object instance, string key)"); - codeString.AppendLine("{"); - codeString.AppendLine("string typeKey= $\"{fromType.FullName}{key}\";"); - codeString.AppendLine("switch (typeKey)"); - codeString.AppendLine("{"); - foreach (var item in singletonDescriptions) - { - codeString.AppendLine($"case \"{item.From.ToDisplayString()}{item.Key}\":"); - codeString.AppendLine("{"); - codeString.AppendLine($"instance = this.{this.GetFieldName(item)}.Value;"); - codeString.AppendLine("return true;"); - codeString.AppendLine("}"); - } - - foreach (var item in transientInjectDescriptions) - { - codeString.AppendLine($"case \"{item.From.ToDisplayString()}{item.Key}\":"); - codeString.AppendLine("{"); - codeString.AppendLine($"instance = this.{this.GetMethodName(item)}();"); - codeString.AppendLine("return true;"); - codeString.AppendLine("}"); - } - - codeString.AppendLine("default:"); - codeString.AppendLine("{"); - codeString.AppendLine("instance = default;"); - codeString.AppendLine("return false;"); - codeString.AppendLine("}"); - codeString.AppendLine("}"); - codeString.AppendLine("}"); - } - else - { - codeString.AppendLine($"private bool PrivateTryResolve(Type fromType, out object instance)"); - codeString.AppendLine("{"); - foreach (var item in singletonDescriptions) - { - codeString.AppendLine($"if(fromType==typeof({item.From.ToDisplayString()}))"); - codeString.AppendLine("{"); - codeString.AppendLine($"instance = this.{this.GetFieldName(item)}.Value;"); - codeString.AppendLine("return true;"); - codeString.AppendLine("}"); - } - - foreach (var item in transientInjectDescriptions) - { - codeString.AppendLine($"if(fromType==typeof({item.From.ToDisplayString()}))"); - codeString.AppendLine("{"); - codeString.AppendLine($"instance = this.{this.GetMethodName(item)}();"); - codeString.AppendLine("return true;"); - codeString.AppendLine("}"); - } - - codeString.AppendLine("instance = default;"); - codeString.AppendLine("return false;"); - codeString.AppendLine("}"); - } - } - - #endregion TryResolve - - private void BuildConstructor(StringBuilder codeString) - { - codeString.AppendLine($"public {this.m_containerClass.Name}()"); - codeString.AppendLine("{"); - codeString.AppendLine("this.ContainerInit();"); - codeString.AppendLine("}"); - } - - private void BuildContainerInit(StringBuilder codeString, List descriptions) - { - if (descriptions.Count == 0) - { - return; - } - codeString.AppendLine($"private void ContainerInit()"); - codeString.AppendLine("{"); - foreach (var item in descriptions) - { - codeString.AppendLine($"this.{this.GetFieldName(item)} = new Lazy<{item.From.ToDisplayString()}>(this.{this.GetMethodName(item)});"); - } - - codeString.AppendLine("}"); - } - - #region SingletonInject - - private void BuildSingletonInjectField(StringBuilder codeString, InjectDescription description) - { - codeString.AppendLine($"private Lazy<{description.From.ToDisplayString()}> {this.GetFieldName(description)};"); - } - - private IMethodSymbol GetConstructor(INamedTypeSymbol namedTypeSymbol) - { - var methodSymbol = namedTypeSymbol.Constructors.FirstOrDefault(); - foreach (var item in namedTypeSymbol.Constructors) - { - if (item.HasAttribute(DependencyInjectAttributeString, out _)) - { - return item; - } - - if (item.Parameters.Length > methodSymbol.Parameters.Length) - { - methodSymbol = item; - } - } - - return methodSymbol; - } - - private string GetFieldName(InjectDescription description) - { - if (string.IsNullOrEmpty(description.Key)) - { - return $"m_{description.From.Name.RenameCamelCase()}"; - } - return $"m_{description.From.Name.RenameCamelCase()}_{description.Key}"; - } - - private string GetMethodName(InjectDescription description) - { - if (string.IsNullOrEmpty(description.Key)) - { - return $"Create{description.From.Name}"; - } - return $"Create{description.From.Name}_{description.Key}"; - } - - #endregion SingletonInject - - private void BuildInject(StringBuilder codeString, InjectDescription description) - { - codeString.AppendLine($"private {description.From.ToDisplayString()} {this.GetMethodName(description)}()"); - codeString.Append('{'); - - var constructor = this.GetConstructor(description.To); - if (constructor == default || constructor.Parameters.Length == 0) - { - codeString.Append($"var obj= new {description.To.ToDisplayString()}();"); - } - else - { - codeString.Append($"var obj= new {description.To.ToDisplayString()}("); - var ps = new List(); - foreach (var item in constructor.Parameters) - { - ps.Add($"({item.Type.ToDisplayString()})this.Resolve(typeof({item.Type.ToDisplayString()}))"); - } - codeString.Append(string.Join(",", ps)); - codeString.Append($");"); - } - var dependencyType = this.GetDependencyType(description.To); - if (dependencyType.HasFlag(DependencyType.Property)) - { - var properties = this.GetInjectProperties(description.To); - foreach (var item in properties) - { - if (string.IsNullOrEmpty(item.Key)) - { - codeString.Append($"obj.{item.Name}=({item.Type.ToDisplayString()})this.Resolve(typeof({item.Type.ToDisplayString()}));"); - } - else - { - codeString.Append($"obj.{item.Name}=({item.Type.ToDisplayString()})this.Resolve(typeof({item.Type.ToDisplayString()}),key:{item.Key});"); - } - } - } - - if (dependencyType.HasFlag(DependencyType.Method)) - { - foreach (var item in this.GetInjectMethods(description.To)) - { - codeString.Append($"obj.{item.Name}("); - var parameters = new List(); - foreach (var item2 in item.Types) - { - if (string.IsNullOrEmpty(item2.Key)) - { - parameters.Add($"({item2.Type.ToDisplayString()})this.Resolve(typeof({item2.Type.ToDisplayString()}))"); - } - else - { - parameters.Add($"({item2.Type.ToDisplayString()})this.Resolve(typeof({item2.Type.ToDisplayString()}),key:{item2.Key})"); - } - } - codeString.Append(string.Join(",", parameters)); - codeString.Append($");"); - } - } - - codeString.Append("return obj;"); - codeString.Append('}'); - } - - private IEnumerable FindSingletonInjects() - { - return this.m_containerClass.GetAttributes() - .Where(a => a.AttributeClass?.ToDisplayString() == AddSingletonInjectAttributeString) - .Select(a => - { - var list = a.ConstructorArguments; - - INamedTypeSymbol fromTypedConstant = null; - INamedTypeSymbol toTypedConstant = null; - var key = string.Empty; - if (list.Length == 1) - { - fromTypedConstant = (INamedTypeSymbol)list[0].Value; - toTypedConstant = (INamedTypeSymbol)list[0].Value; - } - else if (list.Length == 2) - { - fromTypedConstant = (INamedTypeSymbol)list[0].Value; - toTypedConstant = (INamedTypeSymbol)list[1].Value; - } - else if (list.Length == 3) - { - fromTypedConstant = (INamedTypeSymbol)list[0].Value; - toTypedConstant = (INamedTypeSymbol)list[1].Value; - key = list[2].Value.ToString(); - } - - return new InjectDescription() - { - From = fromTypedConstant, - To = toTypedConstant, - Key = key - }; - }) - .Distinct(new InjectDescriptionCompare()); - } - - private IEnumerable FindTransientInjects() - { - return this.m_containerClass.GetAttributes() - .Where(a => a.AttributeClass?.ToDisplayString() == AddTransientInjectAttributeString) - .Select(a => - { - var list = a.ConstructorArguments; - - INamedTypeSymbol fromTypedConstant = null; - INamedTypeSymbol toTypedConstant = null; - var key = string.Empty; - if (list.Length == 1) - { - fromTypedConstant = (INamedTypeSymbol)list[0].Value; - toTypedConstant = (INamedTypeSymbol)list[0].Value; - } - else if (list.Length == 2) - { - fromTypedConstant = (INamedTypeSymbol)list[0].Value; - toTypedConstant = (INamedTypeSymbol)list[1].Value; - } - else if (list.Length == 3) - { - fromTypedConstant = (INamedTypeSymbol)list[0].Value; - toTypedConstant = (INamedTypeSymbol)list[1].Value; - key = list[2].Value.ToString(); - } - - return new InjectDescription() - { - From = fromTypedConstant, - To = toTypedConstant, - Key = key - }; - }).Distinct(new InjectDescriptionCompare()); - } - - private DependencyType GetDependencyType(INamedTypeSymbol namedType) - { - if (namedType.HasAttribute(DependencyTypeAttributeString, out var attributeData)) - { - return (DependencyType)attributeData.ConstructorArguments[0].Value; - } - return DependencyType.Constructor | DependencyType.Property | DependencyType.Method; - } - - private IEnumerable GetInjectMethods(INamedTypeSymbol namedType) - { - //Debugger.Launch(); - var members = namedType.GetMembers(); - - var descriptions = new List(); - foreach (var item in members) - { - if (item is not IMethodSymbol method) - { - continue; - } - - if (method.DeclaredAccessibility != Accessibility.Public) - { - continue; - } - if (method.MethodKind != MethodKind.Ordinary) - { - continue; - } - if (method.HasAttribute(DependencyInjectAttributeString, out var attributeData)) - { - var description = new InjectMethodDescription() - { - Name = method.Name - }; - - description.Types = method.Parameters - .Select(a => - { - var des = new InjectPropertyDescription(); - - if (a.HasAttribute(DependencyInjectAttributeString, out var attributeData)) - { - if (attributeData.ConstructorArguments.Length == 0) - { - des.Type = a.Type; - } - else if (attributeData.ConstructorArguments.Length == 1 && attributeData.ConstructorArguments[0].Kind == TypedConstantKind.Primitive) - { - des.Type = a.Type; - des.Key = attributeData.ConstructorArguments[0].Value.ToString(); - } - else if (attributeData.ConstructorArguments.Length == 1 && attributeData.ConstructorArguments[0].Kind == TypedConstantKind.Type) - { - des.Type = (ITypeSymbol)attributeData.ConstructorArguments[0].Value; - } - else if (attributeData.ConstructorArguments.Length == 2) - { - des.Type = (ITypeSymbol)attributeData.ConstructorArguments[0].Value; - des.Key = attributeData.ConstructorArguments[1].Value.ToString(); - } - } - else - { - des.Type = a.Type; - } - return des; - }); - descriptions.Add(description); - } - } - return descriptions; - } - - private IEnumerable GetInjectProperties(INamedTypeSymbol namedType) - { - // Debugger.Launch(); - var members = namedType.GetMembers(); - - var descriptions = new List(); - foreach (var item in members) - { - if (item is not IPropertySymbol property) - { - continue; - } - - if (property.IsWriteOnly) - { - continue; - } - - if (property.HasAttribute(DependencyInjectAttributeString, out var attributeData)) - { - var description = new InjectPropertyDescription() - { - Name = property.Name - }; - - if (attributeData.ConstructorArguments.Length == 0) - { - description.Type = property.Type; - } - else if (attributeData.ConstructorArguments.Length == 1 && attributeData.ConstructorArguments[0].Kind == TypedConstantKind.Primitive) - { - description.Type = property.Type; - description.Key = attributeData.ConstructorArguments[0].Value.ToString(); - } - else if (attributeData.ConstructorArguments.Length == 1 && attributeData.ConstructorArguments[0].Kind == TypedConstantKind.Type) - { - description.Type = (ITypeSymbol)attributeData.ConstructorArguments[0].Value; - } - else if (attributeData.ConstructorArguments.Length == 2) - { - description.Type = (ITypeSymbol)attributeData.ConstructorArguments[0].Value; - description.Key = attributeData.ConstructorArguments[1].Value.ToString(); - } - descriptions.Add(description); - } - } - return descriptions; - } - - private bool HasOverrideMethod(bool iskey) - { - return this.m_containerClass - .GetMembers() - .OfType() - .Any(m => - { - if (iskey && m.Name == "TryResolve" && m.IsOverride && m.Parameters.Length == 3) - { - return true; - } - - if ((!iskey) && m.Name == "TryResolve" && m.IsOverride && m.Parameters.Length == 2) - { - return true; - } - return false; - }); - } - - private void TryBuildInvokeTryResolve(StringBuilder stringBuilder) - { - if (!this.HasOverrideMethod(true)) - { - stringBuilder.AppendLine("protected override bool TryResolve(Type fromType, out object instance, string key)"); - stringBuilder.AppendLine("{"); - stringBuilder.AppendLine("if (this.PrivateTryResolve(fromType, out instance, key))"); - stringBuilder.AppendLine("{"); - stringBuilder.AppendLine("return true;"); - stringBuilder.AppendLine("}"); - stringBuilder.AppendLine("return base.TryResolve(fromType, out instance, key);"); - stringBuilder.AppendLine("}"); - } - - if (!this.HasOverrideMethod(false)) - { - stringBuilder.AppendLine("protected override bool TryResolve(Type fromType, out object instance)"); - stringBuilder.AppendLine("{"); - stringBuilder.AppendLine("if (this.PrivateTryResolve(fromType, out instance))"); - stringBuilder.AppendLine("{"); - stringBuilder.AppendLine("return true;"); - stringBuilder.AppendLine("}"); - stringBuilder.AppendLine("return base.TryResolve(fromType, out instance);"); - stringBuilder.AppendLine("}"); - } - } -} \ No newline at end of file diff --git a/src/TouchSocket.Core.SourceGenerator/Container/ContainerSourceGenerator.cs b/src/TouchSocket.Core.SourceGenerator/Container/ContainerSourceGenerator.cs deleted file mode 100644 index 077df7279..000000000 --- a/src/TouchSocket.Core.SourceGenerator/Container/ContainerSourceGenerator.cs +++ /dev/null @@ -1,221 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using System.Linq; -using System.Reflection; - -namespace TouchSocket; - -[Generator] -public class ContainerSourceGenerator : ISourceGenerator -{ - private readonly string m_generatorContainerAttribute = @" -/* -此代码由SourceGenerator工具直接生成,非必要请不要修改此处代码 -*/ - -#pragma warning disable - -using System; -using System.Collections.Generic; -using System.Text; - -namespace TouchSocket.Core -{ - /// - /// 源生成容器特性 - /// - /*GeneratedCode*/ - internal class GeneratorContainerAttribute : Attribute - { - } - - /*GeneratedCode*/ - internal class BaseInjectAttribute : Attribute - { - /// - /// 注册类型 - /// - public Type FromType { get; set; } - - /// - /// 实例类型 - /// - public Type ToType { get; set; } - - /// - /// 注册额外键 - /// - public string Key { get; set; } - } - - /// - /// 自动注入为单例。 - /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true)] - /*GeneratedCode*/ - internal class AutoInjectForSingletonAttribute : BaseInjectAttribute - { - } - - /// - /// 自动注入为瞬时。 - /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true)] - /*GeneratedCode*/ - internal class AutoInjectForTransientAttribute : BaseInjectAttribute - { - } - - /// - /// 添加单例注入。 - /// - /// 该标签仅添加在继承的容器上时有用。 - /// - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - /*GeneratedCode*/ - internal class AddSingletonInjectAttribute : BaseInjectAttribute - { - /// - /// 添加单例注入。 - /// - /// 该标签仅添加在继承的容器上时有用。 - /// - /// - /// 注册类型 - /// 实例类型 - /// 注册额外键 - public AddSingletonInjectAttribute(Type fromType, Type toType, string key) - { - this.FromType = fromType; - this.ToType = toType; - this.Key = key; - } - - /// - /// 添加单例注入。 - /// - /// 该标签仅添加在继承的容器上时有用。 - /// - /// - /// 注册类型与实例类型一致 - public AddSingletonInjectAttribute(Type type) : this(type, type, null) - { - } - - /// - /// 添加单例注入。 - /// - /// 该标签仅添加在继承的容器上时有用。 - /// - /// - /// 注册类型 - /// 实例类型 - public AddSingletonInjectAttribute(Type fromType, Type toType) : this(fromType, toType, null) - { - } - } - - /// - /// 添加瞬态注入。 - /// - /// 该标签仅添加在继承的容器上时有用。 - /// - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - /*GeneratedCode*/ - internal class AddTransientInjectAttribute : BaseInjectAttribute - { - /// - /// 添加瞬态注入。 - /// - /// 该标签仅添加在继承的容器上时有用。 - /// - /// - /// 注册类型 - /// 实例类型 - /// 注册额外键 - public AddTransientInjectAttribute(Type fromType, Type toType, string key) - { - this.FromType = fromType; - this.ToType = toType; - this.Key = key; - } - - /// - /// 添加瞬态注入。 - /// - /// 该标签仅添加在继承的容器上时有用。 - /// - /// - /// 注册类型与实例类型一致 - public AddTransientInjectAttribute(Type type) : this(type, type, null) - { - } - - /// - /// 添加瞬态注入。 - /// - /// 该标签仅添加在继承的容器上时有用。 - /// - /// - /// 注册类型 - /// 实例类型 - public AddTransientInjectAttribute(Type fromType, Type toType) : this(fromType, toType, null) - { - } - } -} -"; - - public void Initialize(GeneratorInitializationContext context) - { - context.RegisterForPostInitialization(a => - { - var sourceCode = this.m_generatorContainerAttribute.Replace("/*GeneratedCode*/", $"[global::System.CodeDom.Compiler.GeneratedCode(\"TouchSocket.SourceGenerator\",\"{Assembly.GetExecutingAssembly().GetName().Version.ToString()}\")]"); - - a.AddSource(nameof(this.m_generatorContainerAttribute), sourceCode); - }); - context.RegisterForSyntaxNotifications(() => new ContainerSyntaxReceiver()); - } - - public void Execute(GeneratorExecutionContext context) - { - var s = context.Compilation.GetMetadataReference(context.Compilation.Assembly); - - if (context.SyntaxReceiver is ContainerSyntaxReceiver receiver) - { - var types1 = receiver.GetAutoInjectForSingletonClassTypes(context.Compilation); - var types2 = receiver.GetAutoInjectForTransientClassTypes(context.Compilation); - - var builders = receiver - .GetContainerClassTypes(context.Compilation) - .Select(i => new ContainerCodeBuilder(i, types1, types2)) - .Distinct(CodeBuilderEqualityComparer.Default); - //Debugger.Launch(); - - foreach (var builder in builders) - { - if (builder.TryToSourceText(out var sourceText)) - { - var tree = CSharpSyntaxTree.ParseText(sourceText); - var root = tree.GetRoot().NormalizeWhitespace(); - var ret = root.ToFullString(); - context.AddSource($"{builder.GetFileName()}.g.cs", ret); - } - } - } - } -} \ No newline at end of file diff --git a/src/TouchSocket.Core.SourceGenerator/Container/ContainerSyntaxReceiver.cs b/src/TouchSocket.Core.SourceGenerator/Container/ContainerSyntaxReceiver.cs deleted file mode 100644 index d71215f89..000000000 --- a/src/TouchSocket.Core.SourceGenerator/Container/ContainerSyntaxReceiver.cs +++ /dev/null @@ -1,188 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using System.Collections.Generic; -using System.Collections.Immutable; - -namespace TouchSocket; - -internal sealed class ContainerSyntaxReceiver : ISyntaxReceiver -{ - public const string GeneratorContainerAttributeTypeName = "TouchSocket.Core.GeneratorContainerAttribute"; - public const string AutoInjectForSingletonAttributeTypeName = "TouchSocket.Core.AutoInjectForSingletonAttribute"; - public const string AutoInjectForTransientAttributeTypeName = "TouchSocket.Core.AutoInjectForTransientAttribute"; - public const string ManualContainerTypeName = "TouchSocket.Core.ManualContainer"; - - public static INamedTypeSymbol GeneratorContainerAttribute { get; private set; } - public static INamedTypeSymbol AutoInjectForSingletonAttribute { get; private set; } - public static INamedTypeSymbol AutoInjectForTransientAttribute { get; private set; } - - /// - /// 接口列表 - /// - private readonly List m_classSyntaxList = new List(); - - /// - /// 访问语法树 - /// - /// - void ISyntaxReceiver.OnVisitSyntaxNode(SyntaxNode syntaxNode) - { - if (syntaxNode is ClassDeclarationSyntax syntax) - { - this.m_classSyntaxList.Add(syntax); - } - else if (syntaxNode is InterfaceDeclarationSyntax @interface) - { - this.m_classSyntaxList.Add(@interface); - } - } - - /// - /// 获取所有Container符号 - /// - /// - /// - public IEnumerable GetContainerClassTypes(Compilation compilation) - { - // Debugger.Launch(); - GeneratorContainerAttribute = compilation.GetTypeByMetadataName(GeneratorContainerAttributeTypeName); - if (GeneratorContainerAttribute == null) - { - yield break; - } - foreach (var classSyntax in this.m_classSyntaxList) - { - var @class = compilation.GetSemanticModel(classSyntax.SyntaxTree).GetDeclaredSymbol(classSyntax); - if (@class != null && IsContainerClass(@class)) - { - yield return @class; - } - } - } - - public IEnumerable GetAutoInjectForSingletonClassTypes(Compilation compilation) - { - // Debugger.Launch(); - AutoInjectForSingletonAttribute = compilation.GetTypeByMetadataName(AutoInjectForSingletonAttributeTypeName); - if (AutoInjectForSingletonAttribute == null) - { - yield break; - } - foreach (var classSyntax in this.m_classSyntaxList) - { - var @class = compilation.GetSemanticModel(classSyntax.SyntaxTree).GetDeclaredSymbol(classSyntax); - if (@class != null && IsAutoInjectForSingletonClass(@class, out var attributeData)) - { - yield return this.Create(@class, attributeData); - } - } - } - - public IEnumerable GetAutoInjectForTransientClassTypes(Compilation compilation) - { - // Debugger.Launch(); - AutoInjectForTransientAttribute = compilation.GetTypeByMetadataName(AutoInjectForTransientAttributeTypeName); - if (AutoInjectForTransientAttribute == null) - { - yield break; - } - foreach (var classSyntax in this.m_classSyntaxList) - { - var @class = compilation.GetSemanticModel(classSyntax.SyntaxTree).GetDeclaredSymbol(classSyntax); - if (@class != null && IsAutoInjectForTransientClass(@class, out var attributeData)) - { - yield return this.Create(@class, attributeData); - } - } - } - - private InjectDescription Create(INamedTypeSymbol typeSymbol, AttributeData attributeData) - { - var dic = attributeData.NamedArguments.ToImmutableDictionary(); - var description = new InjectDescription(); - if (dic.TryGetValue("FromType", out var typedConstant)) - { - description.From = (INamedTypeSymbol)typedConstant.Value; - } - if (dic.TryGetValue("ToType", out typedConstant)) - { - description.To = (INamedTypeSymbol)typedConstant.Value; - } - if (dic.TryGetValue("Key", out typedConstant)) - { - description.Key = typedConstant.Value.ToString(); - } - description.From ??= typeSymbol; - description.To ??= typeSymbol; - return description; - } - - /// - /// 是否为容器生成 - /// - /// - /// - public static bool IsContainerClass(INamedTypeSymbol @class) - { - if (GeneratorContainerAttribute is null) - { - return false; - } - //Debugger.Launch(); - - if (!@class.HasAttribute(GeneratorContainerAttributeTypeName, out _)) - { - return false; - } - if (@class.IsInheritFrom(ManualContainerTypeName)) - { - return true; - } - return false; - } - - public static bool IsAutoInjectForSingletonClass(INamedTypeSymbol @class, out AttributeData attributeData) - { - if (AutoInjectForSingletonAttribute is null) - { - attributeData = null; - return false; - } - //Debugger.Launch(); - - if (@class.HasAttribute(AutoInjectForSingletonAttributeTypeName, out attributeData)) - { - return true; - } - return false; - } - - public static bool IsAutoInjectForTransientClass(INamedTypeSymbol @class, out AttributeData attributeData) - { - if (AutoInjectForTransientAttribute is null) - { - attributeData = null; - return false; - } - //Debugger.Launch(); - - if (@class.HasAttribute(AutoInjectForTransientAttributeTypeName, out attributeData)) - { - return true; - } - return false; - } -} \ No newline at end of file diff --git a/src/TouchSocket.Core.SourceGenerator/Container/InjectDescription.cs b/src/TouchSocket.Core.SourceGenerator/Container/InjectDescription.cs deleted file mode 100644 index b1e12bfe2..000000000 --- a/src/TouchSocket.Core.SourceGenerator/Container/InjectDescription.cs +++ /dev/null @@ -1,63 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using Microsoft.CodeAnalysis; -using System.Collections.Generic; - -namespace TouchSocket; - -internal class InjectDescriptionCompare : IEqualityComparer -{ - public bool Equals(InjectDescription x, InjectDescription y) - { - if (string.IsNullOrEmpty(x.Key) || string.IsNullOrEmpty(y.Key)) - { - return x.From.ToDisplayString() == y.From.ToDisplayString(); - } - else - { - return x.From.ToDisplayString() == y.From.ToDisplayString() && x.Key == y.Key; - } - } - - public int GetHashCode(InjectDescription obj) - { - if (string.IsNullOrEmpty(obj.Key)) - { - return obj.From.ToDisplayString().GetHashCode(); - } - else - { - return obj.From.ToDisplayString().GetHashCode() ^ obj.Key.GetHashCode(); - } - } -} - -internal class InjectDescription -{ - public INamedTypeSymbol From { get; set; } - public INamedTypeSymbol To { get; set; } - public string Key { get; set; } -} - -internal class InjectPropertyDescription -{ - public ITypeSymbol Type { get; set; } - public string Name { get; set; } - public string Key { get; set; } -} - -internal class InjectMethodDescription -{ - public IEnumerable Types { get; set; } - public string Name { get; set; } -} \ No newline at end of file diff --git a/src/TouchSocket.Core.SourceGenerator/DependencyProperty/Attributes.cs b/src/TouchSocket.Core.SourceGenerator/DependencyProperty/Attributes.cs new file mode 100644 index 000000000..76d536df1 --- /dev/null +++ b/src/TouchSocket.Core.SourceGenerator/DependencyProperty/Attributes.cs @@ -0,0 +1,73 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +/* +此代码由SourceGenerator工具直接生成,非必要请不要修改此处代码 +*/ + +#pragma warning disable + +using System; + +namespace TouchSocket.Core +{ + /// + /// 标识源生成依赖属性。 + /// + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)] + /*GeneratedCode*/ + internal class GeneratorPropertyAttribute : Attribute + { + /// + /// 要生成依赖属性的目标类型名称。如果未设置,则表示当前属性所在的类型。 + /// + public Type TargetType { get; set; } + + /// + /// 生成的属性类型。 + /// + public PropertyGenerationOptions GenerationOptions { get; set; } = PropertyGenerationOptions.All; + + /// + /// 是否使用Action模式生成Set扩展方法访问器。 + /// + public bool ActionMode { get; set; } + } + + /// + /// 依赖属性生成选项。 + /// + [Flags] + enum PropertyGenerationOptions + { + /// + /// 生成所有访问器。 + /// + All = 0, + /// + /// 包含方法形式的 Getter。 + /// + IncludeMethodGetter = 1, + /// + /// 包含方法形式的 Setter。 + /// + IncludeMethodSetter = 2, + /// + /// 包含属性形式的 Getter。 + /// + IncludePropertyGetter = 4, + /// + /// 包含属性形式的 Setter。 + /// + IncludePropertySetter = 8 + } +} \ No newline at end of file diff --git a/src/TouchSocket.Core.SourceGenerator/DependencyProperty/DependencyPropertyCodeBuilder.cs b/src/TouchSocket.Core.SourceGenerator/DependencyProperty/DependencyPropertyCodeBuilder.cs new file mode 100644 index 000000000..8c9728380 --- /dev/null +++ b/src/TouchSocket.Core.SourceGenerator/DependencyProperty/DependencyPropertyCodeBuilder.cs @@ -0,0 +1,146 @@ +//------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +//------------------------------------------------------------------------------ + +using Microsoft.CodeAnalysis; +using System.Text; +using TouchSocket.Core; + +namespace TouchSocket; + +internal class DependencyPropertyCodeBuilder : TypeCodeBuilder +{ + private readonly DependencyPropertyInfo m_dependencyPropertyInfo; + + public DependencyPropertyCodeBuilder(DependencyPropertyInfo dependencyPropertyInfo) : base(dependencyPropertyInfo.TargetType) + { + this.m_dependencyPropertyInfo = dependencyPropertyInfo; + } + + public override string Id => this.m_dependencyPropertyInfo.TargetType.ToDisplayString() + this.m_dependencyPropertyInfo.Name; + + public override string GetFileName() + { + return $"{this.m_dependencyPropertyInfo.TargetType.Name}_{this.m_dependencyPropertyInfo.Name}_DependencyProperty.g.cs"; + } + + protected override bool GeneratorCode(StringBuilder codeBuilder) + { + using (this.CreateNamespace(codeBuilder)) + { + codeBuilder.AppendLine($"public static partial class {this.m_dependencyPropertyInfo.ContainingType.Name}Extensions"); + using (this.CreateCodeSpace(codeBuilder)) + { + var options = this.m_dependencyPropertyInfo.GenerationOptions; + + // 如果选择生成扩展属性(默认或包含属性访问器) + if (options == PropertyGenerationOptions.All || + this.HasFlag(options, PropertyGenerationOptions.IncludePropertyGetter) || + this.HasFlag(options, PropertyGenerationOptions.IncludePropertySetter)) + { + //生成扩展属性,供依赖对象调用 + codeBuilder.AppendLine($"extension(TDependencyObject dependencyObject)"); + codeBuilder.AppendLine($"where TDependencyObject:{this.m_dependencyPropertyInfo.TargetType.Name}"); + using (this.CreateCodeSpace(codeBuilder)) + { + codeBuilder.AppendLine($"/// "); + var propertyDeclaration = $"public {this.m_dependencyPropertyInfo.DependencyPropertyType.ToDisplayString()} {this.m_dependencyPropertyInfo.Name} {{"; + + // 根据选项生成getter + if (options == PropertyGenerationOptions.All || this.HasFlag(options, PropertyGenerationOptions.IncludePropertyGetter)) + { + propertyDeclaration += $" get => dependencyObject.GetValue({this.GetDependencyPropertyTypeString()});"; + } + + // 根据选项生成setter + if (options == PropertyGenerationOptions.All || this.HasFlag(options, PropertyGenerationOptions.IncludePropertySetter)) + { + propertyDeclaration += $" set => dependencyObject.SetValue({this.GetDependencyPropertyTypeString()}, value);"; + } + + propertyDeclaration += " }"; + codeBuilder.AppendLine(propertyDeclaration); + } + codeBuilder.AppendLine(); + } + + // 如果选择生成方法形式的Getter(默认或包含方法Getter) + if (options == PropertyGenerationOptions.All || this.HasFlag(options, PropertyGenerationOptions.IncludeMethodGetter)) + { + codeBuilder.AppendLine($"///"); + //生成扩展读取方法,供依赖对象调用 + codeBuilder.AppendLine($"public static {this.m_dependencyPropertyInfo.DependencyPropertyType.ToDisplayString()} Get{this.m_dependencyPropertyInfo.Name}(this TDependencyObject dependencyObject)"); + codeBuilder.AppendLine($"where TDependencyObject:{this.m_dependencyPropertyInfo.TargetType.Name}"); + using (this.CreateCodeSpace(codeBuilder)) + { + codeBuilder.AppendLine($"return dependencyObject.GetValue({this.GetDependencyPropertyTypeString()});"); + } + codeBuilder.AppendLine(); + } + + // 如果选择生成方法形式的Setter(默认或包含方法Setter) + if (options == PropertyGenerationOptions.All || this.HasFlag(options, PropertyGenerationOptions.IncludeMethodSetter)) + { + //生成扩展设置方法,供依赖对象调用 + codeBuilder.AppendLine($"///"); + + if (this.m_dependencyPropertyInfo.ActionMode) + { + codeBuilder.AppendLine($"public static TDependencyObject Set{this.m_dependencyPropertyInfo.Name}(this TDependencyObject dependencyObject, Action<{this.m_dependencyPropertyInfo.DependencyPropertyType.ToDisplayString()}> action)"); + + codeBuilder.AppendLine($"where TDependencyObject:{this.m_dependencyPropertyInfo.TargetType.Name}"); + + using (this.CreateCodeSpace(codeBuilder)) + { + codeBuilder.AppendLine($"var value = new {this.m_dependencyPropertyInfo.DependencyPropertyType.ToDisplayString()}();"); + codeBuilder.AppendLine("action(value);"); + codeBuilder.AppendLine($"dependencyObject.SetValue({this.GetDependencyPropertyTypeString()}, value);"); + codeBuilder.AppendLine("return dependencyObject;"); + } + } + else + { + if (this.m_dependencyPropertyInfo.DependencyPropertyType is IArrayTypeSymbol) + { + codeBuilder.AppendLine($"public static TDependencyObject Set{this.m_dependencyPropertyInfo.Name}(this TDependencyObject dependencyObject, params {this.m_dependencyPropertyInfo.DependencyPropertyType.ToDisplayString()} value)"); + } + else + { + codeBuilder.AppendLine($"public static TDependencyObject Set{this.m_dependencyPropertyInfo.Name}(this TDependencyObject dependencyObject, {this.m_dependencyPropertyInfo.DependencyPropertyType.ToDisplayString()} value)"); + } + + + codeBuilder.AppendLine($"where TDependencyObject:{this.m_dependencyPropertyInfo.TargetType.Name}"); + + using (this.CreateCodeSpace(codeBuilder)) + { + codeBuilder.AppendLine($"dependencyObject.SetValue({this.GetDependencyPropertyTypeString()}, value);"); + codeBuilder.AppendLine("return dependencyObject;"); + } + } + + } + } + } + + return true; + } + + private bool HasFlag(PropertyGenerationOptions options, PropertyGenerationOptions flag) + { + return (options & flag) == flag; + } + + private string GetDependencyPropertyTypeString() + { + return this.m_dependencyPropertyInfo.ContainingType.ToDisplayString() + "." + this.m_dependencyPropertyInfo.FieldName; + } +} \ No newline at end of file diff --git a/src/TouchSocket.Core.SourceGenerator/DependencyProperty/DependencyPropertySourceGenerator.cs b/src/TouchSocket.Core.SourceGenerator/DependencyProperty/DependencyPropertySourceGenerator.cs new file mode 100644 index 000000000..86261c7a1 --- /dev/null +++ b/src/TouchSocket.Core.SourceGenerator/DependencyProperty/DependencyPropertySourceGenerator.cs @@ -0,0 +1,417 @@ +//------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +//------------------------------------------------------------------------------ + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using TouchSocket.Core; + +namespace TouchSocket; + +[Generator] +public class DependencyPropertyGenerator : IIncrementalGenerator +{ + public const string GeneratorPropertyAttributeString = "TouchSocket.Core.GeneratorPropertyAttribute"; + + public void Initialize(IncrementalGeneratorInitializationContext context) + { + // 注册GeneratorPropertyAttribute + context.RegisterPostInitializationOutput(ctx => + ctx.AddSource("GeneratorPropertyAttribute.g.cs", SourceText.From(generatorPropertyAttribute, Encoding.UTF8))); + + // 筛选包含GeneratorPropertyAttribute的类 + var provider = context.SyntaxProvider + .CreateSyntaxProvider( + predicate: static (s, _) => s is TypeDeclarationSyntax, + transform: static (ctx, _) => GetClassSymbol(ctx)) + .Where(static m => m is not null); + + var combined = provider.Collect(); + + context.RegisterSourceOutput(combined, (spc, source) => Execute(source, spc)); + } + + private static void Execute(ImmutableArray symbols, SourceProductionContext context) + { + //Debugger.Launch(); + var processed = new HashSet(); + + foreach (var namedTypeSymbol in symbols.Distinct(SymbolEqualityComparer.Default).Cast()) + { + if (namedTypeSymbol == null) + { + continue; + } + + var dependencyProperties = GetDependencyProperties(namedTypeSymbol, context); + + if (dependencyProperties.Count == 0) + { + continue; + } + + var groupedByTargetType = dependencyProperties + .GroupBy(dp => dp.TargetType, SymbolEqualityComparer.Default) + .Cast>(); + + foreach (var dependencyPropertyInfo in dependencyProperties) + { + var builder = new DependencyPropertyCodeBuilder(dependencyPropertyInfo); + if (processed.Add(builder.Id)) + { + context.AddSource(builder); + } + } + } + } + + private static List GetDependencyProperties(INamedTypeSymbol classSymbol, SourceProductionContext context) + { + var properties = new List(); + + // 检查字段 + foreach (var member in classSymbol.GetMembers()) + { + if (member is IFieldSymbol field && + field.HasAttribute(GeneratorPropertyAttributeString, out var attributeData)) + { + var propertyInfo = ProcessDependencyPropertyField(field, attributeData, context); + if (propertyInfo != null) + { + properties.Add(propertyInfo); + } + } + else if (member is IPropertySymbol property && + property.HasAttribute(GeneratorPropertyAttributeString, out attributeData)) + { + var propertyInfo = ProcessDependencyPropertyProperty(property, attributeData, context); + if (propertyInfo != null) + { + properties.Add(propertyInfo); + } + } + } + + return properties; + } + + private static DependencyPropertyInfo ProcessDependencyPropertyField(IFieldSymbol field, AttributeData attributeData, SourceProductionContext context) + { + if (!Utils.IsDependencyProperty(field.Type)) + { + return null; + } + + var targetType = GetTargetTypeFromAttribute(attributeData); + // 如果未设置TargetType,则使用当前属性所在的类型 + if (targetType == null) + { + targetType = field.ContainingType; + } + + var dependencyPropertyType = GetDependencyPropertyGenericType(field.Type); + + // 从字段的构造函数参数中获取属性名称 + var propertyName = GetPropertyNameFromField(field, field.Name); + + // 获取生成选项 + var generationOptions = GetGenerationOptionsFromAttribute(attributeData); + + var actionMode = GetActionModeFromAttribute(attributeData); + + return new DependencyPropertyInfo + { + Name = propertyName, + ActionMode = actionMode, + FieldName = field.Name, + DependencyPropertyType = dependencyPropertyType, + TargetType = targetType, + ContainingType = field.ContainingType, + GenerationOptions = generationOptions + }; + } + + private static DependencyPropertyInfo ProcessDependencyPropertyProperty(IPropertySymbol property, AttributeData attributeData, SourceProductionContext context) + { + if (!Utils.IsDependencyProperty(property.Type)) + { + return null; + } + + var targetType = GetTargetTypeFromAttribute(attributeData); + // 如果未设置TargetType,则使用当前属性所在的类型 + if (targetType == null) + { + targetType = property.ContainingType; + } + + var dependencyPropertyType = GetDependencyPropertyGenericType(property.Type); + + // 从属性的构造函数参数中获取属性名称 + var propertyName = GetPropertyNameFromProperty(property, property.Name); + + // 获取生成选项 + var generationOptions = GetGenerationOptionsFromAttribute(attributeData); + + var actionMode = GetActionModeFromAttribute(attributeData); + + return new DependencyPropertyInfo + { + Name = propertyName, + ActionMode = actionMode, + FieldName = property.Name, + DependencyPropertyType = dependencyPropertyType, + TargetType = targetType, + ContainingType = property.ContainingType, + GenerationOptions = generationOptions + }; + } + + private static PropertyGenerationOptions GetGenerationOptionsFromAttribute(AttributeData attributeData) + { + var namedArguments = attributeData.NamedArguments.ToDictionary(x => x.Key, x => x.Value); + if (namedArguments.TryGetValue("GenerationOptions", out var generationOptionsValue)) + { + if (generationOptionsValue.Value is int intValue) + { + return (PropertyGenerationOptions)intValue; + } + } + + return PropertyGenerationOptions.All; + } + + private static bool GetActionModeFromAttribute(AttributeData attributeData) + { + var namedArguments = attributeData.NamedArguments.ToDictionary(x => x.Key, x => x.Value); + if (namedArguments.TryGetValue("ActionMode", out var actionModeValue)) + { + if (actionModeValue.Value is bool boolValue) + { + return boolValue; + } + } + + return false; + } + + private static ITypeSymbol GetDependencyPropertyGenericType(ITypeSymbol type) + { + if (type is INamedTypeSymbol namedType && namedType.IsGenericType && namedType.TypeArguments.Length > 0) + { + return namedType.TypeArguments[0]; + } + return null; + } + + private static INamedTypeSymbol GetTargetTypeFromAttribute(AttributeData attributeData) + { + // 然后检查命名参数 TargetType 属性 + var namedArguments = attributeData.NamedArguments.ToDictionary(x => x.Key, x => x.Value); + if (namedArguments.TryGetValue("TargetType", out var targetTypeValue) && + targetTypeValue.Value is INamedTypeSymbol namedTargetType) + { + return namedTargetType; + } + + + + // 如果都没有设置,返回 null,将在调用处使用当前类型 + return null; + } + + private static string GetPropertyNameFromField(IFieldSymbol field, string fieldName) + { + // 首先尝试从字段的初始化表达式中获取属性名称 + if (field.DeclaringSyntaxReferences.Length > 0) + { + var syntaxReference = field.DeclaringSyntaxReferences[0]; + if (syntaxReference.GetSyntax() is VariableDeclaratorSyntax variableDeclarator) + { + if (variableDeclarator.Initializer?.Value is ObjectCreationExpressionSyntax objectCreation) + { + if (objectCreation.ArgumentList?.Arguments.Count > 0) + { + var firstArgument = objectCreation.ArgumentList.Arguments[0]; + if (firstArgument.Expression is LiteralExpressionSyntax literalExpression) + { + var value = literalExpression.Token.ValueText; + if (!string.IsNullOrEmpty(value)) + { + return value; + } + } + } + } + } + } + + // 如果无法从构造函数参数获取,则从字段名称推断 + return GetPropertyNameFromFieldName(fieldName); + } + + private static string GetPropertyNameFromProperty(IPropertySymbol property, string propertyName) + { + // 首先尝试从属性的初始化表达式中获取属性名称 + if (property.DeclaringSyntaxReferences.Length > 0) + { + var syntaxReference = property.DeclaringSyntaxReferences[0]; + if (syntaxReference.GetSyntax() is PropertyDeclarationSyntax propertyDeclaration) + { + if (propertyDeclaration.Initializer?.Value is ObjectCreationExpressionSyntax objectCreation) + { + if (objectCreation.ArgumentList?.Arguments.Count > 0) + { + var firstArgument = objectCreation.ArgumentList.Arguments[0]; + if (firstArgument.Expression is LiteralExpressionSyntax literalExpression) + { + var value = literalExpression.Token.ValueText; + if (!string.IsNullOrEmpty(value)) + { + return value; + } + } + } + } + } + } + + // 如果无法从构造函数参数获取,则从属性名称推断 + return GetPropertyNameFromFieldName(propertyName); + } + + private static string GetPropertyNameFromFieldName(string fieldName) + { + if (string.IsNullOrEmpty(fieldName)) + { + return fieldName; + } + + // 移除Property后缀 + if (fieldName.EndsWith("Property")) + { + return fieldName.Substring(0, fieldName.Length - "Property".Length); + } + + return fieldName; + } + + private static INamedTypeSymbol GetClassSymbol(GeneratorSyntaxContext context) + { + var typeSyntax = (TypeDeclarationSyntax)context.Node; + var symbol = context.SemanticModel.GetDeclaredSymbol(typeSyntax) as INamedTypeSymbol; + + // 只返回包含GeneratorPropertyAttribute的类型 + if (symbol != null && HasGeneratorPropertyAttribute(symbol)) + { + return symbol; + } + + return null; + } + + private static bool HasGeneratorPropertyAttribute(INamedTypeSymbol typeSymbol) + { + foreach (var member in typeSymbol.GetMembers()) + { + if ((member is IFieldSymbol || member is IPropertySymbol) && + member.HasAttribute(GeneratorPropertyAttributeString)) + { + return true; + } + } + return false; + } + + private const string generatorPropertyAttribute = @" + +/* +此代码由SourceGenerator工具直接生成,非必要请不要修改此处代码 +*/ + +#pragma warning disable + +using System; + +namespace TouchSocket.Core +{ + /// + /// 标识源生成依赖属性。 + /// + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)] + /*GeneratedCode*/ + internal class GeneratorPropertyAttribute : Attribute + { + /// + /// 要生成依赖属性的目标类型名称。如果未设置,则表示当前属性所在的类型。 + /// + public Type TargetType { get; set; } + + /// + /// 生成的属性类型。 + /// + public PropertyGenerationOptions GenerationOptions { get; set; } = PropertyGenerationOptions.All; + + /// + /// 是否使用Action模式生成Set扩展方法访问器。 + /// + public bool ActionMode { get; set; } + } + + /// + /// 依赖属性生成选项。 + /// + [Flags] + enum PropertyGenerationOptions + { + /// + /// 生成所有访问器。 + /// + All = 0, + /// + /// 包含方法形式的 Getter。 + /// + IncludeMethodGetter = 1, + /// + /// 包含方法形式的 Setter。 + /// + IncludeMethodSetter = 2, + /// + /// 包含属性形式的 Getter。 + /// + IncludePropertyGetter = 4, + /// + /// 包含属性形式的 Setter。 + /// + IncludePropertySetter = 8 + } +} +"; +} + +/// +/// 依赖属性信息 +/// +internal class DependencyPropertyInfo +{ + public string Name { get; set; } + public bool ActionMode { get; set; } + public string FieldName { get; set; } + public ITypeSymbol DependencyPropertyType { get; set; } + public INamedTypeSymbol TargetType { get; set; } + public INamedTypeSymbol ContainingType { get; set; } + public PropertyGenerationOptions GenerationOptions { get; set; } = PropertyGenerationOptions.All; +} \ No newline at end of file diff --git a/src/TouchSocket.Core.SourceGenerator/FastSerialize/FastSerializeCodeBuilder.cs b/src/TouchSocket.Core.SourceGenerator/FastSerialize/FastSerializeCodeBuilder.cs index a75b2c561..d46415f6f 100644 --- a/src/TouchSocket.Core.SourceGenerator/FastSerialize/FastSerializeCodeBuilder.cs +++ b/src/TouchSocket.Core.SourceGenerator/FastSerialize/FastSerializeCodeBuilder.cs @@ -16,93 +16,51 @@ using System.Text; namespace TouchSocket; -internal class FastSerializeCodeBuilder : CodeBuilder +internal class FastSerializeCodeBuilder : TypeCodeBuilder { - private readonly INamedTypeSymbol m_namedTypeSymbol; + private readonly List m_namedTypeSymbols; - public FastSerializeCodeBuilder(INamedTypeSymbol type, List namedTypeSymbols) + public FastSerializeCodeBuilder(INamedTypeSymbol type, List namedTypeSymbols) : base(type) { - this.m_namedTypeSymbol = type; this.m_namedTypeSymbols = namedTypeSymbols; } - public INamedTypeSymbol NamedTypeSymbol => this.m_namedTypeSymbol; - - public virtual IEnumerable Usings - { - get - { - yield return "using System;"; - yield return "using System.Diagnostics;"; - yield return "using TouchSocket.Core;"; - yield return "using System.Threading.Tasks;"; - } - } - - public override string Id => this.m_namedTypeSymbol.ToDisplayString(); - - private readonly List m_namedTypeSymbols; - public override string GetFileName() { return this.GetGeneratorTypeName() + "Generator"; } + protected override bool GeneratorCode(StringBuilder codeBuilder) + { + using (this.CreateNamespace(codeBuilder)) + { + codeBuilder.AppendLine($"partial class {this.GetGeneratorTypeName()}"); + codeBuilder.AppendLine("{"); + + codeBuilder.AppendLine($"public {this.GetGeneratorTypeName()} ()"); + codeBuilder.AppendLine("{"); + foreach (var item in this.m_namedTypeSymbols) + { + this.BuildItems(codeBuilder, item); + } + codeBuilder.AppendLine("}"); + + codeBuilder.AppendLine("}"); + } + + return true; + } + + private void BuildItems(StringBuilder codeBuilder, INamedTypeSymbol namedTypeSymbol) + { + var typeName = namedTypeSymbol.ToDisplayString(); + codeBuilder.AppendLine($"this.AddConverter(typeof({typeName}), new PackageFastBinaryConverter<{typeName}>());"); + } + private string GetGeneratorTypeName() { - var typeName = this.m_namedTypeSymbol.Name; + var typeName = this.TypeSymbol.Name; return typeName; } - - public override string ToString() - { - var codeString = new StringBuilder(); - codeString.AppendLine("/*"); - codeString.AppendLine("此代码由Rpc工具直接生成,非必要请不要修改此处代码"); - codeString.AppendLine("*/"); - codeString.AppendLine("#pragma warning disable"); - - foreach (var item in this.Usings) - { - codeString.AppendLine(item); - } - if (!this.NamedTypeSymbol.ContainingNamespace.IsGlobalNamespace) - { - codeString.AppendLine($"namespace {this.NamedTypeSymbol.ContainingNamespace}"); - codeString.AppendLine("{"); - } - - codeString.AppendLine($"partial class {this.GetGeneratorTypeName()}"); - codeString.AppendLine("{"); - - codeString.AppendLine($"public {this.GetGeneratorTypeName()} ()"); - codeString.AppendLine("{"); - foreach (var item in this.m_namedTypeSymbols) - { - this.BuildItems(codeString, item); - } - codeString.AppendLine("}"); - - codeString.AppendLine("}"); - - if (!this.NamedTypeSymbol.ContainingNamespace.IsGlobalNamespace) - { - codeString.AppendLine("}"); - } - - // System.Diagnostics.Debugger.Launch(); - return codeString.ToString(); - } - - private void BuildItems(StringBuilder codeString, INamedTypeSymbol namedTypeSymbol) - { - var typeName = namedTypeSymbol.ToDisplayString(); - codeString.AppendLine($"this.AddConverter(typeof({typeName}), new PackageFastBinaryConverter<{typeName}>());"); - } - - private string GetTypeName(INamedTypeSymbol namedTypeSymbol) - { - return Utils.MakeIdentifier(namedTypeSymbol.ToDisplayString()); - } } \ No newline at end of file diff --git a/src/TouchSocket.Core.SourceGenerator/FastSerialize/FastSerializeSourceGenerator.cs b/src/TouchSocket.Core.SourceGenerator/FastSerialize/FastSerializeSourceGenerator.cs index 7408a664e..046444b68 100644 --- a/src/TouchSocket.Core.SourceGenerator/FastSerialize/FastSerializeSourceGenerator.cs +++ b/src/TouchSocket.Core.SourceGenerator/FastSerialize/FastSerializeSourceGenerator.cs @@ -11,40 +11,189 @@ //------------------------------------------------------------------------------ using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; +using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; +using System.Text; +using TouchSocket.Core; namespace TouchSocket; [Generator] -public class FastSerializeGenerator : ISourceGenerator +public class FastSerializeGenerator : IIncrementalGenerator { public const string FastSerializableAttributeString = "TouchSocket.Core.FastSerializableAttribute"; - public void Execute(GeneratorExecutionContext context) + + public void Initialize(IncrementalGeneratorInitializationContext context) { - var compilation = context.Compilation; - var s = compilation.GetMetadataReference(context.Compilation.Assembly); + // 注册FastSerializableAttribute + context.RegisterPostInitializationOutput(ctx => + ctx.AddSource("FastSerializableAttribute.g.cs", SourceText.From(fastSerializableAttribute, Encoding.UTF8))); - var fastSerializableAttribute = compilation.GetTypeByMetadataName(FastSerializableAttributeString); + // 筛选包含FastSerializableAttribute的类 + var provider = context.SyntaxProvider + .CreateSyntaxProvider( + predicate: static (s, _) => s is TypeDeclarationSyntax tds + && tds.AttributeLists.Count > 0, + transform: static (ctx, _) => GetClassSymbol(ctx)) + .Where(static m => m is not null); - if (context.SyntaxReceiver is FastSerializeSyntaxReceiver receiver) + var combined = provider.Collect(); + + context.RegisterSourceOutput(combined, (spc, source) => Execute(source, spc)); + } + + private static void Execute(ImmutableArray symbols, SourceProductionContext context) + { + var processed = new HashSet(); + + foreach (var namedTypeSymbol in symbols.Distinct(SymbolEqualityComparer.Default).Cast()) { - var pairs = receiver.GetFastSerializeContexts(context); + if (!namedTypeSymbol.HasAttributes(FastSerializableAttributeString, out var atts)) + continue; - var builders = pairs.Select(a => new FastSerializeCodeBuilder(a.Key, a.Value)); - foreach (var builder in builders) + var pairs = ProcessAttributes(namedTypeSymbol, atts, context); + foreach (var pair in pairs) { - context.AddSource($"{builder.GetFileName()}.g.cs", builder.ToSourceText()); + var builder = new FastSerializeCodeBuilder(pair.Key, pair.Value); + if (processed.Add(builder.Id)) + { + context.AddSource(builder); + } } - - //foreach (var builder in pairs.Keys.Select(a => new MethodInvokeTitleCodeBuilder(a))) - //{ - // context.AddSource($"{builder.GetFileName()}.g.cs", builder.ToSourceText()); - //} } } - private readonly string fastSerializableAttribute = @" + private static Dictionary> ProcessAttributes( + INamedTypeSymbol classSymbol, + IEnumerable attributes, + SourceProductionContext context) + { + var pairs = new Dictionary>(SymbolEqualityComparer.Default); + var types = new List(); + foreach (var item in attributes) + { + var args = item.ConstructorArguments; + if (args.Length == 0) continue; + + var typeSymbol = args[0].Value as INamedTypeSymbol; + if (typeSymbol == null) continue; + + var typeMode = args.Length > 1 && args[1].Value is int mode + ? (TypeMode)mode + : TypeMode.Self; + + CollectTypes(typeSymbol, typeMode, types, context, item); + } + + pairs[classSymbol] = types.Distinct(SymbolEqualityComparer.Default).Cast().ToList(); + return pairs; + } + + private static bool CanBeAdd(INamedTypeSymbol namedTypeSymbol, SourceProductionContext context, AttributeData attributeData) + { + if (namedTypeSymbol.IsPrimitiveAndString()) + { + return false; + } + if (namedTypeSymbol.IsAbstract || namedTypeSymbol.TypeKind == TypeKind.Interface) + { + return false; + } + + if (!namedTypeSymbol.IsInheritFrom(Utils.IPackageTypeName)) + { + context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.m_rule_FastSerialize0001, attributeData.ApplicationSyntaxReference.GetSyntax().GetLocation(), namedTypeSymbol.ToDisplayString())); + return false; + } + return true; + } + + private static void CollectTypes( + INamedTypeSymbol namedTypeSymbol, + TypeMode typeMode, + List namedTypeSymbols, + SourceProductionContext context, + AttributeData attributeData) + { + if (typeMode == TypeMode.Self) + { + //Debugger.Launch(); + if (CanBeAdd(namedTypeSymbol, context, attributeData)) + { + namedTypeSymbols.Add(namedTypeSymbol); + } + return; + } + + foreach (var symbol in namedTypeSymbol.GetMembers()) + { + switch (symbol) + { + case IFieldSymbol targetSymbol: + { + if (typeMode == TypeMode.All || typeMode.HasFlag(TypeMode.Field)) + { + if (targetSymbol.Type is INamedTypeSymbol namedType && CanBeAdd(namedType, context, attributeData)) + { + namedTypeSymbols.Add(namedType); + } + } + break; + } + case IPropertySymbol targetSymbol: + { + if (typeMode == TypeMode.All || typeMode.HasFlag(TypeMode.Property)) + { + if (targetSymbol.Type is INamedTypeSymbol namedType && CanBeAdd(namedType, context, attributeData)) + { + namedTypeSymbols.Add(namedType); + } + } + break; + } + case IMethodSymbol targetSymbol: + { + if (typeMode == TypeMode.All || typeMode.HasFlag(TypeMode.MethodReturn)) + { + if (targetSymbol.HasReturn()) + { + if (targetSymbol.GetRealReturnType() is INamedTypeSymbol namedType && CanBeAdd(namedType, context, attributeData)) + { + namedTypeSymbols.Add(namedType); + } + } + } + + if (typeMode == TypeMode.All || typeMode.HasFlag(TypeMode.MethodParameter)) + { + foreach (var parameterSymbol in targetSymbol.Parameters) + { + if (parameterSymbol.Type is INamedTypeSymbol namedType && CanBeAdd(namedType, context, attributeData)) + { + namedTypeSymbols.Add(namedType); + } + } + } + break; + } + default: + break; + } + } + } + + private static INamedTypeSymbol GetClassSymbol(GeneratorSyntaxContext context) + { + var typeSyntax = (TypeDeclarationSyntax)context.Node; + return context.SemanticModel.GetDeclaredSymbol(typeSyntax) as INamedTypeSymbol; + } + + // 保持原有fastSerializableAttribute字符串 + private const string fastSerializableAttribute = @" /* 此代码由SourceGenerator工具直接生成,非必要请不要修改此处代码 */ @@ -91,15 +240,5 @@ namespace TouchSocket.Core } } - "; - - public void Initialize(GeneratorInitializationContext context) - { - context.RegisterForPostInitialization(a => - { - a.AddSource($"{nameof(this.fastSerializableAttribute)}.g.cs", this.fastSerializableAttribute); - }); - context.RegisterForSyntaxNotifications(() => new FastSerializeSyntaxReceiver()); - } } \ No newline at end of file diff --git a/src/TouchSocket.Core.SourceGenerator/FastSerialize/FastSerializeSyntaxReceiver.cs b/src/TouchSocket.Core.SourceGenerator/FastSerialize/FastSerializeSyntaxReceiver.cs index c30689207..6ebd1fce8 100644 --- a/src/TouchSocket.Core.SourceGenerator/FastSerialize/FastSerializeSyntaxReceiver.cs +++ b/src/TouchSocket.Core.SourceGenerator/FastSerialize/FastSerializeSyntaxReceiver.cs @@ -10,167 +10,160 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using System.Collections.Generic; -using System.Linq; -using TouchSocket.Core; - namespace TouchSocket; -internal sealed class FastSerializeSyntaxReceiver : ISyntaxReceiver -{ - private readonly List m_syntaxList = new List(); +//internal sealed class FastSerializeSyntaxReceiver : ISyntaxReceiver +//{ +// private readonly List m_syntaxList = new List(); - public Dictionary> GetFastSerializeContexts(GeneratorExecutionContext context) - { - var compilation = context.Compilation; +// public Dictionary> GetFastSerializeContexts(GeneratorExecutionContext context) +// { +// var compilation = context.Compilation; - var pairs = new Dictionary>(); - //Debugger.Launch(); - foreach (var syntax in this.m_syntaxList) - { - var namedTypeSymbol = compilation.GetSemanticModel(syntax.SyntaxTree).GetDeclaredSymbol(syntax); - if (!namedTypeSymbol.HasAttributes(FastSerializeGenerator.FastSerializableAttributeString, out var atts)) - { - continue; - } +// var pairs = new Dictionary>(); +// //Debugger.Launch(); +// foreach (var syntax in this.m_syntaxList) +// { +// var namedTypeSymbol = compilation.GetSemanticModel(syntax.SyntaxTree).GetDeclaredSymbol(syntax); +// if (!namedTypeSymbol.HasAttributes(FastSerializeGenerator.FastSerializableAttributeString, out var atts)) +// { +// continue; +// } - var types = new List(); +// var types = new List(); - foreach (var item in atts) - { - if (item.ConstructorArguments.Length == 1) - { - if (item.ConstructorArguments[0].Value is not INamedTypeSymbol typeSymbol) - { - continue; - } +// foreach (var item in atts) +// { +// if (item.ConstructorArguments.Length == 1) +// { +// if (item.ConstructorArguments[0].Value is not INamedTypeSymbol typeSymbol) +// { +// continue; +// } - this.GetINamedTypeSymbols(typeSymbol, TypeMode.Self, ref types, context, item); - } - else if (item.ConstructorArguments.Length == 2) - { - if (item.ConstructorArguments[0].Value is not INamedTypeSymbol typeSymbol) - { - continue; - } +// this.GetINamedTypeSymbols(typeSymbol, TypeMode.Self, ref types, context, item); +// } +// else if (item.ConstructorArguments.Length == 2) +// { +// if (item.ConstructorArguments[0].Value is not INamedTypeSymbol typeSymbol) +// { +// continue; +// } - if (item.ConstructorArguments[1].Value is not int value) - { - continue; - } +// if (item.ConstructorArguments[1].Value is not int value) +// { +// continue; +// } - var typeMode = (TypeMode)value; - this.GetINamedTypeSymbols(typeSymbol, typeMode, ref types, context, item); - } - } +// var typeMode = (TypeMode)value; +// this.GetINamedTypeSymbols(typeSymbol, typeMode, ref types, context, item); +// } +// } - types = types.Distinct().ToList(); - pairs.Add(namedTypeSymbol, types); - } +// types = types.Distinct().ToList(); +// pairs.Add(namedTypeSymbol, types); +// } - return pairs; - } +// return pairs; +// } - public void GetINamedTypeSymbols(INamedTypeSymbol namedTypeSymbol, TypeMode typeMode, ref List namedTypeSymbols, GeneratorExecutionContext context, AttributeData attributeData) - { - if (typeMode == TypeMode.Self) - { - //Debugger.Launch(); - if (CanBeAdd(namedTypeSymbol, context, attributeData)) - { - namedTypeSymbols.Add(namedTypeSymbol); - } - return; - } +// public void GetINamedTypeSymbols(INamedTypeSymbol namedTypeSymbol, TypeMode typeMode, ref List namedTypeSymbols, GeneratorExecutionContext context, AttributeData attributeData) +// { +// if (typeMode == TypeMode.Self) +// { +// //Debugger.Launch(); +// if (CanBeAdd(namedTypeSymbol, context, attributeData)) +// { +// namedTypeSymbols.Add(namedTypeSymbol); +// } +// return; +// } - foreach (var symbol in namedTypeSymbol.GetMembers()) - { - switch (symbol) - { - case IFieldSymbol targetSymbol: - { - if (typeMode == TypeMode.All || typeMode.HasFlag(TypeMode.Field)) - { - if (targetSymbol.Type is INamedTypeSymbol namedType && CanBeAdd(namedType, context, attributeData)) - { - namedTypeSymbols.Add(namedType); - } - } - break; - } - case IPropertySymbol targetSymbol: - { - if (typeMode == TypeMode.All || typeMode.HasFlag(TypeMode.Property)) - { - if (targetSymbol.Type is INamedTypeSymbol namedType && CanBeAdd(namedType, context, attributeData)) - { - namedTypeSymbols.Add(namedType); - } - } - break; - } - case IMethodSymbol targetSymbol: - { - if (typeMode == TypeMode.All || typeMode.HasFlag(TypeMode.MethodReturn)) - { - if (targetSymbol.HasReturn()) - { - if (targetSymbol.GetRealReturnType() is INamedTypeSymbol namedType && CanBeAdd(namedType, context, attributeData)) - { - namedTypeSymbols.Add(namedType); - } - } - } +// foreach (var symbol in namedTypeSymbol.GetMembers()) +// { +// switch (symbol) +// { +// case IFieldSymbol targetSymbol: +// { +// if (typeMode == TypeMode.All || typeMode.HasFlag(TypeMode.Field)) +// { +// if (targetSymbol.Type is INamedTypeSymbol namedType && CanBeAdd(namedType, context, attributeData)) +// { +// namedTypeSymbols.Add(namedType); +// } +// } +// break; +// } +// case IPropertySymbol targetSymbol: +// { +// if (typeMode == TypeMode.All || typeMode.HasFlag(TypeMode.Property)) +// { +// if (targetSymbol.Type is INamedTypeSymbol namedType && CanBeAdd(namedType, context, attributeData)) +// { +// namedTypeSymbols.Add(namedType); +// } +// } +// break; +// } +// case IMethodSymbol targetSymbol: +// { +// if (typeMode == TypeMode.All || typeMode.HasFlag(TypeMode.MethodReturn)) +// { +// if (targetSymbol.HasReturn()) +// { +// if (targetSymbol.GetRealReturnType() is INamedTypeSymbol namedType && CanBeAdd(namedType, context, attributeData)) +// { +// namedTypeSymbols.Add(namedType); +// } +// } +// } - if (typeMode == TypeMode.All || typeMode.HasFlag(TypeMode.MethodParameter)) - { - foreach (var parameterSymbol in targetSymbol.Parameters) - { - if (parameterSymbol.Type is INamedTypeSymbol namedType && CanBeAdd(namedType, context, attributeData)) - { - namedTypeSymbols.Add(namedType); - } - } - } - break; - } - default: - break; - } - } - } +// if (typeMode == TypeMode.All || typeMode.HasFlag(TypeMode.MethodParameter)) +// { +// foreach (var parameterSymbol in targetSymbol.Parameters) +// { +// if (parameterSymbol.Type is INamedTypeSymbol namedType && CanBeAdd(namedType, context, attributeData)) +// { +// namedTypeSymbols.Add(namedType); +// } +// } +// } +// break; +// } +// default: +// break; +// } +// } +// } - /// - /// 访问语法树 - /// - /// - void ISyntaxReceiver.OnVisitSyntaxNode(SyntaxNode syntaxNode) - { - if (syntaxNode is TypeDeclarationSyntax syntax) - { - this.m_syntaxList.Add(syntax); - } - } +// /// +// /// 访问语法树 +// /// +// /// +// void ISyntaxReceiver.OnVisitSyntaxNode(SyntaxNode syntaxNode) +// { +// if (syntaxNode is TypeDeclarationSyntax syntax) +// { +// this.m_syntaxList.Add(syntax); +// } +// } - private static bool CanBeAdd(INamedTypeSymbol namedTypeSymbol, GeneratorExecutionContext context, AttributeData attributeData) - { - if (namedTypeSymbol.IsPrimitive()) - { - return false; - } - if (namedTypeSymbol.IsAbstract || namedTypeSymbol.TypeKind == TypeKind.Interface) - { - return false; - } +// private static bool CanBeAdd(INamedTypeSymbol namedTypeSymbol, GeneratorExecutionContext context, AttributeData attributeData) +// { +// if (namedTypeSymbol.IsPrimitive()) +// { +// return false; +// } +// if (namedTypeSymbol.IsAbstract || namedTypeSymbol.TypeKind == TypeKind.Interface) +// { +// return false; +// } - if (!namedTypeSymbol.IsInheritFrom(PackageSyntaxReceiver.IPackageTypeName)) - { - context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.m_rule_FastSerialize0001, attributeData.ApplicationSyntaxReference.GetSyntax().GetLocation(), namedTypeSymbol.ToDisplayString())); - return false; - } - return true; - } -} \ No newline at end of file +// if (!namedTypeSymbol.IsInheritFrom(PackageSyntaxReceiver.IPackageTypeName)) +// { +// context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.m_rule_FastSerialize0001, attributeData.ApplicationSyntaxReference.GetSyntax().GetLocation(), namedTypeSymbol.ToDisplayString())); +// return false; +// } +// return true; +// } +//} \ No newline at end of file diff --git a/src/TouchSocket.Core.SourceGenerator/LanguageVersion/LanguageVersionSourceGenerator.cs b/src/TouchSocket.Core.SourceGenerator/LanguageVersion/LanguageVersionSourceGenerator.cs new file mode 100644 index 000000000..5dbf6e149 --- /dev/null +++ b/src/TouchSocket.Core.SourceGenerator/LanguageVersion/LanguageVersionSourceGenerator.cs @@ -0,0 +1,121 @@ +//// ------------------------------------------------------------------------------ +//// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +//// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +//// CSDN博客:https://blog.csdn.net/qq_40374647 +//// 哔哩哔哩视频:https://space.bilibili.com/94253567 +//// Gitee源代码仓库:https://gitee.com/RRQM_Home +//// Github源代码仓库:https://github.com/RRQM +//// API首页:https://touchsocket.net/ +//// 交流QQ群:234762506 +//// 感谢您的下载和使用 +//// ------------------------------------------------------------------------------ + +//using Microsoft.CodeAnalysis; +//using Microsoft.CodeAnalysis.CSharp; +//using System.Collections.Generic; + +//namespace TouchSocket; + +//[Generator] +//public class LanguageVersionSourceGenerator : IIncrementalGenerator +//{ +// public void Initialize(IncrementalGeneratorInitializationContext context) +// { +// context.RegisterSourceOutput(context.ParseOptionsProvider, (spc, parseOptions) => +// { +// // 检测C#语言版本并生成预编译定义 +// var languageVersionDefines = GenerateLanguageVersionDefines(parseOptions); +// if (!string.IsNullOrEmpty(languageVersionDefines)) +// { +// spc.AddSource("GeneratorLanguageVersionDefines.g.cs", languageVersionDefines); +// } +// }); +// } + +// /// +// /// 根据解析选项生成C#语言版本的预编译定义 +// /// +// /// 解析选项 +// /// 预编译定义的源代码 +// private static string GenerateLanguageVersionDefines(ParseOptions parseOptions) +// { +// if (parseOptions is not CSharpParseOptions csharpOptions) +// { +// return null; +// } + +// var languageVersion = csharpOptions.LanguageVersion; +// var defines = new List(); + +// if (languageVersion >= LanguageVersion.CSharp1) +// { +// defines.Add("#define CSHARP1_OR_GREATER"); +// } +// if (languageVersion >= LanguageVersion.CSharp2) +// { +// defines.Add("#define CSHARP2_OR_GREATER"); +// } +// if (languageVersion >= LanguageVersion.CSharp3) +// { +// defines.Add("#define CSHARP3_OR_GREATER"); +// } +// if (languageVersion >= LanguageVersion.CSharp4) +// { +// defines.Add("#define CSHARP4_OR_GREATER"); +// } +// if (languageVersion >= LanguageVersion.CSharp5) +// { +// defines.Add("#define CSHARP5_OR_GREATER"); +// } +// if (languageVersion >= LanguageVersion.CSharp6) +// { +// defines.Add("#define CSHARP6_OR_GREATER"); +// } +// if (languageVersion >= LanguageVersion.CSharp7) +// { +// defines.Add("#define CSHARP7_OR_GREATER"); +// } +// if (languageVersion >= LanguageVersion.CSharp7_1) +// { +// defines.Add("#define CSHARP7_1_OR_GREATER"); +// } +// if (languageVersion >= LanguageVersion.CSharp7_2) +// { +// defines.Add("#define CSHARP7_2_OR_GREATER"); +// } +// if (languageVersion >= LanguageVersion.CSharp7_3) +// { +// defines.Add("#define CSHARP7_3_OR_GREATER"); +// } +// if (languageVersion >= LanguageVersion.CSharp8) +// { +// defines.Add("#define CSHARP8_OR_GREATER"); +// } +// if (languageVersion >= LanguageVersion.CSharp9) +// { +// defines.Add("#define CSHARP9_OR_GREATER"); +// } +// if (languageVersion >= LanguageVersion.CSharp10) +// { +// defines.Add("#define CSHARP10_OR_GREATER"); +// } +// if (languageVersion >= LanguageVersion.CSharp11) +// { +// defines.Add("#define CSHARP11_OR_GREATER"); +// } +// if (languageVersion >= LanguageVersion.CSharp12) +// { +// defines.Add("#define CSHARP12_OR_GREATER"); +// } +// if (languageVersion >= LanguageVersion.CSharp13) +// { +// defines.Add("#define CSHARP13_OR_GREATER"); +// } +// if (defines.Count == 0) +// { +// return null; +// } + +// return string.Join("\r\n", defines) + "\r\n"; +// } +//} diff --git a/src/TouchSocket.Core.SourceGenerator/Method/MethodCodeBuilder.cs b/src/TouchSocket.Core.SourceGenerator/Method/MethodCodeBuilder.cs new file mode 100644 index 000000000..bed40d04a --- /dev/null +++ b/src/TouchSocket.Core.SourceGenerator/Method/MethodCodeBuilder.cs @@ -0,0 +1,32 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using Microsoft.CodeAnalysis; + +namespace TouchSocket; + +internal abstract class MethodCodeBuilder : TypeCodeBuilder +{ + protected MethodCodeBuilder(INamedTypeSymbol typeSymbol) : base(typeSymbol) + { + } + + protected string GetGeneratorTypeName() + { + var typeName = $"__{Utils.MakeIdentifier(this.TypeSymbol.ToDisplayString())}MethodExtension"; + + return typeName; + } + + public override string Id => this.TypeSymbol.ToDisplayString(); + protected virtual string GeneratorTypeNamespace => "TouchSocket.Core.__Internals"; +} diff --git a/src/TouchSocket.Core.SourceGenerator/Method/MethodInvokeClassCodeBuilder.cs b/src/TouchSocket.Core.SourceGenerator/Method/MethodInvokeClassCodeBuilder.cs new file mode 100644 index 000000000..8092c0fd3 --- /dev/null +++ b/src/TouchSocket.Core.SourceGenerator/Method/MethodInvokeClassCodeBuilder.cs @@ -0,0 +1,400 @@ +//------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +//------------------------------------------------------------------------------ + +using Microsoft.CodeAnalysis; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TouchSocket; + +internal class MethodInvokeClassCodeBuilder : MethodCodeBuilder +{ + private readonly Compilation m_compilation; + + public MethodInvokeClassCodeBuilder(INamedTypeSymbol type, Compilation compilation, List methodSymbols) : base(type) + { + //Debugger.Launch(); + this.m_compilation = compilation; + this.MethodSymbols = methodSymbols; + } + + public List MethodSymbols { get; } + + public override string GetFileName() + { + return this.GeneratorTypeNamespace + this.GetGeneratorTypeName() + "Class.Generator.g.cs"; + } + + protected override bool GeneratorCode(StringBuilder codeBuilder) + { + using (this.CreateNamespaceIfNotGlobalNamespace(codeBuilder, this.GeneratorTypeNamespace)) + { + codeBuilder.AppendLine($"partial class {this.GetGeneratorTypeName()}"); + using (this.CreateCodeSpace(codeBuilder)) + { + var methods = this.MethodSymbols; + + foreach (var item in methods) + { + //Debugger.Launch(); + this.BuildMethodFunc(codeBuilder, item); + this.BuildClass(codeBuilder, item); + } + } + } + return true; + } + + private void BuildClass(StringBuilder codeBuilder, IMethodSymbol method) + { + var isTypeAwaitable = this.IsTypeAwaitable(method.ReturnType, out var returnType); + + codeBuilder.AppendLine($"class {this.GetMethodName(method)}Class : IDynamicMethodInfo"); + using (this.CreateCodeSpace(codeBuilder)) + { + if (method.ReturnType.IsVoid()) + { + codeBuilder.AppendLine("public Type RealReturnType => default;"); + codeBuilder.AppendLine("public MethodReturnKind ReturnKind => MethodReturnKind.Void;"); + + this.BuildMethod(codeBuilder, method); + + codeBuilder.AppendLine("public Task GetResultAsync(object o)"); + using (this.CreateCodeSpace(codeBuilder)) + { + codeBuilder.AppendLine("throw new NotImplementedException();"); + } + } + else if (isTypeAwaitable) + { + var hasConfigureAwait = this.HasConfigureAwait(method.ReturnType); + if (returnType == null) + { + codeBuilder.AppendLine("public Type RealReturnType => default;"); + codeBuilder.AppendLine("public MethodReturnKind ReturnKind => MethodReturnKind.Awaitable;"); + + this.BuildMethod(codeBuilder, method); + + codeBuilder.AppendLine("public async Task GetResultAsync(object o)"); + using (this.CreateCodeSpace(codeBuilder)) + { + if (method.ReturnType.IsValueType && !this.IsNullableValueType(method.ReturnType)) + { + codeBuilder.AppendLine($"var result = System.Runtime.CompilerServices.Unsafe.Unbox<{method.ReturnType.ToDisplayString()}>(o);"); + } + else + { + codeBuilder.AppendLine($"var result = ({method.ReturnType.ToDisplayString()})o;"); + } + + if (hasConfigureAwait) + { + codeBuilder.AppendLine("await result.ConfigureAwait(EasyTask.ContinueOnCapturedContext);"); + } + else + { + codeBuilder.AppendLine("await result;"); + } + + codeBuilder.AppendLine("return default;"); + } + } + else + { + codeBuilder.AppendLine($"public Type RealReturnType =>typeof({returnType.GetTypeofString()}) ;"); + codeBuilder.AppendLine("public MethodReturnKind ReturnKind => MethodReturnKind.AwaitableObject;"); + + this.BuildMethod(codeBuilder, method); + + codeBuilder.AppendLine("public async Task GetResultAsync(object o)"); + using (this.CreateCodeSpace(codeBuilder)) + { + if (method.ReturnType.IsValueType && !this.IsNullableValueType(method.ReturnType)) + { + codeBuilder.AppendLine($"var result = System.Runtime.CompilerServices.Unsafe.Unbox<{method.ReturnType.ToDisplayString()}>(o);"); + } + else + { + codeBuilder.AppendLine($"var result = ({method.ReturnType.ToDisplayString()})o;"); + } + + if (hasConfigureAwait) + { + codeBuilder.AppendLine("return await result.ConfigureAwait(EasyTask.ContinueOnCapturedContext);"); + } + else + { + codeBuilder.AppendLine("return await result;"); + } + } + } + } + else + { + codeBuilder.AppendLine($"public Type RealReturnType => typeof({method.ReturnType.GetTypeofString()});"); + codeBuilder.AppendLine("public MethodReturnKind ReturnKind => MethodReturnKind.Object;"); + + this.BuildMethod(codeBuilder, method); + codeBuilder.AppendLine("public Task GetResultAsync(object o)"); + using (this.CreateCodeSpace(codeBuilder)) + { + codeBuilder.AppendLine("throw new NotImplementedException();"); + } + } + } + } + + private bool HasConfigureAwait(ITypeSymbol taskType) + { + var configureAwaitMethod = taskType?.GetMembers("ConfigureAwait") + .OfType() + .FirstOrDefault(m => m.Parameters.Length == 1 && m.Parameters[0].Type.SpecialType == SpecialType.System_Boolean); + return configureAwaitMethod != null; + } + + private void BuildMethod(StringBuilder codeBuilder, IMethodSymbol method) + { + codeBuilder.AppendLine($"public object Invoke(object instance, object[] ps)"); + codeBuilder.AppendLine("{"); + + var ps = new List(); + for (var i = 0; i < method.Parameters.Length; i++) + { + var parameter = method.Parameters[i]; + if (parameter.Type.IsValueType && !this.IsNullableValueType(parameter.Type)) + { + codeBuilder.AppendLine($"var {parameter.Name}=System.Runtime.CompilerServices.Unsafe.Unbox<{parameter.Type.ToDisplayString()}>(ps[{i}]);"); + } + else + { + codeBuilder.AppendLine($"var {parameter.Name}=({parameter.Type.ToDisplayString()})ps[{i}];"); + } + + if (parameter.RefKind == RefKind.Ref) + { + ps.Add($"ref {parameter.Name}"); + } + else if (parameter.RefKind == RefKind.Out) + { + ps.Add($"out {parameter.Name}"); + } + else + { + ps.Add(parameter.Name); + } + } + if (ps.Count > 0) + { + if (method.ReturnsVoid) + { + if (method.IsStatic) + { + codeBuilder.AppendLine($"{method.ContainingType.ToDisplayString()}.{method.Name}({string.Join(",", ps)});"); + } + else + { + if (method.ContainingType.IsValueType) + { + codeBuilder.AppendLine($"System.Runtime.CompilerServices.Unsafe.Unbox<{method.ContainingType.ToDisplayString()}>(instance).{method.Name}({string.Join(",", ps)});"); + } + else + { + codeBuilder.AppendLine($"System.Runtime.CompilerServices.Unsafe.As<{method.ContainingType.ToDisplayString()}>(instance).{method.Name}({string.Join(",", ps)});"); + } + } + } + else + { + if (method.IsStatic) + { + codeBuilder.AppendLine($"var result = {method.ContainingType.ToDisplayString()}.{method.Name}({string.Join(",", ps)});"); + } + else + { + if (method.ContainingType.IsValueType) + { + codeBuilder.AppendLine($"var result = System.Runtime.CompilerServices.Unsafe.Unbox<{method.ContainingType.ToDisplayString()}>(instance).{method.Name}({string.Join(",", ps)});"); + } + else + { + codeBuilder.AppendLine($"var result = System.Runtime.CompilerServices.Unsafe.As<{method.ContainingType.ToDisplayString()}>(instance).{method.Name}({string.Join(",", ps)});"); + } + } + } + } + else + { + if (method.ReturnsVoid) + { + if (method.IsStatic) + { + codeBuilder.AppendLine($"{method.ContainingType.ToDisplayString()}.{method.Name}();"); + } + else + { + if (method.ContainingType.IsValueType) + { + codeBuilder.AppendLine($"System.Runtime.CompilerServices.Unsafe.Unbox<{method.ContainingType.ToDisplayString()}>(instance).{method.Name}();"); + } + else + { + codeBuilder.AppendLine($"System.Runtime.CompilerServices.Unsafe.As<{method.ContainingType.ToDisplayString()}>(instance).{method.Name}();"); + } + } + } + else + { + if (method.IsStatic) + { + codeBuilder.AppendLine($"var result = {method.ContainingType.ToDisplayString()}.{method.Name}();"); + } + else + { + if (method.ContainingType.IsValueType) + { + codeBuilder.AppendLine($"var result = System.Runtime.CompilerServices.Unsafe.Unbox<{method.ContainingType.ToDisplayString()}>(instance).{method.Name}();"); + } + else + { + codeBuilder.AppendLine($"var result = System.Runtime.CompilerServices.Unsafe.As<{method.ContainingType.ToDisplayString()}>(instance).{method.Name}();"); + } + } + } + } + + // 处理 ref 和 out 参数的回写 + for (var i = 0; i < method.Parameters.Length; i++) + { + var parameter = method.Parameters[i]; + + if (parameter.RefKind == RefKind.Ref || parameter.RefKind == RefKind.Out) + { + codeBuilder.AppendLine($"ps[{i}]={parameter.Name};"); + } + } + + if (method.ReturnsVoid) + { + codeBuilder.AppendLine("return default;"); + } + else + { + codeBuilder.AppendLine("return result;"); + } + codeBuilder.AppendLine("}"); + } + + private void BuildMethodFunc(StringBuilder codeBuilder, IMethodSymbol method) + { + codeBuilder.AppendLine($"public static IDynamicMethodInfo {this.GetMethodName(method)}ClassProperty => new {this.GetMethodName(method)}Class();"); + } + + private bool IsTypeAwaitable(ITypeSymbol typeSymbol, out ITypeSymbol returnType) + { + returnType = default; + // 查找无参数的GetAwaiter实例方法 + var getAwaiterMethod = typeSymbol.GetMembers("GetAwaiter") + .OfType() + .FirstOrDefault(m => m.Parameters.IsEmpty && m.MethodKind == MethodKind.Ordinary); + + if (getAwaiterMethod == null) + { + return false; // 无符合条件的实例方法 + } + + var awaiterType = getAwaiterMethod.ReturnType as INamedTypeSymbol; + if (awaiterType == null) + { + return false; + } + + // 获取INotifyCompletion和ICriticalNotifyCompletion接口符号 + var inotifyCompletion = this.m_compilation.GetTypeByMetadataName("System.Runtime.CompilerServices.INotifyCompletion"); + var icriticalNotifyCompletion = this.m_compilation.GetTypeByMetadataName("System.Runtime.CompilerServices.ICriticalNotifyCompletion"); + + if (inotifyCompletion == null || icriticalNotifyCompletion == null) + { + return false; // 编译环境中缺少必要接口 + } + + // 检查是否实现任一接口 + var implementsInterface = awaiterType.AllInterfaces.Any(i => + SymbolEqualityComparer.Default.Equals(i, inotifyCompletion) || + SymbolEqualityComparer.Default.Equals(i, icriticalNotifyCompletion)); + + if (!implementsInterface) + { + return false; + } + + // 检查IsCompleted属性是否存在且类型为bool + var isCompleted = awaiterType.GetMembers("IsCompleted") + .OfType() + .FirstOrDefault(p => p.Type.SpecialType == SpecialType.System_Boolean && p.GetMethod != null); + + if (isCompleted == null) + { + return false; + } + + // 检查GetResult方法是否存在并无参数 + var getResult = awaiterType.GetMembers("GetResult") + .OfType() + .FirstOrDefault(m => m.Parameters.IsEmpty); + if (getResult.ReturnType.IsVoid()) + { + returnType = default; + } + else + { + returnType = getResult.ReturnType; + } + + return getResult != null; + } + + private string GetMethodName(IMethodSymbol method) + { + return method.GetDeterminantName(); + } + + private string GetObjectName(IMethodSymbol method) + { + foreach (var item1 in new string[] { "obj", "targetObj", "target", "@obj", "@targetObj", "@target" }) + { + var same = false; + foreach (var item2 in method.Parameters) + { + if (item2.Name == item1) + { + same = true; + break; + } + } + + if (!same) + { + return item1; + } + } + + return "@obj"; + } + + private bool IsNullableValueType(ITypeSymbol typeSymbol) + { + return typeSymbol.IsValueType + && typeSymbol is INamedTypeSymbol namedType + && namedType.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T; + } +} \ No newline at end of file diff --git a/src/TouchSocket.Core.SourceGenerator/Method/MethodInvokeCodeBuilder.cs b/src/TouchSocket.Core.SourceGenerator/Method/MethodInvokeCodeBuilder.cs index 509fc4bb8..763eeb93a 100644 --- a/src/TouchSocket.Core.SourceGenerator/Method/MethodInvokeCodeBuilder.cs +++ b/src/TouchSocket.Core.SourceGenerator/Method/MethodInvokeCodeBuilder.cs @@ -12,101 +12,67 @@ using Microsoft.CodeAnalysis; using System.Collections.Generic; +using System.Linq; using System.Text; namespace TouchSocket; -internal class MethodInvokeCodeBuilder : CodeBuilder +internal class MethodInvokeCodeBuilder : MethodCodeBuilder { - private readonly INamedTypeSymbol m_namedTypeSymbol; + private readonly Compilation m_compilation; - public MethodInvokeCodeBuilder(INamedTypeSymbol type, List methodSymbols) + public MethodInvokeCodeBuilder(INamedTypeSymbol type, Compilation compilation, List methodSymbols) : base(type) { - this.m_namedTypeSymbol = type; + this.m_compilation = compilation; this.MethodSymbols = methodSymbols; } - public INamedTypeSymbol NamedTypeSymbol => this.m_namedTypeSymbol; - - public virtual IEnumerable Usings - { - get - { - yield return "using System;"; - yield return "using System.Diagnostics;"; - yield return "using TouchSocket.Core;"; - yield return "using System.Threading.Tasks;"; - } - } - - protected virtual string GeneratorTypeNamespace => "TouchSocket.Core.__Internals"; - - public override string Id => this.m_namedTypeSymbol.ToDisplayString(); - public List MethodSymbols { get; } public override string GetFileName() { - return this.GeneratorTypeNamespace + this.GetGeneratorTypeName() + "Generator"; + return this.GeneratorTypeNamespace + this.GetGeneratorTypeName() + "Generator.g.cs"; } - private string GetGeneratorTypeName() + protected override bool GeneratorCode(StringBuilder codeBuilder) { - var typeName = $"__{Utils.MakeIdentifier(this.m_namedTypeSymbol.ToDisplayString())}MethodExtension"; + using (this.CreateNamespaceIfNotGlobalNamespace(codeBuilder, this.GeneratorTypeNamespace)) + { + codeBuilder.AppendLine($"partial class {this.GetGeneratorTypeName()}"); + using (this.CreateCodeSpace(codeBuilder)) + { + var methods = this.MethodSymbols; - return typeName; + foreach (var item in methods) + { + this.BuildMethodFunc(codeBuilder, item); + this.BuildMethod(codeBuilder, item); + this.BuildAwaitableReturnTypeMethod(codeBuilder, item); + } + } + } + return true; } - public override string ToString() - { - var codeString = new StringBuilder(); - codeString.AppendLine("/*"); - codeString.AppendLine("此代码由Rpc工具直接生成,非必要请不要修改此处代码"); - codeString.AppendLine("*/"); - codeString.AppendLine("#pragma warning disable"); - - foreach (var item in this.Usings) - { - codeString.AppendLine(item); - } - if (!this.NamedTypeSymbol.ContainingNamespace.IsGlobalNamespace) - { - codeString.AppendLine($"namespace {this.GeneratorTypeNamespace}"); - codeString.AppendLine("{"); - } - - codeString.AppendLine($"partial class {this.GetGeneratorTypeName()}"); - codeString.AppendLine("{"); - var methods = this.MethodSymbols; - - foreach (var item in methods) - { - this.BuildMethodFunc(codeString, item); - this.BuildMethod(codeString, item); - } - codeString.AppendLine("}"); - - if (!this.NamedTypeSymbol.ContainingNamespace.IsGlobalNamespace) - { - codeString.AppendLine("}"); - } - - // System.Diagnostics.Debugger.Launch(); - return codeString.ToString(); - } - - private void BuildMethod(StringBuilder codeString, IMethodSymbol method) + private void BuildMethod(StringBuilder codeBuilder, IMethodSymbol method) { var objectName = this.GetObjectName(method); - codeString.AppendLine($"private static object {this.GetMethodName(method)}(object {objectName}, object[] ps)"); - codeString.AppendLine("{"); + codeBuilder.AppendLine($"private static object {this.GetMethodName(method)}(object {objectName}, object[] ps)"); + codeBuilder.AppendLine("{"); var ps = new List(); for (var i = 0; i < method.Parameters.Length; i++) { var parameter = method.Parameters[i]; - codeString.AppendLine($"var {parameter.Name}=({parameter.Type.ToDisplayString()})ps[{i}];"); + if (parameter.Type.IsValueType && !this.IsNullableValueType(parameter.Type)) + { + codeBuilder.AppendLine($"var {parameter.Name}=System.Runtime.CompilerServices.Unsafe.Unbox<{parameter.Type.ToDisplayString()}>(ps[{i}]);"); + } + else + { + codeBuilder.AppendLine($"var {parameter.Name}=({parameter.Type.ToDisplayString()})ps[{i}];"); + } if (parameter.RefKind == RefKind.Ref) { @@ -127,24 +93,37 @@ internal class MethodInvokeCodeBuilder : CodeBuilder { if (method.IsStatic) { - codeString.AppendLine($"{method.ContainingType.ToDisplayString()}.{method.Name}({string.Join(",", ps)});"); + codeBuilder.AppendLine($"{method.ContainingType.ToDisplayString()}.{method.Name}({string.Join(",", ps)});"); } else { - codeString.AppendLine($"(({method.ContainingType.ToDisplayString()}){objectName}).{method.Name}({string.Join(",", ps)});"); + if (method.ContainingType.IsValueType) + { + codeBuilder.AppendLine($"System.Runtime.CompilerServices.Unsafe.Unbox<{method.ContainingType.ToDisplayString()}>({objectName}).{method.Name}({string.Join(",", ps)});"); + } + else + { + codeBuilder.AppendLine($"System.Runtime.CompilerServices.Unsafe.As<{method.ContainingType.ToDisplayString()}>({objectName}).{method.Name}({string.Join(",", ps)});"); + } } } else { if (method.IsStatic) { - codeString.AppendLine($"var result = {method.ContainingType.ToDisplayString()}.{method.Name}({string.Join(",", ps)});"); + codeBuilder.AppendLine($"var result = {method.ContainingType.ToDisplayString()}.{method.Name}({string.Join(",", ps)});"); } else { - codeString.AppendLine($"var result = (({method.ContainingType.ToDisplayString()}){objectName}).{method.Name}({string.Join(",", ps)});"); + if (method.ContainingType.IsValueType) + { + codeBuilder.AppendLine($"var result = System.Runtime.CompilerServices.Unsafe.Unbox<{method.ContainingType.ToDisplayString()}>({objectName}).{method.Name}({string.Join(",", ps)});"); + } + else + { + codeBuilder.AppendLine($"var result = System.Runtime.CompilerServices.Unsafe.As<{method.ContainingType.ToDisplayString()}>({objectName}).{method.Name}({string.Join(",", ps)});"); + } } - } } else @@ -153,59 +132,170 @@ internal class MethodInvokeCodeBuilder : CodeBuilder { if (method.IsStatic) { - codeString.AppendLine($"{method.ContainingType.ToDisplayString()}.{method.Name}();"); + codeBuilder.AppendLine($"{method.ContainingType.ToDisplayString()}.{method.Name}();"); } else { - codeString.AppendLine($"(({method.ContainingType.ToDisplayString()}){objectName}).{method.Name}();"); + if (method.ContainingType.IsValueType) + { + codeBuilder.AppendLine($"System.Runtime.CompilerServices.Unsafe.Unbox<{method.ContainingType.ToDisplayString()}>({objectName}).{method.Name}();"); + } + else + { + codeBuilder.AppendLine($"System.Runtime.CompilerServices.Unsafe.As<{method.ContainingType.ToDisplayString()}>({objectName}).{method.Name}();"); + } } - } else { if (method.IsStatic) { - codeString.AppendLine($"var result = {method.ContainingType.ToDisplayString()}.{method.Name}();"); + codeBuilder.AppendLine($"var result = {method.ContainingType.ToDisplayString()}.{method.Name}();"); } else { - codeString.AppendLine($"var result = (({method.ContainingType.ToDisplayString()}){objectName}).{method.Name}();"); + if (method.ContainingType.IsValueType) + { + codeBuilder.AppendLine($"var result = System.Runtime.CompilerServices.Unsafe.Unbox<{method.ContainingType.ToDisplayString()}>({objectName}).{method.Name}();"); + } + else + { + codeBuilder.AppendLine($"var result = System.Runtime.CompilerServices.Unsafe.As<{method.ContainingType.ToDisplayString()}>({objectName}).{method.Name}();"); + } } } } + + // 处理 ref 和 out 参数的回写 for (var i = 0; i < method.Parameters.Length; i++) { var parameter = method.Parameters[i]; if (parameter.RefKind == RefKind.Ref || parameter.RefKind == RefKind.Out) { - codeString.AppendLine($"ps[{i}]={parameter.Name};"); + codeBuilder.AppendLine($"ps[{i}]={parameter.Name};"); } } if (method.ReturnsVoid) { - codeString.AppendLine("return default;"); + codeBuilder.AppendLine("return default;"); } else { - codeString.AppendLine("return result;"); + codeBuilder.AppendLine("return result;"); } - codeString.AppendLine("}"); + codeBuilder.AppendLine("}"); } - private void BuildMethodFunc(StringBuilder codeString, IMethodSymbol method) + private void BuildMethodFunc(StringBuilder codeBuilder, IMethodSymbol method) { - codeString.AppendLine($"public static Func {this.GetMethodName(method)}Func => {this.GetMethodName(method)};"); + codeBuilder.AppendLine($"public static Func {this.GetMethodName(method)}Func => {this.GetMethodName(method)};"); } + private void BuildAwaitableReturnTypeMethod(StringBuilder codeBuilder, IMethodSymbol method) + { + var returnType = method.ReturnType; + if (returnType.IsVoid()) + { + return; + } + if (this.IsTypeAwaitable(returnType, out var hasResult)) + { + var methodId = this.GetMethodName(method) + "ReturnTypeMethod"; + codeBuilder.AppendLine($"private static async Task {methodId}(object o)"); + using (this.CreateCodeSpace(codeBuilder)) + { + if (returnType.IsValueType && !this.IsNullableValueType(returnType)) + { + codeBuilder.AppendLine($"var result = System.Runtime.CompilerServices.Unsafe.Unbox<{returnType.ToDisplayString()}>(o);"); + } + else + { + codeBuilder.AppendLine($"var result = ({returnType.ToDisplayString()})o;"); + } + + if (hasResult) + { + codeBuilder.AppendLine("return await result;"); + } + else + { + codeBuilder.AppendLine("await result;"); + codeBuilder.AppendLine("return default;"); + } + } + + codeBuilder.AppendLine($"public static Func> {methodId}Func=>{methodId};"); + } + } + + private bool IsTypeAwaitable(ITypeSymbol typeSymbol, out bool hasResult) + { + // 查找无参数的GetAwaiter实例方法 + var getAwaiterMethod = typeSymbol.GetMembers("GetAwaiter") + .OfType() + .FirstOrDefault(m => m.Parameters.IsEmpty && m.MethodKind == MethodKind.Ordinary); + + if (getAwaiterMethod == null) + { + hasResult = false; + return false; // 无符合条件的实例方法 + } + + var awaiterType = getAwaiterMethod.ReturnType as INamedTypeSymbol; + if (awaiterType == null) + { + hasResult = false; + return false; + } + + // 获取INotifyCompletion和ICriticalNotifyCompletion接口符号 + var inotifyCompletion = this.m_compilation.GetTypeByMetadataName("System.Runtime.CompilerServices.INotifyCompletion"); + var icriticalNotifyCompletion = this.m_compilation.GetTypeByMetadataName("System.Runtime.CompilerServices.ICriticalNotifyCompletion"); + + if (inotifyCompletion == null || icriticalNotifyCompletion == null) + { + hasResult = false; + return false; // 编译环境中缺少必要接口 + } + + // 检查是否实现任一接口 + var implementsInterface = awaiterType.AllInterfaces.Any(i => + SymbolEqualityComparer.Default.Equals(i, inotifyCompletion) || + SymbolEqualityComparer.Default.Equals(i, icriticalNotifyCompletion)); + + if (!implementsInterface) + { + hasResult = false; + return false; + } + + // 检查IsCompleted属性是否存在且类型为bool + var isCompleted = awaiterType.GetMembers("IsCompleted") + .OfType() + .FirstOrDefault(p => p.Type.SpecialType == SpecialType.System_Boolean && p.GetMethod != null); + + if (isCompleted == null) + { + hasResult = false; + return false; + } + + // 检查GetResult方法是否存在并无参数 + var getResult = awaiterType.GetMembers("GetResult") + .OfType() + .FirstOrDefault(m => m.Parameters.IsEmpty); + + hasResult = !getResult.ReturnsVoid; + return getResult != null; + } private string GetMethodName(IMethodSymbol method) { return method.GetDeterminantName(); } - private string GetObjectName(IMethodSymbol method) { foreach (var item1 in new string[] { "obj", "targetObj", "target", "@obj", "@targetObj", "@target" }) @@ -228,4 +318,11 @@ internal class MethodInvokeCodeBuilder : CodeBuilder return "@obj"; } -} \ No newline at end of file + + private bool IsNullableValueType(ITypeSymbol typeSymbol) + { + return typeSymbol.IsValueType + && typeSymbol is INamedTypeSymbol namedType + && namedType.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T; + } +} diff --git a/src/TouchSocket.Core.SourceGenerator/Method/MethodInvokeSourceGenerator.cs b/src/TouchSocket.Core.SourceGenerator/Method/MethodInvokeSourceGenerator.cs index 4cc8b84ef..ddccedf21 100644 --- a/src/TouchSocket.Core.SourceGenerator/Method/MethodInvokeSourceGenerator.cs +++ b/src/TouchSocket.Core.SourceGenerator/Method/MethodInvokeSourceGenerator.cs @@ -11,45 +11,102 @@ //------------------------------------------------------------------------------ using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Collections.Generic; using System.Linq; namespace TouchSocket; [Generator] -public class MethodInvokeSourceGenerator : ISourceGenerator +public class MethodInvokeSourceGenerator : IIncrementalGenerator { - public void Execute(GeneratorExecutionContext context) + public const string DynamicMethod = "TouchSocket.Core.DynamicMethodAttribute"; + public void Initialize(IncrementalGeneratorInitializationContext context) { - var s = context.Compilation.GetMetadataReference(context.Compilation.Assembly); + // 1. 注册语法提供器来捕获类型声明 + var typeDeclarations = context.SyntaxProvider + .CreateSyntaxProvider( + predicate: static (s, _) => s is TypeDeclarationSyntax, + transform: static (ctx, _) => (TypeDeclarationSyntax)ctx.Node); - if (context.SyntaxReceiver is MethodInvokeSyntaxReceiver receiver) + // 2. 组合编译和类型信息 + var compilationAndTypes = context.CompilationProvider.Combine(typeDeclarations.Collect()); + + // 3. 注册生成逻辑 + context.RegisterSourceOutput(compilationAndTypes, (spc, source) => + Execute(source.Left, source.Right, spc)); + } + + private static void Execute( + Compilation compilation, + IEnumerable typeDeclaration, + SourceProductionContext context) + { + var pairs = new Dictionary>(SymbolEqualityComparer.Default); + + foreach (var typeSyntax in typeDeclaration.Distinct()) { + var model = compilation.GetSemanticModel(typeSyntax.SyntaxTree); + var typeSymbol = model.GetDeclaredSymbol(typeSyntax) as INamedTypeSymbol; + + if (typeSymbol == null || typeSymbol.IsGenericType) + { + continue; + } + + // 3. 检查类型和方法是否应用了DynamicMethod属性 + var methods = GetDynamicMethods(typeSymbol); + if (methods.Any()) + { + pairs[typeSymbol] = methods.ToList(); + } + } + + // 4. 生成源代码 + foreach (var pair in pairs) + { + var builder = new MethodInvokeCodeBuilder(pair.Key, compilation, pair.Value); + context.AddSource(builder); + //Debugger.Launch(); - try - { - var pairs = receiver.GetInvokePairs(context.Compilation); + var methodInvokeClassCodeBuilder = new MethodInvokeClassCodeBuilder(pair.Key, compilation, pair.Value); + context.AddSource(methodInvokeClassCodeBuilder); - var builders = pairs.Select(a => new MethodInvokeCodeBuilder(a.Key, a.Value)); - foreach (var builder in builders) - { - context.AddSource($"{builder.GetFileName()}.g.cs", builder.ToSourceText()); - } + var titleBuilder = new MethodInvokeTitleCodeBuilder(pair.Key); + context.AddSource(titleBuilder); - foreach (var builder in pairs.Keys.Select(a => new MethodInvokeTitleCodeBuilder(a))) - { - context.AddSource($"{builder.GetFileName()}.g.cs", builder.ToSourceText()); - } - } - catch (System.Exception ex) - { - throw ex; - } + //var returnTypeInvokeCodeBuilder = new ReturnTypeInvokeCodeBuilder(pair.Key, compilation, pair.Value.Select(a => a.ReturnType).Distinct(SymbolEqualityComparer.Default).Cast()); + //context.AddSource(returnTypeInvokeCodeBuilder); } } - public void Initialize(GeneratorInitializationContext context) + private static IEnumerable GetDynamicMethods(INamedTypeSymbol typeSymbol) { - context.RegisterForSyntaxNotifications(() => new MethodInvokeSyntaxReceiver()); + var hasTypeAttribute = typeSymbol.HasAttribute(MethodInvokeSourceGenerator.DynamicMethod); + + return typeSymbol.GetMembers() + .OfType() + .Where(m => m.MethodKind == MethodKind.Ordinary && + !m.IsGenericMethod && + IsAccessible(m.DeclaredAccessibility) && + (hasTypeAttribute || HasMethodAttribute(m))); } + + private static bool IsAccessible(Accessibility accessibility) + { + return accessibility switch + { + Accessibility.Internal or + Accessibility.ProtectedAndInternal or + Accessibility.Public => true, + _ => false + }; + } + + private static bool HasMethodAttribute(IMethodSymbol method) => + method.GetAttributes().Any(a => + a.AttributeClass?.ToDisplayString() == DynamicMethod || + a.AttributeClass?.GetAttributes().Any(ad => + ad.AttributeClass?.ToDisplayString() == DynamicMethod) == true); } \ No newline at end of file diff --git a/src/TouchSocket.Core.SourceGenerator/Method/MethodInvokeSyntaxReceiver.cs b/src/TouchSocket.Core.SourceGenerator/Method/MethodInvokeSyntaxReceiver.cs deleted file mode 100644 index 769e6f7fd..000000000 --- a/src/TouchSocket.Core.SourceGenerator/Method/MethodInvokeSyntaxReceiver.cs +++ /dev/null @@ -1,168 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace TouchSocket; - -internal sealed class MethodInvokeSyntaxReceiver : ISyntaxReceiver -{ - public const string DynamicMethod = "TouchSocket.Core.DynamicMethodAttribute"; - private readonly List m_syntaxList = new List(); - - public static INamedTypeSymbol GeneratorPluginAttributeAttribute { get; private set; } - - public Dictionary> GetInvokePairs(Compilation compilation) - { - var pairs = new Dictionary>(); - - foreach (var syntax in this.m_syntaxList) - { - var namedTypeSymbol = compilation.GetSemanticModel(syntax.SyntaxTree).GetDeclaredSymbol(syntax); - - switch (namedTypeSymbol.DeclaredAccessibility) - { - case Accessibility.ProtectedAndInternal: - case Accessibility.Internal: - case Accessibility.ProtectedOrInternal: - case Accessibility.Public: - break; - default: - continue; - } - //Debugger.Launch(); - - var methodSymbols = this.GetMethodSymbols(namedTypeSymbol); - - if (methodSymbols.Any()) - { - if (pairs.TryGetValue(namedTypeSymbol, out var methods)) - { - foreach (var item in methodSymbols) - { - if (!methods.Contains(item)) - { - methods.Add(item); - } - } - } - else - { - methods = new List(methodSymbols); - pairs.Add(namedTypeSymbol, methods); - } - } - } - - return pairs; - } - - /// - /// 访问语法树 - /// - /// - void ISyntaxReceiver.OnVisitSyntaxNode(SyntaxNode syntaxNode) - { - if (syntaxNode is TypeDeclarationSyntax syntax) - { - this.m_syntaxList.Add(syntax); - } - } - - private IEnumerable GetMethodSymbols(INamedTypeSymbol namedTypeSymbol) - { - if (namedTypeSymbol.IsGenericType) - { - return Array.Empty(); - } - //Debugger.Launch(); - if (this.IsDynamicMethod(namedTypeSymbol)) - { - return namedTypeSymbol.GetMembers().OfType().Where(a => - { - if (a.MethodKind != MethodKind.Ordinary) - { - return false; - } - - if (a.IsGenericMethod) - { - return false; - } - - switch (a.DeclaredAccessibility) - { - case Accessibility.ProtectedAndInternal: - case Accessibility.Internal: - case Accessibility.ProtectedOrInternal: - case Accessibility.Public: - return true; - - default: - return false; - } - }); - } - else - { - return namedTypeSymbol.GetMembers().OfType().Where(a => - { - if (a.MethodKind != MethodKind.Ordinary) - { - return false; - } - if (a.IsGenericMethod) - { - return false; - } - if (!this.IsDynamicMethod(a)) - { - return false; - } - switch (a.DeclaredAccessibility) - { - case Accessibility.ProtectedAndInternal: - case Accessibility.Protected: - case Accessibility.Internal: - case Accessibility.ProtectedOrInternal: - case Accessibility.Public: - return true; - - default: - return false; - } - }); - } - } - - private bool IsDynamicMethod(ISymbol symbol) - { - foreach (var attribute in symbol.GetAttributes()) - { - if (attribute.AttributeClass?.ToDisplayString() == DynamicMethod) - { - return true; - } - - if (attribute.AttributeClass.HasAttribute(DynamicMethod)) - { - return true; - } - } - return false; - } -} \ No newline at end of file diff --git a/src/TouchSocket.Core.SourceGenerator/Method/MethodInvokeTitleCodeBuilder.cs b/src/TouchSocket.Core.SourceGenerator/Method/MethodInvokeTitleCodeBuilder.cs index 998e5120f..26229a07d 100644 --- a/src/TouchSocket.Core.SourceGenerator/Method/MethodInvokeTitleCodeBuilder.cs +++ b/src/TouchSocket.Core.SourceGenerator/Method/MethodInvokeTitleCodeBuilder.cs @@ -11,95 +11,52 @@ //------------------------------------------------------------------------------ using Microsoft.CodeAnalysis; -using System.Collections.Generic; using System.Text; namespace TouchSocket; -internal class MethodInvokeTitleCodeBuilder : CodeBuilder +internal class MethodInvokeTitleCodeBuilder : MethodCodeBuilder { - private readonly INamedTypeSymbol m_namedTypeSymbol; - - public MethodInvokeTitleCodeBuilder(INamedTypeSymbol type) + public MethodInvokeTitleCodeBuilder(INamedTypeSymbol type) : base(type) { - this.m_namedTypeSymbol = type; } - public override string Id => this.m_namedTypeSymbol.ToDisplayString(); - public INamedTypeSymbol NamedTypeSymbol => this.m_namedTypeSymbol; - - public virtual IEnumerable Usings - { - get - { - yield return "using System;"; - yield return "using System.Diagnostics;"; - yield return "using TouchSocket.Core;"; - yield return "using System.Threading.Tasks;"; - } - } - - protected virtual string GeneratorTypeNamespace => "TouchSocket.Core.__Internals"; - + public override string Id => this.TypeSymbol.ToDisplayString(); public override string GetFileName() { - return this.GeneratorTypeNamespace + this.GetGeneratorTypeName() + "Title.Generator"; + return this.GeneratorTypeNamespace + this.GetGeneratorTypeName() + "Title.Generator.g.cs"; } - - public override string ToString() + protected override bool GeneratorCode(StringBuilder codeBuilder) { - var codeString = new StringBuilder(); - codeString.AppendLine("/*"); - codeString.AppendLine("此代码由Rpc工具直接生成,非必要请不要修改此处代码"); - codeString.AppendLine("*/"); - codeString.AppendLine("#pragma warning disable"); + using (this.CreateNamespaceIfNotGlobalNamespace(codeBuilder, this.GeneratorTypeNamespace)) + { + codeBuilder.AppendLine($"[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]"); + codeBuilder.AppendLine($"[global::System.Obsolete(\"此方法不允许直接调用\")]"); + codeBuilder.AppendLine(Utils.GetGeneratedCodeString()); - foreach (var item in this.Usings) - { - codeString.AppendLine(item); - } - if (!this.NamedTypeSymbol.ContainingNamespace.IsGlobalNamespace) - { - codeString.AppendLine($"namespace {this.GeneratorTypeNamespace}"); - codeString.AppendLine("{"); + codeBuilder.AppendLine($"#if NET6_0_OR_GREATER"); + codeBuilder.AppendLine($"[global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)]"); + codeBuilder.AppendLine($"[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]"); + codeBuilder.AppendLine($"[global::System.Diagnostics.DebuggerNonUserCode]"); + codeBuilder.AppendLine($"#endif"); + codeBuilder.AppendLine($"partial class {this.GetGeneratorTypeName()}"); + using (this.CreateCodeSpace(codeBuilder)) + { + codeBuilder.AppendLine($"#if NET6_0_OR_GREATER"); + codeBuilder.AppendLine("[System.Runtime.CompilerServices.ModuleInitializer]"); + codeBuilder.AppendLine($"[System.Diagnostics.CodeAnalysis.DynamicDependency( System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties,typeof({this.GetGeneratorTypeName()}))]"); + codeBuilder.AppendLine("public static void TouchSocketModuleInitializer()"); + + using (this.CreateCodeSpace(codeBuilder)) + { + codeBuilder.AppendLine(""); + } + + codeBuilder.AppendLine($"#endif"); + } } - codeString.AppendLine($"[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]"); - codeString.AppendLine($"[global::System.Obsolete(\"此方法不允许直接调用\")]"); - codeString.AppendLine(Utils.GetGeneratedCodeString()); - - codeString.AppendLine($"#if NET6_0_OR_GREATER"); - codeString.AppendLine($"[global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)]"); - codeString.AppendLine($"[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]"); - codeString.AppendLine($"[global::System.Diagnostics.DebuggerNonUserCode]"); - codeString.AppendLine($"#endif"); - codeString.AppendLine($"partial class {this.GetGeneratorTypeName()}"); - codeString.AppendLine("{"); - codeString.AppendLine($"#if NET6_0_OR_GREATER"); - codeString.AppendLine("[System.Runtime.CompilerServices.ModuleInitializer]"); - codeString.AppendLine($"[System.Diagnostics.CodeAnalysis.DynamicDependency( System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties,typeof({this.GetGeneratorTypeName()}))]"); - codeString.AppendLine("public static void TouchSocketModuleInitializer()"); - codeString.AppendLine("{"); - codeString.AppendLine(""); - codeString.AppendLine("}"); - codeString.AppendLine($"#endif"); - - codeString.AppendLine("}"); - - if (!this.NamedTypeSymbol.ContainingNamespace.IsGlobalNamespace) - { - codeString.AppendLine("}"); - } - - // System.Diagnostics.Debugger.Launch(); - return codeString.ToString(); - } - - private string GetGeneratorTypeName() - { - var typeName = $"__{Utils.MakeIdentifier(this.m_namedTypeSymbol.ToDisplayString())}MethodExtension"; - - return typeName; + return true; } } \ No newline at end of file diff --git a/src/TouchSocket.Core.SourceGenerator/Plugin/PluginAddSourceGenerator.cs b/src/TouchSocket.Core.SourceGenerator/Plugin/PluginAddSourceGenerator.cs new file mode 100644 index 000000000..1a3f8adb1 --- /dev/null +++ b/src/TouchSocket.Core.SourceGenerator/Plugin/PluginAddSourceGenerator.cs @@ -0,0 +1,243 @@ +//------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +//------------------------------------------------------------------------------ + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Xml.Linq; +using TouchSocket.SourceGenerator; + +namespace TouchSocket; + +[Generator] +public class PluginAddSourceGenerator : IIncrementalGenerator +{ + public void Initialize(IncrementalGeneratorInitializationContext context) + { + // 筛选符合要求的接口语法节点 + var interfaceDeclarations = context.SyntaxProvider + .CreateSyntaxProvider( + predicate: static (s, _) => IsInterfaceSyntax(s), + transform: static (ctx, _) => GetInterfaceSymbol(ctx)) + .Where(static s => s is not null) + .Collect(); + + // 生成源代码 + context.RegisterSourceOutput(interfaceDeclarations, (spc, source) => + this.GenerateSource(spc, source)); + } + + private static INamedTypeSymbol GetInterfaceSymbol(GeneratorSyntaxContext context) + { + var interfaceSyntax = (InterfaceDeclarationSyntax)context.Node; + var symbol = context.SemanticModel.GetDeclaredSymbol(interfaceSyntax); + + // 验证接口是否符合插件要求 + if (symbol is null || !CoreAnalyzer.IsPluginInterface(symbol)) + return null; + + return symbol.DeclaredAccessibility == Accessibility.Public ? symbol : null; + } + + private static bool IsInterfaceSyntax(SyntaxNode node) + { + // 筛选接口声明且具有至少一个方法 + return node is InterfaceDeclarationSyntax { Members.Count: > 0 }; + } + + private void GenerateSource(SourceProductionContext context, ImmutableArray interfaces) + { + foreach (var interfaceSymbol in interfaces.Distinct(SymbolEqualityComparer.Default).Cast()) + { + var source = new PluginAddCodeBuilder(interfaceSymbol); + if (source != null) + { + context.AddSource(source); + } + } + } + + #region Class + + internal sealed class PluginAddCodeBuilder : CodeBuilder + { + private const string IPluginManagerString = "TouchSocket.Core.IPluginManager"; + private const string PluginBaseString = "TouchSocket.Core.PluginBase"; + private const string PluginEventArgsString = "TouchSocket.Core.PluginEventArgs"; + private readonly INamedTypeSymbol m_pluginClass; + + public PluginAddCodeBuilder(INamedTypeSymbol pluginClass) + { + this.m_pluginClass = pluginClass; + } + + public override string Id => this.m_pluginClass.ToDisplayString(); + public string Prefix { get; set; } + + public override string GetFileName() + { + return this.m_pluginClass.ToDisplayString() + "ExtensionsGenerator.g.cs"; + } + + public bool TryToSourceText(out SourceText sourceText) + { + var code = this.ToString(); + if (string.IsNullOrEmpty(code)) + { + sourceText = null; + return false; + } + sourceText = SourceText.From(code, Encoding.UTF8); + return true; + } + + protected override bool GeneratorCode(StringBuilder codeBuilder) + { + var method = this.FindMethod(); + if (method is null) + { + return false; + } + if (method.Parameters.Length != 2) + { + return false; + } + + var pluginClassName = this.GetPluginClassName(); + var pluginMethodName = method.Name; + var firstType = method.Parameters[0].Type; + var secondType = method.Parameters[1].Type; + // var xml = ExtractSummary((method.GetDocumentationCommentXml() ?? this.m_pluginClass.GetDocumentationCommentXml()) ?? string.Empty); + + if (!this.m_pluginClass.ContainingNamespace.IsGlobalNamespace) + { + codeBuilder.AppendLine($"namespace {this.m_pluginClass.ContainingNamespace}"); + codeBuilder.AppendLine("{"); + } + + codeBuilder.AppendLine($"/// "); + codeBuilder.AppendLine($"public static class _{pluginClassName}Extensions"); + codeBuilder.AppendLine("{"); + //1 + codeBuilder.AppendLine($"/// "); + codeBuilder.AppendLine($"public static void Add{pluginClassName}(this IPluginManager pluginManager, Func<{firstType.ToDisplayString()}, {secondType.ToDisplayString()}, Task> func)"); + + codeBuilder.AppendLine("{"); + codeBuilder.AppendLine($"pluginManager.Add(typeof({this.m_pluginClass.ToDisplayString()}), func);"); + codeBuilder.AppendLine("}"); + + //2 + codeBuilder.AppendLine($"/// "); + codeBuilder.AppendLine($"public static void Add{pluginClassName}(this IPluginManager pluginManager, Action<{firstType.ToDisplayString()}> action)"); + + codeBuilder.AppendLine("{"); + codeBuilder.AppendLine("Task newFunc(object sender, PluginEventArgs e)"); + codeBuilder.AppendLine("{"); + codeBuilder.AppendLine($"action(({firstType.ToDisplayString()})sender);"); + codeBuilder.AppendLine("return e.InvokeNext();"); + codeBuilder.AppendLine("}"); + codeBuilder.AppendLine($"pluginManager.Add(typeof({this.m_pluginClass.ToDisplayString()}), newFunc, action);"); + codeBuilder.AppendLine("}"); + + //3 + codeBuilder.AppendLine($"/// "); + codeBuilder.AppendLine($"public static void Add{pluginClassName}(this IPluginManager pluginManager, Action action)"); + + codeBuilder.AppendLine("{"); + codeBuilder.AppendLine($"pluginManager.Add(typeof({this.m_pluginClass.ToDisplayString()}), action);"); + codeBuilder.AppendLine("}"); + + //4 + codeBuilder.AppendLine($"/// "); + codeBuilder.AppendLine($"public static void Add{pluginClassName}(this IPluginManager pluginManager, Func<{secondType.ToDisplayString()},Task> func)"); + + codeBuilder.AppendLine("{"); + codeBuilder.AppendLine($"pluginManager.Add(typeof({this.m_pluginClass.ToDisplayString()}), func);"); + codeBuilder.AppendLine("}"); + + //5 + codeBuilder.AppendLine($"/// "); + codeBuilder.AppendLine($"public static void Add{pluginClassName}(this IPluginManager pluginManager, Func<{firstType.ToDisplayString()},Task> func)"); + + codeBuilder.AppendLine("{"); + codeBuilder.AppendLine("async Task newFunc(object sender, PluginEventArgs e)"); + codeBuilder.AppendLine("{"); + codeBuilder.AppendLine($"await func(({firstType.ToDisplayString()})sender).ConfigureAwait(EasyTask.ContinueOnCapturedContext);"); + codeBuilder.AppendLine("await e.InvokeNext().ConfigureAwait(EasyTask.ContinueOnCapturedContext);"); + codeBuilder.AppendLine("}"); + codeBuilder.AppendLine($"pluginManager.Add(typeof({this.m_pluginClass.ToDisplayString()}), newFunc, func);"); + codeBuilder.AppendLine("}"); + + //6 + codeBuilder.AppendLine($"/// "); + codeBuilder.AppendLine($"public static void Add{pluginClassName}(this IPluginManager pluginManager, Func func)"); + + codeBuilder.AppendLine("{"); + codeBuilder.AppendLine($"pluginManager.Add(typeof({this.m_pluginClass.ToDisplayString()}), func);"); + codeBuilder.AppendLine("}"); + + //class end + codeBuilder.AppendLine("}"); + if (!this.m_pluginClass.ContainingNamespace.IsGlobalNamespace) + { + codeBuilder.AppendLine("}"); + } + return true; + } + + private string ExtractSummary(string xmlDoc) + { + if (string.IsNullOrEmpty(xmlDoc)) + { + return string.Empty; + } + try + { + var doc = XDocument.Parse(xmlDoc); + var summaryElement = doc.Descendants("summary").FirstOrDefault(); + var summary = summaryElement?.Value.Trim(); + + if (string.IsNullOrEmpty(summary)) + { + return string.Empty; + } + //去掉换行符 + return summary.Replace("\n", "").Replace("\r", ""); + } + catch + { + return null; + } + } + + private IMethodSymbol FindMethod() + { + return this.m_pluginClass.GetMembers().OfType().FirstOrDefault(); + } + + private string GetPluginClassName() + { + var name = this.m_pluginClass.Name; + if (name.StartsWith("I")) + { + return name.Substring(1); + } + return this.m_pluginClass.Name; + } + } + + #endregion Class +} \ No newline at end of file diff --git a/src/TouchSocket.Core.SourceGenerator/Plugin/PluginCodeBuilder.cs b/src/TouchSocket.Core.SourceGenerator/Plugin/PluginCodeBuilder.cs deleted file mode 100644 index 6f8371a4f..000000000 --- a/src/TouchSocket.Core.SourceGenerator/Plugin/PluginCodeBuilder.cs +++ /dev/null @@ -1,214 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Text; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Xml.Linq; - -namespace TouchSocket; - -internal sealed class PluginCodeBuilder : CodeBuilder -{ - private const string IPluginManagerString = "TouchSocket.Core.IPluginManager"; - private const string PluginBaseString = "TouchSocket.Core.PluginBase"; - private const string PluginEventArgsString = "TouchSocket.Core.PluginEventArgs"; - private readonly INamedTypeSymbol m_pluginClass; - - public PluginCodeBuilder(INamedTypeSymbol pluginClass) - { - this.m_pluginClass = pluginClass; - } - - public override string Id => this.m_pluginClass.ToDisplayString(); - public string Prefix { get; set; } - - public IEnumerable Usings - { - get - { - yield return "using System;"; - yield return "using System.Diagnostics;"; - yield return "using TouchSocket.Core;"; - yield return "using System.Threading.Tasks;"; - } - } - - public override string GetFileName() - { - return this.m_pluginClass.ToDisplayString() + "ExtensionsGenerator"; - } - - public override string ToString() - { - var method = this.FindMethod(); - if (method is null) - { - return null; - } - if (method.Parameters.Length != 2) - { - return null; - } - var codeString = new StringBuilder(); - codeString.AppendLine("/*"); - codeString.AppendLine("此代码由Plugin工具直接生成,非必要请不要修改此处代码"); - codeString.AppendLine("*/"); - codeString.AppendLine("#pragma warning disable"); - - foreach (var item in this.Usings) - { - codeString.AppendLine(item); - } - - //Debugger.Launch(); - - var pluginClassName = this.GetPluginClassName(); - var pluginMethodName = method.Name; - var firstType = method.Parameters[0].Type; - var secondType = method.Parameters[1].Type; - // var xml = ExtractSummary((method.GetDocumentationCommentXml() ?? this.m_pluginClass.GetDocumentationCommentXml()) ?? string.Empty); - - if (!this.m_pluginClass.ContainingNamespace.IsGlobalNamespace) - { - codeString.AppendLine($"namespace {this.m_pluginClass.ContainingNamespace}"); - codeString.AppendLine("{"); - } - - codeString.AppendLine($"/// "); - codeString.AppendLine($"public static class _{pluginClassName}Extensions"); - codeString.AppendLine("{"); - //1 - codeString.AppendLine($"/// "); - codeString.AppendLine($"public static void Add{pluginClassName}(this IPluginManager pluginManager, Func<{firstType.ToDisplayString()}, {secondType.ToDisplayString()}, Task> func)"); - - codeString.AppendLine("{"); - codeString.AppendLine($"pluginManager.Add(typeof({this.m_pluginClass.ToDisplayString()}), func);"); - codeString.AppendLine("}"); - - //2 - codeString.AppendLine($"/// "); - codeString.AppendLine($"public static void Add{pluginClassName}(this IPluginManager pluginManager, Action<{firstType.ToDisplayString()}> action)"); - - codeString.AppendLine("{"); - codeString.AppendLine("Task newFunc(object sender, PluginEventArgs e)"); - codeString.AppendLine("{"); - codeString.AppendLine($"action(({firstType.ToDisplayString()})sender);"); - codeString.AppendLine("return e.InvokeNext();"); - codeString.AppendLine("}"); - codeString.AppendLine($"pluginManager.Add(typeof({this.m_pluginClass.ToDisplayString()}), newFunc, action);"); - codeString.AppendLine("}"); - - //3 - codeString.AppendLine($"/// "); - codeString.AppendLine($"public static void Add{pluginClassName}(this IPluginManager pluginManager, Action action)"); - - codeString.AppendLine("{"); - codeString.AppendLine($"pluginManager.Add(typeof({this.m_pluginClass.ToDisplayString()}), action);"); - codeString.AppendLine("}"); - - //4 - codeString.AppendLine($"/// "); - codeString.AppendLine($"public static void Add{pluginClassName}(this IPluginManager pluginManager, Func<{secondType.ToDisplayString()},Task> func)"); - - codeString.AppendLine("{"); - codeString.AppendLine($"pluginManager.Add(typeof({this.m_pluginClass.ToDisplayString()}), func);"); - codeString.AppendLine("}"); - - //5 - codeString.AppendLine($"/// "); - codeString.AppendLine($"public static void Add{pluginClassName}(this IPluginManager pluginManager, Func<{firstType.ToDisplayString()},Task> func)"); - - codeString.AppendLine("{"); - codeString.AppendLine("async Task newFunc(object sender, PluginEventArgs e)"); - codeString.AppendLine("{"); - codeString.AppendLine($"await func(({firstType.ToDisplayString()})sender).ConfigureAwait(EasyTask.ContinueOnCapturedContext);"); - codeString.AppendLine("await e.InvokeNext().ConfigureAwait(EasyTask.ContinueOnCapturedContext);"); - codeString.AppendLine("}"); - codeString.AppendLine($"pluginManager.Add(typeof({this.m_pluginClass.ToDisplayString()}), newFunc, func);"); - codeString.AppendLine("}"); - - //6 - codeString.AppendLine($"/// "); - codeString.AppendLine($"public static void Add{pluginClassName}(this IPluginManager pluginManager, Func func)"); - - codeString.AppendLine("{"); - codeString.AppendLine($"pluginManager.Add(typeof({this.m_pluginClass.ToDisplayString()}), func);"); - codeString.AppendLine("}"); - - //class end - codeString.AppendLine("}"); - if (!this.m_pluginClass.ContainingNamespace.IsGlobalNamespace) - { - codeString.AppendLine("}"); - } - - // System.Diagnostics.Debugger.Launch(); - return codeString.ToString(); - } - - private string ExtractSummary(string xmlDoc) - { - if (string.IsNullOrEmpty(xmlDoc)) - { - return string.Empty; - } - try - { - var doc = XDocument.Parse(xmlDoc); - var summaryElement = doc.Descendants("summary").FirstOrDefault(); - var summary = summaryElement?.Value.Trim(); - - if (string.IsNullOrEmpty(summary)) - { - return string.Empty; - } - //去掉换行符 - return summary.Replace("\n", "").Replace("\r", ""); - } - catch - { - return null; - } - } - - private string GetPluginClassName() - { - var name = this.m_pluginClass.Name; - if (name.StartsWith("I")) - { - return name.Substring(1); - } - return this.m_pluginClass.Name; - } - - private IMethodSymbol FindMethod() - { - return this.m_pluginClass.GetMembers().OfType().FirstOrDefault(); - } - - public bool TryToSourceText(out SourceText sourceText) - { - var code = this.ToString(); - if (string.IsNullOrEmpty(code)) - { - sourceText = null; - return false; - } - sourceText = SourceText.From(code, Encoding.UTF8); - return true; - } - - -} \ No newline at end of file diff --git a/src/TouchSocket.Core.SourceGenerator/Plugin/PluginRaiseAttribute.cs b/src/TouchSocket.Core.SourceGenerator/Plugin/PluginRaiseAttribute.cs new file mode 100644 index 000000000..f0e982332 --- /dev/null +++ b/src/TouchSocket.Core.SourceGenerator/Plugin/PluginRaiseAttribute.cs @@ -0,0 +1,28 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System; + +namespace TouchSocket.Core; + +[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] +/*GeneratedCode*/ +internal sealed class PluginRaiseAttribute : Attribute +{ + public Type PluginType { get; } + + public PluginRaiseAttribute(Type pluginType) + { + this.PluginType = pluginType; + } +} + diff --git a/src/TouchSocket.Core.SourceGenerator/Plugin/PluginRaiseSourceGenerator.cs b/src/TouchSocket.Core.SourceGenerator/Plugin/PluginRaiseSourceGenerator.cs new file mode 100644 index 000000000..36c68573e --- /dev/null +++ b/src/TouchSocket.Core.SourceGenerator/Plugin/PluginRaiseSourceGenerator.cs @@ -0,0 +1,159 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; + +namespace TouchSocket; + +[Generator] +internal class PluginRaiseSourceGenerator : IIncrementalGenerator +{ + private const string IPluginString = "TouchSocket.Core.IPlugin"; + + private const string PluginRaiseAttributeNameString = "TouchSocket.Core.PluginRaiseAttribute"; + + private readonly string PluginRaiseAttributeString = @" +using System; + +namespace TouchSocket.Core +{ + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + /*GeneratedCode*/ + internal sealed class PluginRaiseAttribute : Attribute + { + public Type PluginType { get; } + + public PluginRaiseAttribute(Type pluginType) + { + PluginType = pluginType; + } + } +} +"; + + public void Initialize(IncrementalGeneratorInitializationContext context) + { + // 注册预生成的属性代码 + context.RegisterPostInitializationOutput(ctx => + { + var sourceCode = CodeBuilder.ReplaceGeneratedCode(this.PluginRaiseAttributeString); + + ctx.AddSource("PluginRaiseAttribute.g.cs", sourceCode); + }); + + // 1. 筛选所有包含[PluginRaise]特性的类 + var pluginClassDeclarations = context.SyntaxProvider + .CreateSyntaxProvider( + predicate: static (node, _) => node is ClassDeclarationSyntax cds + && cds.AttributeLists.Count > 0, + transform: static (ctx, _) => GetClassWithAttribute(ctx)) + .Where(static c => c is not null).Collect(); + + // 3. 生成最终代码 + context.RegisterSourceOutput(pluginClassDeclarations, + (spc, source) => this.Execute(source, spc)); + } + + private static INamedTypeSymbol GetClassWithAttribute(GeneratorSyntaxContext context) + { + //Debugger.Launch(); + var model = context.SemanticModel; + var classSymbol = model.GetDeclaredSymbol(context.Node) as INamedTypeSymbol; + + // 检查是否包含GeneratorPackageAttribute + var hasAttribute = classSymbol.HasAttributes(PluginRaiseAttributeNameString, out var attributes); + + return hasAttribute == true ? classSymbol : null; + } + + private void Execute(ImmutableArray classes, SourceProductionContext context) + { + var pluginTypes = new List(); + foreach (var classSymbol in classes) + { + if (classSymbol.HasAttributes(PluginRaiseAttributeNameString, out var attributes)) + { + foreach (var attribute in attributes) + { + var pluginTypeArg = (INamedTypeSymbol)attribute.ConstructorArguments[0].Value!; + + if (!pluginTypeArg.IsInheritFrom(IPluginString)) + { + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.m_rule_Plugin0005, + attribute.ApplicationSyntaxReference.GetSyntax().GetLocation(), + pluginTypeArg.Name)); + return; + } + } + + var codeBuilder = new MyCodeBuilder(classSymbol, attributes.Select(a => (INamedTypeSymbol)a.ConstructorArguments.First().Value)); + + context.AddSource(codeBuilder); + } + } + } + + #region Class + + private class MyCodeBuilder : TypeCodeBuilder + { + private readonly IEnumerable m_pluginInterfaces; + + public MyCodeBuilder(INamedTypeSymbol typeSymbol, IEnumerable pluginInterfaces) : base(typeSymbol) + { + this.m_pluginInterfaces = pluginInterfaces; + } + + protected override bool GeneratorCode(StringBuilder codeBuilder) + { + using (this.CreateNamespace(codeBuilder)) + { + codeBuilder.AppendLine($"partial class {this.TypeSymbol.Name}"); + using (this.CreateCodeSpace(codeBuilder)) + { + foreach (var item in this.m_pluginInterfaces) + { + this.AppendMethod(codeBuilder, item); + } + } + } + return true; + } + + private void AppendMethod(StringBuilder codeBuilder, INamedTypeSymbol namedTypeSymbol) + { + var method = namedTypeSymbol.GetMembers().OfType().FirstOrDefault(); + if (method is null) + { + return; + } + + var firstParameter = method.Parameters[0]; + var secondParameter = method.Parameters[1]; + + codeBuilder.AppendLine($"public static ValueTask Raise{namedTypeSymbol.Name}Async(this IPluginManager pluginManager, IResolver resolver, {firstParameter.Type.ToDisplayString()} sender, {secondParameter.Type.ToDisplayString()} e)"); + + using (this.CreateCodeSpace(codeBuilder)) + { + codeBuilder.AppendLine($"return pluginManager.RaiseAsync(typeof({namedTypeSymbol.ToDisplayString()}), resolver, sender, e);"); + } + } + } + + #endregion Class +} \ No newline at end of file diff --git a/src/TouchSocket.Core.SourceGenerator/Plugin/PluginSourceGenerator.cs b/src/TouchSocket.Core.SourceGenerator/Plugin/PluginSourceGenerator.cs deleted file mode 100644 index 86a8c1ce1..000000000 --- a/src/TouchSocket.Core.SourceGenerator/Plugin/PluginSourceGenerator.cs +++ /dev/null @@ -1,91 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using System.Linq; - -namespace TouchSocket; - -[Generator] -public class PluginSourceGenerator : ISourceGenerator -{ - private readonly string m_generatorPluginAttribute = @" - -/* -此代码由SourceGenerator工具直接生成,非必要请不要修改此处代码 -*/ - -#pragma warning disable - -using System; - -namespace TouchSocket.Core -{ - /// - /// 使用源生成插件的调用。 - /// - [AttributeUsage(AttributeTargets.Method)] - [Obsolete(""此特性已被弃用,请直接使用接口实现插件,支持AOT"",true)] - /*GeneratedCode*/ - internal class GeneratorPluginAttribute : Attribute - { - public Type PluginType { get;} - - /// - /// 使用源生成插件的调用。 - /// - /// 插件名称,一般建议使用解决。 - public GeneratorPluginAttribute(Type pluginType) - { - this.PluginType = pluginType; - } - } -} - -"; - - public void Execute(GeneratorExecutionContext context) - { - var s = context.Compilation.GetMetadataReference(context.Compilation.Assembly); - - if (context.SyntaxReceiver is PluginSyntaxReceiver receiver) - { - var builders = receiver - .GetPluginPluginInterfaceTypes(context.Compilation) - .Select(i => new PluginCodeBuilder(i)) - .Distinct(CodeBuilderEqualityComparer.Default); - //Debugger.Launch(); - foreach (var builder in builders) - { - if (builder.TryToSourceText(out var sourceText)) - { - var tree = CSharpSyntaxTree.ParseText(sourceText); - var root = tree.GetRoot().NormalizeWhitespace(); - var ret = root.ToFullString(); - context.AddSource($"{builder.GetFileName()}.g.cs", ret); - } - } - } - } - - public void Initialize(GeneratorInitializationContext context) - { - context.RegisterForPostInitialization(a => - { - var sourceCode = this.m_generatorPluginAttribute.Replace("/*GeneratedCode*/", Utils.GetGeneratedCodeString()); - - a.AddSource(nameof(this.m_generatorPluginAttribute), sourceCode); - }); - context.RegisterForSyntaxNotifications(() => new PluginSyntaxReceiver()); - } -} \ No newline at end of file diff --git a/src/TouchSocket.Core.SourceGenerator/Plugin/PluginSyntaxReceiver.cs b/src/TouchSocket.Core.SourceGenerator/Plugin/PluginSyntaxReceiver.cs deleted file mode 100644 index 78ca249d6..000000000 --- a/src/TouchSocket.Core.SourceGenerator/Plugin/PluginSyntaxReceiver.cs +++ /dev/null @@ -1,72 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using System.Collections.Generic; -using TouchSocket.SourceGenerator; - -namespace TouchSocket; - -/// -/// RpcApi语法接收器 -/// -internal sealed class PluginSyntaxReceiver : ISyntaxReceiver -{ - //public const string GeneratorPluginAttributeTypeName = "TouchSocket.Core.GeneratorPluginAttribute"; - - /// - /// 接口列表 - /// - private readonly List m_classSyntaxList = new(); - - //public static INamedTypeSymbol GeneratorPluginAttributeAttribute { get; private set; } - - - - /// - /// 获取所有插件符号 - /// - /// - /// - public IEnumerable GetPluginPluginInterfaceTypes(Compilation compilation) - { - // Debugger.Launch(); - //GeneratorPluginAttributeAttribute = compilation.GetTypeByMetadataName(GeneratorPluginAttributeTypeName); - //if (GeneratorPluginAttributeAttribute == null) - //{ - // yield break; - //} - foreach (var classSyntax in this.m_classSyntaxList) - { - var @class = compilation.GetSemanticModel(classSyntax.SyntaxTree).GetDeclaredSymbol(classSyntax); - if (@class != null && CoreAnalyzer.IsPluginInterface(@class) && @class.DeclaredAccessibility == Accessibility.Public) - { - yield return @class; - } - } - } - - /// - /// 访问语法树 - /// - /// - void ISyntaxReceiver.OnVisitSyntaxNode(SyntaxNode syntaxNode) - { - if (syntaxNode is InterfaceDeclarationSyntax syntax) - { - - this.m_classSyntaxList.Add(syntax); - } - } -} \ No newline at end of file diff --git a/src/TouchSocket.Core.SourceGenerator/TouchSocket.Core.SourceGenerator.csproj b/src/TouchSocket.Core.SourceGenerator/TouchSocket.Core.SourceGenerator.csproj index ad718b35a..73ae18a06 100644 --- a/src/TouchSocket.Core.SourceGenerator/TouchSocket.Core.SourceGenerator.csproj +++ b/src/TouchSocket.Core.SourceGenerator/TouchSocket.Core.SourceGenerator.csproj @@ -11,8 +11,7 @@ - - + diff --git a/src/TouchSocket.Core.SourceGenerator/_Package/PackageCodeBuilder.cs b/src/TouchSocket.Core.SourceGenerator/_Package/PackageCodeBuilder.cs index b0be793c5..070ced4c2 100644 --- a/src/TouchSocket.Core.SourceGenerator/_Package/PackageCodeBuilder.cs +++ b/src/TouchSocket.Core.SourceGenerator/_Package/PackageCodeBuilder.cs @@ -11,7 +11,6 @@ //------------------------------------------------------------------------------ using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Text; using System.Collections.Generic; using System.Linq; using System.Text; @@ -21,15 +20,12 @@ namespace TouchSocket; internal sealed class PackageCodeBuilder : CodeBuilder { - private readonly string m_byteBlockString = "TByteBlock"; - - private readonly GeneratorExecutionContext m_context; + private readonly SourceProductionContext m_context; private readonly string m_packageBaseString = "TouchSocket.Core.PackageBase"; + private readonly INamedTypeSymbol m_packageClass; private readonly string m_packageMemberAttributeString = "TouchSocket.Core.PackageMemberAttribute"; - private readonly INamedTypeSymbol m_packageClass; - - public PackageCodeBuilder(INamedTypeSymbol packageClass, GeneratorExecutionContext context) + public PackageCodeBuilder(INamedTypeSymbol packageClass, SourceProductionContext context) { this.m_packageClass = packageClass; this.m_context = context; @@ -38,74 +34,21 @@ internal sealed class PackageCodeBuilder : CodeBuilder public override string Id => this.m_packageClass.ToDisplayString(); public string Prefix { get; set; } - public IEnumerable Usings - { - get - { - yield return "using System;"; - yield return "using System.Diagnostics;"; - yield return "using TouchSocket.Core;"; - yield return "using System.Threading.Tasks;"; - } - } - public override string GetFileName() { - return this.m_packageClass.ToDisplayString() + "Generator"; - } - - public override string ToString() - { - var codeString = new StringBuilder(); - codeString.AppendLine("/*"); - codeString.AppendLine("此代码由SourceGenerator工具直接生成,非必要请不要修改此处代码"); - codeString.AppendLine("*/"); - codeString.AppendLine("#pragma warning disable"); - - foreach (var item in this.Usings) - { - codeString.AppendLine(item); - } - - //Debugger.Launch(); - if (!this.m_packageClass.ContainingNamespace.IsGlobalNamespace) - { - codeString.AppendLine($"namespace {this.m_packageClass.ContainingNamespace}"); - codeString.AppendLine("{"); - } - - - codeString.AppendLine($"partial {(this.m_packageClass.TypeKind == TypeKind.Struct ? "struct" : "class")} {this.m_packageClass.Name}"); - codeString.AppendLine("{"); - - var members = this.GetPackageMembers(); - - this.BuildConverter(codeString, members); - - this.m_deep = 0; - this.BuildPackage(codeString, members); - - this.m_deep = 0; - this.BuildUnpackage(codeString, members); - codeString.AppendLine("}"); - - if (!this.m_packageClass.ContainingNamespace.IsGlobalNamespace) - { - codeString.AppendLine("}"); - } - // System.Diagnostics.Debugger.Launch(); - return codeString.ToString(); + return $"{this.m_packageClass.Name}_Package.g.cs"; } #region Converter - private void BuildConverter(StringBuilder codeString, IEnumerable members) + + private void BuildConverter(StringBuilder codeBuilder, IEnumerable members) { foreach (var item in members) { var converter = item.Converter; if (converter != null) { - codeString.AppendLine($"private static readonly IFastBinaryConverter {this.GetConverterVariableName(converter)} = new {converter.ToDisplayString()}();"); + codeBuilder.AppendLine($"private static readonly {converter.ToDisplayString()} {this.GetConverterVariableName(converter)} = new {converter.ToDisplayString()}();"); } } } @@ -114,13 +57,14 @@ internal sealed class PackageCodeBuilder : CodeBuilder { return $"m_{typeSymbol.Name}"; } - #endregion + + #endregion Converter #region Package private int m_deep; - private void AppendArrayWriteString(StringBuilder codeString, PackageMember packageMember, ITypeSymbol typeSymbol, string name) + private void AppendArrayWriteString(StringBuilder codeBuilder, PackageMember packageMember, ITypeSymbol typeSymbol, string name) { var arrayTypeSymbol = (IArrayTypeSymbol)typeSymbol; if (!this.SupportType(arrayTypeSymbol.ElementType)) @@ -129,49 +73,49 @@ internal sealed class PackageCodeBuilder : CodeBuilder return; } - codeString.AppendLine($"byteBlock.WriteIsNull({name});"); - codeString.AppendLine($"if ({name}!=null)"); - codeString.AppendLine("{"); + codeBuilder.AppendLine($"WriterExtension.WriteIsNull(ref writer,{name});"); + codeBuilder.AppendLine($"if ({name}!=null)"); + codeBuilder.AppendLine("{"); var rank = arrayTypeSymbol.Rank; if (rank == 1) { - codeString.AppendLine($"byteBlock.WriteVarUInt32((uint){name}.Length);"); + codeBuilder.AppendLine($"WriterExtension.WriteVarUInt32(ref writer,(uint){name}.Length);"); } else { var dimensionItem = this.GetDeepItemString(); - codeString.AppendLine($"for (var {dimensionItem} = 0; {dimensionItem} < {rank}; {dimensionItem}++)"); - codeString.AppendLine("{"); - codeString.AppendLine($"byteBlock.WriteVarUInt32((uint){name}.GetLength({dimensionItem}));"); - codeString.AppendLine("}"); + codeBuilder.AppendLine($"for (var {dimensionItem} = 0; {dimensionItem} < {rank}; {dimensionItem}++)"); + codeBuilder.AppendLine("{"); + codeBuilder.AppendLine($"WriterExtension.WriteVarUInt32(ref writer,(uint){name}.GetLength({dimensionItem}));"); + codeBuilder.AppendLine("}"); } var item = this.GetDeepItemString(); - codeString.AppendLine($"foreach (var {item} in {name})"); - codeString.AppendLine("{"); - this.AppendObjectWriteString(codeString, packageMember, arrayTypeSymbol.ElementType, item); - codeString.AppendLine("}"); - codeString.AppendLine("}"); + codeBuilder.AppendLine($"foreach (var {item} in {name})"); + codeBuilder.AppendLine("{"); + this.AppendObjectWriteString(codeBuilder, packageMember, arrayTypeSymbol.ElementType, item); + codeBuilder.AppendLine("}"); + codeBuilder.AppendLine("}"); } - private void AppendObjectWriteString(StringBuilder codeString, PackageMember packageMember, ITypeSymbol typeSymbol, string name) + private void AppendObjectWriteString(StringBuilder codeBuilder, PackageMember packageMember, ITypeSymbol typeSymbol, string name) { //Debugger.Launch(); if (typeSymbol.TypeKind == TypeKind.Enum) { //枚举 - this.AppendWriteString(codeString, packageMember, typeSymbol, name); + this.AppendWriteString(codeBuilder, packageMember, typeSymbol, name); } else if (this.CanReadWrite(typeSymbol)) { //直接读写 - this.AppendWriteString(codeString, packageMember, typeSymbol, name); + this.AppendWriteString(codeBuilder, packageMember, typeSymbol, name); } else if (packageMember.Converter != null) { //转换器 - codeString.AppendLine($"{this.GetConverterVariableName(packageMember.Converter)}.Write(ref byteBlock,this.{packageMember.Name});"); + codeBuilder.AppendLine($"{this.GetConverterVariableName(packageMember.Converter)}.Package(ref writer,this.{packageMember.Name});"); } else if (typeSymbol is INamedTypeSymbol listNamedTypeSymbol && listNamedTypeSymbol.IsList()) { @@ -183,16 +127,16 @@ internal sealed class PackageCodeBuilder : CodeBuilder this.m_context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.m_rule_Package0002, packageMember.Location, packageMember.Name, elementType)); return; } - codeString.AppendLine($"byteBlock.WriteIsNull({name});"); - codeString.AppendLine($"if ({name}!=null)"); - codeString.AppendLine("{"); - codeString.AppendLine($"byteBlock.WriteVarUInt32((uint){name}.Count);"); + codeBuilder.AppendLine($"WriterExtension.WriteIsNull(ref writer,{name});"); + codeBuilder.AppendLine($"if ({name}!=null)"); + codeBuilder.AppendLine("{"); + codeBuilder.AppendLine($"WriterExtension.WriteVarUInt32(ref writer,(uint){name}.Count);"); var item = this.GetDeepItemString(); - codeString.AppendLine($"foreach (var {item} in {name})"); - codeString.AppendLine("{"); - this.AppendObjectWriteString(codeString, packageMember, elementType, item); - codeString.AppendLine("}"); - codeString.AppendLine("}"); + codeBuilder.AppendLine($"foreach (var {item} in {name})"); + codeBuilder.AppendLine("{"); + this.AppendObjectWriteString(codeBuilder, packageMember, elementType, item); + codeBuilder.AppendLine("}"); + codeBuilder.AppendLine("}"); } else if (typeSymbol is INamedTypeSymbol dicNamedTypeSymbol && dicNamedTypeSymbol.IsDictionary()) { @@ -210,35 +154,35 @@ internal sealed class PackageCodeBuilder : CodeBuilder this.m_context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.m_rule_Package0002, packageMember.Location, packageMember.Name, elementTypeValue)); return; } - codeString.AppendLine($"byteBlock.WriteIsNull({name});"); - codeString.AppendLine($"if ({name}!=null)"); - codeString.AppendLine("{"); - codeString.AppendLine($"byteBlock.WriteVarUInt32((uint){name}.Count);"); + codeBuilder.AppendLine($"WriterExtension.WriteIsNull(ref writer,{name});"); + codeBuilder.AppendLine($"if ({name}!=null)"); + codeBuilder.AppendLine("{"); + codeBuilder.AppendLine($"WriterExtension.WriteVarUInt32(ref writer,(uint){name}.Count);"); var item = this.GetDeepItemString(); - codeString.AppendLine($"foreach (var {item} in {name})"); - codeString.AppendLine("{"); - this.AppendObjectWriteString(codeString, packageMember, elementTypeKey, $"{item}.Key"); - this.AppendObjectWriteString(codeString, packageMember, elementTypeValue, $"{item}.Value"); - codeString.AppendLine("}"); - codeString.AppendLine("}"); + codeBuilder.AppendLine($"foreach (var {item} in {name})"); + codeBuilder.AppendLine("{"); + this.AppendObjectWriteString(codeBuilder, packageMember, elementTypeKey, $"{item}.Key"); + this.AppendObjectWriteString(codeBuilder, packageMember, elementTypeValue, $"{item}.Value"); + codeBuilder.AppendLine("}"); + codeBuilder.AppendLine("}"); } else if (typeSymbol.TypeKind == TypeKind.Class) { - if (!typeSymbol.IsInheritFrom(PackageSyntaxReceiver.IPackageTypeName)) + if (!typeSymbol.IsInheritFrom(Utils.IPackageTypeName)) { this.m_context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.m_rule_Package0002, packageMember.Location, packageMember.Name, packageMember.Type)); return; } - this.AppendWriteString(codeString, packageMember, typeSymbol, name); + this.AppendWriteString(codeBuilder, packageMember, typeSymbol, name); } else if (typeSymbol.TypeKind == TypeKind.Struct) { if (typeSymbol.NullableAnnotation == NullableAnnotation.Annotated) { var typeSymbolNotNullable = typeSymbol.GetNullableType(); - if (!typeSymbolNotNullable.WithNullableAnnotation(NullableAnnotation.None).IsInheritFrom(PackageSyntaxReceiver.IPackageTypeName)) + if (!typeSymbolNotNullable.IsInheritFrom(Utils.IPackageTypeName)) { this.m_context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.m_rule_Package0002, packageMember.Location, packageMember.Name, packageMember.Type)); return; @@ -246,145 +190,72 @@ internal sealed class PackageCodeBuilder : CodeBuilder } else { - if (!typeSymbol.IsInheritFrom(PackageSyntaxReceiver.IPackageTypeName)) + if (!typeSymbol.IsInheritFrom(Utils.IPackageTypeName)) { this.m_context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.m_rule_Package0002, packageMember.Location, packageMember.Name, packageMember.Type)); return; } } - this.AppendWriteString(codeString, packageMember, typeSymbol, name); + this.AppendWriteString(codeBuilder, packageMember, typeSymbol, name); } else if (typeSymbol.TypeKind == TypeKind.Array) { - this.AppendArrayWriteString(codeString, packageMember, typeSymbol, name); + this.AppendArrayWriteString(codeBuilder, packageMember, typeSymbol, name); } } - private void AppendWriteString(StringBuilder codeString, PackageMember packageMember, ITypeSymbol typeSymbol, string name) + private void AppendWriteString(StringBuilder codeBuilder, PackageMember packageMember, ITypeSymbol typeSymbol, string name) { - //Debugger.Launch(); if (typeSymbol.TypeKind == TypeKind.Enum) { var namedTypeSymbol = (INamedTypeSymbol)typeSymbol; var enumUnderlyingType = namedTypeSymbol.EnumUnderlyingType; var writeString = this.GetWriteString(enumUnderlyingType, $"({enumUnderlyingType.ToDisplayString()}){name}"); - codeString.AppendLine($"{writeString};"); + codeBuilder.AppendLine($"{writeString};"); } else if (this.CanReadWrite(typeSymbol)) { - codeString.AppendLine($"{this.GetWriteString(typeSymbol, name)};"); + codeBuilder.AppendLine($"{this.GetWriteString(typeSymbol, name)};"); } else if (typeSymbol.TypeKind == TypeKind.Struct) { if (typeSymbol.NullableAnnotation == NullableAnnotation.NotAnnotated) { - codeString.AppendLine($"{name}.Package(ref byteBlock);"); + codeBuilder.AppendLine($"{name}.Package(ref writer);"); } else { - codeString.AppendLine($"if ({name}.HasValue)"); - codeString.AppendLine("{"); - codeString.AppendLine($"byteBlock.WriteNotNull();"); - codeString.AppendLine($"this.{name}.Value.Package(ref byteBlock);"); - codeString.AppendLine("}"); - codeString.AppendLine($"else"); - codeString.AppendLine("{"); - codeString.AppendLine("byteBlock.WriteNull();"); - codeString.AppendLine("}"); + codeBuilder.AppendLine($"if ({name}.HasValue)"); + codeBuilder.AppendLine("{"); + codeBuilder.AppendLine($" WriterExtension.WriteNotNull(ref writer);"); + codeBuilder.AppendLine($"this.{name}.Value.Package(ref writer);"); + codeBuilder.AppendLine("}"); + codeBuilder.AppendLine($"else"); + codeBuilder.AppendLine("{"); + codeBuilder.AppendLine("WriterExtension.WriteNull(ref writer);"); + codeBuilder.AppendLine("}"); } } else if (typeSymbol.TypeKind == TypeKind.Array) { - this.AppendArrayWriteString(codeString, packageMember, typeSymbol, name); + this.AppendArrayWriteString(codeBuilder, packageMember, typeSymbol, name); } else { - codeString.AppendLine($"byteBlock.WritePackage({name});"); + codeBuilder.AppendLine($"if ({name}!=null)"); + codeBuilder.AppendLine("{"); + codeBuilder.AppendLine($" WriterExtension.WriteNotNull(ref writer);"); + codeBuilder.AppendLine($"{name}.Package(ref writer);"); + codeBuilder.AppendLine("}"); + codeBuilder.AppendLine($"else"); + codeBuilder.AppendLine("{"); + codeBuilder.AppendLine("WriterExtension.WriteNull(ref writer);"); + codeBuilder.AppendLine("}"); } } - private string GetWriteString(ITypeSymbol typeSymbol, string name) - { - if (typeSymbol.SpecialType == SpecialType.System_Boolean) - { - return $"byteBlock.WriteBoolean({name})"; - } - else if (typeSymbol.SpecialType == SpecialType.System_Byte) - { - return $"byteBlock.WriteByte({name})"; - } - else if (typeSymbol.SpecialType == SpecialType.System_SByte) - { - return $"byteBlock.WriteInt16({name})"; - } - else if (typeSymbol.SpecialType == SpecialType.System_Int16) - { - return $"byteBlock.WriteInt16({name})"; - } - else if (typeSymbol.SpecialType == SpecialType.System_UInt16) - { - return $"byteBlock.WriteUInt16({name})"; - } - else if (typeSymbol.SpecialType == SpecialType.System_Int32) - { - return $"byteBlock.WriteInt32({name})"; - } - else if (typeSymbol.SpecialType == SpecialType.System_UInt32) - { - return $"byteBlock.WriteUInt32({name})"; - } - else if (typeSymbol.SpecialType == SpecialType.System_Int64) - { - return $"byteBlock.WriteInt64({name})"; - } - else if (typeSymbol.SpecialType == SpecialType.System_UInt64) - { - return $"byteBlock.WriteUInt64({name})"; - } - else if (typeSymbol.SpecialType == SpecialType.System_Single) - { - return $"byteBlock.WriteFloat({name})"; - } - else if (typeSymbol.SpecialType == SpecialType.System_Double) - { - return $"byteBlock.WriteDouble({name})"; - } - else if (typeSymbol.SpecialType == SpecialType.System_String) - { - return $"byteBlock.WriteString({name})"; - } - else if (typeSymbol.SpecialType == SpecialType.System_Char) - { - return $"byteBlock.WriteChar({name})"; - } - else if (typeSymbol.SpecialType == SpecialType.System_Decimal) - { - return $"byteBlock.WriteDecimal({name})"; - } - else if (typeSymbol.SpecialType == SpecialType.System_DateTime) - { - return $"byteBlock.WriteDateTime({name})"; - } - else if (typeSymbol.IsTimeSpan()) - { - return $"byteBlock.WriteTimeSpan({name})"; - } - else if (typeSymbol.IsGuid()) - { - return $"byteBlock.WriteGuid({name})"; - } - else if (typeSymbol.ToDisplayString() == "byte[]") - { - return $"byteBlock.WriteBytesPackage({name})"; - } - else - { - return ""; - } - } - - private void BuildPackage(StringBuilder codeString, IEnumerable members) + private void BuildPackage(StringBuilder codeBuilder, IEnumerable members) { if (this.ExistsPackageMethod()) { @@ -394,73 +265,45 @@ internal sealed class PackageCodeBuilder : CodeBuilder var OverrideMethod = this.NeedOverridePackageMethod(this.m_packageClass.BaseType); if (OverrideMethod != null) { - codeString.AppendLine($"public override void Package(ref TByteBlock byteBlock)"); - codeString.AppendLine("{"); + codeBuilder.AppendLine($"public override void Package(ref TWriter writer)"); + codeBuilder.AppendLine("{"); if (OverrideMethod.IsVirtual || this.IsGeneratorPackage(this.m_packageClass.BaseType)) { - codeString.AppendLine("base.Package(ref byteBlock);"); + codeBuilder.AppendLine("base.Package(ref writer);"); } } else { - codeString.AppendLine($"public void Package(ref TByteBlock byteBlock) where TByteBlock:IByteBlock"); - codeString.AppendLine("{"); + codeBuilder.AppendLine($"public void Package(ref TWriter writer) where TWriter: IBytesWriter"); + codeBuilder.AppendLine("{"); } foreach (var packageMember in members) { - this.AppendObjectWriteString(codeString, packageMember, packageMember.Type, packageMember.Name); + this.AppendObjectWriteString(codeBuilder, packageMember, packageMember.Type, packageMember.Name); } - codeString.AppendLine("}"); - } - - private bool IsGeneratorPackage(INamedTypeSymbol namedTypeSymbol) - { - if (namedTypeSymbol == null) - { - return false; - } - return namedTypeSymbol.HasAttribute(PackageSyntaxReceiver.GeneratorPackageAttributeTypeName); + codeBuilder.AppendLine("}"); } private bool CanReadWrite(ITypeSymbol typeSymbol) { - if (typeSymbol.IsTimeSpan()) + //Debugger.Launch(); + var typeSymbolNotNullable = typeSymbol.GetNullableType(); + if (typeSymbolNotNullable.IsInheritFrom(Utils.IPackageTypeName)) + { + return false; + } + + if (typeSymbol.IsUnmanagedType()) { return true; } - if (typeSymbol.IsGuid()) + if (typeSymbol.SpecialType == SpecialType.System_String) { return true; } - if (typeSymbol.ToDisplayString() == "byte[]") - { - return true; - } - switch (typeSymbol.SpecialType) - { - case SpecialType.System_Boolean: - case SpecialType.System_Char: - case SpecialType.System_SByte: - case SpecialType.System_Byte: - case SpecialType.System_Int16: - case SpecialType.System_UInt16: - case SpecialType.System_Int32: - case SpecialType.System_UInt32: - case SpecialType.System_Int64: - case SpecialType.System_UInt64: - case SpecialType.System_Decimal: - case SpecialType.System_Single: - case SpecialType.System_Double: - case SpecialType.System_String: - case SpecialType.System_DateTime: - { - return true; - } - default: - return false; - } + return false; } private int GetDeep() @@ -473,38 +316,26 @@ internal sealed class PackageCodeBuilder : CodeBuilder return "item" + this.GetDeep(); } - #endregion Package - - public bool TryToSourceText(out SourceText sourceText) + private string GetWriteString(ITypeSymbol typeSymbol, string name) { - var b = false; - if (this.m_packageClass.IsInheritFrom(this.m_packageBaseString)) + if (typeSymbol.SpecialType == SpecialType.System_String) { - b = true; + return $"WriterExtension.WriteString(ref writer,{name})"; } - else if (this.m_packageClass.TypeKind == TypeKind.Struct) - { - b = true; - } - else - { - this.m_context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.m_rule_Package0001, this.m_packageClass.Locations[0])); - } - if (b) - { - var code = this.ToString(); - if (string.IsNullOrEmpty(code)) - { - sourceText = null; - return false; - } - sourceText = SourceText.From(code, Encoding.UTF8); - return true; - } - sourceText = null; - return false; + return $"WriterExtension.WriteValue(ref writer,{name})"; } + private bool IsGeneratorPackage(INamedTypeSymbol namedTypeSymbol) + { + if (namedTypeSymbol == null) + { + return false; + } + return namedTypeSymbol.HasAttribute(Utils.GeneratorPackageAttributeTypeName); + } + + #endregion Package + private IEnumerable GetPackageMembers() { var list = new List(); @@ -629,7 +460,7 @@ internal sealed class PackageCodeBuilder : CodeBuilder return true; } - if (typeSymbol.IsInheritFrom(PackageSyntaxReceiver.IPackageTypeName)) + if (typeSymbol.IsInheritFrom(Utils.IPackageTypeName)) { return true; } @@ -649,7 +480,7 @@ internal sealed class PackageCodeBuilder : CodeBuilder #region Unpackage - private void AppendArrayReadString(StringBuilder codeString, PackageMember packageMember, ITypeSymbol typeSymbol, string name) + private void AppendArrayReadString(StringBuilder codeBuilder, PackageMember packageMember, ITypeSymbol typeSymbol, string name) { var arrayTypeSymbol = (IArrayTypeSymbol)typeSymbol; var elementType = arrayTypeSymbol.ElementType; @@ -663,101 +494,100 @@ internal sealed class PackageCodeBuilder : CodeBuilder if (rank == 1) { - codeString.AppendLine("if (!byteBlock.ReadIsNull())"); - codeString.AppendLine("{"); + codeBuilder.AppendLine("if (!ReaderExtension.ReadIsNull(ref reader))"); + codeBuilder.AppendLine("{"); var len = this.GetDeepItemString(); - codeString.AppendLine($"var {len}=(int)byteBlock.ReadVarUInt32();"); - codeString.AppendLine($"{name} = new {elementType.ToDisplayString()}[{len}];"); + codeBuilder.AppendLine($"var {len}=(int)ReaderExtension.ReadVarUInt32(ref reader);"); + codeBuilder.AppendLine($"{name} = new {elementType.ToDisplayString()}[{len}];"); var i = this.GetDeepItemString(); - codeString.AppendLine($"for (var {i} = 0; {i} < {len}; {i}++)"); - codeString.AppendLine("{"); + codeBuilder.AppendLine($"for (var {i} = 0; {i} < {len}; {i}++)"); + codeBuilder.AppendLine("{"); if (this.CanReadWrite(elementType)) { - codeString.AppendLine($"{name}[{i}] = {this.GetReadString(elementType)};"); + codeBuilder.AppendLine($"{name}[{i}] = {this.GetReadString(elementType)};"); } else { var item = this.GetDeepItemString(); - codeString.AppendLine($"{elementType.ToDisplayString()} {item}=default;"); - this.AppendObjectReadString(codeString, packageMember, elementType, item); - codeString.AppendLine($"{name}[{i}] = {item};"); + codeBuilder.AppendLine($"{elementType.ToDisplayString()} {item}=default;"); + this.AppendObjectReadString(codeBuilder, packageMember, elementType, item); + codeBuilder.AppendLine($"{name}[{i}] = {item};"); } - codeString.AppendLine("}"); - codeString.AppendLine("}"); + codeBuilder.AppendLine("}"); + codeBuilder.AppendLine("}"); } else { - codeString.AppendLine("if (!byteBlock.ReadIsNull())"); - codeString.AppendLine("{"); + codeBuilder.AppendLine("if (!ReaderExtension.ReadIsNull(ref reader))"); + codeBuilder.AppendLine("{"); var dimensionNames = new string[rank]; for (var j = 0; j < rank; j++) { var lenName = this.GetDeepItemString(); - codeString.AppendLine($"var {lenName}=(int)byteBlock.ReadVarUInt32();"); + codeBuilder.AppendLine($"var {lenName}=(int)ReaderExtension.ReadVarUInt32(ref reader);"); dimensionNames[j] = lenName; } var array = this.GetDeepItemString(); - codeString.Append($"var {array} = new {elementType.ToDisplayString()}[{string.Join(",", dimensionNames)}];"); + codeBuilder.Append($"var {array} = new {elementType.ToDisplayString()}[{string.Join(",", dimensionNames)}];"); + this.AppendDimensionArrayReadString(codeBuilder, dimensionNames, 0, elementType, array, packageMember, new string[rank]); - this.AppendDimensionArrayReadString(codeString, dimensionNames, 0, elementType, array, packageMember, new string[rank]); - - codeString.Append($"{name} = {array};"); - codeString.AppendLine("}"); + codeBuilder.Append($"{name} = {array};"); + codeBuilder.AppendLine("}"); } } - private void AppendDimensionArrayReadString(StringBuilder codeString, string[] dimensionNames, int dimensionNameIndex, ITypeSymbol elementType, string name, PackageMember packageMember, string[] dimensionIndexNames) + private void AppendDimensionArrayReadString(StringBuilder codeBuilder, string[] dimensionNames, int dimensionNameIndex, ITypeSymbol elementType, string name, PackageMember packageMember, string[] dimensionIndexNames) { var i = this.GetDeepItemString(); dimensionIndexNames[dimensionNameIndex] = i; var len = dimensionNames[dimensionNameIndex]; - codeString.AppendLine($"for (var {i} = 0; {i} < {len}; {i}++)"); - codeString.AppendLine("{"); + codeBuilder.AppendLine($"for (var {i} = 0; {i} < {len}; {i}++)"); + codeBuilder.AppendLine("{"); if (dimensionNameIndex == dimensionNames.Length - 1) { //最后一个维度 if (this.CanReadWrite(elementType)) { - codeString.AppendLine($"{name}[{string.Join(",", dimensionIndexNames)}] = {this.GetReadString(elementType)};"); + codeBuilder.AppendLine($"{name}[{string.Join(",", dimensionIndexNames)}] = {this.GetReadString(elementType)};"); } else { var item = this.GetDeepItemString(); - codeString.AppendLine($"{elementType.ToDisplayString()} {item}=default;"); - this.AppendObjectReadString(codeString, packageMember, elementType, item); - codeString.AppendLine($"{name}[{string.Join(",", dimensionIndexNames)}] = {item};"); + codeBuilder.AppendLine($"{elementType.ToDisplayString()} {item}=default;"); + this.AppendObjectReadString(codeBuilder, packageMember, elementType, item); + codeBuilder.AppendLine($"{name}[{string.Join(",", dimensionIndexNames)}] = {item};"); } } else { - this.AppendDimensionArrayReadString(codeString, dimensionNames, dimensionNameIndex + 1, elementType, name, packageMember, dimensionIndexNames); + this.AppendDimensionArrayReadString(codeBuilder, dimensionNames, dimensionNameIndex + 1, elementType, name, packageMember, dimensionIndexNames); } - codeString.AppendLine("}"); + codeBuilder.AppendLine("}"); } - private void AppendObjectReadString(StringBuilder codeString, PackageMember packageMember, ITypeSymbol typeSymbol, string name) + private void AppendObjectReadString(StringBuilder codeBuilder, PackageMember packageMember, ITypeSymbol typeSymbol, string name) { //Debugger.Launch(); if (typeSymbol.TypeKind == TypeKind.Enum) { //枚举 - codeString.AppendLine($"{name}=({typeSymbol.ToDisplayString()}){this.GetReadString(((INamedTypeSymbol)typeSymbol).EnumUnderlyingType)};"); + codeBuilder.AppendLine($"{name}=({typeSymbol.ToDisplayString()}){this.GetReadString(((INamedTypeSymbol)typeSymbol).EnumUnderlyingType)};"); } else if (this.CanReadWrite(typeSymbol)) { //直接读写 - codeString.AppendLine($"{name}={this.GetReadString(typeSymbol)};"); + codeBuilder.AppendLine($"{name}={this.GetReadString(typeSymbol)};"); } else if (packageMember.Converter != null) { //转换器 - codeString.AppendLine($"this.{packageMember.Name}=({typeSymbol.ToDisplayString()}){this.GetConverterVariableName(packageMember.Converter)}.Read(ref byteBlock,typeof({typeSymbol.ToDisplayString()}));"); + codeBuilder.AppendLine($"this.{packageMember.Name}=({typeSymbol.ToDisplayString()}){this.GetConverterVariableName(packageMember.Converter)}.Unpackage(ref reader);"); } else if (typeSymbol is INamedTypeSymbol namedTypeSymbol && namedTypeSymbol.IsList()) { @@ -767,34 +597,34 @@ internal sealed class PackageCodeBuilder : CodeBuilder { return; } - codeString.AppendLine("if (!byteBlock.ReadIsNull())"); - codeString.AppendLine("{"); + codeBuilder.AppendLine("if (!ReaderExtension.ReadIsNull(ref reader))"); + codeBuilder.AppendLine("{"); var len = this.GetDeepItemString(); - codeString.AppendLine($"var {len}=(int)byteBlock.ReadVarUInt32();"); + codeBuilder.AppendLine($"var {len}=(int)ReaderExtension.ReadVarUInt32(ref reader);"); var list = this.GetDeepItemString(); - codeString.AppendLine($"var {list} = new System.Collections.Generic.List<{elementType.ToDisplayString()}>({len});"); + codeBuilder.AppendLine($"var {list} = new System.Collections.Generic.List<{elementType.ToDisplayString()}>({len});"); var i = this.GetDeepItemString(); - codeString.AppendLine($"for (var {i} = 0; {i} < {len}; {i}++)"); - codeString.AppendLine("{"); + codeBuilder.AppendLine($"for (var {i} = 0; {i} < {len}; {i}++)"); + codeBuilder.AppendLine("{"); if (this.CanReadWrite(elementType)) { - codeString.AppendLine($"{list}.Add({this.GetReadString(elementType)});"); + codeBuilder.AppendLine($"{list}.Add({this.GetReadString(elementType)});"); } else { var itemName = this.GetDeepItemString(); - codeString.AppendLine($"{elementType.ToDisplayString()} {itemName}=default;"); - this.AppendObjectReadString(codeString, packageMember, elementType, itemName); + codeBuilder.AppendLine($"{elementType.ToDisplayString()} {itemName}=default;"); + this.AppendObjectReadString(codeBuilder, packageMember, elementType, itemName); - codeString.AppendLine($"{list}.Add({itemName});"); + codeBuilder.AppendLine($"{list}.Add({itemName});"); } - codeString.AppendLine("}"); - codeString.AppendLine($"{name}={list};"); - codeString.AppendLine("}"); + codeBuilder.AppendLine("}"); + codeBuilder.AppendLine($"{name}={list};"); + codeBuilder.AppendLine("}"); } else if (typeSymbol is INamedTypeSymbol dicNamedTypeSymbol && dicNamedTypeSymbol.IsDictionary()) { @@ -809,56 +639,56 @@ internal sealed class PackageCodeBuilder : CodeBuilder { return; } - codeString.AppendLine("if (!byteBlock.ReadIsNull())"); - codeString.AppendLine("{"); + codeBuilder.AppendLine("if (!ReaderExtension.ReadIsNull(ref reader))"); + codeBuilder.AppendLine("{"); var len = this.GetDeepItemString(); - codeString.AppendLine($"var {len}=(int)byteBlock.ReadVarUInt32();"); + codeBuilder.AppendLine($"var {len}=(int)ReaderExtension.ReadVarUInt32(ref reader);"); var dic = this.GetDeepItemString(); - codeString.AppendLine($"var {dic} = new System.Collections.Generic.Dictionary<{elementTypeKey.ToDisplayString()},{elementTypeValue.ToDisplayString()}>({len});"); + codeBuilder.AppendLine($"var {dic} = new System.Collections.Generic.Dictionary<{elementTypeKey.ToDisplayString()},{elementTypeValue.ToDisplayString()}>({len});"); var i = this.GetDeepItemString(); - codeString.AppendLine($"for (var {i} = 0; {i} < {len}; {i}++)"); - codeString.AppendLine("{"); + codeBuilder.AppendLine($"for (var {i} = 0; {i} < {len}; {i}++)"); + codeBuilder.AppendLine("{"); var key = this.GetDeepItemString(); if (this.CanReadWrite(elementTypeKey)) { - codeString.AppendLine($"var {key} = {this.GetReadString(elementTypeKey)};"); + codeBuilder.AppendLine($"var {key} = {this.GetReadString(elementTypeKey)};"); } else { - codeString.AppendLine($"{elementTypeKey.ToDisplayString()} {key}=default;"); - this.AppendObjectReadString(codeString, packageMember, elementTypeKey, key); + codeBuilder.AppendLine($"{elementTypeKey.ToDisplayString()} {key}=default;"); + this.AppendObjectReadString(codeBuilder, packageMember, elementTypeKey, key); } var value = this.GetDeepItemString(); if (this.CanReadWrite(elementTypeValue)) { - codeString.AppendLine($"var {value} = {this.GetReadString(elementTypeValue)};"); + codeBuilder.AppendLine($"var {value} = {this.GetReadString(elementTypeValue)};"); } else { - codeString.AppendLine($"{elementTypeValue.ToDisplayString()} {value}=default;"); - this.AppendObjectReadString(codeString, packageMember, elementTypeValue, value); + codeBuilder.AppendLine($"{elementTypeValue.ToDisplayString()} {value}=default;"); + this.AppendObjectReadString(codeBuilder, packageMember, elementTypeValue, value); } - codeString.AppendLine($"{dic}.Add({key},{value});"); - codeString.AppendLine("}"); - codeString.AppendLine($"{name}={dic};"); - codeString.AppendLine("}"); + codeBuilder.AppendLine($"{dic}.Add({key},{value});"); + codeBuilder.AppendLine("}"); + codeBuilder.AppendLine($"{name}={dic};"); + codeBuilder.AppendLine("}"); } else if (typeSymbol.TypeKind == TypeKind.Class) { - if (!typeSymbol.IsInheritFrom(PackageSyntaxReceiver.IPackageTypeName)) + if (!typeSymbol.IsInheritFrom(Utils.IPackageTypeName)) { return; } - codeString.AppendLine($"if(!byteBlock.ReadIsNull())"); - codeString.AppendLine("{"); - codeString.AppendLine($"{name}=new {typeSymbol.ToDisplayString()}();"); - codeString.AppendLine($"{name}.Unpackage(ref byteBlock);"); - codeString.AppendLine("}"); + codeBuilder.AppendLine($"if(!ReaderExtension.ReadIsNull(ref reader))"); + codeBuilder.AppendLine("{"); + codeBuilder.AppendLine($"{name}=new {typeSymbol.ToDisplayString()}();"); + codeBuilder.AppendLine($"{name}.Unpackage(ref reader);"); + codeBuilder.AppendLine("}"); } else if (typeSymbol.TypeKind == TypeKind.Struct) { @@ -866,37 +696,37 @@ internal sealed class PackageCodeBuilder : CodeBuilder if (typeSymbol.NullableAnnotation == NullableAnnotation.Annotated) { - if (!typeSymbol.GetNullableType().IsInheritFrom(PackageSyntaxReceiver.IPackageTypeName)) + if (!typeSymbol.GetNullableType().IsInheritFrom(Utils.IPackageTypeName)) { return; } - codeString.AppendLine($"if(!byteBlock.ReadIsNull())"); - codeString.AppendLine("{"); - codeString.AppendLine($"var {propertySymbolName}=new {typeSymbol.GetNullableType().ToDisplayString()}();"); - codeString.AppendLine($"{propertySymbolName}.Unpackage(ref byteBlock);"); - codeString.AppendLine($"this.{name} = {propertySymbolName};"); - codeString.AppendLine("}"); + codeBuilder.AppendLine($"if(!ReaderExtension.ReadIsNull(ref reader))"); + codeBuilder.AppendLine("{"); + codeBuilder.AppendLine($"var {propertySymbolName}=new {typeSymbol.GetNullableType().ToDisplayString()}();"); + codeBuilder.AppendLine($"{propertySymbolName}.Unpackage(ref reader);"); + codeBuilder.AppendLine($"this.{name} = {propertySymbolName};"); + codeBuilder.AppendLine("}"); } else { - if (!typeSymbol.IsInheritFrom(PackageSyntaxReceiver.IPackageTypeName)) + if (!typeSymbol.IsInheritFrom(Utils.IPackageTypeName)) { return; } - codeString.AppendLine($"var {propertySymbolName}=new {typeSymbol.ToDisplayString()}();"); - codeString.AppendLine($"{propertySymbolName}.Unpackage(ref byteBlock);"); - codeString.AppendLine($"{name}={propertySymbolName};"); + codeBuilder.AppendLine($"var {propertySymbolName}=new {typeSymbol.ToDisplayString()}();"); + codeBuilder.AppendLine($"{propertySymbolName}.Unpackage(ref reader);"); + codeBuilder.AppendLine($"{name}={propertySymbolName};"); } } else if (typeSymbol.TypeKind == TypeKind.Array) { - this.AppendArrayReadString(codeString, packageMember, typeSymbol, name); + this.AppendArrayReadString(codeBuilder, packageMember, typeSymbol, name); } } - private void BuildUnpackage(StringBuilder codeString, IEnumerable members) + private void BuildUnpackage(StringBuilder codeBuilder, IEnumerable members) { if (this.ExistsUnpackageMethod()) { @@ -906,110 +736,82 @@ internal sealed class PackageCodeBuilder : CodeBuilder var OverrideMethod = this.NeedOverrideUnpackageMethod(this.m_packageClass.BaseType); if (OverrideMethod != null) { - codeString.AppendLine($"public override void Unpackage(ref TByteBlock byteBlock)"); - codeString.AppendLine("{"); + codeBuilder.AppendLine($"public override void Unpackage(ref TReader reader)"); + codeBuilder.AppendLine("{"); if (OverrideMethod.IsVirtual || this.IsGeneratorPackage(this.m_packageClass.BaseType)) { - codeString.AppendLine("base.Unpackage(ref byteBlock);"); + codeBuilder.AppendLine("base.Unpackage(ref reader);"); } } else { - codeString.AppendLine($"public void Unpackage(ref TByteBlock byteBlock) where TByteBlock : IByteBlock"); - codeString.AppendLine("{"); + codeBuilder.AppendLine($"public void Unpackage(ref TReader reader) where TReader : IBytesReader"); + codeBuilder.AppendLine("{"); } foreach (var packageMember in members) { - this.AppendObjectReadString(codeString, packageMember, packageMember.Type, packageMember.Name); + this.AppendObjectReadString(codeBuilder, packageMember, packageMember.Type, packageMember.Name); } - codeString.AppendLine("}"); + codeBuilder.AppendLine("}"); } private string GetReadString(ITypeSymbol typeSymbol) { - if (typeSymbol.SpecialType == SpecialType.System_Boolean) + if (typeSymbol.SpecialType == SpecialType.System_String) { - return "byteBlock.ReadBoolean()"; - } - else if (typeSymbol.SpecialType == SpecialType.System_Byte) - { - return "byteBlock.ReadByte()"; - } - else if (typeSymbol.SpecialType == SpecialType.System_SByte) - { - return "(sbyte)byteBlock.ReadInt16()"; - } - else if (typeSymbol.SpecialType == SpecialType.System_Int16) - { - return "byteBlock.ReadInt16()"; - } - else if (typeSymbol.SpecialType == SpecialType.System_UInt16) - { - return "byteBlock.ReadUInt16()"; - } - else if (typeSymbol.SpecialType == SpecialType.System_Int32) - { - return "byteBlock.ReadInt32()"; - } - else if (typeSymbol.SpecialType == SpecialType.System_UInt32) - { - return "byteBlock.ReadUInt32()"; - } - else if (typeSymbol.SpecialType == SpecialType.System_Int64) - { - return "byteBlock.ReadInt64()"; - } - else if (typeSymbol.SpecialType == SpecialType.System_UInt64) - { - return "byteBlock.ReadUInt64()"; - } - else if (typeSymbol.SpecialType == SpecialType.System_Single) - { - return "byteBlock.ReadFloat()"; - } - else if (typeSymbol.SpecialType == SpecialType.System_Double) - { - return "byteBlock.ReadDouble()"; - } - else if (typeSymbol.SpecialType == SpecialType.System_String) - { - return "byteBlock.ReadString()"; - } - else if (typeSymbol.SpecialType == SpecialType.System_Char) - { - return "byteBlock.ReadChar()"; - } - else if (typeSymbol.SpecialType == SpecialType.System_Decimal) - { - return "byteBlock.ReadDecimal()"; - } - else if (typeSymbol.SpecialType == SpecialType.System_DateTime) - { - return "byteBlock.ReadDateTime()"; - } - else if (typeSymbol.IsTimeSpan()) - { - return "byteBlock.ReadTimeSpan()"; - } - else if (typeSymbol.IsGuid()) - { - return "byteBlock.ReadGuid()"; - } - else if (typeSymbol.ToDisplayString() == "byte[]") - { - return "byteBlock.ReadBytesPackage()"; - } - else - { - return ""; + return $"ReaderExtension.ReadString(ref reader)"; } + return $"ReaderExtension.ReadValue(ref reader)"; } #endregion Unpackage #region override + protected override bool GeneratorCode(StringBuilder codeBuilder) + { + if (this.m_packageClass.IsInheritFrom(this.m_packageBaseString)) + { + } + else if (this.m_packageClass.TypeKind == TypeKind.Struct) + { + } + else + { + this.m_context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.m_rule_Package0001, this.m_packageClass.Locations[0])); + return false; + } + + //Debugger.Launch(); + if (!this.m_packageClass.ContainingNamespace.IsGlobalNamespace) + { + codeBuilder.AppendLine($"namespace {this.m_packageClass.ContainingNamespace}"); + codeBuilder.AppendLine("{"); + } + + codeBuilder.AppendLine($"partial {(this.m_packageClass.TypeKind == TypeKind.Struct ? "struct" : "class")} {this.m_packageClass.Name}"); + codeBuilder.AppendLine("{"); + + var members = this.GetPackageMembers(); + + this.BuildConverter(codeBuilder, members); + + this.m_deep = 0; + this.BuildPackage(codeBuilder, members); + + this.m_deep = 0; + this.BuildUnpackage(codeBuilder, members); + codeBuilder.AppendLine("}"); + + if (!this.m_packageClass.ContainingNamespace.IsGlobalNamespace) + { + codeBuilder.AppendLine("}"); + } + // System.Diagnostics.Debugger.Launch(); + return true; + } + private bool ExistsPackageMethod() { return this.m_packageClass @@ -1019,7 +821,7 @@ internal sealed class PackageCodeBuilder : CodeBuilder { if (m.Name == "Package" && m.Parameters.Length == 1) { - if (m.Parameters[0].RefKind == RefKind.Ref && m.Parameters[0].Type.ToDisplayString() == this.m_byteBlockString) + if (m.Parameters[0].RefKind == RefKind.Ref && m.Parameters[0].Type.ToDisplayString() == "TWriter") { return true; } @@ -1037,7 +839,7 @@ internal sealed class PackageCodeBuilder : CodeBuilder { if (m.Name == "Unpackage" && m.Parameters.Length == 1) { - if (m.Parameters[0].RefKind == RefKind.Ref && m.Parameters[0].Type.ToDisplayString() == this.m_byteBlockString) + if (m.Parameters[0].RefKind == RefKind.Ref && m.Parameters[0].Type.ToDisplayString() == "TReader") { return true; } @@ -1067,7 +869,7 @@ internal sealed class PackageCodeBuilder : CodeBuilder { return false; } - if (m.Parameters[0].RefKind == RefKind.Ref && m.Parameters[0].Type.ToDisplayString() == this.m_byteBlockString) + if (m.Parameters[0].RefKind == RefKind.Ref && m.Parameters[0].Type.ToDisplayString() == "TWriter") { return true; } @@ -1101,7 +903,7 @@ internal sealed class PackageCodeBuilder : CodeBuilder { return false; } - if (m.Parameters[0].RefKind == RefKind.Ref && m.Parameters[0].Type.ToDisplayString() == this.m_byteBlockString) + if (m.Parameters[0].RefKind == RefKind.Ref && m.Parameters[0].Type.ToDisplayString() == "TReader") { return true; } @@ -1119,13 +921,15 @@ internal sealed class PackageCodeBuilder : CodeBuilder #endregion override #region Class + private class PackageMember { - public int Index { get; set; } public ITypeSymbol Converter { get; set; } + public int Index { get; set; } + public Location Location { get; set; } public string Name { get; set; } public ITypeSymbol Type { get; set; } - public Location Location { get; set; } } - #endregion + + #endregion Class } \ No newline at end of file diff --git a/src/TouchSocket.Core.SourceGenerator/_Package/PackageSourceGenerator.cs b/src/TouchSocket.Core.SourceGenerator/_Package/PackageSourceGenerator.cs index fc7dffe12..a753b4df6 100644 --- a/src/TouchSocket.Core.SourceGenerator/_Package/PackageSourceGenerator.cs +++ b/src/TouchSocket.Core.SourceGenerator/_Package/PackageSourceGenerator.cs @@ -1,26 +1,28 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Collections.Immutable; using System.Linq; using System.Reflection; namespace TouchSocket; [Generator] -public class PackageSourceGenerator : ISourceGenerator +public class PackageSourceGenerator : IIncrementalGenerator { - private readonly string m_generatorPackageAttribute = @" + private const string GeneratorPackageAttribute = @" /* @@ -69,39 +71,54 @@ namespace TouchSocket.Core "; - public void Initialize(GeneratorInitializationContext context) + public void Initialize(IncrementalGeneratorInitializationContext context) { - context.RegisterForPostInitialization(a => + // 注册预生成的属性代码 + context.RegisterPostInitializationOutput(ctx => { - var sourceCode = this.m_generatorPackageAttribute.Replace("/*GeneratedCode*/", $"[global::System.CodeDom.Compiler.GeneratedCode(\"TouchSocket.SourceGenerator\",\"{Assembly.GetExecutingAssembly().GetName().Version.ToString()}\")]"); - a.AddSource(nameof(this.m_generatorPackageAttribute), sourceCode); + var sourceCode = GeneratorPackageAttribute.Replace("/*GeneratedCode*/", $"[global::System.CodeDom.Compiler.GeneratedCode(\"TouchSocket.SourceGenerator\",\"{Assembly.GetExecutingAssembly().GetName().Version.ToString()}\")]"); + + ctx.AddSource("GeneratorPackageAttributes.g.cs", sourceCode); }); - context.RegisterForSyntaxNotifications(() => new PackageSyntaxReceiver()); + + // 筛选包含GeneratorPackageAttribute的类 + var classDeclarations = context.SyntaxProvider + .CreateSyntaxProvider( + predicate: static (s, _) => IsClassWithAttribute(s), + transform: static (ctx, _) => GetClassSymbol(ctx)) + .Where(static m => m is not null) + .Collect(); + + // 生成代码 + context.RegisterSourceOutput(classDeclarations, + (spc, source) => this.Execute(source, spc)); } - public void Execute(GeneratorExecutionContext context) + private static bool IsClassWithAttribute(SyntaxNode node) { - var s = context.Compilation.GetMetadataReference(context.Compilation.Assembly); + return (node is ClassDeclarationSyntax classDeclaration && + classDeclaration.AttributeLists.Count > 0) || (node is StructDeclarationSyntax structDeclaration && + structDeclaration.AttributeLists.Count > 0); + } - if (context.SyntaxReceiver is PackageSyntaxReceiver receiver) + private static INamedTypeSymbol? GetClassSymbol(GeneratorSyntaxContext context) + { + var model = context.SemanticModel; + var classSymbol = model.GetDeclaredSymbol(context.Node) as INamedTypeSymbol; + + // 检查是否包含GeneratorPackageAttribute + var hasAttribute = classSymbol?.GetAttributes() + .Any(ad => ad.AttributeClass?.ToDisplayString() == "TouchSocket.Core.GeneratorPackageAttribute"); + + return hasAttribute == true ? classSymbol : null; + } + + private void Execute(ImmutableArray classes, SourceProductionContext context) + { + foreach (var classSymbol in classes.Distinct(SymbolEqualityComparer.Default).Cast()) { - var builders = receiver - .GetPackageClassTypes(context.Compilation) - .Select(i => new PackageCodeBuilder(i, context)) - .Distinct(CodeBuilderEqualityComparer.Default); - //Debugger.Launch(); - foreach (var builder in builders) - { - if (builder.TryToSourceText(out var sourceText)) - { - var tree = CSharpSyntaxTree.ParseText(sourceText); - var root = tree.GetRoot().NormalizeWhitespace(); - var ret = root.ToFullString(); - context.AddSource($"{builder.GetFileName()}.g.cs", ret); - - - } - } + var builder = new PackageCodeBuilder(classSymbol, context); + context.AddSource(builder); } } } \ No newline at end of file diff --git a/src/TouchSocket.Core.SourceGenerator/_Package/PackageSyntaxReceiver.cs b/src/TouchSocket.Core.SourceGenerator/_Package/PackageSyntaxReceiver.cs index f9476e780..67626f20d 100644 --- a/src/TouchSocket.Core.SourceGenerator/_Package/PackageSyntaxReceiver.cs +++ b/src/TouchSocket.Core.SourceGenerator/_Package/PackageSyntaxReceiver.cs @@ -1,94 +1,92 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using System.Collections.Generic; +//using Microsoft.CodeAnalysis; +//using Microsoft.CodeAnalysis.CSharp; +//using Microsoft.CodeAnalysis.CSharp.Syntax; +//using System.Collections.Generic; -namespace TouchSocket; +//namespace TouchSocket; -internal sealed class PackageSyntaxReceiver : ISyntaxReceiver -{ - public const string GeneratorPackageAttributeTypeName = "TouchSocket.Core.GeneratorPackageAttribute"; - public const string IPackageTypeName = "TouchSocket.Core.IPackage"; +//internal sealed class PackageSyntaxReceiver : ISyntaxReceiver +//{ - public static INamedTypeSymbol GeneratorPackageAttribute { get; private set; } +// public static INamedTypeSymbol GeneratorPackageAttribute { get; private set; } - /// - /// 接口列表 - /// - private readonly List m_classSyntaxList = new List(); +// /// +// /// 接口列表 +// /// +// private readonly List m_classSyntaxList = new List(); - /// - /// 访问语法树 - /// - /// - void ISyntaxReceiver.OnVisitSyntaxNode(SyntaxNode syntaxNode) - { - if (syntaxNode is ClassDeclarationSyntax syntax) - { - this.m_classSyntaxList.Add(syntax); - } - else if (syntaxNode is StructDeclarationSyntax @struct) - { - this.m_classSyntaxList.Add(@struct); - } - } +// /// +// /// 访问语法树 +// /// +// /// +// void ISyntaxReceiver.OnVisitSyntaxNode(SyntaxNode syntaxNode) +// { +// if (syntaxNode is ClassDeclarationSyntax syntax) +// { +// this.m_classSyntaxList.Add(syntax); +// } +// else if (syntaxNode is StructDeclarationSyntax @struct) +// { +// this.m_classSyntaxList.Add(@struct); +// } +// } - /// - /// 获取所有Package符号 - /// - /// - /// - public IEnumerable GetPackageClassTypes(Compilation compilation) - { - // Debugger.Launch(); - GeneratorPackageAttribute = compilation.GetTypeByMetadataName(GeneratorPackageAttributeTypeName); - if (GeneratorPackageAttribute == null) - { - yield break; - } - foreach (var classSyntax in this.m_classSyntaxList) - { - var @class = compilation.GetSemanticModel(classSyntax.SyntaxTree).GetDeclaredSymbol(classSyntax); - if (@class != null && IsPackageClass(@class)) - { - yield return @class; - } - } - } +// /// +// /// 获取所有Package符号 +// /// +// /// +// /// +// public IEnumerable GetPackageClassTypes(Compilation compilation) +// { +// // Debugger.Launch(); +// GeneratorPackageAttribute = compilation.GetTypeByMetadataName(GeneratorPackageAttributeTypeName); +// if (GeneratorPackageAttribute == null) +// { +// yield break; +// } +// foreach (var classSyntax in this.m_classSyntaxList) +// { +// var @class = compilation.GetSemanticModel(classSyntax.SyntaxTree).GetDeclaredSymbol(classSyntax); +// if (@class != null && IsPackageClass(@class)) +// { +// yield return @class; +// } +// } +// } - /// - /// 是否为容器生成 - /// - /// - /// - public static bool IsPackageClass(INamedTypeSymbol @class) - { - if (GeneratorPackageAttribute is null) - { - return false; - } - //Debugger.Launch(); +// /// +// /// 是否为容器生成 +// /// +// /// +// /// +// public static bool IsPackageClass(INamedTypeSymbol @class) +// { +// if (GeneratorPackageAttribute is null) +// { +// return false; +// } +// //Debugger.Launch(); - if (!@class.HasAttribute(GeneratorPackageAttributeTypeName, out _)) - { - return false; - } - if (@class.IsInheritFrom(IPackageTypeName)) - { - return true; - } - return false; - } -} \ No newline at end of file +// if (!@class.HasAttribute(GeneratorPackageAttributeTypeName, out _)) +// { +// return false; +// } +// if (@class.IsInheritFrom(IPackageTypeName)) +// { +// return true; +// } +// return false; +// } +//} \ No newline at end of file diff --git a/src/TouchSocket.Core/AppMessage/AppMessageAttribute.cs b/src/TouchSocket.Core/AppMessage/AppMessageAttribute.cs index 1417de046..b4db651d8 100644 --- a/src/TouchSocket.Core/AppMessage/AppMessageAttribute.cs +++ b/src/TouchSocket.Core/AppMessage/AppMessageAttribute.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// @@ -31,7 +29,7 @@ public sealed class AppMessageAttribute : Attribute /// /// 构造函数 /// - /// + /// 可取消令箭 public AppMessageAttribute(string token) { this.Token = token; diff --git a/src/TouchSocket.Core/AppMessage/AppMessenger.cs b/src/TouchSocket.Core/AppMessage/AppMessenger.cs index 7ecafcc5c..18192f155 100644 --- a/src/TouchSocket.Core/AppMessage/AppMessenger.cs +++ b/src/TouchSocket.Core/AppMessage/AppMessenger.cs @@ -10,15 +10,12 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Threading.Tasks; namespace TouchSocket.Core; /// -/// 消息通知类。内部全为弱引用。 +/// 消息通知类。内部使用弱引用保存订阅者,避免强引用导致的内存泄漏。 /// public class AppMessenger { @@ -32,7 +29,7 @@ public class AppMessenger } /// - /// 默认单例实例 + /// 默认单例实例。 /// public static AppMessenger Default { @@ -43,16 +40,16 @@ public class AppMessenger } /// - /// 允许多广播注册 + /// 是否允许对同一 token 注册多个广播处理器, 表示允许。 /// public bool AllowMultiple { get; set; } + /// - /// 添加 + /// 向指定的 注册消息处理实例。 /// - /// - /// - /// + /// 消息标识符。 + /// 要注册的消息实例。 public void Add(string token, MessageInstance messageInstance) { if (this.m_tokenAndInstance.TryGetValue(token, out var value)) @@ -73,18 +70,19 @@ public class AppMessenger } } + /// - /// 判断能否触发该消息,意味着该消息是否已经注册。 + /// 判断指定的 是否可以发送消息(即是否已注册)。 /// - /// - /// + /// 消息标识符。 + /// 若已注册返回 ,否则返回 public bool CanSendMessage(string token) { return this.m_tokenAndInstance.ContainsKey(token); } /// - /// 清除所有消息 + /// 清除所有已注册的消息订阅。 /// public void Clear() { @@ -92,27 +90,27 @@ public class AppMessenger } /// - /// 获取所有消息 + /// 获取所有已注册的消息标识符集合。 /// - /// + /// 返回一个包含所有消息标识符的枚举。 public IEnumerable GetAllMessage() { return this.m_tokenAndInstance.Keys; } /// - /// 移除 + /// 移除指定 的所有订阅。 /// - /// + /// 消息标识符。 public void Remove(string token) { this.m_tokenAndInstance.TryRemove(token, out _); } /// - /// 按对象移除 + /// 按订阅对象移除其在所有 token 下的消息订阅。 /// - /// + /// 要移除的订阅对象。 public void Remove(IMessageObject messageObject) { var key = new List(); @@ -138,12 +136,13 @@ public class AppMessenger } } + /// - /// 发送消息 + /// 异步向指定的 广播消息,返回任务以便等待完成。 /// - /// - /// - /// + /// 消息标识符。 + /// 要传递的参数数组。 + /// 表示异步操作的任务。 public async Task SendAsync(string token, params object[] parameters) { if (this.m_tokenAndInstance.TryGetValue(token, out var list)) @@ -172,13 +171,12 @@ public class AppMessenger } /// - /// 发送消息,当多播时,只返回最后一个返回值 + /// 异步向指定的 发送消息并获取最后一个处理器的返回值。 /// - /// 返回值类型 - /// - /// - /// - /// + /// 期望的返回值类型。 + /// 消息标识符。 + /// 要传递的参数数组。 + /// 包含最后一个处理器返回值的异步任务;若未找到则返回默认值。 public async Task SendAsync(string token, params object[] parameters) { if (this.m_tokenAndInstance.TryGetValue(token, out var list)) diff --git a/src/TouchSocket.Core/AppMessage/AppMessengerExtensions.cs b/src/TouchSocket.Core/AppMessage/AppMessengerExtensions.cs index 899c304f9..88c01301f 100644 --- a/src/TouchSocket.Core/AppMessage/AppMessengerExtensions.cs +++ b/src/TouchSocket.Core/AppMessage/AppMessengerExtensions.cs @@ -10,21 +10,21 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; +using System.Diagnostics.CodeAnalysis; using System.Reflection; namespace TouchSocket.Core; /// -/// AppMessengerExtensions +/// 针对 的扩展方法,提供基于特性和反射的注册/注销辅助方法。 /// public static class AppMessengerExtensions { /// - /// 注册消息 + /// 将实现 的实例中标记有 的方法注册到 。 /// - /// - /// + /// 目标 实例。 + /// 实现了 的订阅对象。 public static void Register(this AppMessenger appMessenger, IMessageObject messageObject) { var methods = GetInstanceMethods(messageObject.GetType()); @@ -49,47 +49,35 @@ public static class AppMessengerExtensions } /// - /// 注册消息 + /// 将指定的 以给定的 注册到 。 /// - /// - /// - /// - /// - /// + /// 目标 实例。 + /// 承载方法的对象实例;静态方法时可为 。 + /// 消息标识符,可用于后续的发送或解绑。 + /// 要注册的方法的 。 + /// 当同一 token 已被注册且未允许多注册时抛出。 public static void Register(this AppMessenger appMessenger, IMessageObject messageObject, string token, MethodInfo methodInfo) { appMessenger.Add(token, new MessageInstance(methodInfo, messageObject)); } - ///// - ///// 注册消息 - ///// - ///// - ///// - ///// - ///// - ///// - ///// - //public static void Register(this AppMessenger appMessenger, Delegate func, string token = default) - //{ - // RegisterDelegate(appMessenger, token, func); - //} /// - /// 注册类的静态消息 + /// 注册类型 中标记为 的静态方法到 。 /// - /// + /// 包含静态消息方法的类型,必须实现 + /// 目标 实例。 public static void RegisterStatic(this AppMessenger appMessenger) where T : IMessageObject { RegisterStatic(appMessenger, typeof(T)); } /// - /// 注册类的静态消息 + /// 注册给定 中标记为 的静态方法到 。 /// - /// - /// - /// + /// 目标 实例。 + /// 要扫描静态消息方法的类型。 + /// 当某些不支持的成员类型出现时可能抛出。 public static void RegisterStatic(this AppMessenger appMessenger, Type type) { var methods = GetStaticMethods(type); @@ -114,32 +102,34 @@ public static class AppMessengerExtensions } /// - /// 卸载消息 + /// 注销指定对象在 中的所有消息订阅。 /// - /// - /// + /// 目标 实例。 + /// 要注销的订阅对象。 public static void Unregister(this AppMessenger appMessenger, IMessageObject messageObject) { appMessenger.Remove(messageObject); } /// - /// 移除注册 + /// 注销指定 的所有注册项。 /// - /// - /// - /// + /// 目标 实例。 + /// 要注销的消息标识符。 + /// 或空字符串时抛出。 public static void Unregister(this AppMessenger appMessenger, string token) { ThrowHelper.ThrowArgumentNullExceptionIfStringIsNullOrEmpty(token, nameof(token)); appMessenger.Remove(token); } + [UnconditionalSuppressMessage("Trimming", "IL2070:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "源生成器生成的代码在AOT环境中是安全的")] private static MethodInfo[] GetInstanceMethods(Type type) { return type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } + [UnconditionalSuppressMessage("Trimming", "IL2070:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "源生成器生成的代码在AOT环境中是安全的")] private static MethodInfo[] GetStaticMethods(Type type) { return type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); diff --git a/src/TouchSocket.Core/AppMessage/MessageInstance.cs b/src/TouchSocket.Core/AppMessage/MessageInstance.cs index bdebfec2d..e985d32fc 100644 --- a/src/TouchSocket.Core/AppMessage/MessageInstance.cs +++ b/src/TouchSocket.Core/AppMessage/MessageInstance.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Reflection; namespace TouchSocket.Core; diff --git a/src/TouchSocket.Core/BitConverter/BitAccessor.cs b/src/TouchSocket.Core/BitConverter/BitAccessor.cs new file mode 100644 index 000000000..66e8320f8 --- /dev/null +++ b/src/TouchSocket.Core/BitConverter/BitAccessor.cs @@ -0,0 +1,82 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace TouchSocket.Core; + +/// +/// 位访问器:用于对非托管类型 的位进行读取与设置。 +/// +/// 非托管类型。 +public readonly ref struct BitAccessor where T : unmanaged +{ + private readonly Span m_bytes; + + /// + /// 初始化 实例,允许对 类型的数据进行位访问。 + /// + /// 需要进行位访问的非托管类型数据引用。 + public BitAccessor(ref T data) + { + this.m_bytes = MemoryMarshal.AsBytes(EasyMemoryMarshal.CreateSpan(ref data, 1)); + } + + /// + /// 获取位总长度(单位:位)。 + /// + public int Length => this.m_bytes.Length * 8; + + /// + /// 读取指定索引处的位值。 + /// + /// 位索引(从 0 开始)。 + /// 若该位为 1,则返回 ;否则返回 + /// 当索引超出范围时抛出。 + public bool Get(int index) + { + var byteIndex = index / 8; + var bitInByte = index % 8; + if ((uint)byteIndex >= (uint)this.m_bytes.Length) + { + ThrowHelper.ThrowArgumentOutOfRangeException_MoreThan(nameof(index), index, this.Length); + } + return (this.m_bytes[byteIndex] & (1 << bitInByte)) != 0; + } + + /// + /// 将指定索引处的位设置为给定布尔值。 + /// + /// 位索引(从 0 开始)。 + /// 时置 1;为 时置 0。 + /// 当索引超出范围时抛出。 + public void Set(int index, bool value) + { + var byteIndex = index / 8; + var bitInByte = index % 8; + if ((uint)byteIndex >= (uint)this.m_bytes.Length) + { + ThrowHelper.ThrowArgumentOutOfRangeException_MoreThan(nameof(index), index, this.Length); + } + + if (value) + { + this.m_bytes[byteIndex] = (byte)(this.m_bytes[byteIndex] | (1 << bitInByte)); + } + else + { + this.m_bytes[byteIndex] = (byte)(this.m_bytes[byteIndex] & ~(1 << bitInByte)); + } + } +} \ No newline at end of file diff --git a/src/TouchSocket.Core/BitConverter/TouchSocketBitConverter.cs b/src/TouchSocket.Core/BitConverter/TouchSocketBitConverter.cs index ac03d5c60..e8f681c83 100644 --- a/src/TouchSocket.Core/BitConverter/TouchSocketBitConverter.cs +++ b/src/TouchSocket.Core/BitConverter/TouchSocketBitConverter.cs @@ -10,8 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using Newtonsoft.Json.Linq; -using System; +using System.Buffers; using System.Runtime.CompilerServices; namespace TouchSocket.Core; @@ -19,7 +18,7 @@ namespace TouchSocket.Core; /// /// 提供了与TouchSocket库相关的字节序列和对象之间的转换功能。 /// -public sealed partial class TouchSocketBitConverter +public sealed class TouchSocketBitConverter { /// /// 以大端 @@ -111,6 +110,7 @@ public sealed partial class TouchSocketBitConverter /// 字节序类型 /// 对应的字节交换器 /// 当字节序类型不支持时抛出 + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TouchSocketBitConverter GetBitConverter(EndianType endianType) { switch (endianType) @@ -129,6 +129,20 @@ public sealed partial class TouchSocketBitConverter } } + /// + /// 获取指定值的字节表示形式,返回只读内存。转换时会考虑当前实例的字节序设置。 + /// + /// 要转换的值的类型,必须是非托管类型。 + /// 要转换为字节数组的值。 + /// 表示该值的字节只读内存。 + public ReadOnlyMemory GetBytes(T value) where T : unmanaged + { + var size = Unsafe.SizeOf(); + var bytes = new byte[size]; + this.WriteBytes(bytes, value); + return bytes; + } + /// /// 判断当前字节序是否与系统字节序相同 /// @@ -156,7 +170,7 @@ public sealed partial class TouchSocketBitConverter } fixed (byte* p = &span[0]) { - if (this.IsSameOfSet()) + if (this.IsSameOfSet() || size == 1) { return Unsafe.Read(p); } @@ -199,113 +213,6 @@ public sealed partial class TouchSocketBitConverter } } - /// - /// 不安全地将字节引用转换为指定类型 - /// - /// 要转换成的类型 - /// 要转换的字节引用 - /// 转换后的值 - /// 当类型T不支持时抛出 - public unsafe T UnsafeTo(ref byte source) where T : unmanaged - { - var size = sizeof(T); - - fixed (byte* p = &source) - { - if (this.IsSameOfSet()) - { - return Unsafe.Read(p); - } - else - { - if (size == 2) - { - this.ByteTransDataFormat2_Net6(p); - var v = Unsafe.Read(p); - this.ByteTransDataFormat2_Net6(p); - return v; - } - else if (size == 4) - { - this.ByteTransDataFormat4_Net6(p); - var v = Unsafe.Read(p); - this.ByteTransDataFormat4_Net6(p); - return v; - } - else if (size == 8) - { - this.ByteTransDataFormat8_Net6(p); - var v = Unsafe.Read(p); - this.ByteTransDataFormat8_Net6(p); - return v; - } - else if (size == 16) - { - this.ByteTransDataFormat16_Net6(p); - var v = Unsafe.Read(p); - this.ByteTransDataFormat16_Net6(p); - return v; - } - else - { - ThrowHelper.ThrowNotSupportedException(size.ToString()); - return default; - } - } - } - } - - /// - /// 不安全地将值直接写入字节数组中。 - /// - /// 要写入的值的类型,必须是值类型。 - /// 指向字节数组的引用,从这里开始写入。 - /// 要写入的值。 - /// 写入的字节数。 - /// - /// 此方法用于在字节数组中直接插入结构,主要用于性能考虑。 - /// 它绕过了C#的类型安全性,因此使用时必须确保T是固定大小的值类型。 - /// 如果数据格式不匹配或大小不支持,将抛出异常。 - /// - public unsafe int UnsafeWriteBytes(ref byte source, T value) where T : unmanaged - { - // 获取要写入的值的类型大小。 - var size = sizeof(T); - - // 直接将值写入字节数组中。 - Unsafe.As(ref source) = value; - - // 如果当前数据格式不需要转换,则直接返回。 - if (!this.IsSameOfSet()) - { - // 根据值的大小选择不同的字节转换方法。 - if (size == 2) - { - this.ByteTransDataFormat2_Net6(ref source); - } - else if (size == 4) - { - this.ByteTransDataFormat4_Net6(ref source); - } - else if (size == 8) - { - this.ByteTransDataFormat8_Net6(ref source); - } - else if (size == 16) - { - this.ByteTransDataFormat16_Net6(ref source); - } - else - { - // 如果类型大小不支持,则抛出异常。 - ThrowHelper.ThrowNotSupportedException(size.ToString()); - } - } - - // 返回写入的字节数。 - return size; - } - /// /// 将指定值的字节表示形式写入到指定的字节跨度中。 /// @@ -321,6 +228,13 @@ public sealed partial class TouchSocketBitConverter ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(span.Length), span.Length, size); } Unsafe.As(ref span[0]) = value; + + if (size == 1) + { + // 对于单字节类型,不需要转换 + return size; + } + if (!this.IsSameOfSet()) { if (size == 2) @@ -348,890 +262,6 @@ public sealed partial class TouchSocketBitConverter return size; } - #region ushort - - /// - /// 将ushort类型值转换为字节数组。 - /// - /// 要转换的ushort类型值。 - /// 转换后的字节数组。 - public byte[] GetBytes(ushort value) - { - // 使用BitConverter将ushort值转换为字节数组。 - var bytes = BitConverter.GetBytes(value); - // 如果当前环境的字节序与目标字节序不同,则需要进行反转。 - if (!this.IsSameOfSet()) - { - // 反转字节数组,以匹配目标字节序。 - Array.Reverse(bytes); - } - - // 返回最终的字节数组。 - return bytes; - } - - /// - /// 转换为指定端模式的2字节转换为UInt16数据。 - /// - /// - /// - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ushort ToUInt16(byte[] buffer, int offset) - { - var len = buffer.Length - offset; - if (len < 2) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(len), len, 2); - } - - unsafe - { - fixed (byte* p = &buffer[offset]) - { - if (this.IsSameOfSet()) - { - return Unsafe.Read(p); - } - else - { - this.ByteTransDataFormat2_Net6(ref buffer[offset]); - var v = Unsafe.Read(p); - this.ByteTransDataFormat2_Net6(ref buffer[offset]); - return v; - } - } - } - } - - #endregion ushort - - #region ulong - - /// - /// 将指定的ulong类型值转换为8字节的字节数组。 - /// 此方法主要用于处理数据转换,确保数据格式符合特定需求。 - /// - /// 需要转换的ulong类型值。 - /// 转换后的8字节字节数组。 - public byte[] GetBytes(ulong value) - { - // 使用系统方法将ulong值转换为字节数组。 - var bytes = BitConverter.GetBytes(value); - // 如果当前实例的数据格式与预设的不一致,则需要进行数据格式转换。 - if (!this.IsSameOfSet()) - { - // 调用私有方法对字节数组进行格式转换。 - bytes = this.ByteTransDataFormat8(bytes, 0); - } - - // 返回最终的字节数组。 - return bytes; - } - - /// - /// 将ulong类型值转换为指定端的8字节 - /// - /// 指向存放转换后字节的缓冲区的引用 - /// 需要转换的ulong类型值 - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void GetBytes(ref byte buffer, ulong value) - { - // 直接将字节缓冲区的首地址转换为ulong类型引用,并赋值,实现字节顺序的转换 - Unsafe.As(ref buffer) = value; - - // 如果当前实例的字节序与目标字节序不同,则需要进行字节顺序转换 - if (!this.IsSameOfSet()) - { - this.ByteTransDataFormat8_Net6(ref buffer); - } - } - - /// - /// 转换为指定端模式的Ulong数据。 - /// - /// 包含要转换数据的字节数组。 - /// 要转换数据的起始位置。 - /// 转换后的ulong数据。 - /// 如果offset参数导致转换的字节数不足8字节,则抛出此异常。 - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ulong ToUInt64(byte[] buffer, int offset) - { - // 检查字节数组的长度是否足够转换8字节的ulong数据 - if (buffer.Length - offset < 8) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - - unsafe - { - // 固定数组的起始地址,以便能够进行不安全的直接读取 - fixed (byte* p = &buffer[offset]) - { - // 如果是相同集合,则直接读取ulong数据 - if (this.IsSameOfSet()) - { - return Unsafe.Read(p); - } - else - { - // 否则,先转换字节顺序,然后读取ulong数据,最后恢复字节顺序 - this.ByteTransDataFormat8_Net6(ref buffer[offset]); - var v = Unsafe.Read(p); - this.ByteTransDataFormat8_Net6(ref buffer[offset]); - return v; - } - } - } - } - - #endregion ulong - - #region bool - - /// - /// 将布尔值转换为指定端1字节 - /// - /// 要转换的布尔值 - /// 转换后的字节数组 - public byte[] GetBytes(bool value) - { - // 使用BitConverter类的GetBytes方法将布尔值转换为字节数组 - return BitConverter.GetBytes(value); - } - - /// - /// 将布尔数组转为字节数组。不足位补0。 - /// - /// 待转换的布尔数组。 - /// 转换后的字节数组。 - public byte[] GetBytes(ReadOnlySpan values) - { - // 检查传入的布尔数组是否为空 - if (values.IsEmpty) - { - return []; - } - - // 计算所需的字节数组的长度,如果布尔数组的长度不是8的倍数,则向上取整 - var numArray = new byte[values.Length % 8 == 0 ? values.Length / 8 : (values.Length / 8) + 1]; - - // 遍历布尔数组,将布尔值转换为字节数组 - for (var index = 0; index < values.Length; ++index) - { - // 如果当前布尔值为true,则设置对应的字节位为1 - if (values[index]) - { - numArray[index / 8] = numArray[index / 8].SetBit(index % 8, true); - } - } - // 返回转换后的字节数组 - return numArray; - } - - /// - /// 将布尔值转换为指定端1字节 - /// - /// 指向用于存储转换结果的字节的引用 - /// 要转换的布尔值 - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void GetBytes(ref byte buffer, bool value) - { - // 使用不安全代码直接将byte引用转换为bool引用,并赋值 - Unsafe.As(ref buffer) = value; - } - - /// - /// 将布尔值数组转换为字节序列。 - /// - /// 指向目标字节缓冲区的引用。 - /// 待转换的布尔值数组。 - /// 如果values参数为,则抛出此异常。 - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe void GetBytes(ref byte buffer, ReadOnlySpan values) - { - // 检查输入的布尔值数组是否为空 - if (values.IsEmpty) - { - return; - } - // 使用fixed语句固定缓冲区的起始地址,以便直接操作内存 - fixed (byte* p = &buffer) - { - // 遍历布尔值数组 - for (var index = 0; index < values.Length; ++index) - { - // 如果当前布尔值为true,则设置对应位的值为1 - if (values[index]) - { - p[index / 8] = p[index / 8].SetBit(index % 8, true); - } - } - } - } - - /// - /// 转换为指定端模式的bool数据。 - /// - /// 包含转换为bool所需的字节的字节数组。 - /// 从buffer中的哪个位置开始读取字节的偏移量。 - /// 从指定的字节数组和偏移量位置转换得到的bool值。 - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool ToBoolean(byte[] buffer, int offset) - { - return BitConverter.ToBoolean(buffer, offset); - } - - /// - /// 将指定的字节,按位解析为bool数组。 - /// - /// 包含待解析位的字节数组。 - /// 在字节数组中开始解析的起始位置。 - /// 要解析的字节数。 - /// 返回一个bool数组,其中每个元素对应字节中的一个位。 - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool[] ToBooleansByBit(byte[] buffer, int offset, int length) - { - return this.ToBooleansByBit(new ReadOnlySpan(buffer, offset, length)); - } - - /// - /// 将指定的字节,按位解析为bool数组。 - /// - /// - /// 返回一个bool数组,其中每个元素对应字节中的一个位。 - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool[] ToBooleansByBit(ReadOnlySpan span) - { - var bools = new bool[8 * span.Length]; - this.ToBooleansByBit(span, bools); - return bools; - } - - /// - /// 将字节跨度按位解析为布尔值,并存储在布尔值跨度中。 - /// - /// 包含待解析字节的只读跨度。 - /// 存储解析结果的布尔值跨度。 - /// 解析的布尔值数量。 - /// 当布尔值跨度长度不足以存储解析结果时抛出。 - public int ToBooleansByBit(ReadOnlySpan byteSpan,Span boolSpan) - { - if (byteSpan.IsEmpty) - { - return 0; - } - var length = byteSpan.Length * 8; - - if (boolSpan.Length < length) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(boolSpan.Length), boolSpan.Length, length); - } - - for (var i = 0; i < boolSpan.Length; i++) - { - var byteIndex = i / 8; - var bitIndex = i % 8; - boolSpan[i] = byteSpan[byteIndex].GetBit(bitIndex); - } - return length; - } - - /// - /// 将指定的字节,按位解析为bool数组。 - /// - /// 指向待解析的字节缓冲区的引用。 - /// 要解析的字节数。 - /// 包含每个字节按位解析后的bool值的数组。 - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe bool[] ToBooleansByBit(ref byte buffer, int length) - { - fixed (byte* p=&buffer) - { - return this.ToBooleansByBit(new ReadOnlySpan(p, length)); - } - - } - #endregion bool - - #region char - - /// - /// 将指定字符转换为字节数组 - /// - /// 要转换的字符 - /// 字节数组 - public byte[] GetBytes(char value) - { - // 使用BitConverter将字符转换为字节数组 - var bytes = BitConverter.GetBytes(value); - // 如果当前环境的字节顺序与目标端序不一致,则翻转字节数组 - if (!this.IsSameOfSet()) - { - Array.Reverse(bytes); - } - - return bytes; - } - - /// - /// 将指定值转换为2字节,并存储到缓冲区中 - /// - /// 指向存储转换后字节的缓冲区的引用 - /// 要转换的字符值 - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void GetBytes(ref byte buffer, char value) - { - // 直接将字符值写入缓冲区,利用类型转换提高性能 - Unsafe.As(ref buffer) = value; - - // 如果当前格式与目标格式不同,则进行额外的格式转换处理 - if (!this.IsSameOfSet()) - { - this.ByteTransDataFormat2_Net6(ref buffer); - } - } - - /// - /// 转换为指定端模式的Char数据。 - /// - /// 包含要转换数据的字节数组。 - /// 要转换数据的起始位置。 - /// 转换后的Char数据。 - /// 如果offset参数导致无法转换出Char数据,则抛出此异常。 - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public char ToChar(byte[] buffer, int offset) - { - // 检查数组长度是否足够从offset位置读取2个字节,因为Char数据占2个字节。 - if (buffer.Length - offset < 2) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - - unsafe - { - // 使用固定指针直接访问数组中的字节,提高性能。 - fixed (byte* p = &buffer[offset]) - { - // 如果是统一的字节序模式,则直接读取数据。 - if (this.IsSameOfSet()) - { - return Unsafe.Read(p); - } - else - { - // 否则,先转换数组中的数据格式,读取后再次转换回来。 - this.ByteTransDataFormat2_Net6(ref buffer[offset]); - var v = Unsafe.Read(p); - this.ByteTransDataFormat2_Net6(ref buffer[offset]); - return v; - } - } - } - } - - #endregion char - - #region short - - /// - /// 将16位整数转换为指定字节序的字节数组。 - /// - /// 要转换的16位整数。 - /// 包含转换后字节的字节数组。 - public byte[] GetBytes(short value) - { - var bytes = BitConverter.GetBytes(value); - // 如果当前实例的字节序与设定的字节序不同,则需要反转字节数组。 - if (!this.IsSameOfSet()) - { - Array.Reverse(bytes); - } - - return bytes; - } - - /// - /// 将16位整数转换为指定字节序,并存储在指定的字节缓冲区中。 - /// - /// 目标字节缓冲区。 - /// 要转换的16位整数。 - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void GetBytes(ref byte buffer, short value) - { - // 使用不安全代码直接转换字节到16位整数。 - Unsafe.As(ref buffer) = value; - // 如果当前实例的字节序与设定的字节序不同,则需要转换字节顺序。 - if (!this.IsSameOfSet()) - { - this.ByteTransDataFormat2_Net6(ref buffer); - } - } - - /// - /// 从指定字节数组和偏移量处读取两个字节,并根据设定的字节序模式转换为16位整数。 - /// - /// 包含数据的字节数组。 - /// 从字节数组中的哪个位置开始读取。 - /// 转换得到的16位整数。 - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public short ToInt16(byte[] buffer, int offset) - { - // 检查字节数组的长度是否足够从offset开始读取两个字节。 - if (buffer.Length - offset < 2) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - - unsafe - { - fixed (byte* p = &buffer[offset]) - { - // 如果当前实例的字节序与设定的字节序相同,则直接读取。 - if (this.IsSameOfSet()) - { - return Unsafe.Read(p); - } - else - { - // 否则,需要先转换字节序,再读取,然后恢复原来的字节序。 - this.ByteTransDataFormat2_Net6(ref buffer[offset]); - var v = Unsafe.Read(p); - this.ByteTransDataFormat2_Net6(ref buffer[offset]); - return v; - } - } - } - } - - #endregion short - - #region int - - /// - /// 将整数转换为指定字节序的4字节数组。 - /// - /// 要转换的整数。 - /// 转换后的字节数组。 - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public byte[] GetBytes(int value) - { - var bytes = BitConverter.GetBytes(value); - if (!this.IsSameOfSet()) - { - bytes = this.ByteTransDataFormat4(bytes, 0); - } - - return bytes; - } - - /// - /// 将整数转换为指定字节序,并存储在给定的字节数组中。 - /// - /// 存储转换结果的字节数组。 - /// 要转换的整数。 - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void GetBytes(ref byte buffer, int value) - { - Unsafe.As(ref buffer) = value; - if (!this.IsSameOfSet()) - { - this.ByteTransDataFormat4_Net6(ref buffer); - } - } - - /// - /// 从字节数组中根据指定字节序转换为int。 - /// - /// 包含要转换数据的字节数组。 - /// 数据在字节数组中的起始位置。 - /// 转换得到的整数。 - /// 如果从offset开始不足4个字节,抛出此异常。 - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int ToInt32(byte[] buffer, int offset) - { - if (buffer.Length - offset < 4) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - - unsafe - { - fixed (byte* p = &buffer[offset]) - { - if (this.IsSameOfSet()) - { - return Unsafe.Read(p); - } - else - { - this.ByteTransDataFormat4_Net6(ref buffer[offset]); - var v = Unsafe.Read(p); - this.ByteTransDataFormat4_Net6(ref buffer[offset]); - return v; - } - } - } - } - - #endregion int - - #region long - - /// - /// 将长整型值转换为按指定端序排列的8字节数组。 - /// - /// 要转换的长整型值。 - /// 一个包含转换后的8字节的数组。 - public byte[] GetBytes(long value) - { - var bytes = BitConverter.GetBytes(value); - // 如果当前实例的端序与系统默认不同,则进行端序转换 - if (!this.IsSameOfSet()) - { - bytes = this.ByteTransDataFormat8(bytes, 0); - } - - return bytes; - } - - /// - /// 将长整型值转换为按指定端序排列的字节,并存储在缓冲区中。 - /// - /// 存储转换后数据的缓冲区。 - /// 要转换的长整型值。 - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void GetBytes(ref byte buffer, long value) - { - Unsafe.As(ref buffer) = value; - // 如果当前实例的端序与系统默认不同,则进行端序转换 - if (!this.IsSameOfSet()) - { - this.ByteTransDataFormat8_Net6(ref buffer); - } - } - - /// - /// 从字节数组中读取按指定端序排列的long值。 - /// - /// 包含数据的字节数组。 - /// 从数组的哪个位置开始读取数据。 - /// 读取到的长整型值。 - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public long ToInt64(byte[] buffer, int offset) - { - // 确保从offset开始的buffer长度至少为8字节 - if (buffer.Length - offset < 8) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - - unsafe - { - fixed (byte* p = &buffer[offset]) - { - // 如果当前实例的端序与系统默认相同,则直接读取数据 - if (this.IsSameOfSet()) - { - return Unsafe.Read(p); - } - else - { - // 否则,先进行端序转换,然后读取数据,并且再次转换以恢复原状 - this.ByteTransDataFormat8_Net6(ref buffer[offset]); - var v = Unsafe.Read(p); - this.ByteTransDataFormat8_Net6(ref buffer[offset]); - return v; - } - } - } - } - - #endregion long - - #region uint - - /// - /// 将无符号整数转换为指定字节序的4字节数组。 - /// - /// 要转换的无符号整数。 - /// 表示该无符号整数的4字节数组,按指定字节序排列。 - public byte[] GetBytes(uint value) - { - var bytes = BitConverter.GetBytes(value); - // 如果当前实例的字节序与系统默认不同,则进行字节序转换。 - if (!this.IsSameOfSet()) - { - bytes = this.ByteTransDataFormat4(bytes, 0); - } - - return bytes; - } - - /// - /// 将无符号整数转换为指定字节序,并存储在给定的字节数组中。 - /// - /// 存储转换结果的字节数组。 - /// 要转换的无符号整数。 - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void GetBytes(ref byte buffer, uint value) - { - // 直接将无符号整数转换为字节,并考虑字节序转换。 - Unsafe.As(ref buffer) = value; - if (!this.IsSameOfSet()) - { - this.ByteTransDataFormat4_Net6(ref buffer); - } - } - - /// - /// 从指定字节序的字节数组中转换出无符号整数。 - /// - /// 包含要转换数据的字节数组。 - /// 数据在字节数组中的起始位置。 - /// 转换得到的无符号整数。 - /// 如果从offset开始不足4个字节,则抛出此异常。 - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint ToUInt32(byte[] buffer, int offset) - { - // 确保从offset开始至少有4个字节。 - if (buffer.Length - offset < 4) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - - unsafe - { - fixed (byte* p = &buffer[offset]) - { - // 如果当前实例的字节序与系统默认相同,则直接读取。 - if (this.IsSameOfSet()) - { - return Unsafe.Read(p); - } - else - { - // 否则,进行字节序转换,读取数据,再转换回来。 - this.ByteTransDataFormat4_Net6(ref buffer[offset]); - var v = Unsafe.Read(p); - this.ByteTransDataFormat4_Net6(ref buffer[offset]); - return v; - } - } - } - } - - #endregion uint - - #region float - - /// - /// 将浮点数转换为指定字节序的4字节数组。 - /// - /// 要转换的浮点数。 - /// 转换后的字节数组。 - public byte[] GetBytes(float value) - { - var bytes = BitConverter.GetBytes(value); - // 如果当前字节序与目标字节序不同,则进行字节序转换。 - if (!this.IsSameOfSet()) - { - bytes = this.ByteTransDataFormat4(bytes, 0); - } - - return bytes; - } - - /// - /// 将浮点数转换为指定字节序的字节数组,并存储在指定缓冲区中。 - /// - /// 存储转换结果的缓冲区。 - /// 要转换的浮点数。 - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void GetBytes(ref byte buffer, float value) - { - // 直接将浮点数转换为字节,并应用字节序转换(如果需要)。 - Unsafe.As(ref buffer) = value; - if (!this.IsSameOfSet()) - { - this.ByteTransDataFormat4_Net6(ref buffer); - } - } - - /// - /// 从字节数组中按指定字节序读取浮点数。 - /// - /// 包含浮点数数据的字节数组。 - /// 数据在数组中的起始位置。 - /// 读取到的浮点数。 - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public float ToSingle(byte[] buffer, int offset) - { - // 检查数组长度是否足够。 - if (buffer.Length - offset < 4) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - - unsafe - { - fixed (byte* p = &buffer[offset]) - { - // 根据当前和目标字节序是否相同,决定是否需要进行字节序转换。 - if (this.IsSameOfSet()) - { - return Unsafe.Read(p); - } - else - { - this.ByteTransDataFormat4_Net6(ref buffer[offset]); - var v = Unsafe.Read(p); - this.ByteTransDataFormat4_Net6(ref buffer[offset]); - return v; - } - } - } - } - - #endregion float - - #region long - - /// - /// 将指定的双精度浮点数转换为对应字节端序的8字节数组。 - /// - /// 要转换的双精度浮点数。 - /// 对应的字节数组。 - public byte[] GetBytes(double value) - { - var bytes = BitConverter.GetBytes(value); - // 如果当前实例的字节序与系统默认不同,则进行字节序转换。 - if (!this.IsSameOfSet()) - { - bytes = this.ByteTransDataFormat8(bytes, 0); - } - - return bytes; - } - - /// - /// 将指定的双精度浮点数转换为对应字节端序的8字节,并存储在指定的字节数组中。 - /// - /// 用于存储转换结果的字节数组。 - /// 要转换的双精度浮点数。 - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void GetBytes(ref byte buffer, double value) - { - Unsafe.As(ref buffer) = value; - // 如果当前实例的字节序与系统默认不同,则进行字节序转换。 - if (!this.IsSameOfSet()) - { - this.ByteTransDataFormat8_Net6(ref buffer); - } - } - - /// - /// 将指定偏移量的字节数组转换为对应字节端序的双精度浮点数。 - /// - /// 包含数据的字节数组。 - /// 从数组的哪个位置开始读取数据。 - /// 转换得到的双精度浮点数。 - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public double ToDouble(byte[] buffer, int offset) - { - if (buffer.Length - offset < 8) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - - unsafe - { - fixed (byte* p = &buffer[offset]) - { - // 如果当前实例的字节序与系统默认相同,则直接读取数据。 - if (this.IsSameOfSet()) - { - return Unsafe.Read(p); - } - else - { - // 否则,先进行字节序转换,然后读取数据,并再次转换回来。 - this.ByteTransDataFormat8_Net6(ref buffer[offset]); - var v = Unsafe.Read(p); - this.ByteTransDataFormat8_Net6(ref buffer[offset]); - return v; - } - } - } - } - - #endregion long - - #region decimal - - /// - /// 将指定的 decimal 值转换为一个包含该值的字节数组。 - /// - /// 要转换的 decimal 值。 - /// 一个包含转换后的字节的字节数组。 - public byte[] GetBytes(decimal value) - { - var bytes = new byte[16]; - this.GetBytes(ref bytes[0], value); - return bytes; - } - - /// - /// 将指定的 decimal 值转换为一个包含该值的字节数组,并将结果存储在指定的缓冲区中。 - /// - /// 存储转换后的字节的缓冲区。 - /// 要转换的 decimal 值。 - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void GetBytes(ref byte buffer, decimal value) - { - Unsafe.As(ref buffer) = value; - if (!this.IsSameOfSet()) - { - this.ByteTransDataFormat16_Net6(ref buffer); - } - } - - /// - /// 从指定偏移量处的字节数组中读取一个 decimal 值。 - /// - /// 包含要读取的 decimal 值的字节数组。 - /// 从 buffer 的哪个位置开始读取。 - /// 从字节数组中读取到的 decimal 值。 - /// 如果从 offset 开始的 buffer 的长度小于 16,则抛出此异常。 - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public decimal ToDecimal(byte[] buffer, int offset) - { - if (buffer.Length - offset < 16) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - - unsafe - { - fixed (byte* p = &buffer[offset]) - { - if (this.IsSameOfSet()) - { - return Unsafe.Read(p); - } - else - { - this.ByteTransDataFormat16_Net6(ref buffer[offset]); - var v = Unsafe.Read(p); - this.ByteTransDataFormat16_Net6(ref buffer[offset]); - return v; - } - } - } - } - - #endregion decimal - #region Tool [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -1584,10 +614,188 @@ public sealed partial class TouchSocketBitConverter #endregion Tool - #region 其他 + #region Convert + + /// + /// 将指定类型的只读源数据批量转换为目标类型的只读内存,转换时会考虑默认字节序。 + /// + /// 支持所有非托管类型,包括 ,其中 会按位处理。 + /// + /// + /// 源类型,必须是非托管类型。 + /// 目标类型,必须是非托管类型。 + /// 要转换的源数据只读跨度。 + /// 转换后的目标类型只读内存。 + public static ReadOnlyMemory ConvertValues(ReadOnlySpan sourceSpan) + where TSource : unmanaged + where TTarget : unmanaged + { + return ConvertValues(sourceSpan, s_defaultEndianType); + } + + /// + /// 将指定类型的只读源数据批量转换为目标类型的只读内存,转换时会考虑指定字节序。 + /// + /// 支持所有非托管类型,包括 ,其中 会按位处理。 + /// + /// + /// 源类型,必须是非托管类型。 + /// 目标类型,必须是非托管类型。 + /// 要转换的源数据只读跨度。 + /// 指定的字节序类型。 + /// 转换后的目标类型只读内存。 + public static ReadOnlyMemory ConvertValues(ReadOnlySpan sourceSpan, EndianType endianType) + where TSource : unmanaged + where TTarget : unmanaged + { + return ConvertValues(sourceSpan, endianType, endianType); + } + + /// + /// 将指定类型的只读源数据批量转换为目标类型的只读内存,转换时会考虑源字节序和目标字节序。 + /// + /// 支持所有非托管类型,包括 ,其中 会按位处理。 + /// + /// + /// 源类型,必须是非托管类型。 + /// 目标类型,必须是非托管类型。 + /// 要转换的源数据只读跨度。 + /// 源数据的字节序类型。 + /// 目标数据的字节序类型。 + /// 转换后的目标类型只读内存。 + public static ReadOnlyMemory ConvertValues(ReadOnlySpan sourceSpan, EndianType sourceEndianType, EndianType targetEndianType) + where TSource : unmanaged + where TTarget : unmanaged + { + var length = GetConvertedLength(sourceSpan.Length); + Memory target = new TTarget[length]; + ConvertValues(sourceSpan, target.Span, sourceEndianType, targetEndianType); + return target; + } + + /// + /// 将指定类型的只读源数据批量转换为目标类型的可写跨度,转换时会考虑默认字节序。 + /// + /// 支持所有非托管类型,包括 ,其中 会按位处理。 + /// + /// + /// 源类型,必须是非托管类型。 + /// 目标类型,必须是非托管类型。 + /// 要转换的源数据只读跨度。 + /// 转换后的目标类型可写跨度。 + /// 实际转换的目标类型元素数量。 + public static int ConvertValues(ReadOnlySpan sourceSpan, Span targetSpan) + where TSource : unmanaged + where TTarget : unmanaged + { + return ConvertValues(sourceSpan, targetSpan, s_defaultEndianType); + } + + /// + /// 将指定类型的只读源数据批量转换为目标类型的可写跨度,转换时会考虑指定字节序。 + /// + /// 支持所有非托管类型,包括 ,其中 会按位处理。 + /// + /// + /// 源类型,必须是非托管类型。 + /// 目标类型,必须是非托管类型。 + /// 要转换的源数据只读跨度。 + /// 转换后的目标类型可写跨度。 + /// 指定的字节序类型。 + /// 实际转换的目标类型元素数量。 + public static int ConvertValues(ReadOnlySpan sourceSpan, Span targetSpan, EndianType endianType) + where TSource : unmanaged + where TTarget : unmanaged + { + return ConvertValues(sourceSpan, targetSpan, endianType, endianType); + } + + /// + /// 将指定类型的只读源数据批量转换为目标类型的可写跨度,转换时会考虑源字节序和目标字节序。 + /// + /// 支持所有非托管类型,包括 ,其中 会按位处理。 + /// + /// + /// 源类型,必须是非托管类型。 + /// 目标类型,必须是非托管类型。 + /// 要转换的源数据只读跨度。 + /// 转换后的目标类型可写跨度。 + /// 源数据的字节序类型。 + /// 目标数据的字节序类型。 + /// 实际转换的目标类型元素数量。 + public static int ConvertValues(ReadOnlySpan sourceSpan, Span targetSpan, EndianType sourceEndianType, EndianType targetEndianType) + where TSource : unmanaged + where TTarget : unmanaged + { + var sourceConverter = GetBitConverter(sourceEndianType); + var targetConverter = GetBitConverter(targetEndianType); + + int length; + // 如果源类型和目标类型相同,直接转换 + if (typeof(TSource) == typeof(TTarget)) + { + length = Math.Min(sourceSpan.Length, targetSpan.Length); + Span span = stackalloc byte[Unsafe.SizeOf()]; + for (var i = 0; i < length; i++) + { + sourceConverter.WriteBytes(span, sourceSpan[i]); + targetSpan[i] = targetConverter.To(span); + } + + return length; + } + + // 处理bool作为源类型的情况(按比特处理) + if (typeof(TSource) == typeof(bool)) + { + return ConvertFromBool(sourceSpan, targetSpan, targetConverter); + } + + // 处理bool作为目标类型的情况(按比特处理) + if (typeof(TTarget) == typeof(bool)) + { + return ConvertToBool(sourceSpan, targetSpan, sourceConverter); + } + + // 处理其他非bool类型的情况 + var sourceSize = Unsafe.SizeOf(); + var targetSize = Unsafe.SizeOf(); + var sourceBytes = sourceSpan.Length * sourceSize; + var targetBytes = targetSpan.Length * targetSize; + var bytesToConvert = Math.Min(sourceBytes, targetBytes); + + length = bytesToConvert / targetSize; + + var buffer = ArrayPool.Shared.Rent(bytesToConvert); + try + { + var writerSpan = new Span(buffer); + + for (var i = 0; i < bytesToConvert / sourceSize; i++) + { + var size = sourceConverter.WriteBytes(writerSpan, sourceSpan[i]); + writerSpan = writerSpan.Slice(size); + } + var span = new ReadOnlySpan(buffer); + for (var i = 0; i < length; i++) + { + targetSpan[i] = targetConverter.To(span); + span = span.Slice(targetSize); + } + } + finally + { + ArrayPool.Shared.Return(buffer); + } + + return length; + } /// /// 计算从源类型到目标类型的转换长度。 + /// + /// 注意:会被视为1位,即1/8字节,而其他非托管类型会按字节计算。 + /// /// /// 源类型,必须是非托管类型。 /// 目标类型,必须是非托管类型。 @@ -1610,7 +818,7 @@ public sealed partial class TouchSocketBitConverter else { // 其他类型按字节计算 - int sizeT1 = Unsafe.SizeOf(); + var sizeT1 = Unsafe.SizeOf(); sourceBits = (long)length * sizeT1 * 8; } @@ -1638,22 +846,167 @@ public sealed partial class TouchSocketBitConverter throw new InvalidOperationException("Target type cannot have zero size"); } - // 检查是否可整除 - if (sourceBits % targetBitSize != 0) - { - throw new ArgumentException( - $"Source bits ({sourceBits}) must be divisible by target type bit size ({targetBitSize})"); - } - // 计算目标长度并检查溢出 - var resultLength = sourceBits / targetBitSize; - if (resultLength > int.MaxValue) - { - throw new OverflowException($"Converted length ({resultLength}) exceeds maximum Span size"); - } - return (int)resultLength; + var resultLength = (sourceBits + (targetBitSize - 1)) / targetBitSize; + return resultLength > int.MaxValue + ? throw new OverflowException($"Converted length ({resultLength}) exceeds maximum Span size") + : (int)resultLength; } - #endregion + /// + /// 从bool源类型转换 + /// + private static int ConvertFromBool(ReadOnlySpan sourceSpan, Span targetSpan, TouchSocketBitConverter converter) + where TSource : unmanaged + where TTarget : unmanaged + { + var targetSize = Unsafe.SizeOf(); + + var byteCount = (sourceSpan.Length + 7) / 8; + + Span bytes = stackalloc byte[targetSize]; + var sourceIndex = 0; + var length = Math.Min(byteCount / targetSize, targetSpan.Length); + for (var i = 0; i < length; i++) + { + for (var j = 0; j < targetSize; j++) + { + byte value = 0; + TSource source; + + for (var k = 0; k < 8; k++) + { + if (sourceIndex < sourceSpan.Length) + { + source = sourceSpan[sourceIndex++]; + value = value.SetBit(k, Unsafe.As(ref source)); + } + else + { + value = value.SetBit(k, false); + } + } + + bytes[j] = value; + } + var target = converter.To(bytes); + targetSpan[i] = target; + } + + return length; + } + + /// + /// 转换为bool目标类型 + /// + private static int ConvertToBool(ReadOnlySpan sourceSpan, Span targetSpan, TouchSocketBitConverter converter) + where TSource : unmanaged + where TTarget : unmanaged + { + var sourceSize = Unsafe.SizeOf(); + var length = Math.Min(sourceSize * sourceSpan.Length, targetSpan.Length / 8); + + Span bytes = stackalloc byte[sourceSize]; + + var targetIndex = 0; + + for (var i = 0; i < length; i++) + { + var source = sourceSpan[i]; + converter.WriteBytes(bytes, source); + + for (var j = 0; j < bytes.Length; j++) + { + var sourceByte = bytes[j]; + var accessor = new BitAccessor(ref sourceByte); + + for (var bit = 0; bit < 8; bit++) + { + var b = accessor.Get(bit); + targetSpan[targetIndex] = Unsafe.As(ref b); + targetIndex++; + } + } + } + return targetIndex; + } + + #endregion Convert + + #region ToValues + + /// + /// 将指定的字节跨度批量转换为目标类型的只读内存,转换时会考虑指定字节序。 + /// + /// 支持所有非托管类型,包括 ,其中 会按位处理。 + /// + /// + /// 目标类型,必须是非托管类型。 + /// 要转换的字节跨度。 + /// 指定的字节序类型。 + /// 转换后的目标类型只读内存。 + public static ReadOnlyMemory ToValues(ReadOnlySpan span, EndianType endianType) + where T : unmanaged + { + var length = GetConvertedLength(span.Length); + if (length == 0) + { + return ReadOnlyMemory.Empty; + } + + Memory target = new T[length]; + ToValues(span, target.Span, endianType); + return target; + } + + /// + /// 将指定的字节跨度批量转换为目标类型的可写跨度,转换时会考虑指定字节序。 + /// + /// 支持所有非托管类型,包括 ,其中 会按位处理。 + /// + /// + /// 目标类型,必须是非托管类型。 + /// 要转换的字节跨度。 + /// 转换后的目标类型可写跨度。 + /// 指定的字节序类型。 + /// 实际转换的目标类型元素数量。 + public static int ToValues(ReadOnlySpan span, Span targetSpan, EndianType endianType) + where T : unmanaged + { + return ConvertValues(span, targetSpan, endianType, endianType); + } + + /// + /// 将指定的字节跨度批量转换为目标类型的可写跨度,转换时会考虑默认字节序。 + /// + /// 支持所有非托管类型,包括 ,其中 会按位处理。 + /// + /// + /// 目标类型,必须是非托管类型。 + /// 要转换的字节跨度。 + /// 转换后的目标类型可写跨度。 + /// 实际转换的目标类型元素数量。 + public static int ToValues(ReadOnlySpan span, Span targetSpan) + where T : unmanaged + { + return ToValues(span, targetSpan, s_defaultEndianType); + } + + /// + /// 将指定的字节跨度批量转换为目标类型的只读内存,转换时会考虑当前实例的字节序设置。 + /// + /// 支持所有非托管类型,包括 ,其中 会按位处理。 + /// + /// + /// 目标类型,必须是非托管类型。 + /// 要转换的字节跨度。 + /// 转换后的目标类型只读内存。 + public ReadOnlyMemory ToValues(ReadOnlySpan span) + where T : unmanaged + { + return ToValues(span, this.m_endianType); + } + + #endregion ToValues } \ No newline at end of file diff --git a/src/TouchSocket.Core/BytesPool/ByteBlock/ByteBlock.cs b/src/TouchSocket.Core/BytesPool/ByteBlock/ByteBlock.cs new file mode 100644 index 000000000..b1cb0428d --- /dev/null +++ b/src/TouchSocket.Core/BytesPool/ByteBlock/ByteBlock.cs @@ -0,0 +1,339 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.Buffers; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace TouchSocket.Core; + +/// +/// 表示一个字节块,提供高效的字节缓冲区操作,支持自动扩容和内存池管理。 +/// 实现了接口,线程安全。 +/// +/// +/// ByteBlock作为引用类型实现,适用于需要在多个方法间传递或长期持有的场景。 +/// 支持内存池管理、线程安全的释放操作、自动扩容、读写操作等功能。 +/// +[DebuggerDisplay("Length={Length},Position={Position},Capacity={Capacity}")] +public sealed class ByteBlock : IByteBlock +{ + #region Common + + private readonly Func> m_onRent; + private readonly Action> m_onReturn; + private int m_dis; + private bool m_holding; + private int m_length; + private Memory m_memory; + private short m_version; + + /// + /// 使用指定内存块初始化的新实例。 + /// + /// 要使用的内存块。 + public ByteBlock(Memory memory) + { + this.m_memory = memory; + } + + /// + /// 使用指定容量和内存管理委托初始化的新实例。 + /// + /// 初始容量,最小为1024字节。 + /// 内存租赁委托。 + /// 内存归还委托。 + public ByteBlock(int capacity, Func> onRent, Action> onReturn) + { + capacity = Math.Max(capacity, 1024); + this.m_memory = onRent(capacity); + this.m_onRent = onRent; + this.m_onReturn = onReturn; + this.m_length = 0; + } + + /// + /// 使用指定容量初始化的新实例,使用默认的进行内存管理。 + /// + /// 初始容量,最小为1024字节。 + public ByteBlock(int capacity) + { + capacity = Math.Max(capacity, 1024); + this.m_onRent = (c) => + { + return ArrayPool.Shared.Rent(c); + }; + this.m_onReturn = (m) => + { + if (MemoryMarshal.TryGetArray((ReadOnlyMemory)m, out var result)) + { + ArrayPool.Shared.Return(result.Array); + } + }; + + this.m_memory = this.m_onRent(capacity); + } + + /// + public long BytesRemaining => this.Length - this.Position; + + /// + public int CanReadLength => this.Length - this.Position; + + /// + public int Capacity => this.m_memory.Length; + + /// + public int FreeLength => this.Capacity - this.Position; + + /// + public int Length => this.m_length; + + /// + public ReadOnlyMemory Memory => this.m_memory.Slice(0, this.m_length); + + /// + public int Position { get; set; } + + /// + public ReadOnlySpan Span => this.Memory.Span; + + /// + public bool SupportsRewind => true; + + /// + public Memory TotalMemory => this.m_memory; + + /// + public bool Using => this.m_dis == 0; + + /// + public short Version => this.m_version; + + /// + long IBytesWriter.WrittenCount => this.Position; + + /// + public ReadOnlySequence TotalSequence => new ReadOnlySequence(this.Memory); + + /// + public ReadOnlySequence Sequence => this.TotalSequence.Slice(this.Position); + + /// + public long BytesRead { get => this.Position; set => this.Position = (int)value; } + + /// + public void Clear() + { + this.m_memory.Span.Clear(); + } + + /// + public void Dispose() + { + if (this.m_holding) + { + return; + } + + if (Interlocked.Increment(ref this.m_dis) == 1) + { + var memory = this.m_memory; + if (memory.IsEmpty) + { + return; + } + + this.m_memory = default; + this.m_length = 0; + this.Position = 0; + + this.m_onReturn?.Invoke(memory); + } + } + + /// + public void Reset() + { + this.Position = 0; + this.m_length = 0; + } + + /// + /// 将位置设置到数据末尾。 + /// + public void SeekToEnd() + { + this.Position = this.m_length; + } + + /// + /// 将位置设置到数据开头。 + /// + public void SeekToStart() + { + this.Position = 0; + } + + /// + /// 设置字节块的持有状态,防止在特定情况下被意外释放。 + /// + /// 如果为 ,则防止释放;如果为 ,则允许释放并立即调用 。 + /// + /// 当设置为 时,会立即调用 方法释放资源。 + /// + public void SetHolding(bool holding) + { + this.m_holding = holding; + if (!holding) + { + this.Dispose(); + } + } + + /// + /// 返回当前字节块的UTF-8字符串表示形式。 + /// + /// UTF-8编码的字符串。 + public override string ToString() + { + return this.Span.ToString(Encoding.UTF8); + } + #endregion Common + + #region Reader + + /// + public int Read(Span span) + { + if (span.IsEmpty) + { + return 0; + } + + var length = Math.Min(this.CanReadLength, span.Length); + + this.Span.Slice(this.Position, length).CopyTo(span); + this.Position += length; + return length; + } + + #endregion Reader + + #region Writer + + /// + public void Advance(int count) + { + this.Position += count; + this.m_length = this.Position > this.m_length ? this.Position : this.m_length; + } + + /// + /// 扩展内存块大小以满足指定的空间需求。 + /// + /// 需要的额外空间大小。 + /// 当未提供内存管理委托时抛出。 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ExtendSize(int size) + { + if (this.m_onRent == null || this.m_onReturn == null) + { + ThrowHelper.ThrowNotSupportedException("不支持扩容"); + return; + } + + if (this.FreeLength < size) + { + var need = this.Capacity + size - (this.Capacity - this.Position); + long lend = this.Capacity; + while (need > lend) + { + lend *= 2; + } + + if (lend > int.MaxValue) + { + lend = Math.Min(need + 1024 * 1024 * 100, int.MaxValue); + } + + + var bytes = this.m_onRent((int)lend); + + this.m_memory.CopyTo(bytes); + + this.m_onReturn(this.m_memory); + this.m_memory = bytes; + + this.m_version++; + } + } + + /// + public Memory GetMemory(int sizeHint = 0) + { + this.ExtendSize(sizeHint); + return this.m_memory.Slice(this.Position, this.FreeLength); + } + + /// + ReadOnlyMemory IBytesReader.GetMemory(int count) + { + return this.GetMemory(count).Slice(0, count); + } + + /// + public Span GetSpan(int sizeHint = 0) + { + return this.GetMemory(sizeHint).Span; + } + + /// + ReadOnlySpan IBytesReader.GetSpan(int count) + { + return this.GetSpan(count).Slice(0, count); + } + + /// + public void SetLength(int value) + { + if (value > this.m_memory.Length) + { + ThrowHelper.ThrowException("设置的长度超过了内存的长度。"); + } + this.m_length = value; + } + + /// + public void Write(scoped ReadOnlySpan span) + { + if (span.IsEmpty) + { + return; + } + + this.ExtendSize(span.Length); + + var currentSpan = this.GetCurrentSpan(); + span.CopyTo(currentSpan); + this.Position += span.Length; + this.m_length = Math.Max(this.Position, this.m_length); + } + + private Span GetCurrentSpan() + { + return this.m_memory.Span.Slice(this.Position); + } + + #endregion Writer +} \ No newline at end of file diff --git a/src/TouchSocket.Core/BytesPool/ByteBlock/IByteBlock.cs b/src/TouchSocket.Core/BytesPool/ByteBlock/IByteBlock.cs new file mode 100644 index 000000000..6cbc23c89 --- /dev/null +++ b/src/TouchSocket.Core/BytesPool/ByteBlock/IByteBlock.cs @@ -0,0 +1,46 @@ +//------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +//------------------------------------------------------------------------------ + +namespace TouchSocket.Core; + +/// +/// 表示字节块的接口,提供字节缓冲区的读写和管理功能。 +/// 继承自接口。 +/// +/// +/// IByteBlock接口结合了字节读取、写入和资源管理的功能,是字节块操作的核心接口。 +/// 实现此接口的类型应该提供完整的字节缓冲区管理能力。 +/// +public interface IByteBlock : IByteBlockReader, IByteBlockWriter, IDisposable +{ + /// + /// 获取一个值,该值指示字节块当前是否正在使用中。 + /// + /// 如果字节块正在使用中,则为 ;否则为 + bool Using { get; } + + /// + /// 清除字节块中的所有数据,将所有字节设置为零。 + /// + /// + /// 此方法不会改变字节块的容量或位置,只是清零数据内容。 + /// + void Clear(); + + /// + /// 重置字节块到初始状态,将位置和长度重置为零。 + /// + /// + /// 调用此方法后,字节块可以重新开始写入操作。 + /// + void Reset(); +} \ No newline at end of file diff --git a/src/TouchSocket.Core/BytesPool/ByteBlock/IByteBlockCore.cs b/src/TouchSocket.Core/BytesPool/ByteBlock/IByteBlockCore.cs new file mode 100644 index 000000000..44ec2ee6b --- /dev/null +++ b/src/TouchSocket.Core/BytesPool/ByteBlock/IByteBlockCore.cs @@ -0,0 +1,53 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +namespace TouchSocket.Core; + +/// +/// 表示字节块的核心接口,定义了字节块的基本属性和访问方式。 +/// +/// +/// 此接口提供了字节块的核心功能,包括长度信息、位置信息以及内存和跨度的访问方式。 +/// 是所有字节块相关接口的基础接口。 +/// +public interface IByteBlockCore +{ + /// + /// 获取字节块中有效数据的长度。 + /// + /// 表示字节块中包含的有效数据字节数。 + int Length { get; } + + /// + /// 获取字节块的只读内存表示形式。 + /// + /// 返回一个,表示字节块中从索引0到的有效数据。 + ReadOnlyMemory Memory { get; } + + /// + /// 获取或设置字节块中的当前位置。 + /// + /// 表示字节块中的当前读写位置,从0开始的索引。 + /// + /// 位置用于跟踪读取或写入操作的当前位置,通常在0到之间。 + /// + int Position { get; set; } + + /// + /// 获取字节块的只读跨度表示形式。 + /// + /// 返回一个,表示字节块中从索引0到的有效数据。 + /// + /// 跨度提供了对字节块数据的高性能访问方式,适用于需要直接操作字节数据的场景。 + /// + ReadOnlySpan Span { get; } +} \ No newline at end of file diff --git a/src/TouchSocket.Core/BytesPool/ByteBlock/ValueByteBlock.cs b/src/TouchSocket.Core/BytesPool/ByteBlock/ValueByteBlock.cs new file mode 100644 index 000000000..c68040763 --- /dev/null +++ b/src/TouchSocket.Core/BytesPool/ByteBlock/ValueByteBlock.cs @@ -0,0 +1,325 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.Buffers; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace TouchSocket.Core; + +/// +/// 表示一个值类型的字节块,提供高性能的字节缓冲区操作,避免堆分配开销。 +/// 实现了接口。 +/// +/// +/// ValueByteBlock作为值类型实现,适用于高频使用且对性能要求较高的场景。 +/// 支持内存池管理、自动扩容、读写操作等功能。 +/// 注意:由于是值类型,在多线程环境下使用时需要特别小心。 +/// +[DebuggerDisplay("Length={Length},Position={Position},Capacity={Capacity}")] +public struct ValueByteBlock : IByteBlock +{ + #region Common + private readonly Func> m_onRent; + private readonly Action> m_onReturn; + private bool m_dis; + private int m_length; + private Memory m_memory; + private short m_version; + + /// + /// 使用指定内存块初始化的新实例。 + /// + /// 要使用的内存块。 + public ValueByteBlock(Memory memory) + { + this.m_memory = memory; + } + + /// + /// 使用指定容量和内存管理委托初始化的新实例。 + /// + /// 初始容量,最小为1024字节。 + /// 内存租赁委托。 + /// 内存归还委托。 + public ValueByteBlock(int capacity, Func> onRent, Action> onReturn) + { + capacity = Math.Max(capacity, 1024); + this.m_memory = onRent(capacity); + this.m_onRent = onRent; + this.m_onReturn = onReturn; + this.m_length = 0; + } + + /// + /// 使用指定容量初始化的新实例,使用默认的进行内存管理。 + /// + /// 初始容量,最小为1024字节。 + public ValueByteBlock(int capacity) + { + capacity = Math.Max(capacity, 1024); + this.m_onRent = (c) => + { + return ArrayPool.Shared.Rent(c); + }; + this.m_onReturn = (m) => + { + if (MemoryMarshal.TryGetArray((ReadOnlyMemory)m, out var result)) + { + ArrayPool.Shared.Return(result.Array); + } + }; + + this.m_memory = this.m_onRent(capacity); + } + + /// + public long BytesRead { readonly get => this.Position; set => this.Position = (int)value; } + + /// + public readonly long BytesRemaining => this.Length - this.Position; + + /// + public readonly int CanReadLength => this.Length - this.Position; + + /// + public readonly int Capacity => this.m_memory.Length; + + /// + public readonly int FreeLength => this.Capacity - this.Position; + + /// + /// 获取一个值,该值指示内存块是否为空。 + /// + /// 如果内存块为空,则为 ;否则为 + public readonly bool IsEmpty => this.m_memory.IsEmpty; + + /// + public readonly int Length => this.m_length; + + /// + public readonly ReadOnlyMemory Memory => this.m_memory.Slice(0, this.m_length); + + /// + public int Position { get; set; } + + /// + public readonly ReadOnlySpan Span => this.Memory.Span; + + /// + public readonly bool SupportsRewind => true; + + /// + public readonly Memory TotalMemory => this.m_memory; + + /// + public readonly bool Using => !this.m_dis; + + /// + public readonly short Version => this.m_version; + + /// + public readonly ReadOnlySequence TotalSequence => new ReadOnlySequence(this.Memory); + + /// + public readonly ReadOnlySequence Sequence => this.TotalSequence.Slice(this.Position); + + /// + public readonly long WrittenCount => this.Position; + + /// + public readonly void Clear() + { + this.m_memory.Span.Clear(); + } + + /// + public void Dispose() + { + if (this.m_dis) + { + return; + } + this.m_dis = true; + var memory = this.m_memory; + if (memory.IsEmpty) + { + return; + } + + this.m_memory = default; + this.m_length = 0; + this.Position = 0; + + this.m_onReturn?.Invoke(memory); + } + + /// + public void Reset() + { + this.Position = 0; + this.m_length = 0; + } + + /// + /// 将位置设置到数据末尾。 + /// + public void SeekToEnd() + { + this.Position = this.m_length; + } + + /// + /// 将位置设置到数据开头。 + /// + public void SeekToStart() + { + this.Position = 0; + } + + /// + /// 返回当前字节块的UTF-8字符串表示形式。 + /// + /// UTF-8编码的字符串。 + public override readonly string ToString() + { + return this.Span.ToString(Encoding.UTF8); + } + #endregion + + #region Reader + + /// + public int Read(Span span) + { + if (span.IsEmpty) + { + return 0; + } + + var length = Math.Min(this.CanReadLength, span.Length); + + this.Span.Slice(this.Position, length).CopyTo(span); + this.Position += length; + return length; + } + + #endregion + + #region Writer + + /// + public void Advance(int count) + { + this.Position += count; + this.m_length = this.Position > this.m_length ? this.Position : this.m_length; + } + + /// + /// 扩展内存块大小以满足指定的空间需求。 + /// + /// 需要的额外空间大小。 + /// 当未提供内存管理委托时抛出。 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ExtendSize(int size) + { + if (this.m_onRent == null || this.m_onReturn == null) + { + ThrowHelper.ThrowNotSupportedException("不支持扩容"); + return; + } + + if (this.FreeLength < size) + { + var need = this.Capacity + size - (this.Capacity - this.Position); + long lend = this.Capacity; + while (need > lend) + { + lend *= 2; + } + + if (lend > int.MaxValue) + { + lend = Math.Min(need + 1024 * 1024 * 100, int.MaxValue); + } + + + var bytes = this.m_onRent((int)lend); + + this.m_memory.CopyTo(bytes); + + this.m_onReturn(this.m_memory); + this.m_memory = bytes; + + this.m_version++; + } + } + + /// + public Memory GetMemory(int sizeHint = 0) + { + this.ExtendSize(sizeHint); + return this.m_memory.Slice(this.Position, this.FreeLength); + } + + /// + ReadOnlyMemory IBytesReader.GetMemory(int count) + { + return this.GetMemory(count).Slice(0, count); + } + + /// + public Span GetSpan(int sizeHint = 0) + { + return this.GetMemory(sizeHint).Span; + } + + /// + ReadOnlySpan IBytesReader.GetSpan(int count) + { + return this.GetSpan(count).Slice(0, count); + } + + /// + public void SetLength(int value) + { + if (value > this.m_memory.Length) + { + ThrowHelper.ThrowException("设置的长度超过了内存的长度。"); + } + this.m_length = value; + } + + /// + public void Write(scoped ReadOnlySpan span) + { + if (span.IsEmpty) + { + return; + } + + this.ExtendSize(span.Length); + + var currentSpan = this.GetCurrentSpan(); + span.CopyTo(currentSpan); + this.Position += span.Length; + this.m_length = Math.Max(this.Position, this.m_length); + } + + private readonly Span GetCurrentSpan() + { + return this.m_memory.Span.Slice(this.Position); + } + #endregion +} + diff --git a/src/TouchSocket.Core/BytesPool/CircularBuffer.cs b/src/TouchSocket.Core/BytesPool/CircularBuffer.cs new file mode 100644 index 000000000..3bdd5e484 --- /dev/null +++ b/src/TouchSocket.Core/BytesPool/CircularBuffer.cs @@ -0,0 +1,267 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +namespace TouchSocket.Core; + +/// +/// 环形缓冲区,固定容量的读写操作。 +/// +/// 存储的元素类型 +public sealed class CircularBuffer +{ + private readonly T[] m_buffer; + private int m_dataCount; + private int m_readIndex; + private int m_writeIndex; + + /// + /// 创建指定容量的环形缓冲区。 + /// + /// 缓冲区容量,必须大于0 + public CircularBuffer(int capacity) + { + if (capacity <= 0) + { + throw new ArgumentException("容量必须大于0", nameof(capacity)); + } + + this.Capacity = capacity; + this.m_buffer = new T[capacity]; + this.m_readIndex = 0; + this.m_writeIndex = 0; + this.m_dataCount = 0; + } + + /// + /// 缓冲区总容量(最大可存放的元素数量)。 + /// + public int Capacity { get; } + + /// + /// 当前缓冲区中可读取的数据数量。 + /// + public int DataCount => this.m_dataCount; + + /// + /// 是否为空(无可读数据)。 + /// + public bool IsEmpty => this.m_dataCount == 0; + + /// + /// 是否已满(无法再写入数据)。 + /// + public bool IsFull => this.m_dataCount == this.Capacity; + + /// + /// 可用的剩余空间(可写入的元素数量)。 + /// + public int SpaceFree => this.Capacity - this.m_dataCount; + + /// + /// 按相对于当前读位置的偏移获取元素(不移动读指针)。 + /// + /// 相对读偏移索引,从 0 开始 + /// 指定位置的元素 + /// 当索引超出缓冲区数据范围时抛出 + public T this[int index] + { + get + { + if (index < 0 || index >= this.m_dataCount) + { + throw new IndexOutOfRangeException("索引超出缓冲区数据范围"); + } + + var actualIndex = (this.m_readIndex + index) % this.Capacity; + return this.m_buffer[actualIndex]; + } + } + + /// + /// 写入后推进写指针。 + /// + /// 写入数量 + public void AdvanceWrite(int count) + { + if (count < 0 || count > this.SpaceFree) + { + throw new ArgumentOutOfRangeException(nameof(count), "写入数量超出可用空间范围"); + } + this.m_writeIndex = (this.m_writeIndex + count) % this.Capacity; + this.m_dataCount += count; + } + + /// + /// 清空缓冲区,重置读写指针和数据计数。 + /// + public void Clear() + { + this.m_readIndex = 0; + this.m_writeIndex = 0; + this.m_dataCount = 0; + } + + /// + /// 获取可写入的内存块。 + /// + /// 可写入的内存块 + public Memory GetWriteMemory() + { + if (this.IsFull) + { + return Memory.Empty; + } + if (this.m_writeIndex >= this.m_readIndex) + { + var spaceAtEnd = this.Capacity - this.m_writeIndex; + if (spaceAtEnd >= this.SpaceFree) + { + return new Memory(this.m_buffer, this.m_writeIndex, this.SpaceFree); + } + else + { + return new Memory(this.m_buffer, this.m_writeIndex, spaceAtEnd); + } + } + else + { + var spaceAvailable = this.m_readIndex - this.m_writeIndex; + return new Memory(this.m_buffer, this.m_writeIndex, spaceAvailable); + } + } + + /// + /// 将缓冲区中的数据复制到目标 中,但不移动读指针。 + /// + /// 目标缓冲区 + /// 实际复制的元素数量 + public int Peek(Span span) + { + if (span.IsEmpty || this.m_dataCount == 0) + { + return 0; + } + + var toRead = Math.Min(span.Length, this.m_dataCount); + if (toRead == 0) + { + return 0; + } + + var bufferSpan = this.m_buffer.AsSpan(); + var dest = span.Slice(0, toRead); + var tempReadIndex = this.m_readIndex; + + if (tempReadIndex + toRead <= this.Capacity) + { + bufferSpan.Slice(tempReadIndex, toRead).CopyTo(dest); + } + else + { + var firstSegment = this.Capacity - tempReadIndex; + var secondSegment = toRead - firstSegment; + + bufferSpan.Slice(tempReadIndex, firstSegment).CopyTo(dest); + bufferSpan.Slice(0, secondSegment).CopyTo(dest.Slice(firstSegment)); + } + + return toRead; + } + + /// + /// 从缓冲区读取数据到目标 并移动读指针。 + /// + /// 目标缓冲区 + /// 实际读取的元素数量 + public int Read(Span span) + { + if (span.IsEmpty || this.m_dataCount == 0) + { + return 0; + } + + var toRead = Math.Min(span.Length, this.m_dataCount); + if (toRead == 0) + { + return 0; + } + + var bufferSpan = this.m_buffer.AsSpan(); + var dest = span.Slice(0, toRead); + + if (this.m_readIndex + toRead <= this.Capacity) + { + bufferSpan.Slice(this.m_readIndex, toRead).CopyTo(dest); + this.m_readIndex += toRead; + } + else + { + var firstSegment = this.Capacity - this.m_readIndex; + var secondSegment = toRead - firstSegment; + + bufferSpan.Slice(this.m_readIndex, firstSegment).CopyTo(dest); + bufferSpan.Slice(0, secondSegment).CopyTo(dest.Slice(firstSegment)); + + this.m_readIndex = secondSegment; + } + + this.m_dataCount -= toRead; + + if (this.m_dataCount == 0) + { + this.m_readIndex = this.m_writeIndex = 0; + } + + return toRead; + } + + /// + /// 将数据从源 写入缓冲区。 + /// + /// 源数据 + /// 实际写入的元素数量 + public int Write(ReadOnlySpan span) + { + if (span.IsEmpty) + { + return 0; + } + + var toWrite = Math.Min(span.Length, this.SpaceFree); + if (toWrite == 0) + { + return 0; + } + + var bufferSpan = this.m_buffer.AsSpan(); + var src = span.Slice(0, toWrite); + + if (this.m_writeIndex + toWrite <= this.Capacity) + { + src.CopyTo(bufferSpan.Slice(this.m_writeIndex, toWrite)); + this.m_writeIndex += toWrite; + } + else + { + var firstSegment = this.Capacity - this.m_writeIndex; + var secondSegment = toWrite - firstSegment; + + src.Slice(0, firstSegment).CopyTo(bufferSpan.Slice(this.m_writeIndex, firstSegment)); + src.Slice(firstSegment, secondSegment).CopyTo(bufferSpan.Slice(0, secondSegment)); + + this.m_writeIndex = secondSegment; + } + + this.m_dataCount += toWrite; + return toWrite; + } +} \ No newline at end of file diff --git a/src/TouchSocket.Core/BytesPool/ContiguousMemoryBuffer.cs b/src/TouchSocket.Core/BytesPool/ContiguousMemoryBuffer.cs new file mode 100644 index 000000000..40ec6a593 --- /dev/null +++ b/src/TouchSocket.Core/BytesPool/ContiguousMemoryBuffer.cs @@ -0,0 +1,105 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System; +using System.Buffers; +using System.Runtime.InteropServices; + +namespace TouchSocket.Core; + +/// +/// 表示一个连续内存缓冲区,用于将转换为连续的内存块。 +/// 实现了接口以支持资源释放。 +/// +/// +/// 此结构体用于优化多段序列内存的访问性能。如果输入序列已经是单段内存,则直接使用原始内存; +/// 如果是多段内存,则从内存池中租用缓冲区并将所有段复制到连续内存中。 +/// 使用后应调用方法释放可能租用的内存资源。 +/// +public readonly struct ContiguousMemoryBuffer : IDisposable +{ + private readonly ReadOnlyMemory m_memory; + private readonly IMemoryOwner m_memoryOwner; + + /// + /// 使用指定的只读字节序列初始化的新实例。 + /// + /// 要转换为连续内存的只读字节序列。 + /// + /// 如果序列是单段内存,则直接使用原始内存;如果是多段内存,则从 + /// 租用缓冲区并复制所有段的数据。 + /// + public ContiguousMemoryBuffer(ReadOnlySequence sequence) + { + if (sequence.IsEmpty) + { + this.m_memory = default; + this.m_memoryOwner = null; + return; + } + + if (sequence.IsSingleSegment) + { + this.m_memory = SequenceMarshal.TryGetReadOnlyMemory(sequence, out var memory) + ? memory + : sequence.First; + this.m_memoryOwner = null; + return; + } + + var totalLength = (int)sequence.Length; + this.m_memoryOwner = MemoryPool.Shared.Rent(totalLength); + + var destination = this.m_memoryOwner.Memory.Span; + sequence.CopyTo(destination); + + this.m_memory = this.m_memoryOwner.Memory.Slice(0, totalLength); + } + + /// + /// 获取一个值,该值指示是否从内存池租用了新缓冲区。 + /// + /// 如果从内存池租用了缓冲区,则为 ;否则为 + /// + /// 当输入序列是多段内存时,此属性返回 ; + /// 当输入序列是单段内存时,此属性返回 。 + /// + public bool IsRentedFromPool => this.m_memoryOwner != null; + + /// + /// 获取连续的内存块。 + /// + /// 表示连续内存数据的 + /// + /// 返回的内存块保证是连续的,可以安全地用于需要连续内存的操作。 + /// 使用完毕后需要调用方法释放可能租用的资源。 + /// + public ReadOnlyMemory Memory => this.m_memory; + + /// + /// 获取一个值,该值指示当前缓冲区是否为空。 + /// + /// 如果缓冲区为空,则为 ;否则为 + public bool IsEmpty => this.m_memory.IsEmpty; + + /// + /// 获取缓冲区的长度(字节数)。 + /// + /// 缓冲区中的字节数。 + public int Length => this.m_memory.Length; + + /// + public void Dispose() + { + this.m_memoryOwner?.Dispose(); + } +} \ No newline at end of file diff --git a/src/TouchSocket.Core/Pool/ByteBlockBuilderExtension.cs b/src/TouchSocket.Core/BytesPool/Extensions/ByteBlockBuilderExtension.cs similarity index 58% rename from src/TouchSocket.Core/Pool/ByteBlockBuilderExtension.cs rename to src/TouchSocket.Core/BytesPool/Extensions/ByteBlockBuilderExtension.cs index 86cea7d35..7534665be 100644 --- a/src/TouchSocket.Core/Pool/ByteBlockBuilderExtension.cs +++ b/src/TouchSocket.Core/BytesPool/Extensions/ByteBlockBuilderExtension.cs @@ -17,14 +17,51 @@ namespace TouchSocket.Core; /// public static class ByteBlockBuilderExtension { + + #region Extension + //extension(ref T config) + // where T : IBytesReader + //{ + + // public int Available { get => 1; init => value = 10; } + //} + //extension(TouchSocketConfig config) + //{ + + // public int Available3 { get => 1; set => value = 10; } + //} + #endregion /// /// 扩展方法,用于构建给定的字节块。 /// 该方法通过引用传递字节块,以利用ref参数提高性能,避免不必要的复制。 /// /// 实现IByteBlockBuilder接口的构建器对象。 /// 要构建的字节块对象。 - public static void Build(this IByteBlockBuilder builder, ByteBlock byteBlock) + public static void Build(this IBytesBuilder builder, ByteBlock byteBlock) { builder.Build(ref byteBlock); } + + #region BuildAsBytes + + /// + /// 将对象构建到字节数组 + /// + /// + /// + public static ReadOnlyMemory BuildAsBytes(this IBytesBuilder builder) + { + var byteBlock = new ByteBlock(builder.MaxLength); + try + { + builder.Build(ref byteBlock); + return byteBlock.ToArray(); + } + finally + { + byteBlock.Dispose(); + } + } + + #endregion BuildAsBytes } \ No newline at end of file diff --git a/src/TouchSocket.Core/BytesPool/Extensions/ByteBlockExtension.cs b/src/TouchSocket.Core/BytesPool/Extensions/ByteBlockExtension.cs new file mode 100644 index 000000000..6199d5d74 --- /dev/null +++ b/src/TouchSocket.Core/BytesPool/Extensions/ByteBlockExtension.cs @@ -0,0 +1,789 @@ +//------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +//------------------------------------------------------------------------------ + +namespace TouchSocket.Core; + +/// +/// 提供字节块扩展方法的静态类。 +/// +/// +/// 此类为字节块类型提供了丰富的扩展方法,包括类型转换、数据读写、数组操作等功能。 +/// 支持与等字节块类型的操作。 +/// +public static class ByteBlockExtension +{ + /// + /// 将值类型的字节块转换为普通的字节块。 + /// + /// 要转换的值类型字节块。 + /// 一个新的对象。 + /// + /// 此方法会创建一个新的字节块对象,并复制值类型字节块的数据和状态。 + /// 包括位置、长度等信息都会被保留。 + /// + public static ByteBlock AsByteBlock(this in ValueByteBlock valueByteBlock) + { + var byteBlock = new ByteBlock(valueByteBlock.TotalMemory.Slice(0, valueByteBlock.Length)); + byteBlock.Position = valueByteBlock.Position; + byteBlock.SetLength(valueByteBlock.Length); + return byteBlock; + } + + /// + /// 将字节块转换为字节块流。 + /// + /// 要转换的字节块。 + /// 是否在释放字节块时一起释放关联的资源,默认为。 + /// 一个新的对象,具体为实例。 + /// + /// 此方法将字节块包装为流对象,使其能够与标准的API兼容。 + /// 当时,流被释放时会同时释放底层的字节块。 + /// + public static Stream AsStream(this ByteBlock byteBlock, bool releaseTogether = true) + { + return new ByteBlockStream(byteBlock, releaseTogether); + } + + #region ToArray + + /// + /// 将指定的字节块转换为【新】字节数组。 + /// + /// 实现接口的字节块类型。 + /// 字节块对象。 + /// 起始偏移量。 + /// 要转换为数组的长度。 + /// 包含指定范围数据的【新】字节数组。 + /// + /// 此方法会创建一个新的字节数组,从字节块的指定位置复制数据。 + /// 原始字节块的内容不会被修改。 + /// + public static byte[] ToArray(this TByteBlock byteBlock, int offset, int length) where TByteBlock : IByteBlockCore + { + return byteBlock.Span.Slice(offset, length).ToArray(); + } + + /// + /// 将指定的字节块转换为【新】字节数组,从指定偏移量开始,直到字节块的末尾。 + /// + /// 实现接口的字节块类型。 + /// 字节块对象。 + /// 起始偏移量。 + /// 从指定偏移量到字节块末尾的【新】字节数组。 + public static byte[] ToArray(this TByteBlock byteBlock, int offset) where TByteBlock : IByteBlockCore + { + return ToArray(byteBlock, offset, byteBlock.Length - offset); + } + + /// + /// 将指定的字节块转换为【新】字节数组,从索引0开始,直到字节块的末尾。 + /// + /// 实现接口的字节块类型。 + /// 字节块对象。 + /// 整个字节块的【新】字节数组。 + public static byte[] ToArray(this TByteBlock byteBlock) where TByteBlock : IByteBlockCore + { + return ToArray(byteBlock, 0, byteBlock.Length); + } + + /// + /// 将指定的字节块从当前位置转换为【新】字节数组,直到字节块的末尾。 + /// + /// 实现接口的字节块类型。 + /// 字节块对象。 + /// 从当前位置到字节块末尾的【新】字节数组。 + /// + /// 此方法基于字节块的当前位置进行数组转换,适用于读取操作后获取剩余数据的场景。 + /// + public static byte[] ToArrayTake(this TByteBlock byteBlock) where TByteBlock : IByteBlockReader + { + return ToArray(byteBlock, byteBlock.Position, byteBlock.CanReadLength); + } + + /// + /// 将指定的字节块从当前位置转换为【新】字节数组,指定长度。 + /// + /// 实现接口的字节块类型。 + /// 字节块对象。 + /// 要转换为数组的长度。 + /// 从当前位置开始,指定长度的【新】字节数组。 + public static byte[] ToArrayTake(this TByteBlock byteBlock, int length) where TByteBlock : IByteBlockCore + { + return ToArray(byteBlock, byteBlock.Position, length); + } + + #endregion ToArray + + #region Write + + /// + /// 向字节块写入一个字节值。 + /// + /// 要写入的字节块。 + /// 要写入的字节值。 + public static void WriteByte(this ByteBlock byteBlock, byte value) + { + WriterExtension.WriteValue(ref byteBlock, value); + } + + /// + /// 向字节块写入一个日期时间值。 + /// + /// 要写入的字节块。 + /// 要写入的值。 + public static void WriteDateTime(this ByteBlock byteBlock, DateTime value) + { + WriterExtension.WriteValue(ref byteBlock, value); + } + + /// + /// 向字节块写入一个十进制数值。 + /// + /// 要写入的字节块。 + /// 要写入的值。 + public static void WriteDecimal(this ByteBlock byteBlock, decimal value) + { + WriterExtension.WriteValue(ref byteBlock, value); + } + + /// + /// 向字节块写入一个十进制数值,指定字节序。 + /// + /// 要写入的字节块。 + /// 要写入的值。 + /// 字节序类型。 + public static void WriteDecimal(this ByteBlock byteBlock, decimal value, EndianType endianType) + { + WriterExtension.WriteValue(ref byteBlock, value, endianType); + } + + /// + /// 向字节块写入一个双精度浮点数值。 + /// + /// 要写入的字节块。 + /// 要写入的值。 + public static void WriteDouble(this ByteBlock byteBlock, double value) + { + WriterExtension.WriteValue(ref byteBlock, value); + } + + /// + /// 向字节块写入一个双精度浮点数值,指定字节序。 + /// + /// 要写入的字节块。 + /// 要写入的值。 + /// 字节序类型。 + public static void WriteDouble(this ByteBlock byteBlock, double value, EndianType endianType) + { + WriterExtension.WriteValue(ref byteBlock, value, endianType); + } + + /// + /// 向字节块写入一个单精度浮点数值。 + /// + /// 要写入的字节块。 + /// 要写入的值。 + public static void WriteFloat(this ByteBlock byteBlock, float value) + { + WriterExtension.WriteValue(ref byteBlock, value); + } + + /// + /// 向字节块写入一个单精度浮点数值,指定字节序。 + /// + /// 要写入的字节块。 + /// 要写入的值。 + /// 字节序类型。 + public static void WriteFloat(this ByteBlock byteBlock, float value, EndianType endianType) + { + WriterExtension.WriteValue(ref byteBlock, value, endianType); + } + + /// + /// 向字节块写入一个全局唯一标识符。 + /// + /// 要写入的字节块。 + /// 要写入的值。 + public static void WriteGuid(this ByteBlock byteBlock, Guid value) + { + WriterExtension.WriteValue(ref byteBlock, value); + } + + /// + /// 向字节块写入一个16位有符号整数。 + /// + /// 要写入的字节块。 + /// 要写入的值。 + public static void WriteInt16(this ByteBlock byteBlock, short value) + { + WriterExtension.WriteValue(ref byteBlock, value); + } + + /// + /// 向字节块写入一个16位有符号整数,指定字节序。 + /// + /// 要写入的字节块。 + /// 要写入的值。 + /// 字节序类型。 + public static void WriteInt16(this ByteBlock byteBlock, short value, EndianType endianType) + { + WriterExtension.WriteValue(ref byteBlock, value, endianType); + } + + /// + /// 向字节块写入一个32位有符号整数。 + /// + /// 要写入的字节块。 + /// 要写入的值。 + public static void WriteInt32(this ByteBlock byteBlock, int value) + { + WriterExtension.WriteValue(ref byteBlock, value); + } + + /// + /// 向字节块写入一个32位有符号整数,指定字节序。 + /// + /// 要写入的字节块。 + /// 要写入的值。 + /// 字节序类型。 + public static void WriteInt32(this ByteBlock byteBlock, int value, EndianType endianType) + { + WriterExtension.WriteValue(ref byteBlock, value, endianType); + } + + /// + /// 向字节块写入一个64位有符号整数。 + /// + /// 要写入的字节块。 + /// 要写入的值。 + public static void WriteInt64(this ByteBlock byteBlock, long value) + { + WriterExtension.WriteValue(ref byteBlock, value); + } + + /// + /// 向字节块写入一个64位有符号整数,指定字节序。 + /// + /// 要写入的字节块。 + /// 要写入的值。 + /// 字节序类型。 + public static void WriteInt64(this ByteBlock byteBlock, long value, EndianType endianType) + { + WriterExtension.WriteValue(ref byteBlock, value, endianType); + } + + /// + /// 向字节块写入一个8位有符号整数。 + /// + /// 要写入的字节块。 + /// 要写入的值。 + public static void WriteSByte(this ByteBlock byteBlock, sbyte value) + { + WriterExtension.WriteValue(ref byteBlock, value); + } + + /// + /// 向字节块写入一个字符串。 + /// + /// 要写入的字节块。 + /// 要写入的字符串值。 + public static void WriteString(this ByteBlock byteBlock, string value) + { + WriterExtension.WriteString(ref byteBlock, value); + } + + /// + /// 向字节块写入一个字符串,使用指定的固定头部类型。 + /// + /// 要写入的字节块。 + /// 要写入的字符串值。 + /// 固定头部类型,用于指示字符串长度的编码方式。 + public static void WriteString(this ByteBlock byteBlock, string value, FixedHeaderType headerType) + { + WriterExtension.WriteString(ref byteBlock, value, headerType); + } + + /// + /// 向字节块写入一个时间跨度值。 + /// + /// 要写入的字节块。 + /// 要写入的值。 + public static void WriteTimeSpan(this ByteBlock byteBlock, TimeSpan value) + { + WriterExtension.WriteValue(ref byteBlock, value); + } + + /// + /// 向字节块写入一个16位无符号整数。 + /// + /// 要写入的字节块。 + /// 要写入的值。 + public static void WriteUInt16(this ByteBlock byteBlock, ushort value) + { + WriterExtension.WriteValue(ref byteBlock, value); + } + + /// + /// 向字节块写入一个16位无符号整数,指定字节序。 + /// + /// 要写入的字节块。 + /// 要写入的值。 + /// 字节序类型。 + public static void WriteUInt16(this ByteBlock byteBlock, ushort value, EndianType endianType) + { + WriterExtension.WriteValue(ref byteBlock, value, endianType); + } + + /// + /// 向字节块写入一个32位无符号整数。 + /// + /// 要写入的字节块。 + /// 要写入的值。 + public static void WriteUInt32(this ByteBlock byteBlock, uint value) + { + WriterExtension.WriteValue(ref byteBlock, value); + } + + /// + /// 向字节块写入一个32位无符号整数,指定字节序。 + /// + /// 要写入的字节块。 + /// 要写入的值。 + /// 字节序类型。 + public static void WriteUInt32(this ByteBlock byteBlock, uint value, EndianType endianType) + { + WriterExtension.WriteValue(ref byteBlock, value, endianType); + } + + /// + /// 向字节块写入一个64位无符号整数。 + /// + /// 要写入的字节块。 + /// 要写入的值。 + public static void WriteUInt64(this ByteBlock byteBlock, ulong value) + { + WriterExtension.WriteValue(ref byteBlock, value); + } + + /// + /// 向字节块写入一个64位无符号整数,指定字节序。 + /// + /// 要写入的字节块。 + /// 要写入的值。 + /// 字节序类型。 + public static void WriteUInt64(this ByteBlock byteBlock, ulong value, EndianType endianType) + { + WriterExtension.WriteValue(ref byteBlock, value, endianType); + } + + /// + /// 向字节块写入引用类型对象的状态标识。 + /// + /// 引用类型。 + /// 要写入的字节块。 + /// 要检查状态的对象。 + public static void WriteIsNull(this ByteBlock byteBlock, T t) + where T : class + { + WriterExtension.WriteIsNull(ref byteBlock, t); + } + + /// + /// 向字节块写入值类型对象的状态标识。 + /// + /// 值类型。 + /// 要写入的字节块。 + /// 要检查状态的可空值类型对象。 + public static void WriteIsNull(this ByteBlock byteBlock, T? t) + where T : struct + { + WriterExtension.WriteIsNull(ref byteBlock, t); + } + + /// + /// 向字节块写入非标识。 + /// + /// 要写入的字节块。 + public static void WriteNotNull(this ByteBlock byteBlock) + { + WriterExtension.WriteNotNull(ref byteBlock); + } + + /// + /// 向字节块写入标识。 + /// + /// 要写入的字节块。 + public static void WriteNull(this ByteBlock byteBlock) + { + WriterExtension.WriteNull(ref byteBlock); + } + + /// + /// 向字节块写入一个可变长度32位无符号整数。 + /// + /// 要写入的字节块。 + /// 要写入的值。 + /// 写入的字节数。 + public static int WriteVarUInt32(this ByteBlock byteBlock, uint value) + { + return WriterExtension.WriteVarUInt32(ref byteBlock, value); + } + + /// + /// 向字节块写入普通字符串,使用指定编码。 + /// + /// 要写入的字节块。 + /// 要写入的字符串值。 + /// 字符串编码。 + public static void WriteNormalString(this ByteBlock byteBlock, string value, Encoding encoding) + { + WriterExtension.WriteNormalString(ref byteBlock, value, encoding); + } + + /// + /// 向字节块写入字节跨度数据。 + /// + /// 要写入的字节块。 + /// 要写入的字节跨度。 + public static void WriteByteSpan(this ByteBlock byteBlock, scoped ReadOnlySpan span) + { + WriterExtension.WriteByteSpan(ref byteBlock, span); + } + + /// + /// 向字节块写入另一个字节块的数据。 + /// + /// 要写入的目标字节块。 + /// 要写入的源字节块。 + public static void WriteByteBlock(this ByteBlock byteBlock, ByteBlock value) + { + WriterExtension.WriteByteBlock(ref byteBlock, value); + } + + /// + /// 向字节块写入一个数据包对象。 + /// + /// 数据包类型,必须实现接口。 + /// 要写入的字节块。 + /// 要写入的数据包对象。 + public static void WritePackage(this ByteBlock byteBlock, TPackage value) + where TPackage : class, IPackage + { + WriterExtension.WritePackage(ref byteBlock, value); + } + #endregion Write + + #region Read + + /// + /// 从字节块读取一个字节值。 + /// + /// 要读取的字节块。 + /// 读取的字节值。 + public static byte ReadByte(this ByteBlock byteBlock) + { + return ReaderExtension.ReadValue(ref byteBlock); + } + + /// + /// 从字节块读取一个日期时间值。 + /// + /// 要读取的字节块。 + /// 读取的值。 + public static DateTime ReadDateTime(this ByteBlock byteBlock) + { + return ReaderExtension.ReadValue(ref byteBlock); + } + + /// + /// 从字节块读取一个十进制数值。 + /// + /// 要读取的字节块。 + /// 读取的值。 + public static decimal ReadDecimal(this ByteBlock byteBlock) + { + return ReaderExtension.ReadValue(ref byteBlock); + } + + /// + /// 从字节块读取一个十进制数值,指定字节序。 + /// + /// 要读取的字节块。 + /// 字节序类型。 + /// 读取的值。 + public static decimal ReadDecimal(this ByteBlock byteBlock, EndianType endianType) + { + return ReaderExtension.ReadValue(ref byteBlock, endianType); + } + + /// + /// 从字节块读取一个双精度浮点数值。 + /// + /// 要读取的字节块。 + /// 读取的值。 + public static double ReadDouble(this ByteBlock byteBlock) + { + return ReaderExtension.ReadValue(ref byteBlock); + } + + /// + /// 从字节块读取一个双精度浮点数值,指定字节序。 + /// + /// 要读取的字节块。 + /// 字节序类型。 + /// 读取的值。 + public static double ReadDouble(this ByteBlock byteBlock, EndianType endianType) + { + return ReaderExtension.ReadValue(ref byteBlock, endianType); + } + + /// + /// 从字节块读取一个单精度浮点数值。 + /// + /// 要读取的字节块。 + /// 读取的值。 + public static float ReadFloat(this ByteBlock byteBlock) + { + return ReaderExtension.ReadValue(ref byteBlock); + } + + /// + /// 从字节块读取一个单精度浮点数值,指定字节序。 + /// + /// 要读取的字节块。 + /// 字节序类型。 + /// 读取的值。 + public static float ReadFloat(this ByteBlock byteBlock, EndianType endianType) + { + return ReaderExtension.ReadValue(ref byteBlock, endianType); + } + + /// + /// 从字节块读取一个全局唯一标识符。 + /// + /// 要读取的字节块。 + /// 读取的值。 + public static Guid ReadGuid(this ByteBlock byteBlock) + { + return ReaderExtension.ReadValue(ref byteBlock); + } + + /// + /// 从字节块读取一个16位有符号整数。 + /// + /// 要读取的字节块。 + /// 读取的值。 + public static short ReadInt16(this ByteBlock byteBlock) + { + return ReaderExtension.ReadValue(ref byteBlock); + } + + /// + /// 从字节块读取一个16位有符号整数,指定字节序。 + /// + /// 要读取的字节块。 + /// 字节序类型。 + /// 读取的值。 + public static short ReadInt16(this ByteBlock byteBlock, EndianType endianType) + { + return ReaderExtension.ReadValue(ref byteBlock, endianType); + } + + /// + /// 从字节块读取一个32位有符号整数。 + /// + /// 要读取的字节块。 + /// 读取的值。 + public static int ReadInt32(this ByteBlock byteBlock) + { + return ReaderExtension.ReadValue(ref byteBlock); + } + + /// + /// 从字节块读取一个32位有符号整数,指定字节序。 + /// + /// 要读取的字节块。 + /// 字节序类型。 + /// 读取的值。 + public static int ReadInt32(this ByteBlock byteBlock, EndianType endianType) + { + return ReaderExtension.ReadValue(ref byteBlock, endianType); + } + + /// + /// 从字节块读取一个64位有符号整数。 + /// + /// 要读取的字节块。 + /// 读取的值。 + public static long ReadInt64(this ByteBlock byteBlock) + { + return ReaderExtension.ReadValue(ref byteBlock); + } + + /// + /// 从字节块读取一个64位有符号整数,指定字节序。 + /// + /// 要读取的字节块。 + /// 字节序类型。 + /// 读取的值。 + public static long ReadInt64(this ByteBlock byteBlock, EndianType endianType) + { + return ReaderExtension.ReadValue(ref byteBlock, endianType); + } + + /// + /// 从字节块读取一个8位有符号整数。 + /// + /// 要读取的字节块。 + /// 读取的值。 + public static sbyte ReadSByte(this ByteBlock byteBlock) + { + return ReaderExtension.ReadValue(ref byteBlock); + } + + /// + /// 从字节块读取一个字符串。 + /// + /// 要读取的字节块。 + /// 读取的字符串值。 + public static string ReadString(this ByteBlock byteBlock) + { + return ReaderExtension.ReadString(ref byteBlock); + } + + /// + /// 从字节块读取一个字符串,使用指定的固定头部类型。 + /// + /// 要读取的字节块。 + /// 固定头部类型,用于指示字符串长度的编码方式。 + /// 读取的字符串值。 + public static string ReadString(this ByteBlock byteBlock, FixedHeaderType headerType) + { + return ReaderExtension.ReadString(ref byteBlock, headerType); + } + + /// + /// 从字节块读取一个时间跨度值。 + /// + /// 要读取的字节块。 + /// 读取的值。 + public static TimeSpan ReadTimeSpan(this ByteBlock byteBlock) + { + return ReaderExtension.ReadValue(ref byteBlock); + } + + /// + /// 从字节块读取一个16位无符号整数。 + /// + /// 要读取的字节块。 + /// 读取的值。 + public static ushort ReadUInt16(this ByteBlock byteBlock) + { + return ReaderExtension.ReadValue(ref byteBlock); + } + + /// + /// 从字节块读取一个16位无符号整数,指定字节序。 + /// + /// 要读取的字节块。 + /// 字节序类型。 + /// 读取的值。 + public static ushort ReadUInt16(this ByteBlock byteBlock, EndianType endianType) + { + return ReaderExtension.ReadValue(ref byteBlock, endianType); + } + + /// + /// 从字节块读取一个32位无符号整数。 + /// + /// 要读取的字节块。 + /// 读取的值。 + public static uint ReadUInt32(this ByteBlock byteBlock) + { + return ReaderExtension.ReadValue(ref byteBlock); + } + + /// + /// 从字节块读取一个32位无符号整数,指定字节序。 + /// + /// 要读取的字节块。 + /// 字节序类型。 + /// 读取的值。 + public static uint ReadUInt32(this ByteBlock byteBlock, EndianType endianType) + { + return ReaderExtension.ReadValue(ref byteBlock, endianType); + } + + /// + /// 从字节块读取一个64位无符号整数。 + /// + /// 要读取的字节块。 + /// 读取的值。 + public static ulong ReadUInt64(this ByteBlock byteBlock) + { + return ReaderExtension.ReadValue(ref byteBlock); + } + + /// + /// 从字节块读取一个64位无符号整数,指定字节序。 + /// + /// 要读取的字节块。 + /// 字节序类型。 + /// 读取的值。 + public static ulong ReadUInt64(this ByteBlock byteBlock, EndianType endianType) + { + return ReaderExtension.ReadValue(ref byteBlock, endianType); + } + + /// + /// 从字节块读取状态标识。 + /// + /// 要读取的字节块。 + /// 如果读取到标识则返回;否则返回 + public static bool ReadIsNull(this ByteBlock byteBlock) + { + return ReaderExtension.ReadIsNull(ref byteBlock); + } + + /// + /// 从字节块读取一个可变长度32位无符号整数。 + /// + /// 要读取的字节块。 + /// 读取的值。 + public static uint ReadVarUInt32(this ByteBlock byteBlock) + { + return ReaderExtension.ReadVarUInt32(ref byteBlock); + } + + /// + /// 从字节块读取一个字节块对象。 + /// + /// 要读取的字节块。 + /// 读取的对象。 + public static ByteBlock ReadByteBlock(this ByteBlock byteBlock) + { + return ReaderExtension.ReadByteBlock(ref byteBlock); + } + + /// + /// 从字节块读取一个数据包对象。 + /// + /// 数据包类型,必须实现接口并具有无参构造函数。 + /// 要读取的字节块。 + /// 读取的数据包对象。 + public static TPackage ReadPackage(this ByteBlock byteBlock) + where TPackage : class, IPackage, new() + { + return ReaderExtension.ReadPackage(ref byteBlock); + } + #endregion Read +} \ No newline at end of file diff --git a/src/TouchSocket.Core/BytesPool/Extensions/ReaderExtension.cs b/src/TouchSocket.Core/BytesPool/Extensions/ReaderExtension.cs new file mode 100644 index 000000000..78a193acc --- /dev/null +++ b/src/TouchSocket.Core/BytesPool/Extensions/ReaderExtension.cs @@ -0,0 +1,356 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.Runtime.CompilerServices; + +namespace TouchSocket.Core; + +/// +/// 为提供扩展方法的静态类,用于读取各种类型的数据。 +/// +/// +/// 此类提供了丰富的扩展方法,支持读取基本数据类型、字符串、字节块、枚举等。 +/// 所有方法都是针对实现了接口的类型进行扩展。 +/// +public static partial class ReaderExtension +{ + /// + /// 在字节读取器中查找指定字节序列的位置。 + /// + /// 实现接口的读取器类型。 + /// 字节读取器实例。 + /// 要查找的字节序列。 + /// 如果找到,则返回第一次出现的位置;否则返回-1。 + public static long IndexOf(ref TReader reader, ReadOnlySpan span) + where TReader : IBytesReader + { + var sequence = reader.TotalSequence.Slice(reader.BytesRead); + return sequence.IndexOf(span); + } + + /// + /// 从字节读取器中读取一个对象。 + /// + /// 实现接口的读取器类型。 + /// 字节读取器实例。 + /// 读取的对象,如果数据为空则返回默认值。 + /// + /// 此方法首先读取长度信息,然后根据长度创建并填充数据。 + /// + public static ByteBlock ReadByteBlock(ref TReader reader) + where TReader : IBytesReader + { + var len = (int)ReadVarUInt32(ref reader) - 1; + + if (len < 0) + { + return default; + } + + var byteBlock = new ByteBlock(len); + byteBlock.Write(reader.GetSpan(len).Slice(0, len)); + byteBlock.SeekToStart(); + reader.Advance(len); + return byteBlock; + } + + /// + /// 从字节读取器中读取一个字节跨度。 + /// + /// 实现接口的读取器类型。 + /// 字节读取器实例。 + /// 读取的只读字节跨度。 + /// + /// 此方法首先读取长度信息,然后返回对应长度的字节跨度。 + /// + public static ReadOnlySpan ReadByteSpan(ref TReader reader) + where TReader : IBytesReader + { + var length = (int)ReadVarUInt32(ref reader); + var span = reader.GetSpan(length).Slice(0, length); + reader.Advance(length); + return span; + } + + /// + /// 从字节读取器中读取指定类型的枚举值。 + /// + /// 实现接口的读取器类型。 + /// 字节读取器实例。 + /// 枚举的。 + /// 读取的枚举值。 + /// 当枚举的底层类型不受支持时抛出。 + /// + /// 支持的底层类型包括:、 + /// 。 + /// + public static Enum ReadEnum(ref TReader reader, Type enumType) + where TReader : IBytesReader + { + var underlyingType = Enum.GetUnderlyingType(enumType); + if (underlyingType == typeof(byte)) + { + var value = ReadValue(ref reader); + return (Enum)Enum.ToObject(enumType, value); + } + else if (underlyingType == typeof(sbyte)) + { + var value = ReadValue(ref reader); + return (Enum)Enum.ToObject(enumType, value); + } + else if (underlyingType == typeof(short)) + { + var value = ReadValue(ref reader); + return (Enum)Enum.ToObject(enumType, value); + } + else if (underlyingType == typeof(ushort)) + { + var value = ReadValue(ref reader); + return (Enum)Enum.ToObject(enumType, value); + } + else if (underlyingType == typeof(int)) + { + var value = ReadValue(ref reader); + return (Enum)Enum.ToObject(enumType, value); + } + else if (underlyingType == typeof(uint)) + { + var value = ReadValue(ref reader); + return (Enum)Enum.ToObject(enumType, value); + } + else if (underlyingType == typeof(long)) + { + var value = ReadValue(ref reader); + return (Enum)Enum.ToObject(enumType, value); + } + else if (underlyingType == typeof(ulong)) + { + var value = ReadValue(ref reader); + return (Enum)Enum.ToObject(enumType, value); + } + else + { + throw new NotSupportedException($"不支持枚举的底层类型{underlyingType}。"); + } + } + + /// + /// 从字节读取器中读取空值标识。 + /// + /// 实现接口的读取器类型。 + /// 字节读取器实例。 + /// 如果值为空则返回 ;否则返回 + /// 当标识既非Null也非NotNull时抛出。 + /// + /// 此方法读取一个字节作为空值标识,0表示空值,1表示非空值。 + /// + public static bool ReadIsNull(ref TReader reader) + where TReader : IBytesReader + { + var status = ReadValue(ref reader); + return status == 0 || (status == 1 ? false : throw new Exception("标识既非Null,也非NotNull,可能是流位置发生了错误。")); + } + + /// + /// 从字节读取器中读取一个包对象。 + /// + /// 实现接口的读取器类型。 + /// 实现接口的包类型。 + /// 字节读取器实例。 + /// 读取的包对象,如果为空则返回默认值。 + /// + /// 此方法首先检查空值标识,如果不为空则创建包对象并调用其Unpackage方法进行反序列化。 + /// + public static TPackage ReadPackage(ref TReader reader) + where TReader : IBytesReader + where TPackage : class, IPackage, new() + { + if (ReadIsNull(ref reader)) + { + return default; + } + else + { + var package = new TPackage(); + package.Unpackage(ref reader); + return package; + } + } + + /// + /// 从字节读取器中读取UTF-8编码的字符串。 + /// + /// 实现接口的读取器类型。 + /// 字节读取器实例。 + /// 固定头部类型,指定长度字段的大小。默认为。 + /// 读取的字符串,如果为空则返回 + /// + /// 此方法根据指定的头部类型读取长度信息,然后读取对应长度的字节并转换为UTF-8字符串。 + /// 当长度字段为最大值时,表示字符串为空。 + /// + public static string ReadString(ref TReader reader, FixedHeaderType headerType = FixedHeaderType.Int) + where TReader : IBytesReader + { + int len; + switch (headerType) + { + case FixedHeaderType.Byte: + len = ReadValue(ref reader); + if (len == byte.MaxValue) + { + return null; + } + break; + + case FixedHeaderType.Ushort: + len = ReadValue(ref reader); + if (len == ushort.MaxValue) + { + return null; + } + break; + + case FixedHeaderType.Int: + default: + len = ReadValue(ref reader); + if (len == int.MaxValue) + { + return null; + } + break; + } + + var span = reader.GetSpan(len).Slice(0, len); + var str = span.ToString(Encoding.UTF8); + reader.Advance(len); + return str; + } + + /// + /// 从字节读取器中读取指定长度的字节跨度。 + /// + /// 实现接口的读取器类型。 + /// 字节读取器实例。 + /// 要读取的字节长度。 + /// 读取的只读字节跨度。 + /// + /// 此方法读取指定长度的字节并推进读取器位置。 + /// + public static ReadOnlySpan ReadToSpan(ref TReader reader, int length) + where TReader : IBytesReader + { + var span = reader.GetSpan(length).Slice(0, length); + reader.Advance(length); + return span; + } + + /// + /// 从字节读取器中读取指定类型的值,使用指定的字节序。 + /// + /// 实现接口的读取器类型。 + /// 要读取的值类型,必须是非托管类型。 + /// 字节读取器实例。 + /// 字节序类型。 + /// 读取的值。 + /// + /// 此方法使用指定的字节序转换器读取值,并推进读取器位置。 + /// + public static T ReadValue(ref TReader reader, EndianType endianType) + where T : unmanaged + where TReader : IBytesReader + { + var size = Unsafe.SizeOf(); + var span = reader.GetSpan(size); + var value = TouchSocketBitConverter.GetBitConverter(endianType).To(span); + reader.Advance(size); + return value; + } + + /// + /// 从字节读取器中读取指定类型的值,使用默认的字节序。 + /// + /// 实现接口的读取器类型。 + /// 要读取的值类型,必须是非托管类型。 + /// 字节读取器实例。 + /// 读取的值。 + /// + /// 此方法使用默认的字节序转换器读取值,并推进读取器位置。 + /// + public static T ReadValue(ref TReader reader) + where T : unmanaged + where TReader : IBytesReader + { + var size = Unsafe.SizeOf(); + var span = reader.GetSpan(size); + var value = TouchSocketBitConverter.Default.To(span); + reader.Advance(size); + return value; + } + + /// + /// 从字节读取器中读取可变长度编码的无符号32位整数。 + /// + /// 实现接口的读取器类型。 + /// 字节读取器实例。 + /// 读取的值。 + /// + /// 此方法实现了VarInt编码的解码,每个字节的最高位作为继续位, + /// 低7位作为数据位。当最高位为0时表示最后一个字节。 + /// + public static uint ReadVarUInt32(ref TReader reader) + where TReader : IBytesReader + { + uint value = 0; + var byteLength = 0; + while (true) + { + var b = ReadValue(ref reader); + var temp = (b & 0x7F); //取每个字节的后7位 + temp <<= (7 * byteLength); //向左移位,越是后面的字节,移位越多 + value += (uint)temp; //把每个字节的值加起来就是最终的值了 + byteLength++; + if (b <= 0x7F) + { //127=0x7F=0b01111111,小于等于说明msb=0,即最后一个字节 + break; + } + } + return value; + } + + /// + /// 将字节读取器的位置设置到数据末尾。 + /// + /// 实现接口的读取器类型。 + /// 字节读取器实例。 + /// + /// 此方法将读取器的位置设置为总序列的长度,即数据的末尾位置。 + /// + public static void SeekToEnd(ref TReader reader) + where TReader : IBytesReader + { + reader.BytesRead = reader.TotalSequence.Length; + } + + /// + /// 将字节读取器的位置设置到数据开头。 + /// + /// 实现接口的读取器类型。 + /// 字节读取器实例。 + /// + /// 此方法将读取器的位置重置为0,即数据的开头位置。 + /// + public static void SeekToStart(ref TReader reader) + where TReader : IBytesReader + { + reader.BytesRead = 0; + } +} \ No newline at end of file diff --git a/src/TouchSocket.Core/BytesPool/Extensions/ReaderExtensionForClass.cs b/src/TouchSocket.Core/BytesPool/Extensions/ReaderExtensionForClass.cs new file mode 100644 index 000000000..6f2e669c9 --- /dev/null +++ b/src/TouchSocket.Core/BytesPool/Extensions/ReaderExtensionForClass.cs @@ -0,0 +1,228 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.Runtime.CompilerServices; + +namespace TouchSocket.Core; + +/// +/// 为引用类型字节读取器提供扩展方法的静态类。 +/// +/// +/// 此类包含专门针对引用类型字节读取器的扩展方法,提供高效的数据读取功能。 +/// 支持各种数据类型的读取操作,包括基本类型、字符串、字节块等。 +/// +public static partial class ReaderExtension +{ + /// + /// 从字节读取器中读取指定长度的数据到只读字节跨度。 + /// + /// 实现接口的字节读取器类型,必须为引用类型。 + /// 要读取数据的字节读取器。 + /// 要读取的数据长度。 + /// 包含读取数据的只读字节跨度。 + /// + /// 此方法从读取器的当前位置读取指定长度的数据,并自动推进读取器的位置。 + /// 返回的跨度引用读取器的内部缓冲区,因此其生命周期与读取器相关。 + /// + public static ReadOnlySpan ReadToSpan(this TReader reader, int length) + where TReader : class, IBytesReader + { + var span = reader.GetSpan(length).Slice(0, length); + reader.Advance(length); + return span; + } + + /// + /// 从字节读取器中读取一个字节块对象。 + /// + /// 实现接口的字节读取器类型,必须为引用类型。 + /// 要读取数据的字节读取器。 + /// 读取的对象,如果长度小于0则返回默认值。 + /// + /// 此方法首先读取可变长度的32位无符号整数作为长度信息,然后读取相应长度的数据创建字节块。 + /// 创建的字节块会自动设置到起始位置。 + /// + public static ByteBlock ReadByteBlock(this TReader reader) + where TReader : class, IBytesReader + { + var len = (int)ReadVarUInt32(reader) - 1; + + if (len < 0) + { + return default; + } + + var byteBlock = new ByteBlock(len); + byteBlock.Write(reader.GetSpan(len).Slice(0, len)); + byteBlock.SeekToStart(); + reader.Advance(len); + return byteBlock; + } + + /// + /// 从字节读取器中读取一个可变长度32位无符号整数。 + /// + /// 实现接口的字节读取器类型,必须为引用类型。 + /// 要读取数据的字节读取器。 + /// 读取的32位无符号整数值。 + /// + /// 此方法使用变长编码格式读取32位无符号整数,每个字节的最高位用作继续标志。 + /// 当字节值小于等于0x7F时表示这是最后一个字节。 + /// + public static uint ReadVarUInt32(this TReader reader) + where TReader : class, IBytesReader + { + uint value = 0; + var byteLength = 0; + while (true) + { + var b = ReadValue(reader); + var temp = (b & 0x7F); //取每个字节的后7位 + temp <<= (7 * byteLength); //向左移位,越是后面的字节,移位越多 + value += (uint)temp; //把每个字节的值加起来就是最终的值了 + byteLength++; + if (b <= 0x7F) + { //127=0x7F=0b01111111,小于等于说明msb=0,即最后一个字节 + break; + } + } + return value; + } + + /// + /// 从字节读取器中读取字节跨度数据。 + /// + /// 实现接口的字节读取器类型,必须为引用类型。 + /// 要读取数据的字节读取器。 + /// 读取的只读字节跨度。 + /// + /// 此方法首先读取可变长度32位无符号整数作为数据长度,然后读取相应长度的字节数据。 + /// 返回的跨度引用读取器的内部缓冲区。 + /// + public static ReadOnlySpan ReadByteSpan(this TReader reader) + where TReader : class, IBytesReader + { + var length = (int)ReadVarUInt32(reader); + var span = reader.GetSpan(length).Slice(0, length); + reader.Advance(length); + return span; + } + + /// + /// 从字节读取器中读取状态标识。 + /// + /// 实现接口的字节读取器类型,必须为引用类型。 + /// 要读取数据的字节读取器。 + /// 如果读取到标识则返回;如果读取到非标识则返回 + /// 当读取的标识既非也非非时抛出异常。 + /// + /// 此方法读取一个字节作为状态标识,0表示,1表示非。 + /// 如果读取到的值不是0或1,则认为流位置发生了错误。 + /// + public static bool ReadIsNull(this TReader reader) + where TReader : class, IBytesReader + { + var status = ReadValue(reader); + return status == 0 || (status == 1 ? false : throw new Exception("标识既非Null,也非NotNull,可能是流位置发生了错误。")); + } + + /// + /// 从字节读取器中读取指定类型的值,使用指定的字节序。 + /// + /// 实现接口的字节读取器类型,必须为引用类型。 + /// 要读取的值类型,必须为非托管类型。 + /// 要读取数据的字节读取器。 + /// 字节序类型。 + /// 读取的值。 + /// + /// 此方法根据类型的大小从读取器中读取相应字节数的数据,并根据指定的字节序进行转换。 + /// + public static T ReadValue(this TReader reader, EndianType endianType) + where T : unmanaged + where TReader : class, IBytesReader + { + var size = Unsafe.SizeOf(); + var span = reader.GetSpan(size); + var value = TouchSocketBitConverter.GetBitConverter(endianType).To(span); + reader.Advance(size); + return value; + } + + /// + /// 从字节读取器中读取指定类型的值,使用默认字节序。 + /// + /// 实现接口的字节读取器类型,必须为引用类型。 + /// 要读取的值类型,必须为非托管类型。 + /// 要读取数据的字节读取器。 + /// 读取的值。 + /// + /// 此方法根据类型的大小从读取器中读取相应字节数的数据,并使用默认字节序进行转换。 + /// + public static T ReadValue(this TReader reader) + where T : unmanaged + where TReader : class, IBytesReader + { + var size = Unsafe.SizeOf(); + var span = reader.GetSpan(size); + var value = TouchSocketBitConverter.Default.To(span); + reader.Advance(size); + return value; + } + + /// + /// 从字节读取器中读取字符串,使用指定的固定头部类型。 + /// + /// 实现接口的字节读取器类型,必须为引用类型。 + /// 要读取数据的字节读取器。 + /// 固定头部类型,默认为。 + /// 读取的字符串,如果长度为对应类型的最大值则返回 + /// + /// 此方法根据指定的头部类型读取长度信息,然后读取相应长度的UTF-8编码字符串数据。 + /// 当长度为对应数据类型的最大值时,表示字符串。 + /// + public static string ReadString(this TReader reader, FixedHeaderType headerType = FixedHeaderType.Int) + where TReader : class, IBytesReader + { + int len; + switch (headerType) + { + case FixedHeaderType.Byte: + len = ReadValue(reader); + if (len == byte.MaxValue) + { + return null; + } + break; + case FixedHeaderType.Ushort: + len = ReadValue(reader); + if (len == ushort.MaxValue) + { + return null; + } + break; + case FixedHeaderType.Int: + default: + len = ReadValue(reader); + if (len == int.MaxValue) + { + return null; + } + break; + } + + var span = reader.GetSpan(len).Slice(0, len); + var str = span.ToString(Encoding.UTF8); + reader.Advance(len); + return str; + } +} diff --git a/src/TouchSocket.Core/BytesPool/Extensions/SpanExtension.cs b/src/TouchSocket.Core/BytesPool/Extensions/SpanExtension.cs new file mode 100644 index 000000000..57a2aed61 --- /dev/null +++ b/src/TouchSocket.Core/BytesPool/Extensions/SpanExtension.cs @@ -0,0 +1,176 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.Runtime.CompilerServices; + +namespace TouchSocket.Core; + +/// +/// 为提供扩展方法的静态类,用于读写各种类型的数据。 +/// +/// +/// 此类提供了针对字节跨度的高性能读写操作,支持基本数据类型、字符串等的序列化和反序列化。 +/// 所有方法都会自动推进跨度的位置,确保连续的读写操作。 +/// +public static class SpanExtension +{ + #region WriteValue + + /// + /// 将指定类型的值写入字节跨度,使用默认的字节序。 + /// + /// 要写入的值类型,必须是非托管类型。 + /// 要写入的字节跨度,写入后会自动推进位置。 + /// 要写入的值。 + /// + /// 此方法使用默认的字节序转换器写入值,并自动推进跨度位置。 + /// + public static void WriteValue(this ref Span span, T value) + where T : unmanaged + { + var size = TouchSocketBitConverter.Default.WriteBytes(span, value); + span = span.Slice(size); + } + + /// + /// 将指定类型的值写入字节跨度,使用指定的字节序。 + /// + /// 要写入的值类型,必须是非托管类型。 + /// 要写入的字节跨度,写入后会自动推进位置。 + /// 要写入的值。 + /// 字节序类型。 + /// + /// 此方法使用指定的字节序转换器写入值,并自动推进跨度位置。 + /// + public static void WriteValue(this ref Span span, T value, EndianType endianType) + where T : unmanaged + { + var size = TouchSocketBitConverter.GetBitConverter(endianType).WriteBytes(span, value); + span = span.Slice(size); + } + + #endregion WriteValue + + #region ReadValue + + /// + /// 从只读字节跨度中读取UTF-8编码的字符串。 + /// + /// 要读取的只读字节跨度,读取后会自动推进位置。 + /// 固定头部类型,指定长度字段的大小。默认为。 + /// 读取的字符串,如果为空则返回 + /// + /// 此方法根据指定的头部类型读取长度信息,然后读取对应长度的字节并转换为UTF-8字符串。 + /// 当长度字段为最大值时,表示字符串为空。 + /// + public static string ReadString(this ref ReadOnlySpan span, FixedHeaderType headerType = FixedHeaderType.Int) + { + int len; + switch (headerType) + { + case FixedHeaderType.Byte: + len = ReadValue(ref span); + if (len == byte.MaxValue) + { + return null; + } + break; + + case FixedHeaderType.Ushort: + len = ReadValue(ref span); + if (len == ushort.MaxValue) + { + return null; + } + break; + + case FixedHeaderType.Int: + default: + len = ReadValue(ref span); + if (len == int.MaxValue) + { + return null; + } + break; + } + + var spanString = span.Slice(0, len); + var str = spanString.ToString(Encoding.UTF8); + span = span.Slice(len); + return str; + } + + /// + /// 从只读字节跨度中读取指定长度的字节跨度。 + /// + /// 要读取的只读字节跨度,读取后会自动推进位置。 + /// 要读取的字节长度。 + /// 读取的只读字节跨度。 + /// + /// 此方法读取指定长度的字节并推进跨度位置。 + /// + public static ReadOnlySpan ReadToSpan(this ref ReadOnlySpan span, int length) + { + var result = span.Slice(0, length); + span = span.Slice(length); + return result; + } + + /// + /// 从只读字节跨度中读取指定类型的值,使用默认的字节序。 + /// + /// 要读取的值类型,必须是非托管类型。 + /// 要读取的只读字节跨度,读取后会自动推进位置。 + /// 读取的值。 + /// 当跨度长度不足以读取指定类型时抛出。 + /// + /// 此方法使用默认的字节序转换器读取值,并自动推进跨度位置。 + /// + public static T ReadValue(this ref ReadOnlySpan span) + where T : unmanaged + { + var size = Unsafe.SizeOf(); + if (span.Length < size) + { + ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), span.Length, size); + } + var value = TouchSocketBitConverter.Default.To(span); + span = span.Slice(size); + return value; + } + + /// + /// 从只读字节跨度中读取指定类型的值,使用指定的字节序。 + /// + /// 要读取的值类型,必须是非托管类型。 + /// 要读取的只读字节跨度,读取后会自动推进位置。 + /// 字节序类型。 + /// 读取的值。 + /// 当跨度长度不足以读取指定类型时抛出。 + /// + /// 此方法使用指定的字节序转换器读取值,并自动推进跨度位置。 + /// + public static T ReadValue(this ref ReadOnlySpan span, EndianType endianType) + where T : unmanaged + { + var size = Unsafe.SizeOf(); + if (span.Length < size) + { + ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), span.Length, size); + } + var value = TouchSocketBitConverter.GetBitConverter(endianType).To(span); + span = span.Slice(size); + return value; + } + + #endregion ReadValue +} \ No newline at end of file diff --git a/src/TouchSocket.Core/BytesPool/Extensions/WriterExtension.cs b/src/TouchSocket.Core/BytesPool/Extensions/WriterExtension.cs new file mode 100644 index 000000000..b6602b49b --- /dev/null +++ b/src/TouchSocket.Core/BytesPool/Extensions/WriterExtension.cs @@ -0,0 +1,472 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.Runtime.CompilerServices; + +namespace TouchSocket.Core; + +/// +/// 为提供扩展方法的静态类,用于写入各种类型的数据。 +/// +/// +/// 此类提供了丰富的扩展方法,支持写入基本数据类型、字符串、字节块、枚举、包对象等。 +/// 所有方法都是针对实现了接口的类型进行扩展。 +/// +public static partial class WriterExtension +{ + /// + /// 将布尔值数组写入字节写入器。 + /// + /// 实现接口的写入器类型。 + /// 字节写入器实例。 + /// 要写入的布尔值只读跨度。 + /// + /// 此方法将布尔值数组转换为字节数组后写入,如果数组为空则不进行任何操作。 + /// + public static void WriteBooleans(ref TWriter writer, ReadOnlySpan values) + where TWriter : IBytesWriter + { + if (values.IsEmpty) + { + return; + } + var size = TouchSocketBitConverter.GetConvertedLength(values.Length); + + var span = writer.GetSpan(size); + + TouchSocketBitConverter.ConvertValues(values, span); + writer.Advance(size); + } + + /// + /// 将对象写入字节写入器。 + /// + /// 实现接口的写入器类型。 + /// 字节写入器实例。 + /// 要写入的对象。 + /// + /// 如果,则写入长度为0; + /// 否则先写入长度信息(长度+1),再写入字节块内容。 + /// + public static void WriteByteBlock(ref TWriter writer, ByteBlock byteBlock) + where TWriter : IBytesWriter + { + if (byteBlock is null) + { + WriteVarUInt32(ref writer, 0); + } + else + { + WriteVarUInt32(ref writer, (uint)(byteBlock.Length + 1)); + var span = writer.GetSpan(byteBlock.Length); + byteBlock.Span.CopyTo(span); + writer.Advance(byteBlock.Length); + } + } + + /// + /// 将字节跨度写入字节写入器。 + /// + /// 实现接口的写入器类型。 + /// 字节写入器实例。 + /// 要写入的只读字节跨度。 + /// + /// 此方法首先写入长度信息,然后写入字节跨度的内容。如果跨度为空则只写入长度0。 + /// + public static void WriteByteSpan(ref TWriter writer, scoped ReadOnlySpan span) + where TWriter : IBytesWriter + { + WriteVarUInt32(ref writer, (uint)span.Length); + if (span.IsEmpty) + { + return; + } + + var writerSpan = writer.GetSpan(span.Length); + span.CopyTo(writerSpan); + writer.Advance(span.Length); + } + + /// + /// 写入引用类型的空值标识。 + /// + /// 实现接口的写入器类型。 + /// 引用类型。 + /// 字节写入器实例。 + /// 要检查的对象。 + /// + /// 如果对象为 则写入空值标识,否则写入非空值标识。 + /// + public static void WriteIsNull(ref TWriter writer, T t) + where T : class + where TWriter : IBytesWriter + { + if (t == null) + { + WriteNull(ref writer); + } + else + { + WriteNotNull(ref writer); + } + } + + /// + /// 写入可空值类型的空值标识。 + /// + /// 实现接口的写入器类型。 + /// 值类型。 + /// 字节写入器实例。 + /// 要检查的可空值类型。 + /// + /// 如果值有值则写入非空值标识,否则写入空值标识。 + /// + public static void WriteIsNull(ref TWriter writer, T? t) + where T : struct + where TWriter : IBytesWriter + { + if (t.HasValue) + { + WriteNotNull(ref writer); + } + else + { + WriteNull(ref writer); + } + } + + /// + /// 使用指定编码将字符串写入字节写入器。 + /// + /// 实现接口的写入器类型。 + /// 字节写入器实例。 + /// 要写入的字符串。 + /// 要使用的字符编码。 + /// 时抛出。 + /// + /// 此方法直接将字符串按指定编码写入,不包含长度信息。 + /// + public static void WriteNormalString(ref TWriter writer, string value, Encoding encoding) + where TWriter : IBytesWriter + { + ThrowHelper.ThrowIfNull(value, nameof(value)); + var maxSize = encoding.GetMaxByteCount(value.Length); + var span = writer.GetSpan(maxSize); + var chars = value.AsSpan(); + + unsafe + { + fixed (char* p = &chars[0]) + { + fixed (byte* p1 = &span[0]) + { + var len = Encoding.UTF8.GetBytes(p, chars.Length, p1, maxSize); + writer.Advance(len); + } + } + } + } + + /// + /// 写入非空值标识。 + /// + /// 实现接口的写入器类型。 + /// 字节写入器实例。 + /// + /// 写入字节值1,表示非空值。 + /// + public static void WriteNotNull(ref TWriter writer) + where TWriter : IBytesWriter + { + WriteValue(ref writer, 1); + } + + /// + /// 写入空值标识。 + /// + /// 实现接口的写入器类型。 + /// 字节写入器实例。 + /// + /// 写入字节值0,表示空值。 + /// + public static void WriteNull(ref TWriter writer) + where TWriter : IBytesWriter + { + WriteValue(ref writer, 0); + } + + /// + /// 将包对象写入字节写入器。 + /// + /// 实现接口的写入器类型。 + /// 实现接口的包类型。 + /// 字节写入器实例。 + /// 要写入的包对象。 + /// + /// 如果包对象为 ,则写入空值标识;否则写入非空值标识并调用包对象的Package方法进行序列化。 + /// + public static void WritePackage(ref TWriter writer, TPackage package) + where TWriter : IBytesWriter + where TPackage : class, IPackage + { + if (package is null) + { + WriteNull(ref writer); + return; + } + else + { + WriteNotNull(ref writer); + package.Package(ref writer); + } + } + + /// + /// 将UTF-8编码的字符串写入字节写入器。 + /// + /// 实现接口的写入器类型。 + /// 字节写入器实例。 + /// 要写入的字符串。 + /// 固定头部类型,指定长度字段的大小。默认为。 + /// 当字符串长度超过头部类型允许的最大值时抛出。 + /// + /// 此方法根据指定的头部类型写入长度信息,然后写入UTF-8编码的字符串内容。 + /// 当字符串为 时,长度字段写入对应类型的最大值;当字符串为空时,长度字段写入0。 + /// + public static void WriteString(ref TWriter writer, string value, FixedHeaderType headerType = FixedHeaderType.Int) + where TWriter : IBytesWriter + { + if (value == null) + { + switch (headerType) + { + case FixedHeaderType.Byte: + WriteValue(ref writer, byte.MaxValue); + return; + + case FixedHeaderType.Ushort: + WriteValue(ref writer, ushort.MaxValue); + return; + + case FixedHeaderType.Int: + default: + WriteValue(ref writer, int.MaxValue); + return; + } + } + else if (value == string.Empty) + { + switch (headerType) + { + case FixedHeaderType.Byte: + WriteValue(ref writer, 0); + return; + + case FixedHeaderType.Ushort: + WriteValue(ref writer, 0); + return; + + case FixedHeaderType.Int: + default: + WriteValue(ref writer, 0); + return; + } + } + else + { + var maxSize = Encoding.UTF8.GetMaxByteCount(value.Length); + + var chars = value.AsSpan(); + + var headerLength = headerType switch + { + FixedHeaderType.Byte => (byte)1, + FixedHeaderType.Ushort => (byte)2, + _ => (byte)4, + }; + + var writerAnchor = new WriterAnchor(ref writer, headerLength); + //var headerSpan = writer.GetSpan(headerLength); + //writer.Advance(headerLength); + + var bodySpan = writer.GetSpan(maxSize); + + unsafe + { + fixed (char* p = &chars[0]) + { + fixed (byte* p1 = &bodySpan[0]) + { + var len = Encoding.UTF8.GetBytes(p, chars.Length, p1, maxSize); + + writer.Advance(len); + + var headerSpan = writerAnchor.Rewind(ref writer, out _); + switch (headerType) + { + case FixedHeaderType.Byte: + if (len >= byte.MaxValue) + { + ThrowHelper.ThrowArgumentOutOfRangeException_MoreThan(nameof(value), len, byte.MaxValue); + } + + headerSpan.WriteValue((byte)len); + break; + + case FixedHeaderType.Ushort: + if (len >= ushort.MaxValue) + { + ThrowHelper.ThrowArgumentOutOfRangeException_MoreThan(nameof(value), len, ushort.MaxValue); + } + headerSpan.WriteValue((ushort)len); + break; + + case FixedHeaderType.Int: + default: + if (len >= int.MaxValue) + { + ThrowHelper.ThrowArgumentOutOfRangeException_MoreThan(nameof(value), len, int.MaxValue); + } + headerSpan.WriteValue(len); + break; + } + } + } + } + } + } + + /// + /// 将指定类型的值写入字节写入器,使用默认的字节序。 + /// + /// 实现接口的写入器类型。 + /// 要写入的值类型,必须是非托管类型。 + /// 字节写入器实例。 + /// 要写入的值。 + /// + /// 此方法使用默认的字节序转换器写入值,并推进写入器位置。 + /// + public static void WriteValue(ref TWriter writer, T value) + where T : unmanaged + where TWriter : IBytesWriter + { + var size = Unsafe.SizeOf(); + var span = writer.GetSpan(size); + TouchSocketBitConverter.Default.WriteBytes(span, value); + writer.Advance(size); + } + + /// + /// 将指定类型的值写入字节写入器,使用指定的字节序。 + /// + /// 实现接口的写入器类型。 + /// 要写入的值类型,必须是非托管类型。 + /// 字节写入器实例。 + /// 要写入的值。 + /// 字节序类型。 + /// + /// 此方法使用指定的字节序转换器写入值,并推进写入器位置。 + /// + public static void WriteValue(ref TWriter writer, T value, EndianType endianType) + where T : unmanaged + where TWriter : IBytesWriter + { + var size = Unsafe.SizeOf(); + var span = writer.GetSpan(size); + TouchSocketBitConverter.GetBitConverter(endianType).WriteBytes(span, value); + writer.Advance(size); + } + + /// + /// 将可变长度编码的无符号32位整数写入字节写入器。 + /// + /// 实现接口的写入器类型。 + /// 字节写入器实例。 + /// 要写入的值。 + /// 写入的字节数。 + /// + /// 此方法实现了VarInt编码,每个字节的最高位作为继续位, + /// 低7位作为数据位。当最高位为0时表示最后一个字节。最多需要5个字节。 + /// + public static int WriteVarUInt32(ref TWriter writer, uint value) + where TWriter : IBytesWriter + { + var span = writer.GetSpan(5); //最多需要5个字节 + + byte byteLength = 0; + while (value > 0x7F) + { + //127=0x7F=0b01111111,大于说明msb=1,即后续还有字节 + var temp = value & 0x7F; //得到数值的后7位,0x7F=0b01111111,0与任何数与都是0,1与任何数与还是任何数 + temp |= 0x80; //后7位不变最高位固定为1,0x80=0b10000000,1与任何数或都是1,0与任何数或都是任何数 + span[byteLength++] = (byte)temp; //存储msb=1的数据 + value >>= 7; //右移已经计算过的7位得到下次需要计算的数值 + } + span[byteLength++] = (byte)value; //最后一个字节msb=0 + + writer.Advance(byteLength); + return byteLength; + } + + /// + /// 将枚举值写入字节写入器。 + /// + /// 实现接口的写入器类型。 + /// 字节写入器实例。 + /// 要写入的枚举值。 + /// 当枚举的底层类型不受支持时抛出。 + /// + /// 支持的底层类型包括:、 + /// 。 + /// + public static void WriteEnum(ref TWriter writer, Enum value) + where TWriter : IBytesWriter + { + var underlyingType = Enum.GetUnderlyingType(value.GetType()); + if (underlyingType == typeof(byte)) + { + WriteValue(ref writer, Convert.ToByte(value)); + } + else if (underlyingType == typeof(sbyte)) + { + WriteValue(ref writer, Convert.ToSByte(value)); + } + else if (underlyingType == typeof(short)) + { + WriteValue(ref writer, Convert.ToInt16(value)); + } + else if (underlyingType == typeof(ushort)) + { + WriteValue(ref writer, Convert.ToUInt16(value)); + } + else if (underlyingType == typeof(int)) + { + WriteValue(ref writer, Convert.ToInt32(value)); + } + else if (underlyingType == typeof(uint)) + { + WriteValue(ref writer, Convert.ToUInt32(value)); + } + else if (underlyingType == typeof(long)) + { + WriteValue(ref writer, Convert.ToInt64(value)); + } + else if (underlyingType == typeof(ulong)) + { + WriteValue(ref writer, Convert.ToUInt64(value)); + } + else + { + ThrowHelper.ThrowNotSupportedException($"Unsupported enum underlying type: {underlyingType}"); + } + } +} \ No newline at end of file diff --git a/src/TouchSocket.Core/BytesPool/Extensions/WriterExtensionForClass.cs b/src/TouchSocket.Core/BytesPool/Extensions/WriterExtensionForClass.cs new file mode 100644 index 000000000..3c81339ff --- /dev/null +++ b/src/TouchSocket.Core/BytesPool/Extensions/WriterExtensionForClass.cs @@ -0,0 +1,393 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.Runtime.CompilerServices; + +namespace TouchSocket.Core; + +public static partial class WriterExtension +{ + /// + /// 将布尔值数组写入到字节写入器中。 + /// + /// 字节写入器类型,必须继承自 + /// 字节写入器实例。 + /// 要写入的布尔值只读跨度。 + /// + /// 布尔值会被压缩存储,每个字节可存储8个布尔值。 + /// 如果值为空,则不执行任何操作。 + /// + public static void WriteBooleans(this TWriter writer, ReadOnlySpan values) + where TWriter : class, IBytesWriter + { + if (values.IsEmpty) + { + return; + } + var size = TouchSocketBitConverter.GetConvertedLength(values.Length); + + var span = writer.GetSpan(size); + + TouchSocketBitConverter.ConvertValues(values, span); + writer.Advance(size); + } + + /// + /// 将实例写入到字节写入器中。 + /// + /// 字节写入器类型,必须继承自 + /// 字节写入器实例。 + /// 要写入的实例。 + /// + /// 如果,则写入长度值0。 + /// 否则写入长度+1后跟随实际数据。使用变长编码存储长度信息。 + /// + public static void WriteByteBlock(this TWriter writer, ByteBlock byteBlock) + where TWriter : class, IBytesWriter + { + if (byteBlock is null) + { + WriteVarUInt32(writer, 0); + } + else + { + WriteVarUInt32(writer, (uint)(byteBlock.Length + 1)); + var span = writer.GetSpan(byteBlock.Length); + byteBlock.Span.CopyTo(span); + writer.Advance(byteBlock.Length); + } + } + + /// + /// 将字节跨度写入到字节写入器中。 + /// + /// 字节写入器类型,必须继承自 + /// 字节写入器实例。 + /// 要写入的字节只读跨度。 + /// + /// 首先写入跨度的长度(使用变长编码),然后写入实际数据。 + /// 如果跨度为空,则只写入长度0。 + /// + public static void WriteByteSpan(this TWriter writer, scoped ReadOnlySpan span) + where TWriter : class, IBytesWriter + { + WriteVarUInt32(writer, (uint)span.Length); + if (span.IsEmpty) + { + return; + } + + var writerSpan = writer.GetSpan(span.Length); + span.CopyTo(writerSpan); + writer.Advance(span.Length); + } + + /// + /// 写入引用类型的空值标记。 + /// + /// 字节写入器类型,必须继承自 + /// 要检查的引用类型。 + /// 字节写入器实例。 + /// 要检查的对象实例。 + /// + /// 如果对象为,写入0(空值标记); + /// 否则写入1(非空值标记)。 + /// + public static void WriteIsNull(this TWriter writer, T t) + where T : class + where TWriter : class, IBytesWriter + { + if (t == null) + { + WriteNull(writer); + } + else + { + WriteNotNull(writer); + } + } + + /// + /// 写入可空值类型的空值标记。 + /// + /// 字节写入器类型,必须继承自 + /// 要检查的值类型。 + /// 字节写入器实例。 + /// 要检查的可空值类型实例。 + /// + /// 如果可空值类型有值,写入1(非空值标记); + /// 否则写入0(空值标记)。 + /// + public static void WriteIsNull(this TWriter writer, T? t) + where T : struct + where TWriter : class, IBytesWriter + { + if (t.HasValue) + { + WriteNotNull(writer); + } + else + { + WriteNull(writer); + } + } + + /// + /// 将字符串以指定编码写入到字节写入器中(不包含长度前缀)。 + /// + /// 字节写入器类型,必须继承自 + /// 字节写入器实例。 + /// 要写入的字符串。不能为。 + /// 字符编码方式。 + /// 时抛出。 + /// + /// 直接写入字符串的字节表示,不包含长度信息。 + /// 实际编码使用UTF-8,忽略参数。 + /// + public static void WriteNormalString(this TWriter writer, string value, Encoding encoding) + where TWriter : class, IBytesWriter + { + ThrowHelper.ThrowIfNull(value, nameof(value)); + var maxSize = encoding.GetMaxByteCount(value.Length); + var span = writer.GetSpan(maxSize); + var chars = value.AsSpan(); + + unsafe + { + fixed (char* p = &chars[0]) + { + fixed (byte* p1 = &span[0]) + { + var len = Encoding.UTF8.GetBytes(p, chars.Length, p1, maxSize); + writer.Advance(len); + } + } + } + } + + /// + /// 写入非空值标记(值为1的字节)。 + /// + /// 字节写入器类型,必须继承自 + /// 字节写入器实例。 + public static void WriteNotNull(this TWriter writer) + where TWriter : class, IBytesWriter + { + WriteValue(writer, 1); + } + + /// + /// 写入空值标记(值为0的字节)。 + /// + /// 字节写入器类型,必须继承自 + /// 字节写入器实例。 + public static void WriteNull(this TWriter writer) + where TWriter : class, IBytesWriter + { + WriteValue(writer, 0); + } + + /// + /// 将字符串以指定的固定包头类型写入到字节写入器中。 + /// + /// 字节写入器类型,必须继承自 + /// 字节写入器实例。 + /// 要写入的字符串。 + /// 固定包头类型,默认为。 + /// + /// 根据写入不同大小的长度前缀,然后写入UTF-8编码的字符串数据。 + /// + /// 对于字符串,写入相应类型的最大值作为标记。 + /// 对于空字符串,写入长度0。 + /// 对于普通字符串,写入实际字节长度后跟随数据。 + /// + /// + /// 当字符串编码后的长度超过指定包头类型的最大值时抛出。 + public static void WriteString(this TWriter writer, string value, FixedHeaderType headerType = FixedHeaderType.Int) + where TWriter : class, IBytesWriter + { + if (value == null) + { + switch (headerType) + { + case FixedHeaderType.Byte: + WriteValue(writer, byte.MaxValue); + return; + + case FixedHeaderType.Ushort: + WriteValue(writer, ushort.MaxValue); + return; + + case FixedHeaderType.Int: + default: + WriteValue(writer, int.MaxValue); + return; + } + } + else if (value == string.Empty) + { + switch (headerType) + { + case FixedHeaderType.Byte: + WriteValue(writer, 0); + return; + + case FixedHeaderType.Ushort: + WriteValue(writer, 0); + return; + + case FixedHeaderType.Int: + default: + WriteValue(writer, 0); + return; + } + } + else + { + var maxSize = Encoding.UTF8.GetMaxByteCount(value.Length); + + var chars = value.AsSpan(); + + var headerLength = headerType switch + { + FixedHeaderType.Byte => (byte)1, + FixedHeaderType.Ushort => (byte)2, + _ => (byte)4, + }; + + var writerAnchor = new WriterAnchor(ref writer, headerLength); + //var headerSpan = writer.GetSpan(headerLength); + //writer.Advance(headerLength); + + var bodySpan = writer.GetSpan(maxSize); + + unsafe + { + fixed (char* p = &chars[0]) + { + fixed (byte* p1 = &bodySpan[0]) + { + var len = Encoding.UTF8.GetBytes(p, chars.Length, p1, maxSize); + + writer.Advance(len); + + var headerSpan = writerAnchor.Rewind(ref writer, out _); + switch (headerType) + { + case FixedHeaderType.Byte: + if (len >= byte.MaxValue) + { + ThrowHelper.ThrowArgumentOutOfRangeException_MoreThan(nameof(value), len, byte.MaxValue); + } + + headerSpan.WriteValue((byte)len); + break; + + case FixedHeaderType.Ushort: + if (len >= ushort.MaxValue) + { + ThrowHelper.ThrowArgumentOutOfRangeException_MoreThan(nameof(value), len, ushort.MaxValue); + } + headerSpan.WriteValue((ushort)len); + break; + + case FixedHeaderType.Int: + default: + if (len >= int.MaxValue) + { + ThrowHelper.ThrowArgumentOutOfRangeException_MoreThan(nameof(value), len, int.MaxValue); + } + headerSpan.WriteValue(len); + break; + } + } + } + } + } + } + + /// + /// 将非托管类型的值写入到字节写入器中。 + /// + /// 字节写入器类型,必须继承自 + /// 要写入的值的类型,必须是非托管类型。 + /// 字节写入器实例。 + /// 要写入的值。 + /// + /// 使用默认的字节序()将值转换为字节序列并写入。 + /// + public static void WriteValue(this TWriter writer, T value) + where T : unmanaged + where TWriter : class, IBytesWriter + { + var size = Unsafe.SizeOf(); + var span = writer.GetSpan(size); + TouchSocketBitConverter.Default.WriteBytes(span, value); + writer.Advance(size); + } + + /// + /// 将非托管类型的值以指定字节序写入到字节写入器中。 + /// + /// 字节写入器类型,必须继承自 + /// 要写入的值的类型,必须是非托管类型。 + /// 字节写入器实例。 + /// 要写入的值。 + /// 字节序类型。 + /// + /// 使用指定的字节序将值转换为字节序列并写入。 + /// + public static void WriteValue(this TWriter writer, T value, EndianType endianType) + where T : unmanaged + where TWriter : class, IBytesWriter + { + var size = Unsafe.SizeOf(); + var span = writer.GetSpan(size); + TouchSocketBitConverter.GetBitConverter(endianType).WriteBytes(span, value); + writer.Advance(size); + } + + /// + /// 将32位无符号整数以变长编码格式写入到字节写入器中。 + /// + /// 字节写入器类型,必须继承自 + /// 字节写入器实例。 + /// 要写入的32位无符号整数值。 + /// 写入的字节数(1-5个字节)。 + /// + /// 变长编码使用LEB128格式: + /// + /// 每个字节的最高位(MSB)表示是否还有后续字节。 + /// 剩余7位存储实际数据。 + /// 最多需要5个字节来存储32位整数。 + /// + /// + public static int WriteVarUInt32(this TWriter writer, uint value) + where TWriter : class, IBytesWriter + { + var span = writer.GetSpan(5); //最多需要5个字节 + + byte byteLength = 0; + while (value > 0x7F) + { + //127=0x7F=0b01111111,大于说明msb=1,即后续还有字节 + var temp = value & 0x7F; //得到数值的后7位,0x7F=0b01111111,0与任何数与都是0,1与任何数与还是任何数 + temp |= 0x80; //后7位不变最高位固定为1,0x80=0b10000000,1与任何数或都是1,0与任何数或都是任何数 + span[byteLength++] = (byte)temp; //存储msb=1的数据 + value >>= 7; //右移已经计算过的7位得到下次需要计算的数值 + } + span[byteLength++] = (byte)value; //最后一个字节msb=0 + + writer.Advance(byteLength); + return byteLength; + } +} \ No newline at end of file diff --git a/src/TouchSocket.Core/Pool/IByteBlockBuilder.cs b/src/TouchSocket.Core/BytesPool/IBytesBuilder.cs similarity index 86% rename from src/TouchSocket.Core/Pool/IByteBlockBuilder.cs rename to src/TouchSocket.Core/BytesPool/IBytesBuilder.cs index a9067ca30..48deabae1 100644 --- a/src/TouchSocket.Core/Pool/IByteBlockBuilder.cs +++ b/src/TouchSocket.Core/BytesPool/IBytesBuilder.cs @@ -15,7 +15,7 @@ namespace TouchSocket.Core; /// /// 定义了字节块构建器的接口,用于从内存池中构建和管理字节块。 /// -public interface IByteBlockBuilder +public interface IBytesBuilder { /// /// 构建数据时,指示内存池的申请长度。 @@ -28,6 +28,8 @@ public interface IByteBlockBuilder /// /// 构建对象到 /// - /// 要构建的字节块对象引用。 - void Build(ref TByteBlock byteBlock) where TByteBlock : IByteBlock; + /// 要构建的字节块对象引用。 + void Build(ref TWriter writer) where TWriter : IBytesWriter + + ; } \ No newline at end of file diff --git a/src/TouchSocket.Core/BytesPool/Reader/ByteBlockReader.cs b/src/TouchSocket.Core/BytesPool/Reader/ByteBlockReader.cs new file mode 100644 index 000000000..99ec721ec --- /dev/null +++ b/src/TouchSocket.Core/BytesPool/Reader/ByteBlockReader.cs @@ -0,0 +1,81 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +//using System; +//using System.Buffers; +//using System.Diagnostics; + +//namespace TouchSocket.Core; + +//[DebuggerDisplay("Length={Length},Position={Position}")] +//public sealed class ByteBlockReader : IByteBlockReader +//{ +// private readonly ReadOnlyMemory m_memory; + +// public ByteBlockReader(ReadOnlyMemory memory) +// { +// this.m_memory = memory; +// } + +// public long BytesRead { get => this.Position; set => this.Position = (int)value; } +// public long BytesRemaining => this.CanReadLength; +// public int CanReadLength => this.Length - this.Position; + +// public int Length => this.m_memory.Length; + +// public ReadOnlyMemory Memory => this.m_memory; + +// public int Position { get; set; } + +// public ReadOnlySequence Sequence => this.TotalSequence.Slice(this.Position); +// public ReadOnlySpan Span => this.m_memory.Span; +// public ReadOnlySequence TotalSequence => new ReadOnlySequence(this.Memory); + +// public void Advance(int count) +// { +// this.Position += count; +// } + +// public ReadOnlyMemory GetMemory(int count) +// { +// return this.m_memory.Slice(this.Position, count); +// } + +// public ReadOnlySpan GetSpan(int count) +// { +// return this.Span.Slice(this.Position, count); +// } + +// public int Read(Span span) +// { +// if (span.IsEmpty) +// { +// return 0; +// } + +// var length = Math.Min(this.CanReadLength, span.Length); + +// this.Span.Slice(this.Position, length).CopyTo(span); +// this.Position += length; +// return length; +// } + +// public void SeekToEnd() +// { +// this.Position = this.Length; +// } + +// public void SeekToStart() +// { +// this.Position = 0; +// } +//} \ No newline at end of file diff --git a/src/TouchSocket.Core/BytesPool/Reader/BytesReader.cs b/src/TouchSocket.Core/BytesPool/Reader/BytesReader.cs new file mode 100644 index 000000000..f5c72d8bd --- /dev/null +++ b/src/TouchSocket.Core/BytesPool/Reader/BytesReader.cs @@ -0,0 +1,140 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.Buffers; + +namespace TouchSocket.Core; + +/// +/// 表示一个基于字节序列的高性能字节读取器,提供对的读取操作。 +/// 实现了接口。 +/// +/// +/// BytesReader作为值类型实现,适用于高频使用且对性能要求较高的场景。 +/// 支持单段和多段字节序列的读取,当序列为多段时会自动进行内存合并。 +/// 内部使用内存池来优化多段序列的处理性能。 +/// +public struct BytesReader : IDisposable, IBytesReader +{ + private readonly ReadOnlySequence m_sequence; + private IMemoryOwner m_memoryOwner; + + private long m_position = 0; + + /// + /// 使用指定的字节序列初始化的新实例。 + /// + /// 要读取的字节序列。 + public BytesReader(ReadOnlySequence sequence) + { + this.m_sequence = sequence; + } + + /// + /// 使用指定的内存块初始化的新实例。 + /// + /// 要读取的内存块。 + /// + /// 内存块会被转换为单段字节序列进行处理。 + /// + public BytesReader(ReadOnlyMemory memory) + { + this.m_sequence = new ReadOnlySequence(memory); + } + + /// + public long BytesRead { readonly get => this.m_position; set => this.m_position = value; } + + /// + public readonly long BytesRemaining => this.m_sequence.Length - this.m_position; + + /// + /// 获取总的字节序列。 + /// + /// 返回完整的字节序列,从索引0开始到序列末尾。 + public readonly ReadOnlySequence TotalSequence => this.m_sequence; + + /// + public readonly ReadOnlySequence Sequence => this.TotalSequence.Slice(this.BytesRead); + + /// + public void Advance(int count) + { + this.m_position += count; + } + + /// + /// 释放使用的资源。 + /// + /// + /// 主要用于释放内部缓存的内存池资源。 + /// + public void Dispose() => this.m_memoryOwner?.Dispose(); + + /// + public ReadOnlyMemory GetMemory(int count) + { + var sequence = this.m_sequence.Slice(this.m_position, count); + + if (sequence.IsSingleSegment) + { + return sequence.First; // 零拷贝切片 + } + + var cacheMemory = this.GetCacheMemory(count); + sequence.CopyTo(cacheMemory.Span); + return cacheMemory; + } + + /// + public ReadOnlySpan GetSpan(int count) + { + return this.GetMemory(count).Span; + } + + /// + public int Read(Span span) + { + if (span.IsEmpty) + { + return 0; + } + var canReadLength = (int)Math.Min(span.Length, this.BytesRemaining); + var tempSpan = this.GetSpan(canReadLength); + tempSpan.CopyTo(span); + this.Advance(canReadLength); + return canReadLength; + } + + /// + /// 获取或创建指定大小的缓存内存。 + /// + /// 所需的内存大小。 + /// 返回一个精确大小的内存切片。 + /// + /// 使用共享内存池来提高性能,当现有缓存不足时会自动扩容。 + /// 返回的内存大小始终与请求的大小一致。 + /// + private Memory GetCacheMemory(int size) + { + if (this.m_memoryOwner == null) + { + this.m_memoryOwner = MemoryPool.Shared.Rent(size); + } + else if (this.m_memoryOwner.Memory.Length < size) + { + this.m_memoryOwner.Dispose(); + this.m_memoryOwner = MemoryPool.Shared.Rent(size); + } + return this.m_memoryOwner.Memory.Slice(0, size); // 返回精确大小的切片 + } +} \ No newline at end of file diff --git a/src/TouchSocket.Core/BytesPool/Reader/IByteBlockReader.cs b/src/TouchSocket.Core/BytesPool/Reader/IByteBlockReader.cs new file mode 100644 index 000000000..d988b9228 --- /dev/null +++ b/src/TouchSocket.Core/BytesPool/Reader/IByteBlockReader.cs @@ -0,0 +1,33 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +namespace TouchSocket.Core; + +/// +/// 表示字节块读取器接口,提供字节块的读取功能。 +/// 继承自接口。 +/// +/// +/// IByteBlockReader接口结合了通用字节读取和字节块核心功能, +/// 为字节块的读取操作提供了专门的接口定义。 +/// +public interface IByteBlockReader : IBytesReader, IByteBlockCore +{ + /// + /// 获取当前可读取的字节长度。 + /// + /// 表示从当前位置到数据末尾还可以读取的字节数。 + /// + /// 此值等于的差值。 + /// + int CanReadLength { get; } +} \ No newline at end of file diff --git a/src/TouchSocket.Core/BytesPool/Reader/IBytesReader.cs b/src/TouchSocket.Core/BytesPool/Reader/IBytesReader.cs new file mode 100644 index 000000000..01f0c2070 --- /dev/null +++ b/src/TouchSocket.Core/BytesPool/Reader/IBytesReader.cs @@ -0,0 +1,78 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.Buffers; + +namespace TouchSocket.Core; + +/// +/// 表示字节读取器的接口,提供对字节序列的读取、跳过和获取操作。 +/// +public interface IBytesReader +{ + /// + /// 获取或设置已读取的字节数。 + /// + long BytesRead { get; set; } + + /// + /// 获取剩余可读取的字节数。 + /// + long BytesRemaining { get; } + + /// + /// 获取当前可读取的字节序列。 + /// + ReadOnlySequence Sequence { get; } + + /// + /// 获取总的字节序列。 + /// + ReadOnlySequence TotalSequence { get; } + + /// + /// 推进指定数量的字节。 + /// + /// 要推进的字节数。 + void Advance(int count); + + /// + /// 获取指定数量的只读内存字节块。 + /// + /// 要获取的字节数。 + /// 只读内存字节块。 + /// + /// 字节块最大生命期与当前的生命周期一致。 + /// 但是当多次调用时,最后一次调用的返回值会覆盖之前的返回值,导致之前的返回值失效。 + /// 所以请确保在下次获取返回值之前使用完返回的内存,避免在多次调用后使用旧的内存块。 + /// + ReadOnlyMemory GetMemory(int count); + + /// + /// 获取指定数量的只读字节跨度。 + /// + /// 要获取的字节数。 + /// 只读字节跨度。 + /// + /// 字节块最大生命期与当前的生命周期一致。 + /// 但是当多次调用时,最后一次调用的返回值会覆盖之前的返回值,导致之前的返回值失效。 + /// 所以请确保在下次获取返回值之前使用完返回的内存,避免在多次调用后使用旧的内存块。 + /// + ReadOnlySpan GetSpan(int count); + + /// + /// 读取字节到指定的跨度中。 + /// + /// 目标跨度。 + /// 实际读取的字节数。 + int Read(Span span); +} \ No newline at end of file diff --git a/src/TouchSocket.Core/BytesPool/Reader/PooledBytesReader.cs b/src/TouchSocket.Core/BytesPool/Reader/PooledBytesReader.cs new file mode 100644 index 000000000..7ffed402a --- /dev/null +++ b/src/TouchSocket.Core/BytesPool/Reader/PooledBytesReader.cs @@ -0,0 +1,129 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.Buffers; +using System.Runtime.CompilerServices; + +namespace TouchSocket.Core; + +/// +/// 表示一个使用内存池的字节读取器。 +/// +/// +public sealed class PooledBytesReader : IDisposable, IBytesReader +{ + private IMemoryOwner m_memoryOwner; + private long m_position = 0; + private ReadOnlySequence m_sequence; + + /// + public long BytesRead { get => this.m_position; set => this.m_position = value; } + + /// + public long BytesRemaining => this.m_sequence.Length - this.m_position; + + /// + public ReadOnlySequence Sequence => this.TotalSequence.Slice(this.BytesRead); + + /// + public ReadOnlySequence TotalSequence => this.m_sequence; + + /// + public void Advance(int count) + { + this.m_position += count; + } + + /// + public void Dispose() + { + this.Clear(); + } + + /// + public ReadOnlyMemory GetMemory(int count) + { + var sequence = this.m_sequence.Slice(this.m_position, count); + + if (sequence.IsSingleSegment) + { + return sequence.First; // 零拷贝切片 + } + + var cacheMemory = this.GetCacheMemory(count); + sequence.CopyTo(cacheMemory.Span); + return cacheMemory; + } + + /// + public ReadOnlySpan GetSpan(int count) + { + return this.GetMemory(count).Span; + } + + /// + public int Read(Span span) + { + if (span.IsEmpty) + { + return 0; + } + var canReadLength = (int)Math.Min(span.Length, this.BytesRemaining); + var tempSpan = this.GetSpan(canReadLength); + tempSpan.CopyTo(span); + this.Advance(canReadLength); + return canReadLength; + } + + /// + /// 重置读取器,使用新的字节序列。 + /// + /// 新的字节序列。 + public void Reset(ReadOnlySequence sequence) + { + this.m_position = 0; + this.m_sequence = sequence; + } + + /// + /// 重置读取器,使用新的只读内存。 + /// + /// 新的只读内存。 + public void Reset(ReadOnlyMemory memory) + { + this.m_position = 0; + this.m_sequence = new ReadOnlySequence(memory); + } + + /// + /// 清理内存所有者。 + /// + public void Clear() + { + this.m_memoryOwner?.Dispose(); + this.m_memoryOwner = default; + } + + private Memory GetCacheMemory(int size) + { + if (this.m_memoryOwner == null) + { + this.m_memoryOwner = MemoryPool.Shared.Rent(size); + } + else if (this.m_memoryOwner.Memory.Length < size) + { + this.m_memoryOwner.Dispose(); + this.m_memoryOwner = MemoryPool.Shared.Rent(size); + } + return this.m_memoryOwner.Memory.Slice(0, size); // 返回精确大小的切片 + } +} \ No newline at end of file diff --git a/src/TouchSocket.Core/BytesPool/Stream/ByteBlockStream.cs b/src/TouchSocket.Core/BytesPool/Stream/ByteBlockStream.cs new file mode 100644 index 000000000..bfaf1ba8e --- /dev/null +++ b/src/TouchSocket.Core/BytesPool/Stream/ByteBlockStream.cs @@ -0,0 +1,351 @@ +//------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +//------------------------------------------------------------------------------ + +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace TouchSocket.Core; + +/// +/// 字节块流 +/// +[DebuggerDisplay("Len={Length},Pos={Position},Capacity={Capacity}")] +internal sealed partial class ByteBlockStream : Stream +{ + private readonly ByteBlock m_byteBlock; + private readonly bool m_releaseTogether; + + /// + /// 初始化 ByteBlockStream 类的新实例。 + /// + /// 一个 ByteBlock 对象,表示字节块。 + /// 一个布尔值,指示是否在释放流时同时释放字节块。 + public ByteBlockStream(ByteBlock byteBlock, bool releaseTogether) + { + this.m_byteBlock = byteBlock; + this.m_releaseTogether = releaseTogether; + } + + /// + /// 获取此实例关联的 ByteBlock 对象。 + /// + public ByteBlock ByteBlock => this.m_byteBlock; + + /// + /// 仅当内存块可用,且>0时为。 + /// + public override bool CanRead => this.m_byteBlock.Using && this.CanReadLength > 0; + + /// + /// 还能读取的长度,计算为的差值。 + /// + public long CanReadLength => this.m_byteBlock.Length - this.m_byteBlock.Position; + + /// + /// 支持查找 + /// + public override bool CanSeek => this.m_byteBlock.Using; + + /// + /// 可写入 + /// + public override bool CanWrite => this.m_byteBlock.Using; + + /// + /// 容量 + /// + public int Capacity => this.m_byteBlock.Capacity; + + /// + /// 空闲长度,准确掌握该值,可以避免内存扩展,计算为的差值。 + /// + public long FreeLength => this.Capacity - this.Position; + + /// + /// 真实长度 + /// + public override long Length => this.m_byteBlock.Length; + + /// + /// 流位置 + /// + public override long Position + { + get => this.m_byteBlock.Position; + set => this.m_byteBlock.Position = (int)value; + } + + /// + /// 无实际效果 + /// + public override void Flush() + { + } + + /// + /// 异步刷新(无实际效果) + /// + /// 取消令牌 + /// 已完成的任务 + public override Task FlushAsync(CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return CreateCanceledTask(cancellationToken); + } + return Task.CompletedTask; + } + + /// + /// 读取数据,然后递增Pos + /// + /// 目标缓冲区 + /// 偏移量 + /// 读取长度 + /// 实际读取的字节数 + /// 对象已释放 + public override int Read(byte[] buffer, int offset, int length) + { + this.ThrowIfDisposed(); + return this.m_byteBlock.Read(new Span(buffer, offset, length)); + } + + /// + /// 异步读取数据 + /// + /// 目标缓冲区 + /// 偏移量 + /// 读取字节数 + /// 取消令牌 + /// 实际读取的字节数 + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return CreateCanceledTask(cancellationToken); + } + + this.ThrowIfDisposed(); + + try + { + var result = this.Read(buffer, offset, count); + return Task.FromResult(result); + } + catch (Exception ex) + { + return Task.FromException(ex); + } + } + + /// + /// 从当前流位置读取一个值 + /// + /// 读取的字节值,如果到达流末尾则返回-1 + public override int ReadByte() + { + this.ThrowIfDisposed(); + + if (this.CanReadLength <= 0) + { + return -1; + } + + var value = this.m_byteBlock.GetSpan(1)[0]; + this.m_byteBlock.Advance(1); + return value; + } + + /// + /// 设置流位置 + /// + /// 偏移量 + /// 起始位置 + /// 新的流位置 + /// 对象已释放 + public override long Seek(long offset, SeekOrigin origin) + { + this.ThrowIfDisposed(); + switch (origin) + { + case SeekOrigin.Begin: + this.m_byteBlock.Position = (int)offset; + break; + + case SeekOrigin.Current: + this.m_byteBlock.Position += (int)offset; + break; + + case SeekOrigin.End: + this.m_byteBlock.Position = (int)(this.Length + offset); + break; + } + + return this.m_byteBlock.Position; + } + + /// + /// 设置实际长度 + /// + /// 新长度 + /// 对象已释放 + public override void SetLength(long value) + { + this.ThrowIfDisposed(); + this.m_byteBlock.SetLength((int)value); + } + + /// + /// 写入数据 + /// + /// 源数据缓冲区 + /// 偏移量 + /// 写入字节数 + /// 对象已释放 + public override void Write(byte[] buffer, int offset, int count) + { + this.ThrowIfDisposed(); + this.m_byteBlock.Write(new ReadOnlySpan(buffer, offset, count)); + } + + /// + /// 异步写入数据 + /// + /// 源数据缓冲区 + /// 偏移量 + /// 写入字节数 + /// 取消令牌 + /// 表示异步写入操作的任务 + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return CreateCanceledTask(cancellationToken); + } + + this.ThrowIfDisposed(); + + try + { + this.Write(buffer, offset, count); + return Task.CompletedTask; + } + catch (Exception ex) + { + return Task.FromException(ex); + } + } + + /// + /// 写入单个字节 + /// + /// 要写入的字节值 + public override void WriteByte(byte value) + { + this.ThrowIfDisposed(); + + var span = this.m_byteBlock.GetSpan(1); + span[0] = value; + this.m_byteBlock.Advance(1); + } + + /// + /// 高效复制到目标流 + /// + /// 目标流 + public new void CopyTo(Stream destination) + { + this.CopyTo(destination, 81920); + } + + /// + /// 高效复制到目标流 + /// + /// 目标流 + /// 缓冲区大小(此参数被忽略,因为我们直接使用内存块) + public new void CopyTo(Stream destination, int bufferSize) + { + this.ThrowIfDisposed(); + if (destination == null) throw new ArgumentNullException(nameof(destination)); + + if (!destination.CanWrite) + { + throw new NotSupportedException("目标流不支持写入"); + } + + // 直接使用内存块复制,避免额外的缓冲区分配 + var remainingLength = (int)this.CanReadLength; + if (remainingLength > 0) + { + var remainingData = this.m_byteBlock.Memory.Slice(this.m_byteBlock.Position, remainingLength); + WritePlatformOptimized(destination, remainingData); + this.m_byteBlock.Position = this.m_byteBlock.Length; + } + } + + /// + /// 异步复制到目标流 + /// + /// 目标流 + /// 缓冲区大小(此参数被忽略,因为我们直接使用内存块) + /// 取消令牌 + /// 表示异步复制操作的任务 + public override async Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) + { + this.ThrowIfDisposed(); + if (destination == null) throw new ArgumentNullException(nameof(destination)); + + if (cancellationToken.IsCancellationRequested) + { + await CreateCanceledTask(cancellationToken).ConfigureAwait(false); + } + + if (!destination.CanWrite) + { + throw new NotSupportedException("目标流不支持写入"); + } + + // 直接使用内存块异步复制 + var remainingLength = (int)this.CanReadLength; + if (remainingLength > 0) + { + var remainingData = this.m_byteBlock.Memory.Slice(this.m_byteBlock.Position, remainingLength); + await WriteAsyncPlatformOptimized(destination, remainingData, cancellationToken).ConfigureAwait(false); + this.m_byteBlock.Position = this.m_byteBlock.Length; + } + } + + /// + /// 是否正在处置托管资源 + protected override void Dispose(bool disposing) + { + if (disposing && this.m_releaseTogether) + { + this.m_byteBlock.Dispose(); + } + + base.Dispose(disposing); + } + + /// + /// 检查对象是否已释放,如果已释放则抛出异常 + /// + /// 对象已释放 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ThrowIfDisposed() + { + if (!this.m_byteBlock.Using) + { + throw new ObjectDisposedException(this.GetType().FullName); + } + } +} \ No newline at end of file diff --git a/src/TouchSocket.Core/BytesPool/Stream/ByteBlockStreamCompat.cs b/src/TouchSocket.Core/BytesPool/Stream/ByteBlockStreamCompat.cs new file mode 100644 index 000000000..f005c2975 --- /dev/null +++ b/src/TouchSocket.Core/BytesPool/Stream/ByteBlockStreamCompat.cs @@ -0,0 +1,237 @@ +//------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +//------------------------------------------------------------------------------ + +namespace TouchSocket.Core; + +/// +/// ByteBlockStream的兼容性扩展和条件编译方法 +/// +internal sealed partial class ByteBlockStream +{ +#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER + /// + /// 读取数据到Span + /// + /// 目标缓冲区 + /// 实际读取的字节数 + public override int Read(Span buffer) + { + this.ThrowIfDisposed(); + return this.m_byteBlock.Read(buffer); + } + + /// + /// 异步读取数据到Memory + /// + /// 目标缓冲区 + /// 取消令牌 + /// 实际读取的字节数 + public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return CreateCanceledValueTask(cancellationToken); + } + + this.ThrowIfDisposed(); + + try + { + var result = this.m_byteBlock.Read(buffer.Span); + return new ValueTask(result); + } + catch (Exception ex) + { + return new ValueTask(Task.FromException(ex)); + } + } + + /// + /// 写入Span数据 + /// + /// 源数据 + public override void Write(ReadOnlySpan buffer) + { + this.ThrowIfDisposed(); + this.m_byteBlock.Write(buffer); + } + + /// + /// 异步写入Memory数据 + /// + /// 源数据 + /// 取消令牌 + /// 表示异步写入操作的任务 + public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return CreateCanceledValueTask(cancellationToken); + } + + this.ThrowIfDisposed(); + + try + { + this.m_byteBlock.Write(buffer.Span); + return default; + } + catch (Exception ex) + { + return new ValueTask(Task.FromException(ex)); + } + } + + /// + /// 异步释放资源 + /// + /// 表示异步释放操作的任务 + public override ValueTask DisposeAsync() + { + try + { + this.Dispose(true); + GC.SuppressFinalize(this); + return default; + } + catch (Exception ex) + { + return new ValueTask(Task.FromException(ex)); + } + } +#endif + + /// + /// 创建已取消的ValueTask(兼容性方法) + /// + /// 取消令牌 + /// 已取消的ValueTask + private static ValueTask CreateCanceledValueTask(CancellationToken cancellationToken) + { +#if NET6_0_OR_GREATER + return ValueTask.FromCanceled(cancellationToken); +#else + return new ValueTask(CreateCanceledTask(cancellationToken)); +#endif + } + + /// + /// 创建已取消的ValueTask<T>(兼容性方法) + /// + /// 返回类型 + /// 取消令牌 + /// 已取消的ValueTask<T> + private static ValueTask CreateCanceledValueTask(CancellationToken cancellationToken) + { +#if NET6_0_OR_GREATER + return ValueTask.FromCanceled(cancellationToken); +#else + return new ValueTask(CreateCanceledTask(cancellationToken)); +#endif + } + + /// + /// 创建已取消的Task(兼容性方法) + /// + /// 取消令牌 + /// 已取消的Task + private static Task CreateCanceledTask(CancellationToken cancellationToken) + { +#if NET6_0_OR_GREATER + return Task.FromCanceled(cancellationToken); +#else + return TaskExtensions.FromCanceled(cancellationToken); +#endif + } + + /// + /// 创建已取消的Task<T>(兼容性方法) + /// + /// 返回类型 + /// 取消令牌 + /// 已取消的Task<T> + private static Task CreateCanceledTask(CancellationToken cancellationToken) + { +#if NET6_0_OR_GREATER + return Task.FromCanceled(cancellationToken); +#else + return TaskExtensions.FromCanceled(cancellationToken); +#endif + } + + /// + /// 高效的跨平台复制实现 + /// + /// 目标流 + /// 要复制的数据 + private static void WritePlatformOptimized(Stream destination, ReadOnlyMemory data) + { +#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER + destination.Write(data.Span); +#else + // 对于不支持Span的版本,使用传统方式 + var array = data.ToArray(); + destination.Write(array, 0, array.Length); +#endif + } + + /// + /// 高效的跨平台异步复制实现 + /// + /// 目标流 + /// 要复制的数据 + /// 取消令牌 + /// 异步操作任务 + private static Task WriteAsyncPlatformOptimized(Stream destination, ReadOnlyMemory data, CancellationToken cancellationToken) + { +#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER + return destination.WriteAsync(data, cancellationToken).AsTask(); +#else + // 对于不支持Memory的版本,使用传统方式 + var array = data.ToArray(); + return destination.WriteAsync(array, 0, array.Length, cancellationToken); +#endif + } +} + +#if !NET6_0_OR_GREATER +/// +/// Task扩展方法,用于兼容性 +/// +internal static class TaskExtensions +{ + /// + /// 创建已取消的Task + /// + /// 取消令牌 + /// 已取消的Task + public static Task FromCanceled(CancellationToken cancellationToken) + { + var tcs = new TaskCompletionSource(); + tcs.SetCanceled(); + return tcs.Task; + } + + /// + /// 创建已取消的Task<T> + /// + /// 返回类型 + /// 取消令牌 + /// 已取消的Task<T> + public static Task FromCanceled(CancellationToken cancellationToken) + { + var tcs = new TaskCompletionSource(); + tcs.SetCanceled(); + return tcs.Task; + } +} +#endif \ No newline at end of file diff --git a/src/TouchSocket.Core/BytesPool/Stream/ReadOnlyMemoryStream.cs b/src/TouchSocket.Core/BytesPool/Stream/ReadOnlyMemoryStream.cs new file mode 100644 index 000000000..098718869 --- /dev/null +++ b/src/TouchSocket.Core/BytesPool/Stream/ReadOnlyMemoryStream.cs @@ -0,0 +1,189 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +namespace TouchSocket.Core; + +/// +/// 表示一个基于只读内存的流,提供对的流式访问。 +/// 继承自类,仅支持读取和定位操作。 +/// +/// +/// ReadOnlyMemoryStream为只读流实现,不支持写入操作。 +/// 提供了对内存数据的高性能流式访问,适用于需要将内存数据作为流处理的场景。 +/// 支持随机访问和定位操作。 +/// +public sealed class ReadOnlyMemoryStream : Stream +{ + private readonly ReadOnlyMemory m_memory; + private int m_position; + + /// + /// 使用指定的只读内存初始化的新实例。 + /// + /// 作为流数据源的只读内存。 + public ReadOnlyMemoryStream(ReadOnlyMemory memory) + { + this.m_memory = memory; + this.m_position = 0; + } + + /// + /// 获取一个值,该值指示当前流是否支持读取。 + /// + /// 始终返回,因为此流支持读取。 + public override bool CanRead => true; + + /// + /// 获取一个值,该值指示当前流是否支持查找。 + /// + /// 始终返回,因为此流支持查找。 + public override bool CanSeek => true; + + /// + /// 获取一个值,该值指示当前流是否支持写入。 + /// + /// 始终返回,因为此流为只读流。 + public override bool CanWrite => false; + + /// + /// 获取流的长度(以字节为单位)。 + /// + /// 返回底层内存的长度。 + public override long Length => this.m_memory.Length; + + /// + /// 获取或设置流内的当前位置。 + /// + /// 流内的当前位置。 + /// 当设置的值小于0或大于流长度时抛出。 + public override long Position + { + get => this.m_position; + set + { + if (value < 0 || value > this.m_memory.Length) + throw new ArgumentOutOfRangeException(nameof(value)); + this.m_position = (int)value; + } + } + + /// + /// 清除此流的缓冲区,并使得任何缓冲数据都被写入到基础设备。 + /// + /// + /// 对于只读流,此操作为无操作(no-op)。 + /// + public override void Flush() + { + // No-op for read-only stream + } + + /// + /// 从当前流读取字节序列,并将流内的位置提升读取的字节数。 + /// + /// 字节数组。当此方法返回时,该缓冲区包含从当前源读取的字节。 + /// 中的从零开始的字节偏移量,从此处开始存储从当前流中读取的数据。 + /// 要从当前流中读取的最大字节数。 + /// 读入缓冲区中的总字节数。如果当前可用的字节数少于所请求的字节数,则总字节数可能会少于所请求的字节数;如果已到达流的末尾,则为零。 + /// + /// 为负数。 + /// 的组合无效。 + public override int Read(byte[] buffer, int offset, int count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (offset < 0) + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (offset + count > buffer.Length) + { + throw new ArgumentException("Invalid offset and count combination"); + } + + var remainingBytes = this.m_memory.Length - this.m_position; + var bytesToRead = Math.Min(count, remainingBytes); + + if (bytesToRead <= 0) + { + return 0; + } + + var span = this.m_memory.Span.Slice(this.m_position, bytesToRead); + span.CopyTo(buffer.AsSpan(offset, bytesToRead)); + this.m_position += bytesToRead; + + return bytesToRead; + } + + /// + /// 设置当前流内的位置。 + /// + /// 相对于参数的字节偏移量。 + /// 类型的值,指示用于获取新位置的参考点。 + /// 当前流中的新位置。 + /// 为无效值时抛出。 + /// 当计算出的新位置超出流范围时抛出。 + public override long Seek(long offset, SeekOrigin origin) + { + var newPosition = origin switch + { + SeekOrigin.Begin => offset, + SeekOrigin.Current => this.m_position + offset, + SeekOrigin.End => this.m_memory.Length + offset, + _ => throw new ArgumentException("Invalid seek origin", nameof(origin)) + }; + + if (newPosition < 0 || newPosition > this.m_memory.Length) + throw new ArgumentOutOfRangeException(nameof(offset)); + + this.m_position = (int)newPosition; + return this.m_position; + } + + /// + /// 设置当前流的长度。 + /// + /// 所需的当前流的长度(以字节表示)。 + /// 只读流不支持设置长度操作。 + /// + /// 只读流不支持修改长度,调用此方法将抛出。 + /// + public override void SetLength(long value) + { + throw new NotSupportedException("Cannot set length on a read-only stream"); + } + + /// + /// 将字节序列写入当前流,并将此流中的当前位置提升写入的字节数。 + /// + /// 包含要写入当前流的数据的字节数组。 + /// 中的从零开始的字节偏移量,从此处开始将字节复制到当前流。 + /// 要写入当前流的字节数。 + /// 只读流不支持写入操作。 + /// + /// 只读流不支持写入操作,调用此方法将抛出。 + /// + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException("Cannot write to a read-only stream"); + } +} \ No newline at end of file diff --git a/src/TouchSocket.Core/BytesPool/Writer/BytesWriter.cs b/src/TouchSocket.Core/BytesPool/Writer/BytesWriter.cs new file mode 100644 index 000000000..c77eba1f3 --- /dev/null +++ b/src/TouchSocket.Core/BytesPool/Writer/BytesWriter.cs @@ -0,0 +1,105 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +namespace TouchSocket.Core; + +/// +/// 表示一个基于固定内存的字节写入器,提供高性能的字节缓冲区写入功能。 +/// 实现了接口。 +/// +/// +/// BytesWriter作为值类型实现,适用于高频使用且对性能要求较高的场景。 +/// 基于固定大小的内存块进行操作,不支持自动扩容,当空间不足时会抛出异常。 +/// +public struct BytesWriter : IBytesWriter +{ + private readonly Memory m_memory; + + private int m_position = 0; + + /// + /// 使用指定的内存块初始化的新实例。 + /// + /// 要写入的内存块。 + public BytesWriter(Memory memory) + { + this.m_memory = memory; + } + + /// + /// 获取完整的内存块。 + /// + /// 表示完整内存块的 + public readonly Memory TotalMemory => this.m_memory; + + /// + /// 获取已写入数据的只读跨度。 + /// + /// 表示从索引0到当前位置的已写入数据的 + public readonly ReadOnlySpan Span => this.m_memory.Span.Slice(0, this.m_position); + + /// + public readonly long WrittenCount => this.m_position; + + /// + public readonly bool SupportsRewind => true; + + /// + public readonly short Version => 0; + + /// + /// 获取或设置当前写入位置。 + /// + /// 表示当前写入位置的索引值。 + public int Position { readonly get => this.m_position; set => this.m_position = value; } + + /// + public void Advance(int count) + { + this.m_position += count; + if (this.m_position > this.m_memory.Length) + { + throw new InvalidOperationException("Advance exceeds the available buffer."); + } + } + + /// + public readonly Memory GetMemory(int sizeHint = 0) + { + if (this.m_position + sizeHint > this.m_memory.Length) + { + throw new InvalidOperationException("Insufficient space to get memory."); + } + return this.m_memory.Slice(this.m_position, sizeHint); + } + + /// + public readonly Span GetSpan(int sizeHint = 0) + { + return this.m_position + sizeHint > this.m_memory.Length + ? throw new InvalidOperationException("Insufficient space to get span.") + : this.m_memory.Span.Slice(this.m_position, sizeHint); + } + + /// + public void Write(scoped ReadOnlySpan span) + { + var length = span.Length; + if (length == 0) + { + return; + } + var tempSpan = this.GetSpan(length); + span.CopyTo(tempSpan); + this.Advance(length); + } +} \ No newline at end of file diff --git a/src/TouchSocket.Core/BytesPool/Writer/IByteBlockWriter.cs b/src/TouchSocket.Core/BytesPool/Writer/IByteBlockWriter.cs new file mode 100644 index 000000000..318341ef8 --- /dev/null +++ b/src/TouchSocket.Core/BytesPool/Writer/IByteBlockWriter.cs @@ -0,0 +1,61 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +namespace TouchSocket.Core; + +/// +/// 表示字节块写入器接口,提供字节块的写入功能和容量管理。 +/// 继承自接口。 +/// +/// +/// IByteBlockWriter接口结合了通用字节写入和字节块核心功能, +/// 为字节块的写入操作提供了专门的接口定义,并增加了容量管理能力。 +/// +public interface IByteBlockWriter : IBytesWriter, IByteBlockCore +{ + /// + /// 获取字节块的总容量。 + /// + /// 表示字节块可以存储的最大字节数。 + /// + /// 容量通常大于或等于,表示已分配的内存大小。 + /// + int Capacity { get; } + + /// + /// 获取字节块的可用空间长度。 + /// + /// 表示从当前位置到容量末尾还可以写入的字节数。 + /// + /// 此值等于的差值。 + /// + int FreeLength { get; } + + /// + /// 获取字节块的完整内存表示形式。 + /// + /// 返回一个,表示字节块的完整内存空间,从索引0到 + /// + /// 与不同,此属性返回完整的已分配内存,而不仅仅是有效数据部分。 + /// + Memory TotalMemory { get; } + + /// + /// 设置字节块的有效数据长度。 + /// + /// 要设置的新长度值。 + /// 超过时抛出。 + /// + /// 此方法允许直接修改字节块的有效数据长度,通常用于预先知道数据大小的场景。 + /// + void SetLength(int value); +} \ No newline at end of file diff --git a/src/TouchSocket.Core/BytesPool/Writer/IBytesWriter.cs b/src/TouchSocket.Core/BytesPool/Writer/IBytesWriter.cs new file mode 100644 index 000000000..341f4a6a7 --- /dev/null +++ b/src/TouchSocket.Core/BytesPool/Writer/IBytesWriter.cs @@ -0,0 +1,67 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.Buffers; + +namespace TouchSocket.Core; + +/// +/// 表示一个字节写入器接口,用于提供高性能的字节缓冲区写入功能。 +/// 继承自接口,扩展了版本控制、计数统计和回退支持等功能。 +/// +/// +/// IBytesWriter提供了对字节缓冲区的写入操作,支持获取内存、推进位置等基本功能, +/// 同时增加了版本管理、写入计数统计和回退操作支持等高级功能。 +/// 适用于需要高性能字节写入和精确控制的场景。 +/// +public interface IBytesWriter : IBufferWriter +{ + /// + /// 获取写入器的版本号。 + /// + /// 表示写入器当前版本的短整型数值。 + /// + /// 版本号用于检测写入器的状态变化,通常在支持回退操作时用于验证写入器的一致性。 + /// 当写入器的内部状态发生变化时(如缓冲区重新分配),版本号可能会发生变化。 + /// + short Version { get; } + + /// + /// 获取已写入的字节总数。 + /// + /// 表示从写入器创建以来已写入的字节数量。 + /// + /// 此值会随着写入操作的进行而累积增加,提供了写入操作的统计信息。 + /// 可用于跟踪写入进度或进行性能分析。 + /// + long WrittenCount { get; } + + /// + /// 获取一个值,该值指示写入器是否支持回退操作。 + /// + /// 如果写入器支持回退到之前的位置,则为;否则为 + /// + /// 回退功能允许写入器返回到之前的写入位置,这在需要重写或修正之前写入内容的场景中非常有用。 + /// 不是所有的写入器实现都支持此功能,具体取决于底层的缓冲区管理策略。 + /// + bool SupportsRewind { get; } + + /// + /// 将指定的字节跨度写入到写入器中。 + /// + /// 要写入的字节只读跨度。 + /// + /// 此方法提供了直接写入字节数据的便捷方式,等效于获取内存、复制数据、然后推进位置的组合操作。 + /// 写入操作会自动更新计数。 + /// + void Write(scoped ReadOnlySpan span); +} diff --git a/src/TouchSocket.Core/BytesPool/Writer/PipeBytesWriter.cs b/src/TouchSocket.Core/BytesPool/Writer/PipeBytesWriter.cs new file mode 100644 index 000000000..c8d98cf56 --- /dev/null +++ b/src/TouchSocket.Core/BytesPool/Writer/PipeBytesWriter.cs @@ -0,0 +1,103 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.IO.Pipelines; + +namespace TouchSocket.Core; + +/// +/// 表示一个基于的字节写入器,提供对管道写入器的高性能包装。 +/// 实现了接口,支持异步刷新操作。 +/// +/// +/// PipeBytesWriter作为值类型实现,为System.IO.Pipelines的PipeWriter提供了IBytesWriter接口的适配。 +/// 适用于需要与管道系统集成的高性能字节写入场景,不支持回退操作。 +/// 提供了异步刷新功能,可以与异步I/O操作良好配合。 +/// +public struct PipeBytesWriter : IBytesWriter +{ + private readonly PipeWriter m_writer; + private long m_writtenCount; + + /// + /// 使用指定的管道写入器初始化的新实例。 + /// + /// 底层的管道写入器实例。 + public PipeBytesWriter(PipeWriter writer) + { + this.m_writer = writer; + } + + /// + /// + /// 管道写入器不支持回退操作,始终返回。 + /// + public readonly bool SupportsRewind => false; + + /// + /// + /// 管道写入器的版本号固定为0,因为不支持版本跟踪。 + /// + public readonly short Version => 0; + + /// + public readonly long WrittenCount => this.m_writtenCount; + + /// + /// + /// 推进底层管道写入器的位置,同时更新写入计数。 + /// + public void Advance(int count) + { + this.m_writer.Advance(count); + this.m_writtenCount += count; + } + + /// + /// 异步刷新底层管道写入器,确保缓冲的数据被写入到目标。 + /// + /// 用于取消操作的取消令牌。 + /// 表示异步刷新操作的,包含刷新结果。 + /// + /// 此方法提供了对底层PipeWriter.FlushAsync方法的直接访问, + /// 允许调用方控制何时将缓冲数据刷新到目标流。 + /// + public readonly ValueTask FlushAsync(CancellationToken cancellationToken = default) + { + return this.m_writer.FlushAsync(cancellationToken); + } + + /// + public readonly Memory GetMemory(int sizeHint = 0) + { + return this.m_writer.GetMemory(sizeHint); + } + + /// + public readonly Span GetSpan(int sizeHint = 0) + { + return this.m_writer.GetSpan(sizeHint); + } + + /// + public void Write(scoped ReadOnlySpan span) + { + var length = span.Length; + if (length == 0) + { + return; + } + var memory = this.GetMemory(length); + span.CopyTo(memory.Span); + this.Advance(length); + } +} \ No newline at end of file diff --git a/src/TouchSocket.Core/BytesPool/Writer/SegmentedBytesWriter.cs b/src/TouchSocket.Core/BytesPool/Writer/SegmentedBytesWriter.cs new file mode 100644 index 000000000..2c73e5ef7 --- /dev/null +++ b/src/TouchSocket.Core/BytesPool/Writer/SegmentedBytesWriter.cs @@ -0,0 +1,497 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.Buffers; +using System.Runtime.CompilerServices; +#if CollectionsMarshal +using System.Runtime.InteropServices; +#endif + +namespace TouchSocket.Core; + +/// +/// 表示一个分段字节写入器,提供高效的多段缓冲区写入功能。 +/// +/// +/// SegmentedBytesWriter使用列表结构的缓冲段来管理内存,当单个段不足时会自动创建新段。 +/// 每个段的最小大小为4096字节,支持动态扩展。所有缓冲区都使用进行内存池管理。 +/// +public sealed class SegmentedBytesWriter : DisposableObject, IBytesWriter +{ + private const int MinBufferSize = 4096; // 增加最小缓冲区大小 + + private readonly List m_segments; // 使用可扩展列表以支持动态增长 + private readonly ArrayPool m_pool; + private readonly int m_initialCapacity; + private long m_totalBytesWritten; + + // 维护增量构建的序列节点,避免每次重建链表 + private BufferSegmentNode m_firstNode; + private BufferSegmentNode m_lastNode; + + + /// + /// 初始化 类的新实例,使用默认的最小缓冲区大小(4096字节)。 + /// + public SegmentedBytesWriter() : this(MinBufferSize) + { + } + /// + /// 使用指定的初始容量初始化的新实例。 + /// + /// 初始容量,最小为4096字节。 + /// + /// 初始容量如果小于4096字节,将自动调整为4096字节。 + /// + public SegmentedBytesWriter(int initialCapacity) : this(initialCapacity, ArrayPool.Shared) + { + } + + /// + /// 使用指定的初始容量和数组池初始化的新实例。 + /// + /// 初始容量,最小为4096字节。 + /// 用于租用缓冲区的实例。 + /// + /// 初始容量如果小于4096字节,将自动调整为4096字节。 + /// + public SegmentedBytesWriter(int initialCapacity, ArrayPool pool) + { + this.m_pool = pool ?? ArrayPool.Shared; + // 为基准测试优化:确保初始容量足够大以避免频繁分配 + this.m_initialCapacity = Math.Max(initialCapacity, MinBufferSize); + this.m_segments = new List(); + + this.AddNewSegment(this.m_initialCapacity); + } + + /// + /// 获取当前写入器的字节序列。 + /// + public ReadOnlySequence Sequence => this.GetSequence(); + + /// + /// 获取已写入的字节数。 + /// + public long WrittenCount => this.m_totalBytesWritten; + + /// + public short Version => 0; + + /// + public bool SupportsRewind => false; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Advance(int count) + { + if (count < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(count), count, 0); + } + + if (count == 0) + { + return; + } + +#if CollectionsMarshal + Span segSpan = CollectionsMarshal.AsSpan(this.m_segments); + if (segSpan.Length == 0) + { + ThrowHelper.ThrowInvalidOperationException("No segments available"); + } + + var currentSegment = segSpan[^1]; +#else + if (this.m_segments.Count == 0) + { + ThrowHelper.ThrowInvalidOperationException("No segments available"); + } + + var currentSegment = this.m_segments[^1]; +#endif + if (currentSegment.Available < count) + { + ThrowHelper.ThrowInvalidOperationException("Not enough space in current segment"); + } + + currentSegment.Advance(count); + // 更新节点内存范围 + currentSegment.Node.Memory = currentSegment.WrittenMemory; + + this.m_totalBytesWritten += count; + } + + /// + /// 清空所有段的数据,重置写入器到初始状态。 + /// + public void Clear() + { +#if CollectionsMarshal + Span segSpan = CollectionsMarshal.AsSpan(this.m_segments); + if (segSpan.Length == 0) + { + return; + } + + // 保留第一个段,释放其他段 + for (var i = 1; i < segSpan.Length; i++) + { + segSpan[i]?.Dispose(); + } +#else + if (this.m_segments.Count == 0) + { + return; + } + + // 保留第一个段,释放其他段 + for (var i = 1; i < this.m_segments.Count; i++) + { + this.m_segments[i]?.Dispose(); + } +#endif + if (this.m_segments.Count > 1) + { + this.m_segments.RemoveRange(1, this.m_segments.Count - 1); + } + + // 重置第一个段 +#if CollectionsMarshal + segSpan = CollectionsMarshal.AsSpan(this.m_segments); // 重新获取以确保一致性 + var first = segSpan[0]; +#else + var first = this.m_segments[0]; +#endif + first?.Reset(); + if (first is not null) + { + first.Node.Memory = first.WrittenMemory; // 应为长度为0 + } + + // 重置序列头尾 + this.m_firstNode = first?.Node; + this.m_lastNode = this.m_firstNode; + + this.m_totalBytesWritten = 0; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Memory GetMemory(int sizeHint = 0) + { + if (sizeHint <= 0) + { + sizeHint = 1024; // 增加默认值 + } + + this.EnsureCapacity(sizeHint); + return this.GetCurrentSegment().GetMemory(); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span GetSpan(int sizeHint = 0) + { + return this.GetMemory(sizeHint).Span; + } + + /// + /// 将指定的字节范围写入到缓冲区中。 + /// + /// 要写入的字节数据。 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(scoped ReadOnlySpan span) + { + var length = span.Length; + if (length == 0) + { + return; + } + + // 优化:预检查当前段是否有足够空间,避免不必要的分支 +#if CollectionsMarshal + Span segSpan = CollectionsMarshal.AsSpan(this.m_segments); + if (segSpan.Length > 0) + { + var currentSegment = segSpan[^1]; + if (currentSegment.Available >= length) + { + var targetSpan = currentSegment.GetSpan(); + span.CopyTo(targetSpan); + currentSegment.Advance(length); + this.m_totalBytesWritten += length; + + // 更新节点内存范围 + currentSegment.Node.Memory = currentSegment.WrittenMemory; + return; + } + } +#else + if (this.m_segments.Count > 0) + { + var currentSegment = this.m_segments[^1]; + if (currentSegment.Available >= length) + { + var targetSpan = currentSegment.GetSpan(); + span.CopyTo(targetSpan); + currentSegment.Advance(length); + this.m_totalBytesWritten += length; + + // 更新节点内存范围 + currentSegment.Node.Memory = currentSegment.WrittenMemory; + return; + } + } +#endif + + // 需要跨段写入时才调用复杂逻辑 + this.WriteAcrossSegments(span); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void WriteAcrossSegments(ReadOnlySpan span) + { + var remaining = span.Length; + var sourceOffset = 0; + + while (remaining > 0) + { + // 确保有足够的容量(此调用可能修改 List,故不要跨调用持有 span) + this.EnsureCapacity(remaining); + +#if CollectionsMarshal + Span segSpan = CollectionsMarshal.AsSpan(this.m_segments); + var currentSegment = segSpan[^1]; +#else + var currentSegment = this.m_segments[^1]; +#endif + var available = currentSegment.Available; + + if (available == 0) + { + // 这种情况不应该发生,因为 EnsureCapacity 应该保证有空间 + ThrowHelper.ThrowInvalidOperationException("No available space after ensuring capacity"); + } + + var copyCount = remaining < available ? remaining : available; + var sourceSlice = span.Slice(sourceOffset, copyCount); + var targetSpan = currentSegment.GetSpan(); + + sourceSlice.CopyTo(targetSpan); + currentSegment.Advance(copyCount); + currentSegment.Node.Memory = currentSegment.WrittenMemory; + + this.m_totalBytesWritten += copyCount; + remaining -= copyCount; + sourceOffset += copyCount; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private BufferSegment GetCurrentSegment() + { +#if CollectionsMarshal + Span segSpan = CollectionsMarshal.AsSpan(this.m_segments); + if (segSpan.Length == 0) + { + ThrowHelper.ThrowInvalidOperationException("No segments available"); + } + return segSpan[^1]; +#else + if (this.m_segments.Count == 0) + { + ThrowHelper.ThrowInvalidOperationException("No segments available"); + } + return this.m_segments[^1]; +#endif + } + + private void EnsureCapacity(int sizeHint) + { + if (sizeHint <= 0) + { + sizeHint = 1; + } + +#if CollectionsMarshal + Span segSpan = CollectionsMarshal.AsSpan(this.m_segments); + if (segSpan.Length == 0) + { + this.AddNewSegment(sizeHint > this.m_initialCapacity ? sizeHint : this.m_initialCapacity); + return; + } + + var currentSegment = segSpan[^1]; +#else + if (this.m_segments.Count == 0) + { + this.AddNewSegment(sizeHint > this.m_initialCapacity ? sizeHint : this.m_initialCapacity); + return; + } + + var currentSegment = this.m_segments[^1]; +#endif + if (sizeHint <= currentSegment.Available) + { + return; + } + + // 计算下一个段的大小:按需与指数增长结合,减少分段次数 + var nextSegmentSize = CalculateNextSegmentSize(sizeHint, currentSegment.ActualSize); + this.AddNewSegment(nextSegmentSize); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int CalculateNextSegmentSize(int requestedSize, int previousSize) + { + // 使用指数增长(上次大小*2),并满足本次需求,受最大值限制 + var grow = previousSize > 0 ? previousSize << 1 : MinBufferSize; + var baseSize = requestedSize > grow ? requestedSize : grow; + if (baseSize < MinBufferSize) + { + baseSize = MinBufferSize; + } + + return baseSize; + } + + private void AddNewSegment(int size) + { + var segment = new BufferSegment(this.m_pool, size); + this.m_segments.Add(segment); + + // 建立并链接节点;其起始索引为当前总写入量(之前段已定型) + var node = new BufferSegmentNode + { + Memory = segment.WrittenMemory, // 初始为长度0 + RunningIndex = this.m_totalBytesWritten + }; + + segment.Node = node; + + if (this.m_firstNode is null) + { + this.m_firstNode = node; + this.m_lastNode = node; + } + else + { + this.m_lastNode!.Next = node; + this.m_lastNode = node; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ReadOnlySequence GetSequence() + { + if (this.m_firstNode is null || this.m_lastNode is null) + { + return ReadOnlySequence.Empty; + } + return new ReadOnlySequence(this.m_firstNode, 0, this.m_lastNode, this.m_lastNode.Memory.Length); + } + + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { +#if CollectionsMarshal + Span segSpan = CollectionsMarshal.AsSpan(this.m_segments); + for (var i = 0; i < segSpan.Length; i++) + { + segSpan[i]?.Dispose(); + } +#else + for (var i = 0; i < this.m_segments.Count; i++) + { + this.m_segments[i]?.Dispose(); + } +#endif + this.m_segments.Clear(); + this.m_firstNode = null; + this.m_lastNode = null; + } + base.Dispose(disposing); + } + + #region Class + + private sealed class BufferSegment : IDisposable + { + private readonly ArrayPool m_pool; + private byte[] m_buffer; + private int m_writtenCount; + + public BufferSegmentNode Node { get; set; } = null!; + + public BufferSegment(ArrayPool pool, int requestedSize) + { + this.m_pool = pool; + this.m_buffer = pool.Rent(requestedSize); + } + + public int Available => this.m_buffer.Length - this.m_writtenCount; + public int WrittenCount => this.m_writtenCount; + public int ActualSize => this.m_buffer?.Length ?? 0; + public ReadOnlyMemory WrittenMemory => new ReadOnlyMemory(this.m_buffer, 0, this.m_writtenCount); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Advance(int count) => this.m_writtenCount += count; + + public void Dispose() + { + if (this.m_buffer != null) + { + this.m_pool.Return(this.m_buffer); + this.m_buffer = null; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Memory GetMemory() => this.m_buffer.AsMemory(this.m_writtenCount); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span GetSpan() => this.m_buffer.AsSpan(this.m_writtenCount); + + public void Reset() + { + this.m_writtenCount = 0; + } + } + + // 简化的ReadOnlySequenceSegment实现 + private sealed class BufferSegmentNode : ReadOnlySequenceSegment + { + public new ReadOnlyMemory Memory + { + get => base.Memory; + set => base.Memory = value; + } + + public new BufferSegmentNode Next + { + get => (BufferSegmentNode)base.Next; + set => base.Next = value; + } + + public new long RunningIndex + { + get => base.RunningIndex; + set => base.RunningIndex = value; + } + } + + #endregion Class +} \ No newline at end of file diff --git a/src/TouchSocket.Core/BytesPool/WriterAnchor.cs b/src/TouchSocket.Core/BytesPool/WriterAnchor.cs new file mode 100644 index 000000000..6478a523e --- /dev/null +++ b/src/TouchSocket.Core/BytesPool/WriterAnchor.cs @@ -0,0 +1,85 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +namespace TouchSocket.Core; + +/// +/// 表示字节写入器的锚点,用于记录特定位置和获取对应的字节跨度。 +/// +/// 实现接口的字节写入器类型。 +/// +/// WriterAnchor是一个只读的ref结构体,用于在字节写入操作中标记特定位置, +/// 并在后续操作中能够回退到该位置或获取从该位置开始的字节跨度。 +/// 支持版本检查以确保数据一致性,并提供回退功能用于高级字节写入场景。 +/// +public readonly ref struct WriterAnchor + where TWriter : IBytesWriter +{ + private readonly long m_position; + private readonly Span m_span; + private readonly short m_version; + private readonly int m_size; + + /// + /// 初始化写入器锚点的新实例。 + /// + /// 字节写入器引用。 + /// 要预留的字节跨度大小。 + /// + /// 此构造函数会记录当前写入器的位置和版本信息,并预留指定大小的字节跨度。 + /// 同时会推进写入器的位置以确保后续写入操作不会覆盖预留的空间。 + /// + public WriterAnchor(ref TWriter writer, int size) + { + this.m_version = writer.Version; + var span = writer.GetSpan(size).Slice(0, size); + this.m_span = span; + writer.Advance(size); + this.m_position = writer.WrittenCount; + this.m_size = size; + } + + /// + /// 回退到锚点位置并获取对应的字节跨度。 + /// + /// 字节写入器引用。 + /// 输出参数,表示从锚点位置到当前位置的数据长度。 + /// 对应锚点位置的字节跨度。 + /// 当写入器版本不匹配或不支持回退操作时抛出异常。 + /// + /// 此方法会计算从锚点创建时到当前位置的数据长度。 + /// 如果写入器版本未改变,则直接返回缓存的字节跨度; + /// 如果版本已改变但写入器支持回退操作,则通过回退重新获取字节跨度; + /// 否则抛出异常表示操作无效。 + /// + public Span Rewind(ref TWriter writer, out int length) + { + length = (int)(writer.WrittenCount - this.m_position); + + if (this.m_version == writer.Version) + { + return this.m_span; + } + else if (writer.SupportsRewind) + { + writer.Advance(-(length + this.m_size)); + var span = writer.GetSpan(this.m_span.Length).Slice(0, this.m_span.Length); + writer.Advance(length + this.m_size); + return span; + } + else + { + ThrowHelper.ThrowInvalidOperationException("Writer version mismatch or does not support rewind."); + return []; + } + } +} \ No newline at end of file diff --git a/src/TouchSocket.Core/Caching/CacheEntry.cs b/src/TouchSocket.Core/Caching/CacheEntry.cs index 06e0881b1..193dbbde2 100644 --- a/src/TouchSocket.Core/Caching/CacheEntry.cs +++ b/src/TouchSocket.Core/Caching/CacheEntry.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/Caching/CacheManagementExtensions.cs b/src/TouchSocket.Core/Caching/CacheManagementExtensions.cs index f8b841384..acda72a98 100644 --- a/src/TouchSocket.Core/Caching/CacheManagementExtensions.cs +++ b/src/TouchSocket.Core/Caching/CacheManagementExtensions.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/Caching/ICache.cs b/src/TouchSocket.Core/Caching/ICache.cs index c6398ece8..4eaf7e9c9 100644 --- a/src/TouchSocket.Core/Caching/ICache.cs +++ b/src/TouchSocket.Core/Caching/ICache.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/Caching/ICacheAsync.cs b/src/TouchSocket.Core/Caching/ICacheAsync.cs index a9be66093..20385657e 100644 --- a/src/TouchSocket.Core/Caching/ICacheAsync.cs +++ b/src/TouchSocket.Core/Caching/ICacheAsync.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; - namespace TouchSocket.Core; /// @@ -24,43 +21,48 @@ public partial interface ICacheAsync /// 添加缓存。当缓存存在时,不会添加成功。 /// /// 要添加的缓存项,类型为ICacheEntry泛型接口。 + /// /// 返回一个类型的异步操作结果,表示添加缓存项的操作是否成功。 - Task AddCacheAsync(ICacheEntry entity); + Task AddCacheAsync(ICacheEntry entity, CancellationToken cancellationToken = default); /// /// 清空所有缓存 /// /// 一个异步任务,表示清空缓存操作 - Task ClearCacheAsync(); + Task ClearCacheAsync(CancellationToken cancellationToken = default); /// /// 异步判断指定键的缓存是否存在且在生命周期内。 /// /// 缓存的键。 + /// /// 返回一个Task对象,其结果指示缓存是否存在且在生命周期内。 /// 当键值为空时抛出。 - Task ContainsCacheAsync(TKey key); + Task ContainsCacheAsync(TKey key, CancellationToken cancellationToken = default); /// /// 异步获取指定键的缓存条目。 /// /// 用于检索缓存条目的键。 + /// /// 返回一个任务,该任务结果包含缓存条目。 /// 当键为时抛出此异常。 - Task> GetCacheAsync(TKey key); + Task> GetCacheAsync(TKey key, CancellationToken cancellationToken = default); /// /// 异步移除缓存项。 /// /// 缓存项的键。 + /// /// 移除操作是否成功的布尔值。 - Task RemoveCacheAsync(TKey key); + Task RemoveCacheAsync(TKey key, CancellationToken cancellationToken = default); /// /// 设置缓存,不管缓存存不存在,都会添加。 /// /// 要添加到缓存中的项,类型为ICacheEntry泛型接口。 + /// /// 返回一个类型的异步操作结果,表示缓存设置操作是否成功。 /// 当尝试将null作为缓存项添加时,抛出此异常。 - Task SetCacheAsync(ICacheEntry entity); + Task SetCacheAsync(ICacheEntry entity, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/TouchSocket.Core/Caching/ICacheEntry.cs b/src/TouchSocket.Core/Caching/ICacheEntry.cs index 2a8a01e02..4b8a942a3 100644 --- a/src/TouchSocket.Core/Caching/ICacheEntry.cs +++ b/src/TouchSocket.Core/Caching/ICacheEntry.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/Caching/MemoryCache.cs b/src/TouchSocket.Core/Caching/MemoryCache.cs index d90d20546..a987b7ccf 100644 --- a/src/TouchSocket.Core/Caching/MemoryCache.cs +++ b/src/TouchSocket.Core/Caching/MemoryCache.cs @@ -10,12 +10,8 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Collections; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; namespace TouchSocket.Core; @@ -61,7 +57,7 @@ public class MemoryCache : IEnumerable>, } /// - public Task AddCacheAsync(ICacheEntry entity) + public Task AddCacheAsync(ICacheEntry entity, CancellationToken cancellationToken = default) { return Task.FromResult(this.AddCache(entity)); } @@ -73,7 +69,7 @@ public class MemoryCache : IEnumerable>, } /// - public Task ClearCacheAsync() + public Task ClearCacheAsync(CancellationToken cancellationToken = default) { this.ClearCache(); return EasyTask.CompletedTask; @@ -105,7 +101,7 @@ public class MemoryCache : IEnumerable>, } /// - public Task ContainsCacheAsync(TKey key) + public Task ContainsCacheAsync(TKey key, CancellationToken cancellationToken = default) { return Task.FromResult(this.ContainsCache(key)); } @@ -136,7 +132,7 @@ public class MemoryCache : IEnumerable>, } /// - public Task> GetCacheAsync(TKey key) + public Task> GetCacheAsync(TKey key, CancellationToken cancellationToken = default) { return Task.FromResult(this.GetCache(key)); } @@ -165,7 +161,7 @@ public class MemoryCache : IEnumerable>, } /// - public Task RemoveCacheAsync(TKey key) + public Task RemoveCacheAsync(TKey key, CancellationToken cancellationToken = default) { return Task.FromResult(this.RemoveCache(key)); } @@ -181,7 +177,7 @@ public class MemoryCache : IEnumerable>, } /// - public Task SetCacheAsync(ICacheEntry entity) + public Task SetCacheAsync(ICacheEntry entity, CancellationToken cancellationToken = default) { return Task.FromResult(this.SetCache(entity)); } diff --git a/src/TouchSocket.Core/CodeAnalysis/Attributes/AsyncToSyncWarningAttribute.cs b/src/TouchSocket.Core/CodeAnalysis/Attributes/AsyncToSyncWarningAttribute.cs index 4edb0556f..f9624d9e3 100644 --- a/src/TouchSocket.Core/CodeAnalysis/Attributes/AsyncToSyncWarningAttribute.cs +++ b/src/TouchSocket.Core/CodeAnalysis/Attributes/AsyncToSyncWarningAttribute.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 // ------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/CodeAnalysis/CodeAnalysis_AOT.cs b/src/TouchSocket.Core/CodeAnalysis/CodeAnalysis_AOT.cs new file mode 100644 index 000000000..3d561f7ad --- /dev/null +++ b/src/TouchSocket.Core/CodeAnalysis/CodeAnalysis_AOT.cs @@ -0,0 +1,122 @@ +//------------------------------------------------------------------------------ +//此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +//------------------------------------------------------------------------------ + + + +namespace System.Diagnostics.CodeAnalysis; + +#if NET462 || NETSTANDARD2_0 || NETSTANDARD2_1||NET6_0 +/// +/// 指示方法需要动态代码支持的特性 +/// +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Class, Inherited = false)] +public sealed class RequiresDynamicCodeAttribute : Attribute +{ + /// + /// 初始化 类的新实例 + /// + /// 描述为什么需要动态代码的消息 + public RequiresDynamicCodeAttribute(string message) + { + this.Message = message; + } + + /// + /// 获取描述为什么需要动态代码的消息 + /// + public string Message { get; } + + /// + /// 获取或设置可选的URL,提供有关该方法为何需要动态代码的更多信息 + /// + public string Url { get; set; } +} +#endif + + +#if NET462 || NETSTANDARD2_0 || NETSTANDARD2_1 +/// +/// 无条件抑制消息特性 +/// +[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)] +public sealed class UnconditionalSuppressMessageAttribute : Attribute +{ + /// + /// 初始化 类的新实例 + /// + /// 抑制的类别 + /// 抑制的检查ID + public UnconditionalSuppressMessageAttribute(string category, string checkId) + { + this.Category = category; + this.CheckId = checkId; + } + + /// + /// 获取抑制的类别 + /// + public string Category { get; } + + /// + /// 获取抑制的检查ID + /// + public string CheckId { get; } + + /// + /// 获取或设置抑制的理由 + /// + public string Justification { get; set; } + + /// + /// 获取或设置消息ID + /// + public string MessageId { get; set; } + + /// + /// 获取或设置范围 + /// + public string Scope { get; set; } + + /// + /// 获取或设置目标 + /// + public string Target { get; set; } +} +#endif + +#if NET462 || NETSTANDARD2_0 || NETSTANDARD2_1 +/// +/// 指示方法需要未引用的代码的特性 +/// +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Class, Inherited = false)] +public sealed class RequiresUnreferencedCodeAttribute : Attribute +{ + /// + /// 初始化 类的新实例 + /// + /// 描述为什么需要未引用代码的消息 + public RequiresUnreferencedCodeAttribute(string message) + { + this.Message = message; + } + + /// + /// 获取描述为什么需要未引用代码的消息 + /// + public string Message { get; } + + /// + /// 获取或设置可选的URL,提供有关该方法为何需要未引用代码的更多信息 + /// + public string Url { get; set; } +} +#endif \ No newline at end of file diff --git a/src/TouchSocket.Core/CodeAnalysis/CodeAnalysis_NET45_OR_GREATER.cs b/src/TouchSocket.Core/CodeAnalysis/CodeAnalysis_NET45_OR_GREATER.cs deleted file mode 100644 index 4e9dc7c80..000000000 --- a/src/TouchSocket.Core/CodeAnalysis/CodeAnalysis_NET45_OR_GREATER.cs +++ /dev/null @@ -1,53 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -//#if NET45_OR_GREATER || NETSTANDARD2_0 -//using System; -//using System.Collections.Generic; -//using System.Linq; -//using System.Text; -//using System.Threading.Tasks; - -//namespace System.Diagnostics.CodeAnalysis -//{ -// /// -// /// 表示方法在返回特定值时不会返回 null。 -// /// -// /// -// /// 该属性用于对方法的返回值进行说明,即在方法返回特定值时,其返回的对象不会是 null。 -// /// 这对于代码分析工具和编译器来说,是一个重要的元数据,可以帮助提高代码质量和安全性。 -// /// -// public sealed class NotNullWhenAttribute : Attribute -// { -// /// -// /// 初始化 NotNullWhenAttribute 类的实例。 -// /// -// /// -// /// 表示当方法返回此值时,方法的返回对象不会是 null。 -// /// -// public NotNullWhenAttribute(bool returnValue) -// { -// } -// } -// /// -// /// 指示方法不会返回值的属性。 -// /// -// /// -// /// 此属性用于指示方法在正常执行过程中不会返回控制权,例如,因为它会引发异常或执行无限循环。 -// /// -// [AttributeUsage(AttributeTargets.Method, Inherited = false)] -// public sealed class DoesNotReturnAttribute : Attribute -// { - -// } -//} -//#endif \ No newline at end of file diff --git a/src/TouchSocket.Core/Collections/AsyncQueue.cs b/src/TouchSocket.Core/Collections/AsyncQueue.cs index 37c4cb9ca..c4607ac21 100644 --- a/src/TouchSocket.Core/Collections/AsyncQueue.cs +++ b/src/TouchSocket.Core/Collections/AsyncQueue.cs @@ -10,11 +10,7 @@ // 感谢您的下载和使用 // ------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; using TouchSocket.Resources; namespace TouchSocket.Core; @@ -330,6 +326,17 @@ public class AsyncQueue } } + /// + /// 清空队列中的所有元素。 + /// + public void Clear() + { + lock (this.SyncRoot) + { + this.m_queueElements?.Clear(); + } + } + /// /// 如果队列头部有满足指定检查的可用元素,则立即将其出队; /// 否则返回而不出队。 diff --git a/src/TouchSocket.Core/Collections/Concurrent/ConcurrentList.cs b/src/TouchSocket.Core/Collections/Concurrent/ConcurrentList.cs index 1e0206a9f..e922cce82 100644 --- a/src/TouchSocket.Core/Collections/Concurrent/ConcurrentList.cs +++ b/src/TouchSocket.Core/Collections/Concurrent/ConcurrentList.cs @@ -10,32 +10,29 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Collections; -using System.Collections.Generic; -using System.Linq; namespace TouchSocket.Core; /// -/// 线程安全的List,其基本操作和List一致。 +/// 线程安全的,其基本操作和一致。 /// -/// +/// 元素类型 public class ConcurrentList : IList, IReadOnlyList { private readonly List m_list; /// - /// 构造函数 + /// 初始化类的新实例。 /// - /// + /// 用于填充列表的集合。 public ConcurrentList(IEnumerable collection) { this.m_list = new List(collection); } /// - /// 构造函数 + /// 初始化类的新实例。 /// public ConcurrentList() { @@ -43,17 +40,15 @@ public class ConcurrentList : IList, IReadOnlyList } /// - /// 构造函数 + /// 初始化类的新实例。 /// - /// + /// 初始容量。 public ConcurrentList(int capacity) { this.m_list = new List(capacity); } - /// - /// 元素数量 - /// + /// public int Count { get @@ -65,16 +60,10 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// 是否为只读 - /// + /// public bool IsReadOnly => false; - /// - /// 获取索引元素 - /// - /// - /// + /// public T this[int index] { get @@ -93,10 +82,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// 添加元素 - /// - /// + /// public void Add(T item) { lock (((ICollection)this.m_list).SyncRoot) @@ -105,9 +91,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// 清空所有元素 - /// + /// public void Clear() { lock (((ICollection)this.m_list).SyncRoot) @@ -116,11 +100,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// 是否包含某个元素 - /// - /// - /// + /// public bool Contains(T item) { lock (((ICollection)this.m_list).SyncRoot) @@ -129,11 +109,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// 复制到 - /// - /// - /// + /// public void CopyTo(T[] array, int arrayIndex) { lock (((ICollection)this.m_list).SyncRoot) @@ -142,10 +118,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// 返回迭代器 - /// - /// + /// public IEnumerator GetEnumerator() { lock (((ICollection)this.m_list).SyncRoot) @@ -154,10 +127,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// 返回迭代器组合 - /// - /// + /// IEnumerator IEnumerable.GetEnumerator() { lock (((ICollection)this.m_list).SyncRoot) @@ -166,11 +136,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// 索引 - /// - /// - /// + /// public int IndexOf(T item) { lock (((ICollection)this.m_list).SyncRoot) @@ -179,11 +145,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// 插入 - /// - /// - /// + /// public void Insert(int index, T item) { lock (((ICollection)this.m_list).SyncRoot) @@ -192,11 +154,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// 移除元素 - /// - /// - /// + /// public bool Remove(T item) { lock (((ICollection)this.m_list).SyncRoot) @@ -205,10 +163,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// 按索引移除 - /// - /// + /// public void RemoveAt(int index) { lock (((ICollection)this.m_list).SyncRoot) @@ -221,7 +176,7 @@ public class ConcurrentList : IList, IReadOnlyList } /// - /// 获取或设置容量 + /// 获取或设置容量。 /// public int Capacity { @@ -241,10 +196,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// - /// - /// + /// public void AddRange(IEnumerable collection) { lock (((ICollection)this.m_list).SyncRoot) @@ -253,11 +205,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// - /// - /// - /// + /// public int BinarySearch(T item) { lock (((ICollection)this.m_list).SyncRoot) @@ -266,12 +214,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// - /// - /// - /// - /// + /// public int BinarySearch(T item, IComparer comparer) { lock (((ICollection)this.m_list).SyncRoot) @@ -280,14 +223,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// - /// - /// - /// - /// - /// - /// + /// public int BinarySearch(int index, int count, T item, IComparer comparer) { lock (((ICollection)this.m_list).SyncRoot) @@ -296,12 +232,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// - /// - /// - /// - /// + /// public List ConvertAll(Converter converter) { lock (((ICollection)this.m_list).SyncRoot) @@ -310,11 +241,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// - /// - /// - /// + /// public T Find(Predicate match) { lock (((ICollection)this.m_list).SyncRoot) @@ -323,11 +250,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// - /// - /// - /// + /// public List FindAll(Predicate match) { lock (((ICollection)this.m_list).SyncRoot) @@ -336,13 +259,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// - /// - /// - /// - /// - /// + /// public int FindIndex(int startIndex, int count, Predicate match) { lock (((ICollection)this.m_list).SyncRoot) @@ -351,12 +268,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// - /// - /// - /// - /// + /// public int FindIndex(int startIndex, Predicate match) { lock (((ICollection)this.m_list).SyncRoot) @@ -365,11 +277,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// - /// - /// - /// + /// public int FindIndex(Predicate match) { lock (((ICollection)this.m_list).SyncRoot) @@ -378,11 +286,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// - /// - /// - /// + /// public T FindLast(Predicate match) { lock (((ICollection)this.m_list).SyncRoot) @@ -391,13 +295,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// - /// - /// - /// - /// - /// + /// public int FindLastIndex(int startIndex, int count, Predicate match) { lock (((ICollection)this.m_list).SyncRoot) @@ -406,12 +304,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// - /// - /// - /// - /// + /// public int FindLastIndex(int startIndex, Predicate match) { lock (((ICollection)this.m_list).SyncRoot) @@ -420,11 +313,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// - /// - /// - /// + /// public int FindLastIndex(Predicate match) { lock (((ICollection)this.m_list).SyncRoot) @@ -433,10 +322,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// - /// - /// + /// public void ForEach(Action action) { lock (((ICollection)this.m_list).SyncRoot) @@ -445,12 +331,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// - /// - /// - /// - /// + /// public List GetRange(int index, int count) { lock (((ICollection)this.m_list).SyncRoot) @@ -459,12 +340,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// - /// - /// - /// - /// + /// public int IndexOf(T item, int index) { lock (((ICollection)this.m_list).SyncRoot) @@ -473,13 +349,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// - /// - /// - /// - /// - /// + /// public int IndexOf(T item, int index, int count) { lock (((ICollection)this.m_list).SyncRoot) @@ -488,11 +358,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// - /// - /// - /// + /// public void InsertRange(int index, IEnumerable collection) { lock (((ICollection)this.m_list).SyncRoot) @@ -501,25 +367,16 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// - /// - /// - /// + /// public int LastIndexOf(T item) { lock (((ICollection)this.m_list).SyncRoot) { - return this.m_list.IndexOf(item); + return this.m_list.LastIndexOf(item); } } - /// - /// - /// - /// - /// - /// + /// public int LastIndexOf(T item, int index) { lock (((ICollection)this.m_list).SyncRoot) @@ -528,13 +385,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// - /// - /// - /// - /// - /// + /// public int LastIndexOf(T item, int index, int count) { lock (((ICollection)this.m_list).SyncRoot) @@ -543,10 +394,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// - /// - /// + /// public void RemoveAll(Predicate match) { lock (((ICollection)this.m_list).SyncRoot) @@ -555,11 +403,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// - /// - /// - /// + /// public void RemoveRange(int index, int count) { lock (((ICollection)this.m_list).SyncRoot) @@ -568,9 +412,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// - /// + /// public void Reverse() { lock (((ICollection)this.m_list).SyncRoot) @@ -579,11 +421,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// - /// - /// - /// + /// public void Reverse(int index, int count) { lock (((ICollection)this.m_list).SyncRoot) @@ -592,9 +430,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// - /// + /// public void Sort() { lock (((ICollection)this.m_list).SyncRoot) @@ -603,10 +439,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// - /// - /// + /// public void Sort(Comparison comparison) { lock (((ICollection)this.m_list).SyncRoot) @@ -615,10 +448,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// - /// - /// + /// public void Sort(IComparer comparer) { lock (((ICollection)this.m_list).SyncRoot) @@ -627,12 +457,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// - /// - /// - /// - /// + /// public void Sort(int index, int count, IComparer comparer) { lock (((ICollection)this.m_list).SyncRoot) @@ -641,10 +466,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// - /// - /// + /// public T[] ToArray() { lock (((ICollection)this.m_list).SyncRoot) @@ -653,9 +475,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// - /// + /// public void TrimExcess() { lock (((ICollection)this.m_list).SyncRoot) @@ -664,11 +484,7 @@ public class ConcurrentList : IList, IReadOnlyList } } - /// - /// - /// - /// - /// + /// public bool TrueForAll(Predicate match) { lock (((ICollection)this.m_list).SyncRoot) diff --git a/src/TouchSocket.Core/Collections/Concurrent/ConcurrentMultiDictionary.cs b/src/TouchSocket.Core/Collections/Concurrent/ConcurrentMultiDictionary.cs index 3d06a5731..1a25f8c42 100644 --- a/src/TouchSocket.Core/Collections/Concurrent/ConcurrentMultiDictionary.cs +++ b/src/TouchSocket.Core/Collections/Concurrent/ConcurrentMultiDictionary.cs @@ -11,7 +11,6 @@ //------------------------------------------------------------------------------ using System.Collections.Concurrent; -using System.Collections.Generic; namespace TouchSocket.Core; diff --git a/src/TouchSocket.Core/Collections/Concurrent/IntelligentConcurrentQueue.cs b/src/TouchSocket.Core/Collections/Concurrent/IntelligentConcurrentQueue.cs index 6f96b5708..2a17e0d2b 100644 --- a/src/TouchSocket.Core/Collections/Concurrent/IntelligentConcurrentQueue.cs +++ b/src/TouchSocket.Core/Collections/Concurrent/IntelligentConcurrentQueue.cs @@ -11,7 +11,6 @@ //------------------------------------------------------------------------------ using System.Collections.Concurrent; -using System.Threading; namespace TouchSocket.Core; diff --git a/src/TouchSocket.Core/Collections/Concurrent/IntelligentDataQueue.cs b/src/TouchSocket.Core/Collections/Concurrent/IntelligentDataQueue.cs index 60a7fbe00..eb0644162 100644 --- a/src/TouchSocket.Core/Collections/Concurrent/IntelligentDataQueue.cs +++ b/src/TouchSocket.Core/Collections/Concurrent/IntelligentDataQueue.cs @@ -10,9 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Collections.Concurrent; -using System.Threading; namespace TouchSocket.Core; diff --git a/src/TouchSocket.Core/Collections/IgnoreCaseNameValueCollection.cs b/src/TouchSocket.Core/Collections/IgnoreCaseNameValueCollection.cs index 1bbcaa86b..0e418eb2c 100644 --- a/src/TouchSocket.Core/Collections/IgnoreCaseNameValueCollection.cs +++ b/src/TouchSocket.Core/Collections/IgnoreCaseNameValueCollection.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Collections.Specialized; using System.Diagnostics; diff --git a/src/TouchSocket.Core/Collections/NameValueCollectionDebugView.cs b/src/TouchSocket.Core/Collections/NameValueCollectionDebugView.cs index 4956ee935..78d818103 100644 --- a/src/TouchSocket.Core/Collections/NameValueCollectionDebugView.cs +++ b/src/TouchSocket.Core/Collections/NameValueCollectionDebugView.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; diff --git a/src/TouchSocket.Core/Config/ConfigObject.cs b/src/TouchSocket.Core/Config/ConfigObject.cs index dd32c22f2..6c3908e0e 100644 --- a/src/TouchSocket.Core/Config/ConfigObject.cs +++ b/src/TouchSocket.Core/Config/ConfigObject.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Runtime.CompilerServices; using TouchSocket.Resources; diff --git a/src/TouchSocket.Core/Config/ILoadedConfigPlugin.cs b/src/TouchSocket.Core/Config/ILoadedConfigPlugin.cs index 2d4cc63b2..fafb02800 100644 --- a/src/TouchSocket.Core/Config/ILoadedConfigPlugin.cs +++ b/src/TouchSocket.Core/Config/ILoadedConfigPlugin.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/Config/ILoadingConfigPlugin.cs b/src/TouchSocket.Core/Config/ILoadingConfigPlugin.cs index 46e29edbf..40bd93f39 100644 --- a/src/TouchSocket.Core/Config/ILoadingConfigPlugin.cs +++ b/src/TouchSocket.Core/Config/ILoadingConfigPlugin.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/Config/ISetupConfigObject.cs b/src/TouchSocket.Core/Config/ISetupConfigObject.cs index d1b06c60e..cc0cfaaed 100644 --- a/src/TouchSocket.Core/Config/ISetupConfigObject.cs +++ b/src/TouchSocket.Core/Config/ISetupConfigObject.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/Config/PluginRaiseExtension.cs b/src/TouchSocket.Core/Config/PluginRaiseExtension.cs index 689b50aa0..83f946699 100644 --- a/src/TouchSocket.Core/Config/PluginRaiseExtension.cs +++ b/src/TouchSocket.Core/Config/PluginRaiseExtension.cs @@ -12,8 +12,8 @@ namespace TouchSocket.Core; -//[PluginRaise(typeof(ILoadingConfigPlugin))] -//[PluginRaise(typeof(ILoadedConfigPlugin))] -//internal static partial class PluginRaiseExtension -//{ -//} +[PluginRaise(typeof(ILoadingConfigPlugin))] +[PluginRaise(typeof(ILoadedConfigPlugin))] +internal static partial class PluginRaiseExtension +{ +} diff --git a/src/TouchSocket.Core/Config/SetupConfigObject.cs b/src/TouchSocket.Core/Config/SetupConfigObject.cs index 37143306a..fd361a874 100644 --- a/src/TouchSocket.Core/Config/SetupConfigObject.cs +++ b/src/TouchSocket.Core/Config/SetupConfigObject.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; - namespace TouchSocket.Core; /// @@ -42,20 +39,20 @@ public abstract class SetupConfigObject : ResolverConfigObject, ISetupConfigObje this.BuildConfig(config); - await this.PluginManager.RaiseAsync(typeof(ILoadingConfigPlugin), this.Resolver, this, new ConfigEventArgs(config)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.PluginManager.RaiseILoadingConfigPluginAsync(this.Resolver, this, new ConfigEventArgs(config)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); this.LoadConfig(config); - await this.PluginManager.RaiseAsync(typeof(ILoadedConfigPlugin), this.Resolver, this, new ConfigEventArgs(config)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.PluginManager.RaiseILoadedConfigPluginAsync(this.Resolver, this, new ConfigEventArgs(config)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// - protected override void Dispose(bool disposing) + protected override void SafetyDispose(bool disposing) { if (disposing) { this.m_scopedResolver.SafeDispose(); this.ClearConfig(); } - base.Dispose(disposing); + base.SafetyDispose(disposing); } /// @@ -78,7 +75,7 @@ public abstract class SetupConfigObject : ResolverConfigObject, ISetupConfigObje } if (!registrator.IsRegistered(typeof(ILog))) { - registrator.RegisterSingleton(new LoggerGroup()); + registrator.RegisterSingleton(new LoggerGroup()); } if (this.m_config.GetValue(TouchSocketCoreConfigExtension.ConfigureContainerProperty) is Action actionContainer) diff --git a/src/TouchSocket.Core/Config/TouchSocketConfig.cs b/src/TouchSocket.Core/Config/TouchSocketConfig.cs index 19ee28ed2..f54c40d6e 100644 --- a/src/TouchSocket.Core/Config/TouchSocketConfig.cs +++ b/src/TouchSocket.Core/Config/TouchSocketConfig.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/Config/TouchSocketCoreConfigExtension.cs b/src/TouchSocket.Core/Config/TouchSocketCoreConfigExtension.cs index cce16be96..6c6ca4993 100644 --- a/src/TouchSocket.Core/Config/TouchSocketCoreConfigExtension.cs +++ b/src/TouchSocket.Core/Config/TouchSocketCoreConfigExtension.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// @@ -64,12 +62,14 @@ public static class TouchSocketCoreConfigExtension /// /// 容器注册 /// + [GeneratorProperty(TargetType = typeof(TouchSocketConfig))] public static readonly DependencyProperty RegistratorProperty = new("Registrator", default); /// /// 容器提供者 /// + [GeneratorProperty(TargetType = typeof(TouchSocketConfig))] public static readonly DependencyProperty ResolverProperty = new("Resolver", null); @@ -97,32 +97,5 @@ public static class TouchSocketCoreConfigExtension // 返回配置对象,以支持链式调用 return config; } - - /// - /// 设置 - /// - /// 待设置的配置对象 - /// 要设置的解析器实例 - /// 返回配置对象 - public static TouchSocketConfig SetResolver(this TouchSocketConfig config, IResolver value) - { - config.SetValue(ResolverProperty, value); - return config; - } - - /// - /// 设置 - /// - /// 当前的配置对象 - /// 要设置的实例 - /// 返回配置对象自身,以便进行链式调用 - public static TouchSocketConfig SetRegistrator(this TouchSocketConfig config, IRegistrator value) - { - // 使用扩展方法的特性,通过调用config的SetValue方法来为RegistratorProperty属性设置值 - config.SetValue(RegistratorProperty, value); - // 返回配置对象自身,以支持链式调用 - return config; - } - #endregion 容器 } \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Channel/IDmtpChannelAsync.cs b/src/TouchSocket.Core/Container/Attribute/DependencyInjectAttribute.cs similarity index 79% rename from src/TouchSocket.Dmtp/Channel/IDmtpChannelAsync.cs rename to src/TouchSocket.Core/Container/Attribute/DependencyInjectAttribute.cs index ebfc7f9d3..9acc145d8 100644 --- a/src/TouchSocket.Dmtp/Channel/IDmtpChannelAsync.cs +++ b/src/TouchSocket.Core/Container/Attribute/DependencyInjectAttribute.cs @@ -10,15 +10,13 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Collections.Generic; -using TouchSocket.Core; +namespace TouchSocket.Core; -namespace TouchSocket.Dmtp; - -#if AsyncEnumerable - -public partial interface IDmtpChannel : IAsyncEnumerable +/// +/// 指定依赖类型。 +/// +[AttributeUsage(AttributeTargets.Constructor)] +public sealed class DependencyInjectAttribute : Attribute { -} -#endif \ No newline at end of file +} diff --git a/src/TouchSocket.Core/Container/Attribute/DependencyInjectionAttribute.cs b/src/TouchSocket.Core/Container/Attribute/DependencyInjectionAttribute.cs deleted file mode 100644 index 27dff8eae..000000000 --- a/src/TouchSocket.Core/Container/Attribute/DependencyInjectionAttribute.cs +++ /dev/null @@ -1,90 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; - -namespace TouchSocket.Core; - -/// -/// 指定依赖类型。 -/// -[AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Property | AttributeTargets.Method | AttributeTargets.Parameter)] -public class DependencyInjectAttribute : Attribute -{ - /// - /// 默认注入配置 - /// - public DependencyInjectAttribute() - { - } - - /// - /// 使用指定Key参数注入。 - /// - /// - public DependencyInjectAttribute(string key) - { - this.Key = key; - } - - /// - /// 类型,Key指定性注入。 - /// - /// - /// - public DependencyInjectAttribute(Type type, string key) - { - this.Key = key; - this.Type = type; - } - - /// - /// 类型,指定性注入。 - /// - /// - public DependencyInjectAttribute(Type type) - { - this.Key = string.Empty; - this.Type = type; - } - - /// - /// 指定键。 - /// - public string Key { get; } - - /// - /// 注入类型 - /// - public Type Type { get; } -} - -/// -/// 指定依赖类型。 -/// -[AttributeUsage(AttributeTargets.Class)] -public class DependencyTypeAttribute : Attribute -{ - /// - /// 初始化一个依赖类型。当确定某个类型仅以某种特定方式注入时,可以过滤不必要的注入操作,以提高效率。 - /// - /// 可以叠加位域 - public DependencyTypeAttribute(DependencyType type) - { - this.Type = type; - } - - /// - /// 支持类型。 - /// - public DependencyType Type { get; } -} \ No newline at end of file diff --git a/src/TouchSocket.Core/Container/Container.cs b/src/TouchSocket.Core/Container/Container.cs index 063d78de9..088b871ee 100644 --- a/src/TouchSocket.Core/Container/Container.cs +++ b/src/TouchSocket.Core/Container/Container.cs @@ -10,12 +10,9 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using TouchSocket.Resources; +using System.Diagnostics.CodeAnalysis; +using System.Linq.Expressions; namespace TouchSocket.Core; @@ -24,7 +21,8 @@ namespace TouchSocket.Core; /// public sealed class Container : IContainer { - private readonly ConcurrentDictionary m_registrations = new ConcurrentDictionary(); + private readonly ConcurrentDictionary m_services = new(); + private readonly ConcurrentDictionary> m_factories = new(); /// /// IOC容器 @@ -36,321 +34,423 @@ public sealed class Container : IContainer } /// - public IResolver BuildResolver() - { - return this; - } + public IResolver BuildResolver() => this; /// - public IScopedResolver CreateScopedResolver() - { - return new InternalScopedResolver(this); - } + public IScopedResolver CreateScopedResolver() => new ScopedResolver(this); /// public IEnumerable GetDescriptors() { - return this.m_registrations.Values; + return this.m_services.Values.Select(e => e.Descriptor); } /// - public object GetService(Type serviceType) - { - return this.Resolve(serviceType); - } + public object GetService(Type serviceType) => this.Resolve(serviceType); + /// - public bool IsRegistered(Type fromType, string key) - { - return this.m_registrations.ContainsKey($"{fromType.FullName}{key}"); - } + public bool IsRegistered(Type fromType) => this.IsRegistered(fromType, null); + /// - public bool IsRegistered(Type fromType) - { - return this.m_registrations.ContainsKey(fromType.FullName); - } + public void Register(DependencyDescriptor descriptor) => this.Register(descriptor, null); + /// - public void Register(DependencyDescriptor descriptor, string key) - { - var k = $"{descriptor.FromType.FullName}{key}"; - this.m_registrations.AddOrUpdate(k, descriptor, (k, v) => { return descriptor; }); - } + public object Resolve(Type fromType) => this.Resolve(fromType, null); + /// - public void Register(DependencyDescriptor descriptor) + public void Unregister(DependencyDescriptor descriptor) => this.Unregister(descriptor, null); + + /// + /// 判断某类型是否已经注册 + /// + /// 要检查的类型 + /// 与类型关联的唯一键 + /// 如果类型已注册,则返回 true;否则返回 false + public bool IsRegistered(Type fromType, object key) { - var k = descriptor.FromType.FullName; - this.m_registrations.AddOrUpdate(k, descriptor, (k, v) => { return descriptor; }); + var serviceKey = new ServiceKey(fromType, key); + return this.m_services.ContainsKey(serviceKey); } - /// - public object Resolve(Type fromType, string key) + /// + /// 添加类型描述符 + /// + /// 依赖描述符 + /// 注册键 + public void Register(DependencyDescriptor descriptor, object key) { - string k; - DependencyDescriptor descriptor; + var serviceKey = new ServiceKey(descriptor.FromType, key); + var entry = new ServiceEntry(descriptor); + // 如果是实例注册,则直接作为单例实例缓存 + if (descriptor.ToInstance != null) + { + entry.SingletonInstance = descriptor.ToInstance; + } + this.m_services.AddOrUpdate(serviceKey, entry, (k, v) => entry); + } + + /// + /// 解析给定类型和键对应的实例 + /// + /// 要解析的目标类型 + /// 可选的实例标识符 + /// 解析出的实例 + public object Resolve(Type fromType, object key) + { + var serviceKey = new ServiceKey(fromType, key); + + if (this.m_services.TryGetValue(serviceKey, out var entry)) + { + return this.ResolveFromEntry(entry, this); + } + + // 尝试泛型类型解析 if (fromType.IsGenericType) { - var type = fromType.GetGenericTypeDefinition(); - k = $"{type.FullName}{key}"; - if (this.m_registrations.TryGetValue(k, out descriptor)) + var genericTypeDefinition = fromType.GetGenericTypeDefinition(); + var genericServiceKey = new ServiceKey(genericTypeDefinition, key); + + if (this.m_services.TryGetValue(genericServiceKey, out var genericEntry)) { - if (descriptor.ImplementationFactory != null) - { - return descriptor.ImplementationFactory.Invoke(this); - } - - if (descriptor.Lifetime == Lifetime.Singleton) - { - if (descriptor.ToInstance != null) - { - return descriptor.ToInstance; - } - lock (descriptor) - { - if (descriptor.ToInstance != null) - { - return descriptor.ToInstance; - } - else - { - if (descriptor.ToType.IsGenericType) - { - return (descriptor.ToInstance = this.Create(descriptor, descriptor.ToType.MakeGenericType(fromType.GetGenericArguments()))); - } - else - { - return (descriptor.ToInstance = this.Create(descriptor, descriptor.ToType)); - } - } - } - } - - if (descriptor.ToType.IsGenericType) - { - return this.Create(descriptor, descriptor.ToType.MakeGenericType(fromType.GetGenericArguments())); - } - else - { - return this.Create(descriptor, descriptor.ToType); - } + return this.ResolveFromEntry(genericEntry, this, fromType.GetGenericArguments()); } } - k = $"{fromType.FullName}{key}"; - if (this.m_registrations.TryGetValue(k, out descriptor)) + + return null; + } + + /// + /// 移除注册信息 + /// + /// 依赖描述符 + /// 注册键 + public void Unregister(DependencyDescriptor descriptor, object key) + { + var serviceKey = new ServiceKey(descriptor.FromType, key); + this.m_services.TryRemove(serviceKey, out _); + } + + [UnconditionalSuppressMessage("AOT", "IL2072:RequiresUnreferencedCode", Justification = "通过依赖注册确保类型的构造函数可用")] + [UnconditionalSuppressMessage("Trim", "IL2026:RequiresUnreferencedCode", Justification = "通过依赖注册确保类型的构造函数可用")] + [UnconditionalSuppressMessage("AOT", "IL3050:RequiresDynamicCode", Justification = "通过依赖注册确保类型的构造函数可用")] + private object ResolveFromEntry(ServiceEntry entry, IResolver resolver, Type[] genericArguments = null) + { + var descriptor = entry.Descriptor; + + // 如果使用实例注册,直接返回该实例 + if (descriptor.ToInstance != null) { - if (descriptor.ImplementationFactory != null) + return descriptor.ToInstance; + } + + // 处理单例(包含工厂的情况):统一加锁并缓存 + if (descriptor.Lifetime == Lifetime.Singleton) + { + if (entry.SingletonInstance != null) { - return descriptor.ImplementationFactory.Invoke(this); + return entry.SingletonInstance; } + + lock (entry.Lock) + { + if (entry.SingletonInstance != null) + { + return entry.SingletonInstance; + } + + var instance = descriptor.ImplementationFactory != null + ? descriptor.ImplementationFactory(resolver) + : this.CreateInstance(descriptor.ToType, resolver, genericArguments); + + entry.SingletonInstance = instance; + descriptor.OnResolved?.Invoke(instance); + return instance; + } + } + + // 处理瞬态和作用域(在Container级别,作用域和瞬态表现相同):每次创建 + var newInstance = descriptor.ImplementationFactory != null + ? descriptor.ImplementationFactory(resolver) + : this.CreateInstance(descriptor.ToType, resolver, genericArguments); + descriptor.OnResolved?.Invoke(newInstance); + return newInstance; + } + + [RequiresUnreferencedCode("此方法使用表达式树和反射创建类型实例,可能在AOT环境下不兼容")] + [RequiresDynamicCode("此方法使用表达式树编译,在AOT环境下可能不兼容")] + [UnconditionalSuppressMessage("AOT", "IL2055:MakeGenericType", Justification = "泛型类型参数在运行时通过依赖注册确保可用")] + internal object CreateInstance([DynamicallyAccessedMembers( + AOT.Container)] Type implementationType, IResolver resolver, Type[] genericArguments = null) + { + var targetType = implementationType; + + // 处理泛型类型 + if (genericArguments != null && implementationType.IsGenericTypeDefinition) + { + targetType = implementationType.MakeGenericType(genericArguments); + } + + // 获取或创建工厂方法 + var factory = this.m_factories.GetOrAdd(targetType, CreateFactoryWrapper); + return factory(resolver); + } + + [RequiresUnreferencedCode("表达式树需要访问类型成员")] + [RequiresDynamicCode("表达式树编译需要动态代码生成")] + [UnconditionalSuppressMessage("Trim", "IL2070:GetConstructors", Justification = "类型已通过DynamicallyAccessedMembers标注确保构造函数可用")] + private static Func CreateFactoryWrapper([DynamicallyAccessedMembers(AOT.Container)] Type type) + { + return CreateFactory(type); + } + + [RequiresUnreferencedCode("表达式树需要访问类型成员")] + [UnconditionalSuppressMessage("AOT", "IL2070:GetConstructors", Justification = "AOT环境下构造函数访问是必需的")] + [UnconditionalSuppressMessage("Trim", "IL2026:RequiresUnreferencedCode", Justification = "表达式树编译需要访问类型成员")] + private static Func CreateFactory([DynamicallyAccessedMembers(AOT.Container)] Type implementationType) + { + var constructors = implementationType.GetConstructors(); + + if (constructors.Length == 0) + { + throw new InvalidOperationException($"类型 {implementationType.FullName} 没有公共构造函数"); + } + + // 选择参数最多的构造函数 + var constructor = constructors.OrderByDescending(c => c.GetParameters().Length).First(); + var parameters = constructor.GetParameters(); + + var resolverParam = Expression.Parameter(typeof(IResolver), "resolver"); + var parameterExpressions = new Expression[parameters.Length]; + + for (var i = 0; i < parameters.Length; i++) + { + var paramType = parameters[i].ParameterType; + + // 调用resolver.Resolve(Type)方法 + var resolveMethod = typeof(IResolver).GetMethod(nameof(IResolver.Resolve), new[] { typeof(Type) }); + var resolveCall = Expression.Call(resolverParam, resolveMethod, Expression.Constant(paramType)); + + // 如果参数类型是值类型或者不允许为空,需要特殊处理 + if (paramType.IsValueType) + { + // 值类型需要提供默认值 + var defaultValue = Expression.Default(paramType); + var nullCheck = Expression.Equal(resolveCall, Expression.Constant(null, typeof(object))); + parameterExpressions[i] = Expression.Condition( + nullCheck, + Expression.Convert(defaultValue, typeof(object)), + resolveCall); + parameterExpressions[i] = Expression.Convert(parameterExpressions[i], paramType); + } + else + { + // 引用类型直接转换 + parameterExpressions[i] = Expression.Convert(resolveCall, paramType); + } + } + + // 创建构造函数调用表达式 + var constructorCall = Expression.New(constructor, parameterExpressions); + + // 转换为object类型 + var convertToObject = Expression.Convert(constructorCall, typeof(object)); + + // 编译为委托 + var lambda = Expression.Lambda>(convertToObject, resolverParam); + return lambda.Compile(); + } + + private void RegisterSingleton<[DynamicallyAccessedMembers(AOT.Container)] T>(T instance) where T : class + { + var descriptor = new DependencyDescriptor(typeof(T), typeof(T), instance); + this.Register(descriptor); + } + + /// + /// 服务键,用于唯一标识一个服务注册 + /// + private readonly struct ServiceKey : IEquatable + { + public readonly Type Type; + public readonly object Key; + private readonly int m_hashCode; + + public ServiceKey(Type type, object key) + { + this.Type = type; + this.Key = key; + unchecked + { + this.m_hashCode = 17; + this.m_hashCode = this.m_hashCode * 31 + (type?.GetHashCode() ?? 0); + this.m_hashCode = this.m_hashCode * 31 + (key?.GetHashCode() ?? 0); + } + } + + public bool Equals(ServiceKey other) + { + return this.Type == other.Type && Equals(this.Key, other.Key); + } + + public override bool Equals(object obj) + { + return obj is ServiceKey other && this.Equals(other); + } + + public override int GetHashCode() => this.m_hashCode; + } + + /// + /// 服务条目,包含描述符和单例实例 + /// + private sealed class ServiceEntry + { + public readonly DependencyDescriptor Descriptor; + public readonly object Lock = new(); + public object SingletonInstance; + + public ServiceEntry(DependencyDescriptor descriptor) + { + this.Descriptor = descriptor; + } + } + + /// + /// 作用域解析器实现 + /// + private sealed class ScopedResolver : DisposableObject, IScopedResolver + { + private readonly Container m_rootContainer; + private readonly ConcurrentDictionary m_scopedInstances = new(); + + public ScopedResolver(Container rootContainer) + { + this.m_rootContainer = rootContainer; + this.Resolver = new ScopedResolverImpl(this); + } + + public IResolver Resolver { get; } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + // 释放所有作用域内的IDisposable对象 + foreach (var instance in this.m_scopedInstances.Values) + { + if (instance is IDisposable disposable) + { + disposable.Dispose(); + } + } + this.m_scopedInstances.Clear(); + } + base.Dispose(disposing); + } + + internal object ResolveScoped(Type fromType, object key) + { + var serviceKey = new ServiceKey(fromType, key); + + // 如果已经在作用域中创建过,直接返回 + if (this.m_scopedInstances.TryGetValue(serviceKey, out var existingInstance)) + { + return existingInstance; + } + + // 检查根容器中的注册 + if (this.m_rootContainer.m_services.TryGetValue(serviceKey, out var entry)) + { + return this.ResolveFromScopedEntry(entry, serviceKey); + } + + // 尝试泛型类型解析 + if (fromType.IsGenericType) + { + var genericTypeDefinition = fromType.GetGenericTypeDefinition(); + var genericServiceKey = new ServiceKey(genericTypeDefinition, key); + + if (this.m_rootContainer.m_services.TryGetValue(genericServiceKey, out var genericEntry)) + { + return this.ResolveFromScopedEntry(genericEntry, serviceKey, fromType.GetGenericArguments()); + } + } + + return null; + } + + [UnconditionalSuppressMessage("AOT", "IL2072:RequiresUnreferencedCode", Justification = "通过依赖注册确保类型的构造函数可用")] + [UnconditionalSuppressMessage("Trim", "IL2026:RequiresUnreferencedCode", Justification = "通过依赖注册确保类型的构造函数可用")] + [UnconditionalSuppressMessage("AOT", "IL3050:RequiresDynamicCode", Justification = "通过依赖注册确保类型的构造函数可用")] + private object ResolveFromScopedEntry(ServiceEntry entry, ServiceKey serviceKey, Type[] genericArguments = null) + { + var descriptor = entry.Descriptor; + + // 实例注册直接返回实例,若为Scoped生命周期则缓存 + if (descriptor.ToInstance != null) + { + if (descriptor.Lifetime == Lifetime.Scoped) + { + this.m_scopedInstances.TryAdd(serviceKey, descriptor.ToInstance); + } + return descriptor.ToInstance; + } + + // 单例直接从根容器获取(根容器内部会按需缓存,包括工厂) if (descriptor.Lifetime == Lifetime.Singleton) { - if (descriptor.ToInstance != null) - { - return descriptor.ToInstance; - } - lock (descriptor) - { - return descriptor.ToInstance ??= this.Create(descriptor, descriptor.ToType); - } + return this.m_rootContainer.ResolveFromEntry(entry, this.Resolver, genericArguments); } - return this.Create(descriptor, descriptor.ToType); - } - return default; - } - /// - public object Resolve(Type fromType) - { - string k; - DependencyDescriptor descriptor; - if (fromType.IsGenericType) - { - var type = fromType.GetGenericTypeDefinition(); - k = type.FullName; - if (this.m_registrations.TryGetValue(k, out descriptor)) + // 作用域生命周期:检查是否已在当前作用域创建(支持工厂) + if (descriptor.Lifetime == Lifetime.Scoped) { - if (descriptor.ImplementationFactory != null) + return this.m_scopedInstances.GetOrAdd(serviceKey, _ => { - return descriptor.ImplementationFactory.Invoke(this); - } - - if (descriptor.Lifetime == Lifetime.Singleton) - { - if (descriptor.ToInstance != null) - { - return descriptor.ToInstance; - } - lock (descriptor) - { - if (descriptor.ToInstance != null) - { - return descriptor.ToInstance; - } - else - { - if (descriptor.ToType.IsGenericType) - { - return (descriptor.ToInstance = this.Create(descriptor, descriptor.ToType.MakeGenericType(fromType.GetGenericArguments()))); - } - else - { - return (descriptor.ToInstance = this.Create(descriptor, descriptor.ToType)); - } - } - } - } - - if (descriptor.ToType.IsGenericType) - { - return this.Create(descriptor, descriptor.ToType.MakeGenericType(fromType.GetGenericArguments())); - } - else - { - return this.Create(descriptor, descriptor.ToType); - } + var instance = descriptor.ImplementationFactory != null + ? descriptor.ImplementationFactory(this.Resolver) + : this.m_rootContainer.CreateInstance(descriptor.ToType, this.Resolver, genericArguments); + descriptor.OnResolved?.Invoke(instance); + return instance; + }); } + + // 瞬态生命周期:每次都创建(支持工厂) + var newInstance = descriptor.ImplementationFactory != null + ? descriptor.ImplementationFactory(this.Resolver) + : this.m_rootContainer.CreateInstance(descriptor.ToType, this.Resolver, genericArguments); + descriptor.OnResolved?.Invoke(newInstance); + return newInstance; } - k = fromType.FullName; - if (this.m_registrations.TryGetValue(k, out descriptor)) + + /// + /// 作用域解析器的IResolver实现 + /// + private sealed class ScopedResolverImpl : IResolver { - if (descriptor.ImplementationFactory != null) + private readonly ScopedResolver m_scopedResolver; + + public ScopedResolverImpl(ScopedResolver scopedResolver) { - return descriptor.ImplementationFactory.Invoke(this); + this.m_scopedResolver = scopedResolver; } - if (descriptor.Lifetime == Lifetime.Singleton) + + public IScopedResolver CreateScopedResolver() => new ScopedResolver(this.m_scopedResolver.m_rootContainer); + + public object GetService(Type serviceType) => this.Resolve(serviceType); + + + public object Resolve(Type fromType) => this.Resolve(fromType, null); + + public object Resolve(Type fromType, object key) { - if (descriptor.ToInstance != null) - { - return descriptor.ToInstance; - } - lock (descriptor) - { - return descriptor.ToInstance ??= this.Create(descriptor, descriptor.ToType); - } - } - return this.Create(descriptor, descriptor.ToType); - } - return default; - } - - /// - public void Unregister(DependencyDescriptor descriptor, string key) - { - var k = $"{descriptor.FromType.FullName}{key}"; - this.m_registrations.TryRemove(k, out _); - } - - /// - public void Unregister(DependencyDescriptor descriptor) - { - var k = descriptor.FromType.FullName; - this.m_registrations.TryRemove(k, out _); - } - - private object Create(DependencyDescriptor descriptor, Type toType) - { - var ctor = toType.GetConstructors().FirstOrDefault(x => x.IsDefined(typeof(DependencyInjectAttribute), true)); - if (ctor is null) - { - //如果没有被特性标记,那就取构造函数参数最多的作为注入目标 - if (toType.GetConstructors().Length == 0) - { - throw new Exception(TouchSocketCoreResource.NotFindPublicConstructor.Format(toType)); - } - ctor = toType.GetConstructors().OrderByDescending(x => x.GetParameters().Length).First(); - } - - DependencyTypeAttribute dependencyTypeAttribute = null; - if (toType.IsDefined(typeof(DependencyTypeAttribute), true)) - { - dependencyTypeAttribute = toType.GetCustomAttribute(); - } - - var parameters = ctor.GetParameters(); - var ps = new object[parameters.Length]; - - if (dependencyTypeAttribute == null || dependencyTypeAttribute.Type.HasFlag(DependencyType.Constructor)) - { - for (var i = 0; i < parameters.Length; i++) - { - if (parameters[i].ParameterType.IsPrimitive || parameters[i].ParameterType == typeof(string)) - { - ps[i] = parameters[i].HasDefaultValue ? parameters[i].DefaultValue : default; - } - else - { - if (parameters[i].IsDefined(typeof(DependencyInjectAttribute), true)) - { - var attribute = parameters[i].GetCustomAttribute(); - var type = attribute.Type ?? parameters[i].ParameterType; - ps[i] = this.Resolve(type, attribute.Key); - } - else - { - ps[i] = this.Resolve(parameters[i].ParameterType, null); - } - } + this.m_scopedResolver.ThrowIfDisposed(); + return this.m_scopedResolver.ResolveScoped(fromType, key); } } - - var instance = ps.Length == 0 ? Activator.CreateInstance(toType) : Activator.CreateInstance(toType, ps); - - if (dependencyTypeAttribute == null || dependencyTypeAttribute.Type.HasFlag(DependencyType.Property)) - { - var propetys = toType.GetProperties().Where(x => x.IsDefined(typeof(DependencyInjectAttribute), true)); - foreach (var item in propetys) - { - if (item.CanWrite) - { - object obj; - if (item.IsDefined(typeof(DependencyInjectAttribute), true)) - { - var attribute = item.GetCustomAttribute(); - var type = attribute.Type ?? item.PropertyType; - obj = this.Resolve(type, attribute.Key); - } - else - { - obj = this.Resolve(item.PropertyType, null); - } - item.SetValue(instance, obj); - } - } - } - - if (dependencyTypeAttribute == null || dependencyTypeAttribute.Type.HasFlag(DependencyType.Method)) - { - var methods = toType.GetMethods(BindingFlags.Default | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).Where(x => x.IsDefined(typeof(DependencyInjectAttribute), true)).ToList(); - foreach (var item in methods) - { - parameters = item.GetParameters(); - ps = new object[parameters.Length]; - for (var i = 0; i < ps.Length; i++) - { - if (parameters[i].ParameterType.IsPrimitive || parameters[i].ParameterType == typeof(string)) - { - ps[i] = parameters[i].HasDefaultValue ? parameters[i].DefaultValue : default; - } - else - { - if (parameters[i].IsDefined(typeof(DependencyInjectAttribute), true)) - { - var attribute = parameters[i].GetCustomAttribute(); - var type = attribute.Type ?? parameters[i].ParameterType; - ps[i] = this.Resolve(type, attribute.Key); - } - else - { - ps[i] = this.Resolve(parameters[i].ParameterType, null); - } - } - } - item.Invoke(instance, ps); - } - } - descriptor.OnResolved?.Invoke(instance); - return instance; } } \ No newline at end of file diff --git a/src/TouchSocket.Core/Container/CoreContainerExtension.cs b/src/TouchSocket.Core/Container/CoreContainerExtension.cs index 8489afd9c..295c4ef9c 100644 --- a/src/TouchSocket.Core/Container/CoreContainerExtension.cs +++ b/src/TouchSocket.Core/Container/CoreContainerExtension.cs @@ -1,528 +1,698 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ -using System; using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Reflection; -using TouchSocket.Resources; namespace TouchSocket.Core; /// -/// ContainerExtension +/// 容器扩展类,提供依赖注入容器的注册、解析等扩展方法 /// public static class CoreContainerExtension { - /// - /// DynamicallyAccessed - /// - public const DynamicallyAccessedMemberTypes DynamicallyAccessed = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicProperties; - #region RegisterSingleton /// - /// 注册单例 + /// 注册单例实例 /// - /// - /// - /// - /// - /// - public static IRegistrator RegisterSingleton(this IRegistrator registrator, TTo instance) - where TFrom : class - where TTo : class, TFrom + /// 服务类型 + /// 实现类型 + /// 容器注册器 + /// 单例实例对象 + public static void RegisterSingleton(this IRegistrator registrator, TTo instance) + where TFrom : class + where TTo : class, TFrom { - RegisterSingleton(registrator, typeof(TFrom), instance); - return registrator; + ThrowHelper.ThrowIfNull(instance, nameof(instance)); + registrator.Register(new DependencyDescriptor(typeof(TFrom), typeof(TTo), instance)); } /// - /// 注册单例 + /// 注册单例实例,使用实例的运行时类型作为服务类型 /// - /// - /// - /// - public static IRegistrator RegisterSingleton(this IRegistrator registrator, object instance) + /// 容器注册器 + /// 单例实例对象 + public static void RegisterSingleton<[DynamicallyAccessedMembers(AOT.Container)] T>(this IRegistrator registrator, T instance) + where T : class { - if (instance is null) - { - throw new ArgumentNullException(nameof(instance)); - } - - RegisterSingleton(registrator, instance.GetType(), instance); - return registrator; + ThrowHelper.ThrowIfNull(instance, nameof(instance)); + var instanceType = typeof(T); + registrator.Register(new DependencyDescriptor(instanceType, instanceType, instance)); } /// - /// 注册单例 + /// 注册单例服务,容器将自动创建实例 /// - /// - /// - /// - /// - public static IRegistrator RegisterSingleton(this IRegistrator registrator, [DynamicallyAccessedMembers(DynamicallyAccessed)] Type fromType) + /// 容器注册器 + /// 服务类型 + public static void RegisterSingleton(this IRegistrator registrator, [DynamicallyAccessedMembers(AOT.Container)] Type fromType) { - if (fromType is null) - { - throw new ArgumentNullException(nameof(fromType)); - } - - RegisterSingleton(registrator, fromType, fromType); - return registrator; + ThrowHelper.ThrowIfNull(fromType, nameof(fromType)); + registrator.Register(new DependencyDescriptor(fromType, fromType, Lifetime.Singleton)); } /// - /// 注册单例 + /// 注册单例实例,使用指定的键 /// - /// - /// - /// - /// - /// - /// - public static IRegistrator RegisterSingleton(this IRegistrator registrator, string key, TTo instance) - where TFrom : class - where TTo : class, TFrom + /// 服务类型 + /// 实现类型 + /// 容器注册器 + /// 注册键,用于区分同一类型的不同注册 + /// 单例实例对象 + public static void RegisterSingleton(this IRegistrator registrator, object key, TTo instance) + where TFrom : class + where TTo : class, TFrom { - RegisterSingleton(registrator, typeof(TFrom), instance, key); - return registrator; + ThrowHelper.ThrowIfNull(instance, nameof(instance)); + registrator.Register(new DependencyDescriptor(typeof(TFrom), typeof(TTo), instance), key); } /// - /// 注册单例 + /// 注册单例实例,使用指定的服务类型和键 /// - /// - /// - /// - /// - /// - public static IRegistrator RegisterSingleton(this IRegistrator registrator, Type fromType, object instance, string key) + /// 实现类型 + /// 容器注册器 + /// 服务类型 + /// 单例实例对象 + /// 注册键 + public static void RegisterSingleton<[DynamicallyAccessedMembers(AOT.Container)] T>(this IRegistrator registrator, Type fromType, T instance, object key) + where T : class { - registrator.Register(new DependencyDescriptor(fromType, instance), key); - return registrator; + ThrowHelper.ThrowIfNull(fromType, nameof(fromType)); + ThrowHelper.ThrowIfNull(instance, nameof(instance)); + registrator.Register(new DependencyDescriptor(fromType, typeof(T), instance), key); } /// - /// 注册单例 + /// 注册单例实例,使用指定的服务类型 /// - /// - /// - /// - /// - public static IRegistrator RegisterSingleton(this IRegistrator registrator, Type fromType, object instance) + /// 实现类型 + /// 容器注册器 + /// 服务类型 + /// 单例实例对象 + public static void RegisterSingleton<[DynamicallyAccessedMembers(AOT.Container)] T>(this IRegistrator registrator, Type fromType, T instance) + where T : class { - registrator.Register(new DependencyDescriptor(fromType, instance)); - return registrator; + ThrowHelper.ThrowIfNull(fromType, nameof(fromType)); + ThrowHelper.ThrowIfNull(instance, nameof(instance)); + registrator.Register(new DependencyDescriptor(fromType, typeof(T), instance)); } /// - /// 注册单例 + /// 注册单例实例,使用指定的键 /// - /// - /// - /// - /// - /// - public static IRegistrator RegisterSingleton(this IRegistrator registrator, object instance, string key) + /// 服务和实现类型 + /// 容器注册器 + /// 单例实例对象 + /// 注册键 + public static void RegisterSingleton<[DynamicallyAccessedMembers(AOT.Container)] T>(this IRegistrator registrator, T instance, object key) + where T : class { - registrator.Register(new DependencyDescriptor(typeof(TFrom), instance), key); - return registrator; + ThrowHelper.ThrowIfNull(instance, nameof(instance)); + registrator.Register(new DependencyDescriptor(typeof(T), typeof(T), instance), key); } /// - /// 注册单例 + /// 注册单例服务,容器将自动创建实例 /// - /// - /// - /// - /// - public static IRegistrator RegisterSingleton(this IRegistrator registrator, object instance) - { - registrator.Register(new DependencyDescriptor(typeof(TFrom), instance)); - return registrator; - } - - /// - /// 注册单例 - /// - /// - /// - /// - /// - public static IRegistrator RegisterSingleton(this IRegistrator registrator, object instance, string key) - { - registrator.Register(new DependencyDescriptor(instance.GetType(), instance), key); - return registrator; - } - - /// - /// 注册单例 - /// - /// - /// - /// - public static IRegistrator RegisterSingleton<[DynamicallyAccessedMembers(DynamicallyAccessed)] TFrom>(this IRegistrator registrator) + /// 服务类型 + /// 容器注册器 + public static void RegisterSingleton<[DynamicallyAccessedMembers(AOT.Container)] TFrom>(this IRegistrator registrator) { registrator.Register(new DependencyDescriptor(typeof(TFrom), typeof(TFrom), Lifetime.Singleton)); - return registrator; } /// - /// 注册单例 + /// 注册单例服务映射,容器将自动创建实例 /// - /// - /// - /// - /// - public static IRegistrator RegisterSingleton<[DynamicallyAccessedMembers(DynamicallyAccessed)] TFrom>(this IRegistrator registrator, string key) + /// 服务类型 + /// 实现类型 + /// 容器注册器 + public static void RegisterSingleton(this IRegistrator registrator) + where TFrom : class + where TTo : class, TFrom + { + registrator.Register(new DependencyDescriptor(typeof(TFrom), typeof(TTo), Lifetime.Singleton)); + } + + /// + /// 注册单例服务,容器将自动创建实例,使用指定的键 + /// + /// 服务类型 + /// 容器注册器 + /// 注册键 + public static void RegisterSingleton<[DynamicallyAccessedMembers(AOT.Container)] TFrom>(this IRegistrator registrator, object key) { registrator.Register(new DependencyDescriptor(typeof(TFrom), typeof(TFrom), Lifetime.Singleton), key); - return registrator; } /// - /// 注册单例 + /// 注册单例服务映射,容器将自动创建实例,使用指定的键 /// - /// - /// - /// - /// - public static IRegistrator RegisterSingleton(this IRegistrator registrator, Type fromType, [DynamicallyAccessedMembers(DynamicallyAccessed)] Type toType) + /// 服务类型 + /// 实现类型 + /// 容器注册器 + /// 注册键 + public static void RegisterSingleton(this IRegistrator registrator, object key) + where TFrom : class + where TTo : class, TFrom { + registrator.Register(new DependencyDescriptor(typeof(TFrom), typeof(TTo), Lifetime.Singleton), key); + } + + /// + /// 注册单例服务映射,容器将自动创建实例 + /// + /// 容器注册器 + /// 服务类型 + /// 实现类型 + public static void RegisterSingleton(this IRegistrator registrator, Type fromType, [DynamicallyAccessedMembers(AOT.Container)] Type toType) + { + ThrowHelper.ThrowIfNull(fromType, nameof(fromType)); + ThrowHelper.ThrowIfNull(toType, nameof(toType)); registrator.Register(new DependencyDescriptor(fromType, toType, Lifetime.Singleton)); - return registrator; } /// - /// 注册单例 + /// 注册单例服务映射,使用指定的键,容器将自动创建实例 /// - /// - /// - /// - /// - /// - public static IRegistrator RegisterSingleton(this IRegistrator registrator, Type fromType, [DynamicallyAccessedMembers(DynamicallyAccessed)] Type toType, string key) - + /// 容器注册器 + /// 服务类型 + /// 实现类型 + /// 注册键 + public static void RegisterSingleton(this IRegistrator registrator, Type fromType, [DynamicallyAccessedMembers(AOT.Container)] Type toType, object key) { + ThrowHelper.ThrowIfNull(fromType, nameof(fromType)); + ThrowHelper.ThrowIfNull(toType, nameof(toType)); registrator.Register(new DependencyDescriptor(fromType, toType, Lifetime.Singleton), key); - return registrator; } /// - /// 注册单例 + /// 注册单例服务,使用工厂方法创建实例 /// - /// - /// - /// - public static IRegistrator RegisterSingleton(this IRegistrator registrator, Func func) + /// 服务类型 + /// 容器注册器 + /// 工厂委托,接收 参数并返回服务实例 + public static void RegisterSingleton<[DynamicallyAccessedMembers(AOT.Container)] TType>(this IRegistrator registrator, Func func) { - registrator.Register(new DependencyDescriptor(typeof(TFrom), Lifetime.Singleton) + ThrowHelper.ThrowIfNull(func, nameof(func)); + registrator.Register(new DependencyDescriptor(typeof(TType), Lifetime.Singleton) { ImplementationFactory = func }); - return registrator; } /// - /// 注册单例 + /// 注册单例服务映射,使用工厂方法创建实例 /// - /// - /// - /// - /// - /// - public static IRegistrator RegisterSingleton(this IRegistrator registrator, Func func) + /// 服务类型 + /// 实现类型 + /// 容器注册器 + /// 工厂委托,接收 参数并返回服务实例 + public static void RegisterSingleton(this IRegistrator registrator, Func func) { + ThrowHelper.ThrowIfNull(func, nameof(func)); registrator.Register(new DependencyDescriptor(typeof(TFrom), typeof(TTo), Lifetime.Singleton) { ImplementationFactory = func }); - return registrator; } /// - /// 注册单例 + /// 注册单例服务,使用工厂方法创建实例,并指定键 /// - /// - /// - /// - /// - public static IRegistrator RegisterSingleton(this IRegistrator registrator, Func func, string key) + /// 服务类型 + /// 容器注册器 + /// 工厂委托 + /// 注册键 + public static void RegisterSingleton<[DynamicallyAccessedMembers(AOT.Container)] TFrom>(this IRegistrator registrator, Func func, object key) { + ThrowHelper.ThrowIfNull(func, nameof(func)); registrator.Register(new DependencyDescriptor(typeof(TFrom), Lifetime.Singleton) { ImplementationFactory = func }, key); - return registrator; } /// - /// 注册单例 + /// 注册单例服务映射,使用工厂方法创建实例,并指定键 /// - /// - /// - /// - /// - /// - /// - public static IRegistrator RegisterSingleton(this IRegistrator registrator, Func func, string key) + /// 服务类型 + /// 实现类型 + /// 容器注册器 + /// 工厂委托 + /// 注册键 + public static void RegisterSingleton(this IRegistrator registrator, Func func, object key) { + ThrowHelper.ThrowIfNull(func, nameof(func)); registrator.Register(new DependencyDescriptor(typeof(TFrom), typeof(TTo), Lifetime.Singleton) { ImplementationFactory = func }, key); - return registrator; } /// - /// 注册单例 + /// 注册单例服务,使用工厂方法创建实例 /// - /// - /// - /// - /// - public static IRegistrator RegisterSingleton(this IRegistrator registrator, Type fromType, Func func) + /// 容器注册器 + /// 服务类型 + /// 工厂委托 + public static void RegisterSingleton(this IRegistrator registrator, [DynamicallyAccessedMembers(AOT.Container)] Type fromType, Func func) { + ThrowHelper.ThrowIfNull(fromType, nameof(fromType)); + ThrowHelper.ThrowIfNull(func, nameof(func)); registrator.Register(new DependencyDescriptor(fromType, Lifetime.Singleton) { ImplementationFactory = func }); - return registrator; } /// - /// 注册单例 + /// 注册单例服务,使用工厂方法创建实例,并指定键 /// - /// - /// - /// - /// - /// - public static IRegistrator RegisterSingleton(this IRegistrator registrator, Type fromType, Func func, string key) + /// 容器注册器 + /// 服务类型 + /// 工厂委托 + /// 注册键 + public static void RegisterSingleton(this IRegistrator registrator, [DynamicallyAccessedMembers(AOT.Container)] Type fromType, Func func, object key) { + ThrowHelper.ThrowIfNull(fromType, nameof(fromType)); + ThrowHelper.ThrowIfNull(func, nameof(func)); registrator.Register(new DependencyDescriptor(fromType, Lifetime.Singleton) { ImplementationFactory = func }, key); - return registrator; - } - - /// - /// 注册单例 - /// - /// - /// - /// - /// - public static IRegistrator RegisterSingleton(this IRegistrator registrator) where TFrom : class - where TTO : class, TFrom - { - RegisterSingleton(registrator, typeof(TFrom), typeof(TTO)); - return registrator; - } - - /// - /// 注册单例 - /// - /// - /// - /// - /// - /// - public static IRegistrator RegisterSingleton(this IRegistrator registrator, string key) - where TFrom : class - where TTO : class, TFrom - { - RegisterSingleton(registrator, typeof(TFrom), typeof(TTO), key); - return registrator; } #endregion RegisterSingleton + #region RegisterScoped + + /// + /// 注册作用域服务映射,在每个作用域内为单例,不同作用域之间是不同的实例 + /// + /// 服务类型 + /// 实现类型 + /// 容器注册器 + public static void RegisterScoped(this IRegistrator registrator) + where TFrom : class + where TTO : class, TFrom + { + registrator.Register(new DependencyDescriptor(typeof(TFrom), typeof(TTO), Lifetime.Scoped)); + } + + /// + /// 注册作用域服务,在每个作用域内为单例 + /// + /// 服务类型 + /// 容器注册器 + public static void RegisterScoped<[DynamicallyAccessedMembers(AOT.Container)] TFrom>(this IRegistrator registrator) + where TFrom : class + { + registrator.Register(new DependencyDescriptor(typeof(TFrom), typeof(TFrom), Lifetime.Scoped)); + } + + /// + /// 注册作用域服务,使用指定的键 + /// + /// 服务类型 + /// 容器注册器 + /// 注册键 + public static void RegisterScoped<[DynamicallyAccessedMembers(AOT.Container)] TFrom>(this IRegistrator registrator, object key) + where TFrom : class + { + registrator.Register(new DependencyDescriptor(typeof(TFrom), typeof(TFrom), Lifetime.Scoped), key); + } + + /// + /// 注册作用域服务映射,使用指定的键 + /// + /// 服务类型 + /// 实现类型 + /// 容器注册器 + /// 注册键 + public static void RegisterScoped(this IRegistrator registrator, object key) + where TFrom : class + where TTO : class, TFrom + { + registrator.Register(new DependencyDescriptor(typeof(TFrom), typeof(TTO), Lifetime.Scoped), key); + } + + /// + /// 注册作用域服务 + /// + /// 容器注册器 + /// 服务类型 + public static void RegisterScoped(this IRegistrator registrator, [DynamicallyAccessedMembers(AOT.Container)] Type fromType) + { + ThrowHelper.ThrowIfNull(fromType, nameof(fromType)); + registrator.Register(new DependencyDescriptor(fromType, fromType, Lifetime.Scoped)); + } + + /// + /// 注册作用域服务,使用指定的键 + /// + /// 容器注册器 + /// 服务类型 + /// 注册键 + public static void RegisterScoped(this IRegistrator registrator, [DynamicallyAccessedMembers(AOT.Container)] Type fromType, object key) + { + ThrowHelper.ThrowIfNull(fromType, nameof(fromType)); + registrator.Register(new DependencyDescriptor(fromType, fromType, Lifetime.Scoped), key); + } + + /// + /// 注册作用域服务映射 + /// + /// 容器注册器 + /// 服务类型 + /// 实现类型 + public static void RegisterScoped(this IRegistrator registrator, Type fromType, [DynamicallyAccessedMembers(AOT.Container)] Type toType) + { + ThrowHelper.ThrowIfNull(fromType, nameof(fromType)); + ThrowHelper.ThrowIfNull(toType, nameof(toType)); + registrator.Register(new DependencyDescriptor(fromType, toType, Lifetime.Scoped)); + } + + /// + /// 注册作用域服务映射,使用指定的键 + /// + /// 容器注册器 + /// 服务类型 + /// 实现类型 + /// 注册键 + public static void RegisterScoped(this IRegistrator registrator, Type fromType, [DynamicallyAccessedMembers(AOT.Container)] Type toType, object key) + { + ThrowHelper.ThrowIfNull(fromType, nameof(fromType)); + ThrowHelper.ThrowIfNull(toType, nameof(toType)); + registrator.Register(new DependencyDescriptor(fromType, toType, Lifetime.Scoped), key); + } + + /// + /// 注册作用域服务,使用工厂方法创建实例 + /// + /// 服务类型 + /// 容器注册器 + /// 工厂委托 + public static void RegisterScoped<[DynamicallyAccessedMembers(AOT.Container)] TFrom>(this IRegistrator registrator, Func func) + { + ThrowHelper.ThrowIfNull(func, nameof(func)); + registrator.Register(new DependencyDescriptor(typeof(TFrom), Lifetime.Scoped) + { + ImplementationFactory = func + }); + } + + /// + /// 注册作用域服务映射,使用工厂方法创建实例 + /// + /// 服务类型 + /// 实现类型 + /// 容器注册器 + /// 工厂委托 + public static void RegisterScoped(this IRegistrator registrator, Func func) + { + ThrowHelper.ThrowIfNull(func, nameof(func)); + registrator.Register(new DependencyDescriptor(typeof(TFrom), typeof(TTo), Lifetime.Scoped) + { + ImplementationFactory = func + }); + } + + /// + /// 注册作用域服务,使用工厂方法创建实例,并指定键 + /// + /// 服务类型 + /// 容器注册器 + /// 工厂委托 + /// 注册键 + public static void RegisterScoped<[DynamicallyAccessedMembers(AOT.Container)] TFrom>(this IRegistrator registrator, Func func, object key) + { + ThrowHelper.ThrowIfNull(func, nameof(func)); + registrator.Register(new DependencyDescriptor(typeof(TFrom), Lifetime.Scoped) + { + ImplementationFactory = func + }, key); + } + + /// + /// 注册作用域服务映射,使用工厂方法创建实例,并指定键 + /// + /// 服务类型 + /// 实现类型 + /// 容器注册器 + /// 工厂委托 + /// 注册键 + public static void RegisterScoped(this IRegistrator registrator, Func func, object key) + { + ThrowHelper.ThrowIfNull(func, nameof(func)); + registrator.Register(new DependencyDescriptor(typeof(TFrom), typeof(TTo), Lifetime.Scoped) + { + ImplementationFactory = func + }, key); + } + + /// + /// 注册作用域服务,使用工厂方法创建实例 + /// + /// 容器注册器 + /// 服务类型 + /// 工厂委托 + public static void RegisterScoped(this IRegistrator registrator, [DynamicallyAccessedMembers(AOT.Container)] Type fromType, Func func) + { + ThrowHelper.ThrowIfNull(fromType, nameof(fromType)); + ThrowHelper.ThrowIfNull(func, nameof(func)); + registrator.Register(new DependencyDescriptor(fromType, Lifetime.Scoped) + { + ImplementationFactory = func + }); + } + + /// + /// 注册作用域服务,使用工厂方法创建实例,并指定键 + /// + /// 容器注册器 + /// 服务类型 + /// 工厂委托 + /// 注册键 + public static void RegisterScoped(this IRegistrator registrator, [DynamicallyAccessedMembers(AOT.Container)] Type fromType, Func func, object key) + { + ThrowHelper.ThrowIfNull(fromType, nameof(fromType)); + ThrowHelper.ThrowIfNull(func, nameof(func)); + registrator.Register(new DependencyDescriptor(fromType, Lifetime.Scoped) + { + ImplementationFactory = func + }, key); + } + + #endregion RegisterScoped + #region Transient /// - /// 注册临时映射 + /// 注册瞬态服务映射,每次解析都会创建新实例 /// - /// - /// - /// - /// - public static IRegistrator RegisterTransient(this IRegistrator registrator) - where TFrom : class - where TTO : class, TFrom + /// 服务类型 + /// 实现类型 + /// 容器注册器 + public static void RegisterTransient(this IRegistrator registrator) + where TFrom : class + where TTO : class, TFrom { - RegisterTransient(registrator, typeof(TFrom), typeof(TTO)); - return registrator; + registrator.Register(new DependencyDescriptor(typeof(TFrom), typeof(TTO), Lifetime.Transient)); } /// - /// 注册临时映射 + /// 注册瞬态服务,每次解析都会创建新实例 /// - /// - /// - /// - public static IRegistrator RegisterTransient<[DynamicallyAccessedMembers(DynamicallyAccessed)] TFrom>(this IRegistrator registrator) - where TFrom : class + /// 服务类型 + /// 容器注册器 + public static void RegisterTransient<[DynamicallyAccessedMembers(AOT.Container)] TFrom>(this IRegistrator registrator) + where TFrom : class { - RegisterTransient(registrator, typeof(TFrom), typeof(TFrom)); - return registrator; + registrator.Register(new DependencyDescriptor(typeof(TFrom), typeof(TFrom), Lifetime.Transient)); } /// - /// 注册临时映射 + /// 注册瞬态服务,使用指定的键 /// - /// - /// - /// - /// - public static IRegistrator RegisterTransient<[DynamicallyAccessedMembers(DynamicallyAccessed)] TFrom>(this IRegistrator registrator, string key) - where TFrom : class + /// 服务类型 + /// 容器注册器 + /// 注册键 + public static void RegisterTransient<[DynamicallyAccessedMembers(AOT.Container)] TFrom>(this IRegistrator registrator, object key) + where TFrom : class { - RegisterTransient(registrator, typeof(TFrom), typeof(TFrom), key); - return registrator; + registrator.Register(new DependencyDescriptor(typeof(TFrom), typeof(TFrom), Lifetime.Transient), key); } /// - /// 注册临时映射 + /// 注册瞬态服务映射,使用指定的键 /// - /// - /// - /// - /// - /// - public static IRegistrator RegisterTransient(this IRegistrator registrator, string key) - where TFrom : class - where TTO : class, TFrom + /// 服务类型 + /// 实现类型 + /// 容器注册器 + /// 注册键 + public static void RegisterTransient(this IRegistrator registrator, object key) + where TFrom : class + where TTO : class, TFrom { - RegisterTransient(registrator, typeof(TFrom), typeof(TTO), key); - return registrator; + registrator.Register(new DependencyDescriptor(typeof(TFrom), typeof(TTO), Lifetime.Transient), key); } /// - /// 注册临时映射 + /// 注册瞬态服务 /// - /// - /// - /// - public static IRegistrator RegisterTransient(this IRegistrator registrator, [DynamicallyAccessedMembers(DynamicallyAccessed)] Type fromType) + /// 容器注册器 + /// 服务类型 + public static void RegisterTransient(this IRegistrator registrator, [DynamicallyAccessedMembers(AOT.Container)] Type fromType) { - RegisterTransient(registrator, fromType, fromType); - return registrator; + ThrowHelper.ThrowIfNull(fromType, nameof(fromType)); + registrator.Register(new DependencyDescriptor(fromType, fromType, Lifetime.Transient)); } /// - /// 注册临时映射 + /// 注册瞬态服务,使用指定的键 /// - /// - /// - /// - /// - public static IRegistrator RegisterTransient(this IRegistrator registrator, [DynamicallyAccessedMembers(DynamicallyAccessed)] Type fromType, string key) + /// 容器注册器 + /// 服务类型 + /// 注册键 + public static void RegisterTransient(this IRegistrator registrator, [DynamicallyAccessedMembers(AOT.Container)] Type fromType, object key) { - RegisterTransient(registrator, fromType, fromType, key); - return registrator; + ThrowHelper.ThrowIfNull(fromType, nameof(fromType)); + registrator.Register(new DependencyDescriptor(fromType, fromType, Lifetime.Transient), key); } /// - /// 注册临时映射 + /// 注册瞬态服务映射 /// - /// - /// - /// - /// - public static IRegistrator RegisterTransient(this IRegistrator registrator, Type fromType, [DynamicallyAccessedMembers(DynamicallyAccessed)] Type toType) + /// 容器注册器 + /// 服务类型 + /// 实现类型 + public static void RegisterTransient(this IRegistrator registrator, Type fromType, [DynamicallyAccessedMembers(AOT.Container)] Type toType) { + ThrowHelper.ThrowIfNull(fromType, nameof(fromType)); + ThrowHelper.ThrowIfNull(toType, nameof(toType)); registrator.Register(new DependencyDescriptor(fromType, toType, Lifetime.Transient)); - return registrator; } /// - /// 注册临时映射 + /// 注册瞬态服务映射,使用指定的键 /// - /// - /// - /// - /// - /// - public static IRegistrator RegisterTransient(this IRegistrator registrator, Type fromType, [DynamicallyAccessedMembers(DynamicallyAccessed)] Type toType, string key) + /// 容器注册器 + /// 服务类型 + /// 实现类型 + /// 注册键 + public static void RegisterTransient(this IRegistrator registrator, Type fromType, [DynamicallyAccessedMembers(AOT.Container)] Type toType, object key) { + ThrowHelper.ThrowIfNull(fromType, nameof(fromType)); + ThrowHelper.ThrowIfNull(toType, nameof(toType)); registrator.Register(new DependencyDescriptor(fromType, toType, Lifetime.Transient), key); - return registrator; } /// - /// 注册临时映射 + /// 注册瞬态服务,使用工厂方法创建实例 /// - /// - /// - /// - public static IRegistrator RegisterTransient(this IRegistrator registrator, Func func) + /// 服务类型 + /// 容器注册器 + /// 工厂委托 + public static void RegisterTransient<[DynamicallyAccessedMembers(AOT.Container)] TFrom>(this IRegistrator registrator, Func func) { + ThrowHelper.ThrowIfNull(func, nameof(func)); registrator.Register(new DependencyDescriptor(typeof(TFrom), Lifetime.Transient) { ImplementationFactory = func }); - return registrator; } /// - /// 注册临时映射 + /// 注册瞬态服务映射,使用工厂方法创建实例 /// - /// - /// - /// - /// - public static IRegistrator RegisterTransient(this IRegistrator registrator, Func func, string key) + /// 服务类型 + /// 实现类型 + /// 容器注册器 + /// 工厂委托,接收 参数并返回服务实例 + public static void RegisterTransient(this IRegistrator registrator, Func func) { + ThrowHelper.ThrowIfNull(func, nameof(func)); + registrator.Register(new DependencyDescriptor(typeof(TFrom), typeof(TTo), Lifetime.Transient) + { + ImplementationFactory = func + }); + } + + /// + /// 注册瞬态服务,使用工厂方法创建实例,并指定键 + /// + /// 服务类型 + /// 容器注册器 + /// 工厂委托 + /// 注册键 + public static void RegisterTransient<[DynamicallyAccessedMembers(AOT.Container)] TFrom>(this IRegistrator registrator, Func func, object key) + { + ThrowHelper.ThrowIfNull(func, nameof(func)); registrator.Register(new DependencyDescriptor(typeof(TFrom), Lifetime.Transient) { ImplementationFactory = func }, key); - return registrator; } /// - /// 注册临时映射 + /// 注册瞬态服务映射,使用工厂方法创建实例,并指定键 /// - /// - /// - /// - /// - public static IRegistrator RegisterTransient(this IRegistrator registrator, Type fromType, Func func) + /// 服务类型 + /// 实现类型 + /// 容器注册器 + /// 工厂委托 + /// 注册键 + public static void RegisterTransient(this IRegistrator registrator, Func func, object key) { + ThrowHelper.ThrowIfNull(func, nameof(func)); + registrator.Register(new DependencyDescriptor(typeof(TFrom), typeof(TTo), Lifetime.Transient) + { + ImplementationFactory = func + }, key); + } + + /// + /// 注册瞬态服务,使用工厂方法创建实例 + /// + /// 容器注册器 + /// 服务类型 + /// 工厂委托 + public static void RegisterTransient(this IRegistrator registrator, [DynamicallyAccessedMembers(AOT.Container)] Type fromType, Func func) + { + ThrowHelper.ThrowIfNull(fromType, nameof(fromType)); + ThrowHelper.ThrowIfNull(func, nameof(func)); registrator.Register(new DependencyDescriptor(fromType, Lifetime.Transient) { ImplementationFactory = func }); - return registrator; } /// - /// 注册临时映射 + /// 注册瞬态服务,使用工厂方法创建实例,并指定键 /// - /// - /// - /// - /// - /// - public static IRegistrator RegisterTransient(this IRegistrator registrator, Type fromType, Func func, string key) + /// 容器注册器 + /// 服务类型 + /// 工厂委托 + /// 注册键 + public static void RegisterTransient(this IRegistrator registrator, [DynamicallyAccessedMembers(AOT.Container)] Type fromType, Func func, object key) { + ThrowHelper.ThrowIfNull(fromType, nameof(fromType)); + ThrowHelper.ThrowIfNull(func, nameof(func)); registrator.Register(new DependencyDescriptor(fromType, Lifetime.Transient) { ImplementationFactory = func }, key); - return registrator; } #endregion Transient @@ -530,51 +700,47 @@ public static class CoreContainerExtension #region Unregister /// - /// 移除注册信息 + /// 从容器中移除指定类型的注册信息 /// - /// - /// - /// - public static IRegistrator Unregister(this IRegistrator registrator, Type fromType) + /// 容器注册器 + /// 要移除的服务类型 + public static void Unregister(this IRegistrator registrator, [DynamicallyAccessedMembers(AOT.Container)] Type fromType) { + ThrowHelper.ThrowIfNull(fromType, nameof(fromType)); registrator.Unregister(new DependencyDescriptor(fromType)); - return registrator; } /// - /// 移除注册信息 + /// 从容器中移除指定类型和键的注册信息 /// - /// - /// - /// - /// - public static IRegistrator Unregister(this IRegistrator registrator, Type fromType, string key) + /// 容器注册器 + /// 要移除的服务类型 + /// 注册键 + public static void Unregister(this IRegistrator registrator, [DynamicallyAccessedMembers(AOT.Container)] Type fromType, object key) { + ThrowHelper.ThrowIfNull(fromType, nameof(fromType)); registrator.Unregister(new DependencyDescriptor(fromType), key); - return registrator; } /// - /// 移除注册信息 + /// 从容器中移除指定类型的注册信息 /// - /// - /// - public static IRegistrator Unregister(this IRegistrator registrator) + /// 要移除的服务类型 + /// 容器注册器 + public static void Unregister<[DynamicallyAccessedMembers(AOT.Container)] TFrom>(this IRegistrator registrator) { registrator.Unregister(new DependencyDescriptor(typeof(TFrom))); - return registrator; } /// - /// 移除注册信息 + /// 从容器中移除指定类型和键的注册信息 /// - /// - /// - /// - public static IRegistrator Unregister(this IRegistrator registrator, string key) + /// 要移除的服务类型 + /// 容器注册器 + /// 注册键 + public static void Unregister<[DynamicallyAccessedMembers(AOT.Container)] TFrom>(this IRegistrator registrator, object key) { registrator.Unregister(new DependencyDescriptor(typeof(TFrom)), key); - return registrator; } #endregion Unregister @@ -582,123 +748,138 @@ public static class CoreContainerExtension #region Resolve /// - /// 创建类型对应的实例。如果解析失败,则返回 null。 + /// 从容器中解析指定类型的服务实例 /// - /// - /// - /// - public static T Resolve<[DynamicallyAccessedMembers(DynamicallyAccessed)] T>(this IServiceProvider resolver) + /// 要解析的服务类型 + /// 服务提供程序 + /// 解析的服务实例,如果解析失败则返回 + public static T Resolve<[DynamicallyAccessedMembers(AOT.Container)] T>(this IServiceProvider resolver) { return (T)resolver.GetService(typeof(T)); } /// - /// 创建类型对应的实例。如果解析失败,则返回 null。 + /// 从容器中解析指定类型和键的服务实例 /// - /// - /// - /// - /// - public static T Resolve<[DynamicallyAccessedMembers(DynamicallyAccessed)] T>(this IResolver resolver, string key) + /// 要解析的服务类型 + /// 服务解析器 + /// 注册键 + /// 解析的服务实例,如果解析失败则返回 + public static T Resolve<[DynamicallyAccessedMembers(AOT.Container)] T>(this IResolver resolver, object key) { return (T)resolver.Resolve(typeof(T), key); } /// - /// 创建生命的未注册的根类型实例。一般适用于:目标类型没有注册,但是其成员类型已经注册的情况。 + /// 创建未在容器中注册的类型实例,但会使用容器解析其构造函数参数 /// - /// - /// - /// - /// - public static object ResolveWithoutRoot(this IServiceProvider resolver, [DynamicallyAccessedMembers(DynamicallyAccessed)] Type fromType) + /// + /// 此方法适用于目标类型本身未注册,但其构造函数参数类型已在容器中注册的场景。 + /// 创建的实例具有 生命周期特性。 + /// + /// 服务提供程序 + /// 要创建实例的目标类型 + /// 创建的实例 + /// 时抛出 + /// 当类型没有公共构造函数时抛出 + public static object ResolveWithoutRoot(this IServiceProvider resolver, [DynamicallyAccessedMembers(AOT.Container)] Type fromType) { - object[] ops = null; - var ctor = fromType.GetConstructors().FirstOrDefault(x => x.IsDefined(typeof(DependencyInjectAttribute), true)); - if (ctor is null) + ThrowHelper.ThrowIfNull(fromType, nameof(fromType)); + + var constructors = fromType.GetConstructors(); + if (constructors.Length == 0) { - //如果没有被特性标记,那就取构造函数参数最多的作为注入目标 - if (fromType.GetConstructors().Length == 0) - { - throw new Exception(TouchSocketCoreResource.NotFindPublicConstructor.Format(fromType)); - } - ctor = fromType.GetConstructors().OrderByDescending(x => x.GetParameters().Length).First(); - } - DependencyTypeAttribute dependencyTypeAttribute = null; - if (fromType.IsDefined(typeof(DependencyTypeAttribute), true)) - { - dependencyTypeAttribute = fromType.GetCustomAttribute(); + throw new InvalidOperationException($"类型 {fromType.FullName} 没有公共构造函数"); } + var ctor = SelectConstructorForResolve(constructors); var parameters = ctor.GetParameters(); - var ps = new object[parameters.Length]; - if (dependencyTypeAttribute == null || dependencyTypeAttribute.Type.HasFlag(DependencyType.Constructor)) + if (parameters.Length == 0) { - for (var i = 0; i < parameters.Length; i++) + return Activator.CreateInstance(fromType); + } + + var ps = new object[parameters.Length]; + for (var i = 0; i < parameters.Length; i++) + { + var paramType = parameters[i].ParameterType; + + if (paramType.IsPrimitive || paramType == typeof(string)) { - if (ops != null && ops.Length - 1 >= i) - { - ps[i] = ops[i]; - } - else - { - if (parameters[i].ParameterType.IsPrimitive || parameters[i].ParameterType == typeof(string)) - { - ps[i] = parameters[i].HasDefaultValue ? parameters[i].DefaultValue : default; - } - else - { - if (parameters[i].IsDefined(typeof(DependencyInjectAttribute), true)) - { - var attribute = parameters[i].GetCustomAttribute(); - var type = attribute.Type ?? parameters[i].ParameterType; - ps[i] = resolver.GetService(type); - } - else - { - ps[i] = resolver.GetService(parameters[i].ParameterType); - } - } - } + ps[i] = parameters[i].HasDefaultValue ? parameters[i].DefaultValue : default; + } + else + { + ps[i] = resolver.GetService(paramType); } } - return ps == null || ps.Length == 0 ? Activator.CreateInstance(fromType) : Activator.CreateInstance(fromType, ps); + + return Activator.CreateInstance(fromType, ps); } /// - /// 创建生命的未注册的根类型实例。一般适用于:目标类型没有注册,但是其成员类型已经注册的情况。 + /// 创建未在容器中注册的类型实例,但会使用容器解析其构造函数参数 /// - /// - /// - /// - public static T ResolveWithoutRoot<[DynamicallyAccessedMembers(DynamicallyAccessed)] T>(this IServiceProvider resolver) + /// 要创建实例的目标类型 + /// 服务提供程序 + /// 创建的实例 + /// 当类型没有公共构造函数时抛出 + public static T ResolveWithoutRoot<[DynamicallyAccessedMembers(AOT.Container)] T>(this IServiceProvider resolver) { return (T)ResolveWithoutRoot(resolver, typeof(T)); } + + /// + /// 为 方法选择最适合的构造函数 + /// + /// + /// 选择逻辑: + /// + /// 优先选择标记了 的构造函数 + /// 如果有多个标记的构造函数,选择参数最多的 + /// 如果没有标记的构造函数,选择所有构造函数中参数最多的 + /// + /// + /// 候选构造函数数组 + /// 选中的构造函数 + private static System.Reflection.ConstructorInfo SelectConstructorForResolve(System.Reflection.ConstructorInfo[] constructors) + { + var attributedConstructors = constructors + .Where(c => c.GetCustomAttributes(typeof(DependencyInjectAttribute), true).Length > 0) + .ToList(); + + if (attributedConstructors.Count > 0) + { + return attributedConstructors.OrderByDescending(c => c.GetParameters().Length).First(); + } + + return constructors.OrderByDescending(c => c.GetParameters().Length).First(); + } + #endregion Resolve #region IsRegistered /// - /// + /// 检查指定类型是否已在容器中注册 /// - /// - /// - /// - public static bool IsRegistered<[DynamicallyAccessedMembers(DynamicallyAccessed)] T>(this IRegistered registered) + /// 要检查的服务类型 + /// 注册检查器 + /// 如果类型已注册则返回 ;否则返回 + public static bool IsRegistered<[DynamicallyAccessedMembers(AOT.Container)] T>(this IRegistered registered) { return registered.IsRegistered(typeof(T)); } /// - /// + /// 检查指定类型和键是否已在容器中注册 /// - /// - /// - /// - /// - public static bool IsRegistered<[DynamicallyAccessedMembers(DynamicallyAccessed)] T>(this IRegistered registered, string key) + /// 要检查的服务类型 + /// 注册检查器 + /// 注册键 + /// 如果类型和键的组合已注册则返回 ;否则返回 + public static bool IsRegistered<[DynamicallyAccessedMembers(AOT.Container)] T>(this IRegistered registered, object key) { return registered.IsRegistered(typeof(T), key); } diff --git a/src/TouchSocket.Core/Dependency/DependencyDescriptor.cs b/src/TouchSocket.Core/Container/DependencyDescriptor.cs similarity index 73% rename from src/TouchSocket.Core/Dependency/DependencyDescriptor.cs rename to src/TouchSocket.Core/Container/DependencyDescriptor.cs index 512e1f169..19e80c355 100644 --- a/src/TouchSocket.Core/Dependency/DependencyDescriptor.cs +++ b/src/TouchSocket.Core/Container/DependencyDescriptor.cs @@ -10,7 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; +using System.Diagnostics.CodeAnalysis; namespace TouchSocket.Core; @@ -23,13 +23,14 @@ public class DependencyDescriptor /// 初始化一个单例实例。 /// /// 要从该类型创建实例的类型。 + /// 服务在容器中实际使用的类型 /// 创建的单例实例。 - public DependencyDescriptor(Type fromType, object instance) + public DependencyDescriptor(Type fromType, [DynamicallyAccessedMembers(AOT.Container)] Type toType, object instance) { - this.FromType = fromType; // 设置从哪个类型创建实例 - this.ToInstance = instance; // 设置要创建的实例 - this.Lifetime = Lifetime.Singleton; // 设置实例的生命周期为单例 - this.ToType = instance.GetType(); // 设置实例的类型 + this.FromType = fromType; + this.ToInstance = instance; + this.Lifetime = Lifetime.Singleton; + this.ToType = toType; } /// @@ -38,7 +39,7 @@ public class DependencyDescriptor /// 要注册的服务的类型 /// 服务在容器中实际使用的类型 /// 服务的生命周期 - public DependencyDescriptor(Type fromType, Type toType, Lifetime lifetime) + public DependencyDescriptor(Type fromType, [DynamicallyAccessedMembers(AOT.Container)] Type toType, Lifetime lifetime) { this.FromType = fromType; this.Lifetime = lifetime; @@ -48,10 +49,13 @@ public class DependencyDescriptor /// /// 初始化一个简单的服务描述 /// - /// 指定服务的类型 - public DependencyDescriptor(Type fromType) + /// 指定服务的类型 + /// 生命周期,默认为瞬态 + public DependencyDescriptor([DynamicallyAccessedMembers(AOT.Container)] Type type, Lifetime lifetime = Lifetime.Transient) { - this.FromType = fromType; + this.FromType = type; + this.Lifetime = lifetime; + this.ToType = type; } /// @@ -85,5 +89,6 @@ public class DependencyDescriptor /// /// 实例类型 /// + [DynamicallyAccessedMembers(AOT.Container)] public Type ToType { get; } } \ No newline at end of file diff --git a/src/TouchSocket.Core/Container/IRegistered.cs b/src/TouchSocket.Core/Container/IRegistered.cs index 034d39847..072da40db 100644 --- a/src/TouchSocket.Core/Container/IRegistered.cs +++ b/src/TouchSocket.Core/Container/IRegistered.cs @@ -25,7 +25,7 @@ public interface IRegistered /// 要检查的类型 /// 与类型关联的唯一键,用于特定的注册场景 /// 如果类型已注册,则返回 true;否则返回 false - bool IsRegistered(Type fromType, string key); + bool IsRegistered(Type fromType, object key); /// /// 判断某类型是否已经注册 diff --git a/src/TouchSocket.Core/Container/IRegistrator.cs b/src/TouchSocket.Core/Container/IRegistrator.cs index 968b2f20c..a256c9815 100644 --- a/src/TouchSocket.Core/Container/IRegistrator.cs +++ b/src/TouchSocket.Core/Container/IRegistrator.cs @@ -22,9 +22,9 @@ public interface IRegistrator : IRegistered /// /// 添加类型描述符。 /// - /// /// - void Register(DependencyDescriptor descriptor, string key); + /// + void Register(DependencyDescriptor descriptor, object key); /// /// 添加类型描述符 @@ -37,8 +37,7 @@ public interface IRegistrator : IRegistered /// /// /// - /// - void Unregister(DependencyDescriptor descriptor, string key); + void Unregister(DependencyDescriptor descriptor, object key); /// /// 移除注册信息 diff --git a/src/TouchSocket.Core/Container/IResolver.cs b/src/TouchSocket.Core/Container/IResolver.cs index dccd88081..80f633f79 100644 --- a/src/TouchSocket.Core/Container/IResolver.cs +++ b/src/TouchSocket.Core/Container/IResolver.cs @@ -26,7 +26,7 @@ public interface IResolver : IServiceProvider /// 要解析的目标类型。 /// 可选的实例标识符。 /// 解析出的实例。 - object Resolve(Type fromType, string key); + object Resolve(Type fromType, object key); /// /// 解析给定类型的实例。如果解析失败,则返回 null。 diff --git a/src/TouchSocket.Core/Container/ManualContainer.cs b/src/TouchSocket.Core/Container/ManualContainer.cs deleted file mode 100644 index 9efdfc0ff..000000000 --- a/src/TouchSocket.Core/Container/ManualContainer.cs +++ /dev/null @@ -1,189 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using TouchSocket.Resources; - -namespace TouchSocket.Core; - -/// -/// 手动IOC容器 -/// -[Obsolete("此类由于功能不完善,且没有完善的意义,所以不再使用", true)] -public abstract class ManualContainer : IContainer -{ - private readonly ConcurrentDictionary m_singletonInstances = new ConcurrentDictionary(); - - /// - public IResolver BuildResolver() - { - return this; - } - - /// - public IScopedResolver CreateScopedResolver() - { - throw new NotImplementedException(); - } - - /// - public IEnumerable GetDescriptors() - { - throw new NotImplementedException(); - } - - /// - public object GetService(Type serviceType) - { - return this.Resolve(serviceType); - } - - /// - /// 判断指定的类型是否已在容器中注册。 - /// - /// 在本容器中,一般均会返回。 - /// - /// - /// - /// - /// - public virtual bool IsRegistered(Type fromType, string key) - { - return true; - } - - /// - /// 判断指定的类型是否已在容器中注册。 - /// - /// 在本容器中,一般均会返回。 - /// - /// - /// - /// - public virtual bool IsRegistered(Type fromType) - { - return true; - } - - /// - /// 注册描述符。 - /// - /// 一般情况下,本容器只会处理单例实例模式。 - /// - /// - /// - /// - public virtual void Register(DependencyDescriptor descriptor, string key) - { - if (descriptor.Lifetime == Lifetime.Singleton) - { - if (descriptor.ToInstance != null) - { - this.m_singletonInstances.AddOrUpdate($"{descriptor.FromType.FullName}{key}", descriptor.ToInstance, (k, v) => descriptor.ToInstance); - } - } - } - - /// - /// 注册描述符。 - /// - /// 一般情况下,本容器只会处理单例实例模式。 - /// - /// - /// - public virtual void Register(DependencyDescriptor descriptor) - { - if (descriptor.Lifetime == Lifetime.Singleton) - { - if (descriptor.ToInstance != null) - { - this.m_singletonInstances.AddOrUpdate(descriptor.FromType.FullName, descriptor.ToInstance, (k, v) => descriptor.ToInstance); - } - } - } - - /// - /// - public object Resolve(Type fromType, string key) - { - return fromType == typeof(IResolver) || fromType == typeof(IServiceProvider) - ? this - : this.TryResolve(fromType, out var instance, key) - ? instance - : throw new Exception(TouchSocketCoreResource.UnregisteredType.Format(fromType)); - } - - /// - /// - public object Resolve(Type fromType) - { - return fromType == typeof(IResolver) || fromType == typeof(IServiceProvider) - ? this - : this.TryResolve(fromType, out var instance) - ? instance - : throw new Exception(TouchSocketCoreResource.UnregisteredType.Format(fromType)); - } - - /// - /// 默认不实现该功能 - /// - /// - /// - /// - public virtual void Unregister(DependencyDescriptor descriptor, string key) - { - throw new NotImplementedException(); - } - - /// - /// 默认不实现该功能 - /// - /// - /// - public virtual void Unregister(DependencyDescriptor descriptor) - { - throw new NotImplementedException(); - } - - /// - /// 尝试解决Ioc容器所需类型。 - /// - /// 本方法仅实现了在单例实例注册下的获取。 - /// - /// - /// - /// - /// - /// - protected virtual bool TryResolve(Type fromType, out object instance, string key) - { - return key.IsNullOrEmpty() - ? this.m_singletonInstances.TryGetValue(fromType.FullName, out instance) - : this.m_singletonInstances.TryGetValue($"{fromType.FullName}{key}", out instance); - } - - /// - /// 尝试解决Ioc容器所需类型。 - /// - /// 本方法仅实现了在单例实例注册下的获取。 - /// - /// - /// - /// - /// - protected virtual bool TryResolve(Type fromType, out object instance) - { - return this.m_singletonInstances.TryGetValue(fromType.FullName, out instance); - } -} \ No newline at end of file diff --git a/src/TouchSocket.Core/Core/AOT.cs b/src/TouchSocket.Core/Core/AOT.cs new file mode 100644 index 000000000..a266ab24e --- /dev/null +++ b/src/TouchSocket.Core/Core/AOT.cs @@ -0,0 +1,57 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.Diagnostics.CodeAnalysis; + +namespace TouchSocket.Core; + +/// +/// AOT相关成员类型定义。 +/// +public static class AOT +{ + /// + /// 序列化格式化器成员类型。 + /// + public const DynamicallyAccessedMemberTypes SerializerFormatterMemberType = DynamicallyAccessedMemberTypes.All; + + + /// + /// 容器成员类型。 + /// + public const DynamicallyAccessedMemberTypes Container = DynamicallyAccessedMemberTypes.PublicConstructors; + + /// + /// 插件成员类型。 + /// + public const DynamicallyAccessedMemberTypes PluginMemberType = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods; + + /// + /// Rpc调用成员类型。 + /// + public const DynamicallyAccessedMemberTypes RpcInvoke = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties; + + /// + /// 成员访问器成员类型。 + /// + public const DynamicallyAccessedMemberTypes MemberAccessor = DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties; + + /// + /// 快速二进制格式化器成员类型。 + /// + public const DynamicallyAccessedMemberTypes FastBinaryFormatter = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties; + + /// + /// Rpc注册成员类型。 + /// + public const DynamicallyAccessedMemberTypes RpcRegister = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.NonPublicMethods; +} diff --git a/src/TouchSocket.Core/Core/GlobalEnvironment.cs b/src/TouchSocket.Core/Core/GlobalEnvironment.cs index 7643d9218..807aaa967 100644 --- a/src/TouchSocket.Core/Core/GlobalEnvironment.cs +++ b/src/TouchSocket.Core/Core/GlobalEnvironment.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Runtime.CompilerServices; namespace TouchSocket.Core; @@ -20,10 +19,10 @@ namespace TouchSocket.Core; /// public static class GlobalEnvironment { - /// - /// 动态构建类型,默认使用IL - /// - public static DynamicBuilderType DynamicBuilderType { get; set; } = DynamicBuilderType.IL; + ///// + ///// 动态构建类型时仅仅使用反射。一般在unity等不支持动态代码的平台使用。 + ///// + //public static bool ReflectOnly { get; set; } = false; #if NET6_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER @@ -41,10 +40,5 @@ public static class GlobalEnvironment /// /// 获取应用程序的基础目录。 /// - public static string BaseDirectory => -#if NET45 - AppDomain.CurrentDomain.BaseDirectory; -#else - AppContext.BaseDirectory; -#endif + public static string BaseDirectory => AppContext.BaseDirectory; } \ No newline at end of file diff --git a/src/TouchSocket.Core/Core/Metadata.cs b/src/TouchSocket.Core/Core/Metadata.cs index 7ed478149..cd740ecf2 100644 --- a/src/TouchSocket.Core/Core/Metadata.cs +++ b/src/TouchSocket.Core/Core/Metadata.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Collections.Generic; - namespace TouchSocket.Core; /// @@ -29,12 +27,12 @@ public sealed class Metadata : Dictionary, IPackage { get { - ThrowHelper.ThrowArgumentNullExceptionIf(key, nameof(key)); + ThrowHelper.ThrowIfNull(key, nameof(key)); return this.TryGetValue(key, out var value) ? value : null; } set { - ThrowHelper.ThrowArgumentNullExceptionIf(key, nameof(key)); + ThrowHelper.ThrowIfNull(key, nameof(key)); base[key] = value; } } @@ -51,26 +49,35 @@ public sealed class Metadata : Dictionary, IPackage return this; } + /// - public void Package(ref TByteBlock byteBlock) where TByteBlock : IByteBlock + public void Package(ref TWriter writer) + where TWriter : IBytesWriter + { - byteBlock.WriteInt32(this.Count); + WriterExtension.WriteValue(ref writer, this.Count); foreach (var item in this) { - byteBlock.WriteString(item.Key, FixedHeaderType.Byte); - byteBlock.WriteString(item.Value, FixedHeaderType.Byte); + WriterExtension.WriteString(ref writer, item.Key, FixedHeaderType.Byte); + WriterExtension.WriteString(ref writer, item.Value, FixedHeaderType.Byte); } } /// - public void Unpackage(ref TByteBlock byteBlock) where TByteBlock : IByteBlock + public void Unpackage(ref TReader reader) + where TReader : IBytesReader +#if AllowsRefStruct +, allows ref struct +#endif { - var count = byteBlock.ReadInt32(); + var count = ReaderExtension.ReadValue(ref reader); for (var i = 0; i < count; i++) { - var key = byteBlock.ReadString(FixedHeaderType.Byte); - var value = byteBlock.ReadString(FixedHeaderType.Byte); + var key = ReaderExtension.ReadString(ref reader, FixedHeaderType.Byte); + var value = ReaderExtension.ReadString(ref reader, FixedHeaderType.Byte); this.Add(key, value); } } + + } \ No newline at end of file diff --git a/src/TouchSocket.Core/Core/SnowflakeIDGenerator.cs b/src/TouchSocket.Core/Core/SnowflakeIDGenerator.cs index eb8176018..75737c575 100644 --- a/src/TouchSocket.Core/Core/SnowflakeIDGenerator.cs +++ b/src/TouchSocket.Core/Core/SnowflakeIDGenerator.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/Core/StringResStore.cs b/src/TouchSocket.Core/Core/StringResStore.cs index 66829645c..87ae278fd 100644 --- a/src/TouchSocket.Core/Core/StringResStore.cs +++ b/src/TouchSocket.Core/Core/StringResStore.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Collections.Concurrent; using System.ComponentModel; diff --git a/src/TouchSocket.Core/Core/TouchSocketCoreUtility.cs b/src/TouchSocket.Core/Core/TouchSocketCoreUtility.cs index 5e0b4a9ec..3bfb4af39 100644 --- a/src/TouchSocket.Core/Core/TouchSocketCoreUtility.cs +++ b/src/TouchSocket.Core/Core/TouchSocketCoreUtility.cs @@ -10,9 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Collections; -using System.Runtime.CompilerServices; using System.Text.RegularExpressions; namespace TouchSocket.Core; @@ -50,14 +48,6 @@ public class TouchSocketCoreUtility /// public const string Empty = ""; - /// - /// 0长度字节数组 - /// -#if NET45 - public static readonly byte[] ZeroBytes = new byte[0]; -#else - public static readonly byte[] ZeroBytes = Array.Empty(); -#endif private static int s_seed; /// @@ -152,34 +142,4 @@ public class TouchSocketCoreUtility return regex1.IsMatch(input); } } - - /// - /// 命中BufferLength - /// - /// - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int HitBufferLength(long value) - { - if (value < 1024 * 100) - { - return 1024; - } - else if (value < 1024 * 512) - { - return 1024 * 10; - } - else if (value < 1024 * 1024) - { - return 1024 * 64; - } - else - { - return value < 1024 * 1024 * 50 - ? 1024 * 512 - : value < 1024 * 1024 * 100 - ? 1024 * 1024 - : value < 1024 * 1024 * 1024 ? 1024 * 1024 * 2 : value < 1024 * 1024 * 1024 * 10L ? 1024 * 1024 * 5 : 1024 * 1024 * 10; - } - } } \ No newline at end of file diff --git a/src/TouchSocket.Core/Data/Compress/GZipDataCompressor.cs b/src/TouchSocket.Core/Data/Compress/GZipDataCompressor.cs index ae1c16507..4246dc094 100644 --- a/src/TouchSocket.Core/Data/Compress/GZipDataCompressor.cs +++ b/src/TouchSocket.Core/Data/Compress/GZipDataCompressor.cs @@ -10,23 +10,28 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// -/// GZip压缩算法的压缩机 +/// 表示一个GZip数据压缩器,提供基于GZip算法的数据压缩和解压缩功能。 +/// 实现了接口。 /// - +/// +/// GZipDataCompressor使用GZip压缩算法对数据进行压缩和解压缩操作。 +/// GZip是一种广泛使用的无损数据压缩算法,具有良好的压缩率和兼容性。 +/// 适用于需要减少数据传输量或存储空间的场景。 +/// public sealed partial class GZipDataCompressor : IDataCompressor { - byte[] IDataCompressor.Compress(ArraySegment data) + /// + public void Compress(ref TWriter writer, ReadOnlySpan data) where TWriter : IBytesWriter { - return GZip.Compress(data.Array, data.Offset, data.Count); + GZip.Compress(ref writer, data); } - byte[] IDataCompressor.Decompress(ArraySegment data) + /// + public void Decompress(ref TWriter writer, ReadOnlySpan data) where TWriter : IBytesWriter { - return GZip.Decompress(data.Array, data.Offset, data.Count); + GZip.Decompress(ref writer, data); } } \ No newline at end of file diff --git a/src/TouchSocket.Core/Data/Compress/IDataCompressor.cs b/src/TouchSocket.Core/Data/Compress/IDataCompressor.cs index 80f41ccd6..ddb230ce4 100644 --- a/src/TouchSocket.Core/Data/Compress/IDataCompressor.cs +++ b/src/TouchSocket.Core/Data/Compress/IDataCompressor.cs @@ -10,26 +10,42 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// -/// 数据压缩机接口 +/// 定义数据压缩器的接口,提供数据压缩和解压缩的抽象操作。 /// +/// +/// IDataCompressor接口为不同的压缩算法提供统一的接口定义, +/// 支持将数据压缩后写入到字节写入器中,以及从压缩数据中解压缩并写入到字节写入器中。 +/// 实现此接口的类可以提供如GZip、Deflate、Brotli等各种压缩算法的支持。 +/// public interface IDataCompressor { /// - /// 压缩数据 + /// 将指定的数据进行压缩,并将压缩结果写入到字节写入器中。 /// - /// - /// - byte[] Compress(ArraySegment data); + /// 字节写入器类型,必须继承自 + /// 用于写入压缩数据的字节写入器。 + /// 要压缩的原始数据。 + /// + /// 此方法将原始数据使用特定的压缩算法进行压缩, + /// 然后将压缩后的数据写入到提供的字节写入器中。 + /// 压缩算法的选择取决于具体的实现类。 + /// + void Compress(ref TWriter writer, ReadOnlySpan data) where TWriter : IBytesWriter; /// - /// 解压数据 + /// 将指定的压缩数据进行解压缩,并将解压缩结果写入到字节写入器中。 /// - /// - /// - byte[] Decompress(ArraySegment data); + /// 字节写入器类型,必须继承自 + /// 用于写入解压缩数据的字节写入器。 + /// 要解压缩的压缩数据。 + /// + /// 此方法将压缩数据使用对应的解压缩算法进行解压缩, + /// 然后将解压缩后的原始数据写入到提供的字节写入器中。 + /// 解压缩算法必须与压缩时使用的算法相匹配。 + /// + /// 当压缩数据格式无效或损坏时可能抛出。 + void Decompress(ref TWriter writer, ReadOnlySpan data) where TWriter : IBytesWriter; } \ No newline at end of file diff --git a/src/TouchSocket.Core/Data/Crc.cs b/src/TouchSocket.Core/Data/Crc.cs index 480c9df9a..981ff1e3c 100644 --- a/src/TouchSocket.Core/Data/Crc.cs +++ b/src/TouchSocket.Core/Data/Crc.cs @@ -10,835 +10,48 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// -/// Crc相关。 -/// 该代码来源于网络 +/// CRC校验工具类 /// public static class Crc { - /// ********************************************************************** - /// Name: CRC-4/ITU x4+x+1 - /// Poly: 0x03 - /// Init: 0x00 - /// Refin: true - /// Refout: true - /// Xorout: 0x00 - ///************************************************************************* - public static byte[] Crc1(byte[] buffer, int start = 0, int len = 0) - { - if (buffer == null || buffer.Length == 0) - { - return null; - } - - if (start < 0) - { - return null; - } - - if (len == 0) - { - len = buffer.Length - start; - } - - var length = start + len; - if (length > buffer.Length) - { - return null; - } - - byte crc = 0;// Initial value - for (var i = start; i < length; i++) - { - crc ^= buffer[i]; - for (var j = 0; j < 8; j++) - { - if ((crc & 1) > 0) - { - crc = (byte)((crc >> 1) ^ 0x0C);//0x0C = (reverse 0x03)>>(8-4) - } - else - { - crc = (byte)(crc >> 1); - } - } - } - return new byte[] { crc }; - } - - /// ********************************************************************** - /// Name: CRC-5/EPC x5+x3+1 - /// Poly: 0x09 - /// Init: 0x09 - /// Refin: false - /// Refout: false - /// Xorout: 0x00 - ///************************************************************************* - public static byte[] Crc2(byte[] buffer, int start = 0, int len = 0) - { - if (buffer == null || buffer.Length == 0) - { - return null; - } - - if (start < 0) - { - return null; - } - - if (len == 0) - { - len = buffer.Length - start; - } - - var length = start + len; - if (length > buffer.Length) - { - return null; - } - - byte crc = 0x48;// Initial value: 0x48 = 0x09<<(8-5) - for (var i = start; i < length; i++) - { - crc ^= buffer[i]; - for (var j = 0; j < 8; j++) - { - if ((crc & 0x80) > 0) - { - crc = (byte)((crc << 1) ^ 0x48);// 0x48 = 0x09<<(8-5) - } - else - { - crc = (byte)(crc << 1); - } - } - } - return new byte[] { (byte)(crc >> 3) }; - } - - /// ********************************************************************** - /// Name: CRC-5/ITU x5+x4+x2+1 - /// Poly: 0x15 - /// Init: 0x00 - /// Refin: true - /// Refout: true - /// Xorout: 0x00 - ///************************************************************************* - public static byte[] Crc3(byte[] buffer, int start = 0, int len = 0) - { - if (buffer == null || buffer.Length == 0) - { - return null; - } - - if (start < 0) - { - return null; - } - - if (len == 0) - { - len = buffer.Length - start; - } - - var length = start + len; - if (length > buffer.Length) - { - return null; - } - - byte crc = 0;// Initial value - for (var i = start; i < length; i++) - { - crc ^= buffer[i]; - for (var j = 0; j < 8; j++) - { - if ((crc & 1) > 0) - { - crc = (byte)((crc >> 1) ^ 0x15);// 0x15 = (reverse 0x15)>>(8-5) - } - else - { - crc = (byte)(crc >> 1); - } - } - } - return new byte[] { crc }; - } - - /// ********************************************************************** - /// Name: CRC-5/USB x5+x2+1 - /// Poly: 0x05 - /// Init: 0x1F - /// Refin: true - /// Refout: true - /// Xorout: 0x1F - ///************************************************************************* - public static byte[] Crc4(byte[] buffer, int start = 0, int len = 0) - { - if (buffer == null || buffer.Length == 0) - { - return null; - } - - if (start < 0) - { - return null; - } - - if (len == 0) - { - len = buffer.Length - start; - } - - var length = start + len; - if (length > buffer.Length) - { - return null; - } - - byte crc = 0x1F;// Initial value - for (var i = start; i < length; i++) - { - crc ^= buffer[i]; - for (var j = 0; j < 8; j++) - { - if ((crc & 1) > 0) - { - crc = (byte)((crc >> 1) ^ 0x14);// 0x14 = (reverse 0x05)>>(8-5) - } - else - { - crc = (byte)(crc >> 1); - } - } - } - return new byte[] { (byte)(crc ^ 0x1F) }; - } - - /// ********************************************************************** - /// Name: CRC-6/ITU x6+x+1 - /// Poly: 0x03 - /// Init: 0x00 - /// Refin: true - /// Refout: true - /// Xorout: 0x00 - ///************************************************************************* - public static byte[] Crc5(byte[] buffer, int start = 0, int len = 0) - { - if (buffer == null || buffer.Length == 0) - { - return null; - } - - if (start < 0) - { - return null; - } - - if (len == 0) - { - len = buffer.Length - start; - } - - var length = start + len; - if (length > buffer.Length) - { - return null; - } - - byte crc = 0;// Initial value - for (var i = start; i < length; i++) - { - crc ^= buffer[i]; - for (var j = 0; j < 8; j++) - { - if ((crc & 1) > 0) - { - crc = (byte)((crc >> 1) ^ 0x30);// 0x30 = (reverse 0x03)>>(8-6) - } - else - { - crc = (byte)(crc >> 1); - } - } - } - return new byte[] { crc }; - } - - /// ********************************************************************** - /// Name: CRC-7/MMC x7+x3+1 - /// Poly: 0x09 - /// Init: 0x00 - /// Refin: false - /// Refout: false - /// Xorout: 0x00 - ///************************************************************************* - public static byte[] Crc6(byte[] buffer, int start = 0, int len = 0) - { - if (buffer == null || buffer.Length == 0) - { - return null; - } - - if (start < 0) - { - return null; - } - - if (len == 0) - { - len = buffer.Length - start; - } - - var length = start + len; - if (length > buffer.Length) - { - return null; - } - - byte crc = 0;// Initial value - for (var i = start; i < length; i++) - { - crc ^= buffer[i]; - for (var j = 0; j < 8; j++) - { - if ((crc & 0x80) > 0) - { - crc = (byte)((crc << 1) ^ 0x12);// 0x12 = 0x09<<(8-7) - } - else - { - crc = (byte)(crc << 1); - } - } - } - return new byte[] { (byte)(crc >> 1) }; - } - - /// ********************************************************************** - /// Name: CRC8 x8+x2+x+1 - /// Poly: 0x07 - /// Init: 0x00 - /// Refin: false - /// Refout: false - /// Xorout: 0x00 - ///************************************************************************* - public static byte[] Crc7(byte[] buffer, int start = 0, int len = 0) - { - if (buffer == null || buffer.Length == 0) - { - return null; - } - - if (start < 0) - { - return null; - } - - if (len == 0) - { - len = buffer.Length - start; - } - - var length = start + len; - if (length > buffer.Length) - { - return null; - } - - byte crc = 0;// Initial value - for (var i = start; i < length; i++) - { - crc ^= buffer[i]; - for (var j = 0; j < 8; j++) - { - crc = (crc & 0x80) > 0 ? (byte)((crc << 1) ^ 0x07) : (byte)(crc << 1); - } - } - return new byte[] { crc }; - } - - /// ********************************************************************** - /// Name: CRC-8/ITU x8+x2+x+1 - /// Poly: 0x07 - /// Init: 0x00 - /// Refin: false - /// Refout: false - /// Xorout: 0x55 - ///************************************************************************* - public static byte[] Crc8(byte[] buffer, int start = 0, int len = 0) - { - if (buffer == null || buffer.Length == 0) - { - return null; - } - - if (start < 0) - { - return null; - } - - if (len == 0) - { - len = buffer.Length - start; - } - - var length = start + len; - if (length > buffer.Length) - { - return null; - } - - byte crc = 0;// Initial value - for (var i = start; i < length; i++) - { - crc ^= buffer[i]; - for (var j = 0; j < 8; j++) - { - crc = (crc & 0x80) > 0 ? (byte)((crc << 1) ^ 0x07) : (byte)(crc << 1); - } - } - return new byte[] { (byte)(crc ^ 0x55) }; - } - - /// ********************************************************************** - /// Name: CRC-8/MAXIM x8+x5+x4+1 - /// Poly: 0x31 - /// Init: 0x00 - /// Refin: true - /// Refout: true - /// Xorout: 0x00 - ///************************************************************************* - public static byte[] Crc9(byte[] buffer, int start = 0, int len = 0) - { - if (buffer == null || buffer.Length == 0) - { - return null; - } - - if (start < 0) - { - return null; - } - - if (len == 0) - { - len = buffer.Length - start; - } - - var length = start + len; - if (length > buffer.Length) - { - return null; - } - - byte crc = 0;// Initial value - for (var i = start; i < length; i++) - { - crc ^= buffer[i]; - for (var j = 0; j < 8; j++) - { - if ((crc & 1) > 0) - { - crc = (byte)((crc >> 1) ^ 0x8C);// 0x8C = reverse 0x31 - } - else - { - crc = (byte)(crc >> 1); - } - } - } - return new byte[] { crc }; - } - - /// ********************************************************************** - /// Name: CRC-8/ROHC x8+x2+x+1 - /// Poly: 0x07 - /// Init: 0xFF - /// Refin: true - /// Refout: true - /// Xorout: 0x00 - ///************************************************************************* - public static byte[] Crc10(byte[] buffer, int start = 0, int len = 0) - { - if (buffer == null || buffer.Length == 0) - { - return null; - } - - if (start < 0) - { - return null; - } - - if (len == 0) - { - len = buffer.Length - start; - } - - var length = start + len; - if (length > buffer.Length) - { - return null; - } - - byte crc = 0xFF;// Initial value - for (var i = start; i < length; i++) - { - crc ^= buffer[i]; - for (var j = 0; j < 8; j++) - { - if ((crc & 1) > 0) - { - crc = (byte)((crc >> 1) ^ 0xE0);// 0xE0 = reverse 0x07 - } - else - { - crc = (byte)(crc >> 1); - } - } - } - return new byte[] { crc }; - } - - /// Z1协议校验码计算 - private static readonly byte[] table = { 0x00, 0x1C, 0x38, 0x24, 0x70, 0x6C, 0x48, 0x54, 0xE0, 0xFC, - 0xD8, 0xC4, 0x90, 0x8C, 0xA8, 0xB4, 0xDC, 0xC0, 0xE4, 0xF8, - 0xAC, 0xB0, 0x94, 0x88, 0x3C, 0x20, 0x04, 0x18, 0x4C, 0x50, - 0x74, 0x68, 0xA4, 0xB8, 0x9C, 0x80, 0xD4, 0xC8, 0xEC, 0xF0, - 0x44, 0x58, 0x7C, 0x60, 0x34, 0x28, 0x0C, 0x10, 0x78, 0x64, - 0x40, 0x5C, 0x08, 0x14, 0x30, 0x2C, 0x98, 0x84, 0xA0, 0xBC, - 0xE8, 0xF4, 0xD0, 0xCC, 0x54, 0x48, 0x6C, 0x70, 0x24, 0x38, - 0x1C, 0x00, 0xB4, 0xA8, 0x8C, 0x90, 0xC4, 0xD8, 0xFC, 0xE0, - 0x88, 0x94, 0xB0, 0xAC, 0xF8, 0xE4, 0xC0, 0xDC, 0x68, 0x74, - 0x50, 0x4C, 0x18, 0x04, 0x20, 0x3C, 0xF0, 0xEC, 0xC8, 0xD4, - 0x80, 0x9C, 0xB8, 0xA4, 0x10, 0x0C, 0x28, 0x34, 0x60, 0x7C, - 0x58, 0x44, 0x2C, 0x30, 0x14, 0x08, 0x5C, 0x40, 0x64, 0x78, - 0xCC, 0xD0, 0xF4, 0xE8, 0xBC, 0xA0, 0x84, 0x98, 0xA8, 0xB4, - 0x90, 0x8C, 0xD8, 0xC4, 0xE0, 0xFC, 0x48, 0x54, 0x70, 0x6C, - 0x38, 0x24, 0x00, 0x1C, 0x74, 0x68, 0x4C, 0x50, 0x04, 0x18, - 0x3C, 0x20, 0x94, 0x88, 0xAC, 0xB0, 0xE4, 0xF8, 0xDC, 0xC0, - 0x0C, 0x10, 0x34, 0x28, 0x7C, 0x60, 0x44, 0x58, 0xEC, 0xF0, - 0xD4, 0xC8, 0x9C, 0x80, 0xA4, 0xB8, 0xD0, 0xCC, 0xE8, 0xF4, - 0xA0, 0xBC, 0x98, 0x84, 0x30, 0x2C, 0x08, 0x14, 0x40, 0x5C, - 0x78, 0x64, 0xFC, 0xE0, 0xC4, 0xD8, 0x8C, 0x90, 0xB4, 0xA8, - 0x1C, 0x00, 0x24, 0x38, 0x6C, 0x70, 0x54, 0x48, 0x20, 0x3C, - 0x18, 0x04, 0x50, 0x4C, 0x68, 0x74, 0xC0, 0xDC, 0xF8, 0xE4, - 0xB0, 0xAC, 0x88, 0x94, 0x58, 0x44, 0x60, 0x7C, 0x28, 0x34, - 0x10, 0x0C, 0xB8, 0xA4, 0x80, 0x9C, 0xC8, 0xD4, 0xF0, 0xEC, - 0x84, 0x98, 0xBC, 0xA0, 0xF4, 0xE8, 0xCC, 0xD0, 0x64, 0x78, - 0x5C, 0x40, 0x14, 0x08, 0x2C, 0x30 - }; - /// - /// Crc11 + /// CRC16查找表 /// - /// - /// - /// - /// - public static byte[] Crc11(byte[] buffer, int start = 0, int len = 0) + private static readonly ushort[] s_crc16Table = GenerateCrc16Table(); + + private static ushort[] GenerateCrc16Table() { - if (buffer == null || buffer.Length == 0) - { - return null; - } + var table = new ushort[256]; + const ushort polynomial = 0xA001; - if (start < 0) + for (var i = 0; i < 256; i++) { - return null; - } - - if (len == 0) - { - len = buffer.Length - start; - } - - var length = start + len; - if (length > buffer.Length) - { - return null; - } - - int i; - byte crc = 0x00; - int tableIndex; - for (i = start; i < length; i++) - { - tableIndex = crc ^ (buffer[i] & 0xFF); - crc = table[tableIndex]; - } - return new byte[] { crc }; - } - - /// ********************************************************************** - /// Name: CRC-12 x16+x12+x5+1 - /// Poly: 0x80 - /// Init: 0x0000 - /// Refin: true - /// Refout: true - /// Xorout: 0x0000 - ///************************************************************************* - public static byte[] Crc12(byte[] buffer, int start = 0, int len = 0) - { - if (buffer == null || buffer.Length == 0) - { - return null; - } - - if (start < 0) - { - return null; - } - - if (len == 0) - { - len = buffer.Length - start; - } - - var length = start + len; - if (length > buffer.Length) - { - return null; - } - - ushort crc = 0;// Initial value - short iQ = 0, iR = 0; - for (var i = start; i < length; i++) - { - // 多项式除法 - // 如果该位为1 - if ((buffer[i] & (0x80 >> iR)) > 0) - { - // 则在余数尾部添1否则添0 - crc |= 0x01; - } - // 如果12位除数中的最高位为1,则够除 - if (crc >= 0x1000) - { - crc ^= 0x180D; - } - crc <<= 1; - iR++; - if (8 == iR) - { - iR = 0; - iQ++; - } - } - // 对后面添加的12个0做处理 - for (var i = 0; i < 12; i++) - { - if (crc >= 0x1000) - { - crc ^= 0x180D; - } - crc <<= 1; - } - crc >>= 1; - var ret = BitConverter.GetBytes(crc); - Array.Reverse(ret); - return ret; - } - - /// ********************************************************************** - /// Name: CRC-16/CCITT x16+x12+x5+1 - /// Poly: 0x1021 - /// Init: 0x0000 - /// Refin: true - /// Refout: true - /// Xorout: 0x0000 - ///************************************************************************* - public static byte[] Crc13(byte[] buffer, int start = 0, int len = 0) - { - if (buffer == null || buffer.Length == 0) - { - return null; - } - - if (start < 0) - { - return null; - } - - if (len == 0) - { - len = buffer.Length - start; - } - - var length = start + len; - if (length > buffer.Length) - { - return null; - } - - ushort crc = 0;// Initial value - for (var i = start; i < length; i++) - { - crc ^= buffer[i]; + var crc = (ushort)i; for (var j = 0; j < 8; j++) { if ((crc & 1) > 0) { - crc = (ushort)((crc >> 1) ^ 0x8408);// 0x8408 = reverse 0x1021 + crc = (ushort)((crc >> 1) ^ polynomial); } else { crc = (ushort)(crc >> 1); } } - } - var ret = BitConverter.GetBytes(crc); - Array.Reverse(ret); - return ret; - } - - /// ********************************************************************** - /// Name: CRC-16/CCITT FALSE x16+x12+x5+1 - /// Poly: 0x1021 - /// Init: 0xFFFF - /// Refin: false - /// Refout: false - /// Xorout: 0x0000 - ///************************************************************************* - public static byte[] Crc14(byte[] buffer, int start = 0, int len = 0) - { - if (buffer == null || buffer.Length == 0) - { - return null; + table[i] = crc; } - if (start < 0) - { - return null; - } - - if (len == 0) - { - len = buffer.Length - start; - } - - var length = start + len; - if (length > buffer.Length) - { - return null; - } - - ushort crc = 0xFFFF;// Initial value - for (var i = start; i < length; i++) - { - crc ^= (ushort)(buffer[i] << 8); - for (var j = 0; j < 8; j++) - { - crc = (crc & 0x8000) > 0 ? (ushort)((crc << 1) ^ 0x1021) : (ushort)(crc << 1); - } - } - var ret = BitConverter.GetBytes(crc); - Array.Reverse(ret); - return ret; - } - - /// ********************************************************************** - /// Name: CRC-16/DNP x16+x13+x12+x11+x10+x8+x6+x5+x2+1 - /// Poly: 0x3D65 - /// Init: 0x0000 - /// Refin: true - /// Refout: true - /// Xorout: 0xFFFF - ///************************************************************************* - public static byte[] Crc15(byte[] buffer, int start = 0, int len = 0) - { - if (buffer == null || buffer.Length == 0) - { - return null; - } - - if (start < 0) return null; - if (len == 0) len = buffer.Length - start; - var length = start + len; - if (length > buffer.Length) return null; - ushort crc = 0;// Initial value - for (var i = start; i < length; i++) - { - crc ^= buffer[i]; - for (var j = 0; j < 8; j++) - { - if ((crc & 1) > 0) - crc = (ushort)((crc >> 1) ^ 0xA6BC);// 0xA6BC = reverse 0x3D65 - else - crc = (ushort)(crc >> 1); - } - } - var ret = BitConverter.GetBytes((ushort)~crc); - Array.Reverse(ret); - return ret; + return table; } /// - /// 计算并写入CRC16校验和 + /// 计算CRC16校验值并返回字节数组(大端序) /// - /// 用于存储CRC16校验和的目标字节序列 - /// 用于计算CRC16校验和的源字节序列 - public static void WriteCrc16(Span storeSpan, ReadOnlySpan span) - { - // 确保有足够的空间来存储CRC16校验和(2字节) - if (storeSpan.Length < 2) - { - // 如果空间不足,抛出异常 - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(storeSpan.Length), storeSpan.Length, 2); - } - - // 如果源字节序列为空,则无需计算CRC16校验和 - if (span.IsEmpty) - { - return; - } - - // 初始化CRC16校验和的计算 - var length = span.Length; - var start = 0; - - // 初始值为0,CRC16校验和的计算起点 - ushort crc = 0; - // 遍历源字节序列以计算CRC16校验和 - for (var i = start; i < length; i++) - { - // 异或操作更新CRC值 - crc ^= span[i]; - // 对当前CRC值进行8次位移操作,完成一个字节的处理 - for (var j = 0; j < 8; j++) - { - // 如果CRC值的最低位为1,则进行位移并异或操作,否则仅进行位移操作 - if ((crc & 1) > 0) - { - // 位移并异或操作,使用0xA001(逆0x8005)进行校验和计算 - crc = (ushort)((crc >> 1) ^ 0xA001); - } - else - { - // 仅进行位移操作 - crc = (ushort)(crc >> 1); - } - } - } - - // 将计算得到的CRC16校验和按大端字节序写入目标字节序列 - TouchSocketBitConverter.BigEndian.WriteBytes(storeSpan, crc); - } - - /// ********************************************************************** - /// Name: CRC-16/IBM x16+x15+x2+1 - /// Poly: 0x8005 - /// Init: 0x0000 - /// Refin: true - /// Refout: true - /// Xorout: 0x0000 - ///************************************************************************* + /// 待计算的字节序列 + /// CRC16校验值的字节数组 public static byte[] Crc16(ReadOnlySpan span) { var ret = BitConverter.GetBytes(Crc16Value(span)); @@ -847,266 +60,51 @@ public static class Crc } /// - /// 计算给定字节序列的CRC-16校验值。 + /// 计算CRC16校验值 /// - /// 要计算CRC-16校验值的字节序列。 - /// 计算得到的CRC-16校验值。 - /// 如果提供的字节序列为空,则抛出ArgumentNullException。 + /// 待计算的字节序列 + /// CRC16校验值 public static ushort Crc16Value(ReadOnlySpan span) { - // 检查输入的字节序列是否为空 if (span.IsEmpty) { - // 如果为空,则抛出ArgumentNullException ThrowHelper.ThrowArgumentNullException(nameof(span)); } - // 获取字节序列的长度 - var length = span.Length; - // 初始化起始位置为0 - var start = 0; - - // 初始化CRC校验值为0 ushort crc = 0; - // 遍历字节序列 - for (var i = start; i < length; i++) + for (var i = 0; i < span.Length; i++) { - // 对每个字节与当前CRC校验值进行异或操作 - crc ^= span[i]; - // 对每个字节的8位进行处理 - for (var j = 0; j < 8; j++) - { - // 如果CRC校验值的最低位为1 - if ((crc & 1) > 0) - { - // 右移一位并异或0xA001(0xA001是0x8005的反转) - crc = (ushort)((crc >> 1) ^ 0xA001); - } - else - { - // 如果最低位为0,直接右移一位 - crc = (ushort)(crc >> 1); - } - } + var index = (byte)(crc ^ span[i]); + crc = (ushort)((crc >> 8) ^ s_crc16Table[index]); } - // 返回计算得到的CRC校验值 + return crc; } - /// ********************************************************************** - /// Name: CRC-16/MAXIM x16+x15+x2+1 - /// Poly: 0x8005 - /// Init: 0x0000 - /// Refin: true - /// Refout: true - /// Xorout: 0xFFFF - ///************************************************************************* - public static byte[] Crc17(byte[] buffer, int start = 0, int len = 0) - { - if (buffer == null || buffer.Length == 0) return null; - if (start < 0) return null; - if (len == 0) len = buffer.Length - start; - var length = start + len; - if (length > buffer.Length) return null; - ushort crc = 0;// Initial value - for (var i = start; i < length; i++) - { - crc ^= buffer[i]; - for (var j = 0; j < 8; j++) - { - if ((crc & 1) > 0) - crc = (ushort)((crc >> 1) ^ 0xA001);// 0xA001 = reverse 0x8005 - else - crc = (ushort)(crc >> 1); - } - } - var ret = BitConverter.GetBytes((ushort)~crc); - Array.Reverse(ret); - return ret; - } - /// ********************************************************************** - /// Name: CRC-16/MODBUS x16+x15+x2+1 - /// Poly: 0x8005 - /// Init: 0xFFFF - /// Refin: true - /// Refout: true - /// Xorout: 0x0000 - ///************************************************************************* - public static byte[] Crc18(byte[] buffer, int start = 0, int len = 0) + /// + /// 计算CRC16校验值并写入指定的字节序列 + /// + /// 用于存储CRC16校验值的字节序列(至少2字节) + /// 待计算的字节序列 + public static void WriteCrc16(Span storeSpan, ReadOnlySpan span) { - if (buffer == null || buffer.Length == 0) return null; - if (start < 0) return null; - if (len == 0) len = buffer.Length - start; - var length = start + len; - if (length > buffer.Length) return null; - ushort crc = 0xFFFF;// Initial value - for (var i = start; i < length; i++) + if (storeSpan.Length < 2) { - crc ^= buffer[i]; - for (var j = 0; j < 8; j++) - { - if ((crc & 1) > 0) - crc = (ushort)((crc >> 1) ^ 0xA001);// 0xA001 = reverse 0x8005 - else - crc = (ushort)(crc >> 1); - } + ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(storeSpan.Length), storeSpan.Length, 2); } - var ret = BitConverter.GetBytes(crc); - Array.Reverse(ret); - return ret; - } - /// ********************************************************************** - /// Name: CRC-16/USB x16+x15+x2+1 - /// Poly: 0x8005 - /// Init: 0xFFFF - /// Refin: true - /// Refout: true - /// Xorout: 0xFFFF - ///************************************************************************* - public static byte[] Crc19(byte[] buffer, int start = 0, int len = 0) - { - if (buffer == null || buffer.Length == 0) return null; - if (start < 0) return null; - if (len == 0) len = buffer.Length - start; - var length = start + len; - if (length > buffer.Length) return null; - ushort crc = 0xFFFF;// Initial value - for (var i = start; i < length; i++) + if (span.IsEmpty) { - crc ^= buffer[i]; - for (var j = 0; j < 8; j++) - { - if ((crc & 1) > 0) - crc = (ushort)((crc >> 1) ^ 0xA001);// 0xA001 = reverse 0x8005 - else - crc = (ushort)(crc >> 1); - } + return; } - var ret = BitConverter.GetBytes((ushort)~crc); - Array.Reverse(ret); - return ret; - } - /// ********************************************************************** - /// Name: CRC-16/X25 x16+x12+x5+1 - /// Poly: 0x1021 - /// Init: 0xFFFF - /// Refin: true - /// Refout: true - /// Xorout: 0xFFFF - ///************************************************************************* - public static byte[] Crc20(byte[] buffer, int start = 0, int len = 0) - { - if (buffer == null || buffer.Length == 0) return null; - if (start < 0) return null; - if (len == 0) len = buffer.Length - start; - var length = start + len; - if (length > buffer.Length) return null; - ushort crc = 0xFFFF;// Initial value - for (var i = start; i < length; i++) + ushort crc = 0; + for (var i = 0; i < span.Length; i++) { - crc ^= buffer[i]; - for (var j = 0; j < 8; j++) - { - if ((crc & 1) > 0) - crc = (ushort)((crc >> 1) ^ 0x8408);// 0x8408 = reverse 0x1021 - else - crc = (ushort)(crc >> 1); - } + var index = (byte)(crc ^ span[i]); + crc = (ushort)((crc >> 8) ^ s_crc16Table[index]); } - var ret = BitConverter.GetBytes((ushort)~crc); - Array.Reverse(ret); - return ret; - } - /// ********************************************************************** - /// Name: CRC-16/XMODEM x16+x12+x5+1 - /// Poly: 0x1021 - /// Init: 0x0000 - /// Refin: false - /// Refout: false - /// Xorout: 0x0000 - ///************************************************************************* - public static byte[] Crc21(byte[] buffer, int start = 0, int len = 0) - { - if (buffer == null || buffer.Length == 0) return null; - if (start < 0) return null; - if (len == 0) len = buffer.Length - start; - var length = start + len; - if (length > buffer.Length) return null; - ushort crc = 0;// Initial value - for (var i = start; i < length; i++) - { - crc ^= (ushort)(buffer[i] << 8); - for (var j = 0; j < 8; j++) - { - crc = (crc & 0x8000) > 0 ? (ushort)((crc << 1) ^ 0x1021) : (ushort)(crc << 1); - } - } - var ret = BitConverter.GetBytes(crc); - Array.Reverse(ret); - return ret; - } - - /// ********************************************************************** - /// Name: CRC32 x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1 - /// Poly: 0x04C11DB7 - /// Init: 0xFFFFFFFF - /// Refin: true - /// Refout: true - /// Xorout: 0xFFFFFFFF - ///************************************************************************* - public static byte[] Crc22(byte[] buffer, int start = 0, int len = 0) - { - if (buffer == null || buffer.Length == 0) return null; - if (start < 0) return null; - if (len == 0) len = buffer.Length - start; - var length = start + len; - if (length > buffer.Length) return null; - var crc = 0xFFFFFFFF;// Initial value - for (var i = start; i < length; i++) - { - crc ^= buffer[i]; - for (var j = 0; j < 8; j++) - { - if ((crc & 1) > 0) - crc = (crc >> 1) ^ 0xEDB88320;// 0xEDB88320= reverse 0x04C11DB7 - else - crc >>= 1; - } - } - var ret = BitConverter.GetBytes(~crc); - Array.Reverse(ret); - return ret; - } - - /// ********************************************************************** - /// Name: CRC32/MPEG-2 x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1 - /// Poly: 0x04C11DB7 - /// Init: 0xFFFFFFFF - /// Refin: false - /// Refout: false - /// Xorout: 0x00000000 - ///************************************************************************* - public static byte[] Crc23(byte[] buffer, int start = 0, int len = 0) - { - if (buffer == null || buffer.Length == 0) return null; - if (start < 0) return null; - if (len == 0) len = buffer.Length - start; - var length = start + len; - if (length > buffer.Length) return null; - var crc = 0xFFFFFFFF;// Initial value - for (var i = start; i < length; i++) - { - crc ^= (uint)(buffer[i] << 24); - for (var j = 0; j < 8; j++) - { - crc = (crc & 0x80000000) > 0 ? (crc << 1) ^ 0x04C11DB7 : crc << 1; - } - } - var ret = BitConverter.GetBytes(crc); - Array.Reverse(ret); - return ret; + TouchSocketBitConverter.BigEndian.WriteBytes(storeSpan, crc); } } \ No newline at end of file diff --git a/src/TouchSocket.Core/Data/GZip.cs b/src/TouchSocket.Core/Data/GZip.cs index e605ab88f..acdb54244 100644 --- a/src/TouchSocket.Core/Data/GZip.cs +++ b/src/TouchSocket.Core/Data/GZip.cs @@ -11,124 +11,137 @@ //------------------------------------------------------------------------------ using System.Buffers; -using System.IO; using System.IO.Compression; namespace TouchSocket.Core; /// -/// Gzip操作类 +/// 提供GZip压缩和解压缩功能的静态工具类。 /// +/// +/// 此类封装了基于的压缩和解压缩操作, +/// 支持对字节数据、流、字节块和字节写入器进行GZip格式的压缩和解压缩处理。 +/// public static partial class GZip { /// - /// 压缩数据 + /// 将字节跨度压缩并写入到指定流中。 /// - /// - /// - /// - /// - /// - public static void Compress(Stream stream, byte[] buffer, int offset, int length) + /// 要写入压缩数据的流。 + /// 要压缩的只读字节跨度。 + /// + /// 此方法使用GZip压缩算法将数据压缩后写入流,压缩完成后会关闭GZip流但不关闭底层流。 + /// + public static void Compress(Stream stream, ReadOnlySpan span) { using (var gZipStream = new GZipStream(stream, CompressionMode.Compress, true)) { - gZipStream.Write(buffer, offset, length); + gZipStream.Write(span); gZipStream.Close(); } } /// - /// 压缩数据 + /// 将字节跨度压缩并写入到字节写入器中。 /// - /// - /// - /// - public static void Compress(Stream stream, byte[] buffer) + /// 实现接口的写入器类型。 + /// 字节写入器实例。 + /// 要压缩的只读字节跨度。 + /// + /// 此方法内部使用临时进行压缩操作,压缩完成后将结果写入指定的写入器。 + /// + public static void Compress(ref TWriter writer, ReadOnlySpan span) + where TWriter : IBytesWriter { - Compress(stream, buffer, 0, buffer.Length); + using (var byteBlock = new ByteBlock(span.Length)) + { + Compress(byteBlock.AsStream(), span); + writer.Write(byteBlock.Span); + } } /// - /// 压缩数据 + /// 将字节跨度压缩并写入到字节块中。 /// - /// - /// - /// - /// - public static byte[] Compress(byte[] buffer, int offset, int length) + /// 要写入压缩数据的。 + /// 要压缩的只读字节跨度。 + /// + /// 此方法将压缩数据直接写入指定的字节块中。 + /// + public static void Compress(ByteBlock byteBlock, ReadOnlySpan span) { - using (var byteBlock = new ByteBlock(length)) + Compress(byteBlock.AsStream(), span); + } + + /// + /// 压缩字节跨度并返回压缩后的数据。 + /// + /// 要压缩的只读字节跨度。 + /// 包含压缩数据的 + /// + /// 此方法返回压缩后数据的副本,调用者负责管理返回的内存。 + /// + public static ReadOnlyMemory Compress(ReadOnlySpan span) + { + using (var byteBlock = new ByteBlock(span.Length)) { - Compress(byteBlock.AsStream(), buffer, offset, length); + Compress(byteBlock.AsStream(), span); return byteBlock.ToArray(); } } /// - /// 压缩数据 + /// 解压缩字节跨度并将结果写入到字节写入器中。 /// - /// - /// - public static byte[] Compress(byte[] buffer) + /// 实现接口的写入器类型。 + /// 字节写入器实例。 + /// 要解压缩的只读字节跨度。 + /// + /// 此方法使用64KB的缓冲区进行解压缩操作,通过流式读取方式处理大数据。 + /// 内部使用来管理缓冲区内存。 + /// + public static void Decompress(ref TWriter writer, ReadOnlySpan span) + where TWriter : IBytesWriter { - return Compress(buffer, 0, buffer.Length); - } - - /// - /// 解压数据 - /// - /// - /// - /// - /// - /// - public static void Decompress(ref TByteBlock byteBlock, byte[] data, int offset, int length) - where TByteBlock : IByteBlock - { - using (var gZipStream = new GZipStream(new MemoryStream(data, offset, length), CompressionMode.Decompress)) + using (var streamByteBlock = new ByteBlock(span.Length)) { - var bytes = ArrayPool.Shared.Rent(1024 * 64); - try + streamByteBlock.Write(span); + streamByteBlock.SeekToStart(); + + using (var gZipStream = new GZipStream(streamByteBlock.AsStream(), CompressionMode.Decompress)) { - int r; - while ((r = gZipStream.Read(bytes, 0, bytes.Length)) != 0) + var bytes = ArrayPool.Shared.Rent(1024 * 64); + try { - byteBlock.Write(new System.ReadOnlySpan(bytes, 0, r)); + int r; + while ((r = gZipStream.Read(bytes, 0, bytes.Length)) != 0) + { + writer.Write(new System.ReadOnlySpan(bytes, 0, r)); + } + gZipStream.Close(); + } + finally + { + ArrayPool.Shared.Return(bytes); } - gZipStream.Close(); - } - finally - { - ArrayPool.Shared.Return(bytes); } } } /// - /// 解压数据 + /// 解压缩字节跨度并返回解压缩后的数据。 /// - /// - /// - public static void Decompress(ref TByteBlock byteBlock, byte[] data) - where TByteBlock : IByteBlock + /// 要解压缩的只读字节跨度。 + /// 包含解压缩数据的 + /// + /// 此方法返回解压缩后数据的副本,调用者负责管理返回的内存。 + /// + public static ReadOnlyMemory Decompress(ReadOnlySpan span) { - Decompress(ref byteBlock, data, 0, data.Length); - } - - /// - /// 解压数据 - /// - /// - /// - /// - /// - public static byte[] Decompress(byte[] data, int offset, int length) - { - var byteBlock = new ByteBlock(length); + var byteBlock = new ByteBlock(span.Length); try { - Decompress(ref byteBlock, data, offset, length); + Decompress(ref byteBlock, span); return byteBlock.ToArray(); } finally @@ -136,14 +149,4 @@ public static partial class GZip byteBlock.Dispose(); } } - - /// - /// 解压数据 - /// - /// - /// - public static byte[] Decompress(byte[] data) - { - return Decompress(data, 0, data.Length); - } } \ No newline at end of file diff --git a/src/TouchSocket.Core/Data/MD5.cs b/src/TouchSocket.Core/Data/MD5.cs index 518128b39..3b2ad6709 100644 --- a/src/TouchSocket.Core/Data/MD5.cs +++ b/src/TouchSocket.Core/Data/MD5.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.IO; -using System.Text; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/Data/Security/DataSecurity.cs b/src/TouchSocket.Core/Data/Security/DataSecurity.cs index aaadb0385..bc113869c 100644 --- a/src/TouchSocket.Core/Data/Security/DataSecurity.cs +++ b/src/TouchSocket.Core/Data/Security/DataSecurity.cs @@ -10,9 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.IO; using System.Security.Cryptography; -using System.Text; namespace TouchSocket.Core; diff --git a/src/TouchSocket.Core/Data/Swap.cs b/src/TouchSocket.Core/Data/Swap.cs index 72cb622a8..3a68958a4 100644 --- a/src/TouchSocket.Core/Data/Swap.cs +++ b/src/TouchSocket.Core/Data/Swap.cs @@ -25,7 +25,7 @@ public static class Swap /// public static void Execute(ref T x, ref T y) { -#if NET45_OR_GREATER +#if NET462_OR_GREATER var temp = x; x = y; y = temp; diff --git a/src/TouchSocket.Core/DataAdapter/AdapterOption.cs b/src/TouchSocket.Core/DataAdapter/AdapterOption.cs index 72b5cbed6..e68bb8eae 100644 --- a/src/TouchSocket.Core/DataAdapter/AdapterOption.cs +++ b/src/TouchSocket.Core/DataAdapter/AdapterOption.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// @@ -33,9 +31,4 @@ public class AdapterOption /// 适配器数据包最大值。默认缺省(null),当该值有效时会在设置适配器时,直接作用于 /// public int? MaxPackageSize { get; set; } - - /// - /// 适配器数据包缓存策略。默认缺省(null),当该值有效时会在设置适配器时,直接作用于 - /// - public bool? UpdateCacheTimeWhenRev { get; set; } } \ No newline at end of file diff --git a/src/TouchSocket.Core/DataAdapter/CacheDataHandlingAdapter.cs b/src/TouchSocket.Core/DataAdapter/CacheDataHandlingAdapter.cs index 0f00d147a..9a6450109 100644 --- a/src/TouchSocket.Core/DataAdapter/CacheDataHandlingAdapter.cs +++ b/src/TouchSocket.Core/DataAdapter/CacheDataHandlingAdapter.cs @@ -1,99 +1,66 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ -using System; +//using System; +//using System.Buffers; +//using System.Data.SqlTypes; +//using System.Threading.Tasks; -namespace TouchSocket.Core; +//namespace TouchSocket.Core; -/// -/// CacheDataHandlingAdapter -/// -public abstract class CacheDataHandlingAdapter : SingleStreamDataHandlingAdapter -{ - /// - /// 缓存数据,如果需要手动释放,请先判断,然后到调用后,再置空; - /// - protected ByteBlock m_cacheByteBlock; +///// +///// CacheDataHandlingAdapter +///// +//public abstract class CacheDataHandlingAdapter : SingleStreamDataHandlingAdapter +//{ +// /// +// /// 缓存数据,如果需要手动释放,请先判断,然后到调用后,再置空; +// /// +// private SegmentedBytesWriter m_cacheByteBlock; - /// - /// 将数据缓存起来 - /// - /// - /// - /// - protected void Cache(byte[] buffer, int offset, int length) - { - this.m_cacheByteBlock ??= new ByteBlock(length); - this.m_cacheByteBlock.Write(new ReadOnlySpan(buffer, offset, length)); - if (this.UpdateCacheTimeWhenRev) - { - this.LastCacheTime = DateTimeOffset.UtcNow; - } - } +// /// +// /// 将数据缓存起来 +// /// +// protected void Cache(ref TReader reader) +// where TReader : IBytesReader +// { +// var sequence = reader.Sequence; +// this.m_cacheByteBlock ??= new SegmentedBytesWriter((int)sequence.Length); +// foreach (var item in sequence) +// { +// this.m_cacheByteBlock.Write(item.Span); +// } - /// - protected override void Reset() - { - this.m_cacheByteBlock.SafeDispose(); - this.m_cacheByteBlock = null; - base.Reset(); - } +// if (this.UpdateCacheTimeWhenRev) +// { +// this.LastCacheTime = DateTimeOffset.UtcNow; +// } +// } - /// - /// 获取当前缓存, - /// 如果缓存超时,或者不存在,均会返回。 - /// 如果获取成功,则会清空内部缓存。 - /// - /// - protected bool TryGetCache(out byte[] buffer) - { - if (this.m_cacheByteBlock == null) - { - buffer = null; - return false; - } - if (DateTimeOffset.UtcNow - this.LastCacheTime > this.CacheTimeout) - { - this.m_cacheByteBlock.SafeDispose(); - this.m_cacheByteBlock = null; - buffer = null; - return false; - } - buffer = this.m_cacheByteBlock.ToArray(); - this.m_cacheByteBlock.SafeDispose(); - this.m_cacheByteBlock = null; - return true; - } +// /// +// protected override void Reset() +// { +// this.m_cacheByteBlock.SafeDispose(); +// this.m_cacheByteBlock = null; +// base.Reset(); +// } - /// - /// 获取缓存,注意:获取的ByteBlock需要手动释放。 - /// - /// - /// - protected bool TryGetCache(out ByteBlock byteBlock) - { - if (this.m_cacheByteBlock == null) - { - byteBlock = null; - return false; - } - if (DateTimeOffset.UtcNow - this.LastCacheTime > this.CacheTimeout) - { - this.m_cacheByteBlock.SafeDispose(); - this.m_cacheByteBlock = null; - byteBlock = null; - return false; - } - byteBlock = this.m_cacheByteBlock; - return true; - } -} \ No newline at end of file +// private long m_cacheSize; + +// protected sealed override Task PreviewReceivedAsync(TReader reader) +// { + +// } + +// protected abstract Task PreviewReceivedAsyncAfterCacheVerification(ref TReader reader) +// where TReader : IBytesReader; +//} \ No newline at end of file diff --git a/src/TouchSocket.Core/DataAdapter/Custom/CustomBetweenAndDataHandlingAdapter.cs b/src/TouchSocket.Core/DataAdapter/Custom/CustomBetweenAndDataHandlingAdapter.cs index ae40a139e..b91a062cf 100644 --- a/src/TouchSocket.Core/DataAdapter/Custom/CustomBetweenAndDataHandlingAdapter.cs +++ b/src/TouchSocket.Core/DataAdapter/Custom/CustomBetweenAndDataHandlingAdapter.cs @@ -10,7 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; +using System.Buffers; namespace TouchSocket.Core; @@ -22,7 +22,7 @@ public abstract class CustomBetweenAndDataHandlingAdapter /// 起始字符,不可以为,可以为0长度 /// - public abstract byte[] StartCode { get; } + public abstract ReadOnlyMemory StartCode { get; } /// /// 即使找到了终止因子,也不会结束,默认0 @@ -32,7 +32,7 @@ public abstract class CustomBetweenAndDataHandlingAdapter /// 结束字符,不可以为,不可以为0长度,必须具有有效值。 /// - public abstract byte[] EndCode { get; } + public abstract ReadOnlyMemory EndCode { get; } /// /// 筛选解析数据。实例化的TRequest会一直保存,直至解析成功,或手动清除。 @@ -40,22 +40,21 @@ public abstract class CustomBetweenAndDataHandlingAdapter当数据部分异常时,请移动到指定位置,然后返回 /// 当完全满足解析条件时,请返回最后将移至指定位置。 /// - /// 字节块 + /// 字节块 /// 是否为上次遗留对象,当该参数为时,request也将是上次实例化的对象。 /// 对象。 - /// 缓存容量。当需要首次缓存时,指示申请的ByteBlock的容量。合理的值可避免ByteBlock扩容带来的性能消耗。 /// - protected override FilterResult Filter(ref T byteBlock, bool beCached, ref TBetweenAndRequestInfo request, ref int tempCapacity) + protected override FilterResult Filter(ref T reader, bool beCached, ref TBetweenAndRequestInfo request) { - var startCode = this.StartCode ?? ReadOnlySpan.Empty; - var endCode = this.EndCode ?? ReadOnlySpan.Empty; + var startCode = this.StartCode.Span; + var endCode = this.EndCode.Span; // 检查终止字符是否为空 if (endCode.IsEmpty) { ThrowHelper.ThrowException("区间字符适配器的终止字符不能为空"); } // 获取可读取的字节范围 - var canReadSpan = byteBlock.Span.Slice(byteBlock.Position, byteBlock.CanReadLength); + var canReadSpan = reader.Sequence; // 查找起始码的索引 var startCodeIndex = canReadSpan.IndexOf(startCode); if (startCodeIndex < 0) @@ -65,7 +64,7 @@ public abstract class CustomBetweenAndDataHandlingAdapter数据体 /// 泛型实例 protected abstract TBetweenAndRequestInfo GetInstance(ReadOnlySpan body); -} - -/// -/// 区间类型的适配器数据模型接口。 -/// -[Obsolete("此接口已被弃用,请使用IRequestInfo代替约束。具体数据会在CustomBetweenAndDataHandlingAdapter.GetInstance(ReadOnlySpan body)直接投递。", true)] -public interface IBetweenAndRequestInfo : IRequestInfo -{ /// - /// 当解析到起始字符时。 + /// 获取泛型实例。 /// - /// - /// - bool OnParsingStartCode(ReadOnlySpan startCode); - - /// - /// 当解析数据体。 - /// 在此方法中,您必须手动保存Body内容 - /// - /// - void OnParsingBody(ReadOnlySpan body); - - /// - /// 当解析到起始字符时。 - /// - /// - /// - bool OnParsingEndCode(ReadOnlySpan endCode); + /// 数据体 + /// 泛型实例 + protected virtual TBetweenAndRequestInfo GetInstance(ReadOnlySequence body) + { + using (var memoryBuffer = new ContiguousMemoryBuffer(body)) + { + return this.GetInstance(memoryBuffer.Memory.Span); + } + } } \ No newline at end of file diff --git a/src/TouchSocket.Core/DataAdapter/Custom/CustomBigFixedHeaderDataHandlingAdapter.cs b/src/TouchSocket.Core/DataAdapter/Custom/CustomBigFixedHeaderDataHandlingAdapter.cs index 2e5d1b96b..5d8583d1f 100644 --- a/src/TouchSocket.Core/DataAdapter/Custom/CustomBigFixedHeaderDataHandlingAdapter.cs +++ b/src/TouchSocket.Core/DataAdapter/Custom/CustomBigFixedHeaderDataHandlingAdapter.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// @@ -31,22 +29,21 @@ public abstract class CustomBigFixedHeaderDataHandlingAdapter当数据部分异常时,请移动到指定位置,然后返回 /// 当完全满足解析条件时,请返回最后将移至指定位置。 /// - /// 字节块 + /// 字节块 /// 是否为上次遗留对象,当该参数为时,request也将是上次实例化的对象。 /// 对象。 - /// 缓存容量。当需要首次缓存时,指示申请的ByteBlock的容量。合理的值可避免ByteBlock扩容带来的性能消耗。 /// - protected override FilterResult Filter(ref TByteBlock byteBlock, bool beCached, ref TFixedHeaderRequestInfo request, ref int tempCapacity) + protected override FilterResult Filter(ref TReader reader, bool beCached, ref TFixedHeaderRequestInfo request) { if (beCached) { - while (this.m_surLen > 0 && byteBlock.CanReadLength > 0) + while (this.m_surLen > 0 && reader.BytesRemaining > 0) { - var r = (int)Math.Min(this.m_surLen, byteBlock.CanReadLength); - var bytes = byteBlock.Span.Slice(byteBlock.Position, r); + var r = (int)Math.Min(this.m_surLen, reader.BytesRemaining); + var bytes = reader.GetSpan(r); request.OnAppendBody(bytes); this.m_surLen -= r; - byteBlock.Position += r; + reader.Advance(r); if (this.m_surLen == 0) { if (request.OnFinished()) @@ -61,16 +58,16 @@ public abstract class CustomBigFixedHeaderDataHandlingAdapter byteBlock.CanReadLength) + if (this.HeaderLength > reader.BytesRemaining) { return FilterResult.Cache; } var requestInfo = this.GetInstance(); - var header = byteBlock.Span.Slice(byteBlock.Position, this.HeaderLength); + var header = reader.GetSpan(this.HeaderLength); if (requestInfo.OnParsingHeader(header)) { - byteBlock.Position += this.HeaderLength; + reader.Advance(this.HeaderLength); request = requestInfo; if (requestInfo.BodyLength == 0) { @@ -83,13 +80,13 @@ public abstract class CustomBigFixedHeaderDataHandlingAdapter 0 && byteBlock.CanReadLength > 0) + while (this.m_surLen > 0 && reader.BytesRemaining > 0) { - var r = (int)Math.Min(this.m_surLen, byteBlock.CanReadLength); - var bytes = byteBlock.Span.Slice(byteBlock.Position, r); + var r = (int)Math.Min(this.m_surLen, reader.BytesRemaining); + var bytes = reader.GetSpan(r); request.OnAppendBody(bytes); this.m_surLen -= r; - byteBlock.Position += r; + reader.Advance(r); if (this.m_surLen == 0) { if (request.OnFinished()) @@ -104,7 +101,7 @@ public abstract class CustomBigFixedHeaderDataHandlingAdapter @@ -26,22 +24,21 @@ public abstract class CustomBigUnfixedHeaderDataHandlingAdapter当数据部分异常时,请移动到指定位置,然后返回 /// 当完全满足解析条件时,请返回最后将移至指定位置。 /// - /// 字节块 + /// 字节块 /// 是否为上次遗留对象,当该参数为时,request也将是上次实例化的对象。 /// 对象。 - /// 缓存容量。当需要首次缓存时,指示申请的ByteBlock的容量。合理的值可避免ByteBlock扩容带来的性能消耗。 /// - protected override FilterResult Filter(ref TByteBlock byteBlock, bool beCached, ref TFixedHeaderRequestInfo request, ref int tempCapacity) + protected override FilterResult Filter(ref TReader reader, bool beCached, ref TFixedHeaderRequestInfo request) { if (beCached) { - while (this.m_surLen > 0 && byteBlock.CanReadLength > 0) + while (this.m_surLen > 0 && reader.BytesRemaining > 0) { - var r = (int)Math.Min(this.m_surLen, byteBlock.CanReadLength); - var bytes = byteBlock.Span.Slice(byteBlock.Position, r); + var r = (int)Math.Min(this.m_surLen, reader.BytesRemaining); + var bytes = reader.GetSpan(r); request.OnAppendBody(bytes); this.m_surLen -= r; - byteBlock.Position += r; + reader.Advance(r); if (this.m_surLen == 0) { if (request.OnFinished()) @@ -57,7 +54,7 @@ public abstract class CustomBigUnfixedHeaderDataHandlingAdapter 0 && byteBlock.CanReadLength > 0) + while (this.m_surLen > 0 && reader.BytesRemaining > 0) { - var r = (int)Math.Min(this.m_surLen, byteBlock.CanReadLength); - var bytes = byteBlock.Span.Slice(byteBlock.Position, r); + var r = (int)Math.Min(this.m_surLen, reader.BytesRemaining); + var bytes = reader.GetSpan(r); request.OnAppendBody(bytes); this.m_surLen -= r; - byteBlock.Position += r; + reader.Advance(r); if (this.m_surLen == 0) { if (request.OnFinished()) @@ -92,7 +89,6 @@ public abstract class CustomBigUnfixedHeaderDataHandlingAdapter如果返回,意味着缓存剩余数据,此时如果仅仅是因为长度不足,则不必修改其他。 /// 但是如果是因为数据错误,则需要修改到正确位置,如果都不正确,则设置等于 /// - /// + /// /// 是否满足解析包头 - bool OnParsingHeader(ref TByteBlock byteBlock) where TByteBlock : IByteBlock; + bool OnParsingHeader(ref TReader reader) where TReader : IBytesReader; /// /// 当收到数据,由框架封送数据。 diff --git a/src/TouchSocket.Core/DataAdapter/Custom/CustomCountSpliterDataHandlingAdapter.cs b/src/TouchSocket.Core/DataAdapter/Custom/CustomCountSpliterDataHandlingAdapter.cs index 020706a1e..0a5f36f1f 100644 --- a/src/TouchSocket.Core/DataAdapter/Custom/CustomCountSpliterDataHandlingAdapter.cs +++ b/src/TouchSocket.Core/DataAdapter/Custom/CustomCountSpliterDataHandlingAdapter.cs @@ -10,70 +10,103 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; +using System.Buffers; namespace TouchSocket.Core; /// /// 自定义计数分隔符数据处理适配器。 /// -/// 请求信息类型。 +/// 请求信息类型,必须实现接口。 +/// +/// 此适配器通过计数特定分隔符的出现次数来分割数据流。 +/// 当达到指定的分隔符计数时,将提取相应的数据段并创建请求信息实例。 +/// 适用于基于固定分隔符模式的协议解析场景。 +/// public abstract class CustomCountSpliterDataHandlingAdapter : CustomDataHandlingAdapter where TCountSpliterRequestInfo : IRequestInfo { /// - /// 获取计数。 + /// 初始化类的新实例。 /// - public int Count { get; } - - /// - /// 获取分隔符。 - /// - public byte[] Spliter { get; } - - /// - /// 初始化 类的新实例。 - /// - /// 计数。 - /// 分隔符。 - /// 当计数小于2时抛出。 - /// 当分隔符为空时抛出。 - public CustomCountSpliterDataHandlingAdapter(int count, byte[] spliter) + /// 分隔符的计数阈值,必须大于等于2。 + /// 用于分割数据的分隔符字节序列。 + /// 小于2时抛出。 + /// 为空时抛出。 + /// + /// 构造函数验证参数的有效性。计数必须至少为2才能进行有意义的数据分割, + /// 分隔符不能为空以确保能够正确识别数据边界。 + /// + public CustomCountSpliterDataHandlingAdapter(int count, ReadOnlyMemory spliter) { if (count < 2) { ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(count), count, 2); } - - ThrowHelper.ThrowArgumentNullExceptionIf(spliter, nameof(spliter)); - this.Count = count; this.Spliter = spliter; } - /// - protected override FilterResult Filter(ref TByteBlock byteBlock, bool beCached, ref TCountSpliterRequestInfo request, ref int tempCapacity) + /// + /// 获取分隔符的计数阈值。 + /// + /// 当分隔符出现次数达到此值时,将触发数据提取操作。 + /// + /// 此属性定义了适配器在提取数据段之前需要计数的分隔符数量。 + /// 值必须大于等于2,以确保能够识别数据的开始和结束边界。 + /// + public int Count { get; } + + /// + /// 获取用于分割数据的分隔符。 + /// + /// 包含分隔符字节序列的只读内存块。 + /// + /// 此分隔符用于在数据流中标识数据段的边界。 + /// 适配器将在输入流中搜索此字节序列的出现次数。 + /// + public ReadOnlyMemory Spliter { get; } + + /// + /// 筛选解析数据,通过计数分隔符来确定数据边界。 + /// + /// 字节块类型,必须实现接口。 + /// 要解析的字节块。 + /// 指示当前请求对象是否为缓存的上次实例。 + /// 输出的请求信息对象。 + /// + /// :当分隔符计数未达到阈值时,需要缓存更多数据。 + /// :成功解析出完整的数据段并创建了请求信息实例。 + /// + /// + /// 此方法实现了核心的数据分割逻辑: + /// + /// 在字节块中搜索分隔符出现的位置 + /// 计数分隔符的出现次数 + /// 当计数达到时,提取相应的数据段 + /// 调用创建请求信息实例 + /// + /// 如果分隔符计数不足,方法返回以等待更多数据。 + /// + protected override FilterResult Filter(ref TByteBlock byteBlock, bool beCached, ref TCountSpliterRequestInfo request) { - var position = byteBlock.Position; var count = 0; - var spanAll = byteBlock.Span.Slice(position); - var startIndex = 0; - var length = 0; + var spanAll = byteBlock.Sequence; + var startIndex = 0L; + var length = 0L; - var spliterSpan = new ReadOnlySpan(this.Spliter); + var spliterSpan = this.Spliter.Span; while (true) { if (spanAll.Length <= startIndex + length) { - byteBlock.Position = position; return FilterResult.Cache; } var currentSpan = spanAll.Slice(startIndex + length); var r = currentSpan.IndexOf(spliterSpan); if (r < 0) { - byteBlock.Position = position; return FilterResult.Cache; } @@ -91,29 +124,38 @@ public abstract class CustomCountSpliterDataHandlingAdapter - /// 获取请求信息实例。 + /// 从数据跨度创建请求信息实例。 /// - /// 数据跨度。 - /// 请求信息实例。 + /// 包含请求数据的字节跨度。 + /// 基于提供数据创建的请求信息实例。 + /// + /// 此抽象方法由派生类实现,用于将解析出的数据段转换为具体的请求信息对象。 + /// 数据跨度包含了从第一个分隔符到最后一个分隔符(包含)的完整数据段。 + /// protected abstract TCountSpliterRequestInfo GetInstance(in ReadOnlySpan dataSpan); -} -/// -/// 计数分隔符请求信息接口。 -/// -public interface ICountSpliterRequestInfo : IRequestInfo -{ /// - /// 解析开始代码。 + /// 从数据序列创建请求信息实例。 /// - /// 开始代码。 - /// 是否成功解析。 - bool OnParsing(ReadOnlySpan startCode); + /// 包含请求数据的字节序列。 + /// 基于提供数据创建的请求信息实例。 + /// + /// 此虚拟方法提供了处理非连续内存数据的默认实现。 + /// 它将数据序列转换为连续内存,然后调用方法。 + /// 派生类可以重写此方法以提供更高效的实现。 + /// + protected virtual TCountSpliterRequestInfo GetInstance(in ReadOnlySequence dataSequence) + { + using (var memoryBuffer = new ContiguousMemoryBuffer(dataSequence)) + { + return this.GetInstance(memoryBuffer.Memory.Span); + } + } } \ No newline at end of file diff --git a/src/TouchSocket.Core/DataAdapter/Custom/CustomDataHandlingAdapter.cs b/src/TouchSocket.Core/DataAdapter/Custom/CustomDataHandlingAdapter.cs index 840b0e419..03c014de2 100644 --- a/src/TouchSocket.Core/DataAdapter/Custom/CustomDataHandlingAdapter.cs +++ b/src/TouchSocket.Core/DataAdapter/Custom/CustomDataHandlingAdapter.cs @@ -10,195 +10,81 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; - namespace TouchSocket.Core; /// /// 用户自定义数据处理适配器,使用该适配器时,接收方收到的数据中,将为, /// 同时将实现为TRequest,发送数据直接发送。 /// -public abstract class CustomDataHandlingAdapter : SingleStreamDataHandlingAdapter where TRequest : IRequestInfo +public abstract class CustomDataHandlingAdapter : SingleStreamDataHandlingAdapter + where TRequest : IRequestInfo { + private TRequest m_tempRequest; - private ValueByteBlock m_tempByteBlock; - - private readonly Type m_requestType; - + #region ParseRequest /// - /// 初始化自定义数据处理适配器。 + /// 尝试从字节读取器中解析出请求信息。 /// - /// - /// 该构造函数在创建实例时,会指定请求类型。 - /// - public CustomDataHandlingAdapter() + /// 字节读取器类型。 + /// 字节读取器的引用。 + /// 解析出的请求信息。 + /// 解析成功返回 true,否则返回 false。 + public bool TryParseRequest(ref TReader reader, out TRequest request) + where TReader : IBytesReader { - this.m_requestType = typeof(TRequest); + this.CacheVerify(ref reader); + return this.ParseRequestCore(ref reader, out request); } + /// + /// 尝试从字节读取器中解析出请求信息的核心方法。 + /// + /// 字节读取器类型。 + /// 字节读取器的引用。 + /// 解析出的请求信息。 + /// 解析成功返回 ,否则返回 + protected virtual bool ParseRequestCore(ref TReader reader, out TRequest request) + where TReader : IBytesReader + { + var result = this.Filter(ref reader, this.IsBeCached(this.m_tempRequest), ref this.m_tempRequest); + switch (result) + { + case FilterResult.Success: + { + request = this.m_tempRequest; + this.m_tempRequest = default; + return true; + } + case FilterResult.Cache: + { + request = this.m_tempRequest; + return false; + } + case FilterResult.GoOn: + default: + { + return this.ParseRequestCore(ref reader, out request); + } + } + } - private TRequest m_tempRequest; + #endregion /// public override bool CanSendRequestInfo => false; - /// - /// 默认不支持拼接发送 - /// - public override bool CanSplicingSend => false; - - /// - /// 指示需要解析当前包的剩余长度。如果不能得知,请赋值。 - /// - protected int SurLength { get; set; } - - /// - /// 尝试解析请求数据块。 - /// - /// 字节块类型,必须实现IByteBlock接口。 - /// 待解析的字节块。 - /// 解析出的请求对象。 - /// 解析是否成功。 - public bool TryParseRequest(ref TByteBlock byteBlock, out TRequest request) where TByteBlock : IByteBlock - { - // 检查缓存是否超时,如果超时则清除缓存。 - if (this.CacheTimeoutEnable && DateTimeOffset.UtcNow - this.LastCacheTime > this.CacheTimeout) - { - this.Reset(); - } - - // 如果临时字节块为空,则尝试直接解析。 - if (this.m_tempByteBlock.IsEmpty) - { - return this.Single(ref byteBlock, out request) == FilterResult.Success; - } - else - { - // 如果剩余长度小于等于0,则抛出异常。 - if (this.SurLength <= 0) - { - throw new Exception(); - } - - // 计算本次可以读取的长度。 - var len = Math.Min(this.SurLength, byteBlock.CanReadLength); - - // 从输入字节块中读取数据到临时字节块中。 - var block = this.m_tempByteBlock; - block.Write(byteBlock.Span.Slice(byteBlock.Position, len)); - byteBlock.Position += len; - this.SurLength -= len; - - // 重置临时字节块并准备下一次使用。 - this.m_tempByteBlock = ValueByteBlock.Empty; - - // 回到字节块的起始位置。 - block.SeekToStart(); - try - { - // 尝试解析字节块。 - var filterResult = this.Single(ref block, out request); - switch (filterResult) - { - case FilterResult.Cache: - { - // 如果临时字节块不为空,则继续缓存。 - if (!this.m_tempByteBlock.IsEmpty) - { - byteBlock.Position += this.m_tempByteBlock.Length; - } - return false; - } - case FilterResult.Success: - { - // 如果字节块中还有剩余数据,则回退指针。 - if (block.CanReadLength > 0) - { - byteBlock.Position -= block.CanReadLength; - } - return true; - } - case FilterResult.GoOn: - default: - // 对于需要继续解析的情况,也回退指针。 - if (block.CanReadLength > 0) - { - byteBlock.Position -= block.CanReadLength; - } - return false; - } - } - finally - { - // 释放字节块资源。 - block.Dispose(); - } - } - } - /// /// 筛选解析数据。实例化的TRequest会一直保存,直至解析成功,或手动清除。 /// 当不满足解析条件时,请返回,此时会保存的数据 /// 当数据部分异常时,请移动到指定位置,然后返回 /// 当完全满足解析条件时,请返回最后将移至指定位置。 /// - /// 字节块 + /// 字节块 /// 是否为上次遗留对象,当该参数为时,request也将是上次实例化的对象。 /// 对象。 - /// 缓存容量。当需要首次缓存时,指示申请的ByteBlock的容量。合理的值可避免ByteBlock扩容带来的性能消耗。 /// - protected abstract FilterResult Filter(ref TByteBlock byteBlock, bool beCached, ref TRequest request, ref int tempCapacity) - where TByteBlock : IByteBlock; - - /// - /// 成功执行接收以后。 - /// - /// - protected virtual void OnReceivedSuccess(TRequest request) - { - } - - /// - /// 即将执行。 - /// - /// - /// 返回值标识是否继续执行 - protected virtual bool OnReceivingSuccess(TRequest request) - { - return true; - } - - /// - /// - protected override async Task PreviewReceivedAsync(ByteBlock byteBlock) - { - if (this.CacheTimeoutEnable && DateTimeOffset.UtcNow - this.LastCacheTime > this.CacheTimeout) - { - this.Reset(); - } - if (this.m_tempByteBlock.IsEmpty) - { - await this.SingleAsync(byteBlock, false).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); - } - } - - /// - protected override void Reset() - { - this.m_tempByteBlock.SafeDispose(); - this.m_tempByteBlock = ValueByteBlock.Empty; - this.m_tempRequest = default; - this.SurLength = 0; - base.Reset(); - } + protected abstract FilterResult Filter(ref TReader reader, bool beCached, ref TRequest request) + where TReader : IBytesReader; /// /// 判断请求对象是否应该被缓存。 @@ -207,124 +93,49 @@ public abstract class CustomDataHandlingAdapter : SingleStreamDataHand /// 返回布尔值,指示请求对象是否应该被缓存。 protected virtual bool IsBeCached(in TRequest request) { - // 如果请求对象类型是值类型,则判断其哈希码是否与默认值不同; - // 如果是引用类型,则判断对象本身是否为。 - return this.m_requestType.IsValueType ? request.GetHashCode() != default(TRequest).GetHashCode() : request != null; - } - - /// - /// 处理单个字节块,提取请求对象。 - /// - /// 字节块类型,需要实现IByteBlock接口。 - /// 字节块,将被解析以提取请求对象。 - /// 输出参数,提取出的请求对象。 - /// 返回过滤结果,指示处理的状态。 - protected FilterResult Single(ref TByteBlock byteBlock, out TRequest request) where TByteBlock : IByteBlock - { - // 初始化临时缓存容量。 - var tempCapacity = 1024 * 64; - // 执行过滤操作,根据是否应该缓存来决定如何处理字节块和请求对象。 - var filterResult = this.Filter(ref byteBlock, this.IsBeCached(this.m_tempRequest), ref this.m_tempRequest, ref tempCapacity); - switch (filterResult) + if (typeof(TRequest).IsValueType) { - case FilterResult.Success: - // 如果过滤结果是成功,则设置请求对象并重置临时请求对象为默认值。 - request = this.m_tempRequest; - this.m_tempRequest = default; - return filterResult; - - case FilterResult.Cache: - // 如果过滤结果需要缓存,则创建一个新的字节块来缓存数据。 - if (byteBlock.CanReadLength > 0) - { - this.m_tempByteBlock = new ValueByteBlock(tempCapacity); - this.m_tempByteBlock.Write(byteBlock.Span.Slice(byteBlock.Position, byteBlock.CanReadLength)); - - // 如果缓存的数据长度超过设定的最大包大小,则抛出异常。 - if (this.m_tempByteBlock.Length > this.MaxPackageSize) - { - throw new Exception("缓存的数据长度大于设定值的情况下未收到解析信号"); - } - - // 将字节块指针移到末尾。 - byteBlock.SeekToEnd(); - } - // 更新缓存时间。 - if (this.UpdateCacheTimeWhenRev) - { - this.LastCacheTime = DateTimeOffset.UtcNow; - } - request = default; - return filterResult; - - case FilterResult.GoOn: - default: - // 对于继续或默认的过滤结果,更新缓存时间。 - if (this.UpdateCacheTimeWhenRev) - { - this.LastCacheTime = DateTimeOffset.UtcNow; - } - request = default; - return filterResult; + // 判断值类型是否为默认值 + return !EqualityComparer.Default.Equals(request, default); + } + else + { + // 判断引用类型是否为null + return request != null; } } - private async Task SingleAsync(TByteBlock byteBlock, bool temp) where TByteBlock : IByteBlock + /// + protected override async Task PreviewReceivedAsync(TReader reader) { - byteBlock.Position = 0; - while (byteBlock.Position < byteBlock.Length) + while (reader.BytesRemaining > 0) { 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); switch (filterResult) { case FilterResult.Success: - if (this.OnReceivingSuccess(this.m_tempRequest)) { await this.GoReceivedAsync(null, this.m_tempRequest).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.OnReceivedSuccess(this.m_tempRequest); + this.m_tempRequest = default; + break; } - this.m_tempRequest = default; - break; - case FilterResult.Cache: - if (byteBlock.CanReadLength > 0) - { - if (temp) - { - this.m_tempByteBlock = new ValueByteBlock(tempCapacity); - 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.Slice(byteBlock.Position, byteBlock.CanReadLength)); - } - - if (this.m_tempByteBlock.Length > this.MaxPackageSize) - { - this.OnError(default, "缓存的数据长度大于设定值的情况下未收到解析信号", true, true); - } - } - if (this.UpdateCacheTimeWhenRev) - { - this.LastCacheTime = DateTimeOffset.UtcNow; - } - return; - + return;//缓存数据,等待下次接收 case FilterResult.GoOn: - if (this.UpdateCacheTimeWhenRev) - { - this.LastCacheTime = DateTimeOffset.UtcNow; - } - break; + break;//继续处理数据 } } } + + /// + protected override void Reset() + { + this.m_tempRequest = default; + base.Reset(); + } } \ No newline at end of file diff --git a/src/TouchSocket.Core/DataAdapter/Custom/CustomDataHandlingAdapterGroup.cs b/src/TouchSocket.Core/DataAdapter/Custom/CustomDataHandlingAdapterGroup.cs new file mode 100644 index 000000000..593f1d7d1 --- /dev/null +++ b/src/TouchSocket.Core/DataAdapter/Custom/CustomDataHandlingAdapterGroup.cs @@ -0,0 +1,53 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +namespace TouchSocket.Core; + +/// +/// 用户自定义数据处理适配器组,用于管理多个自定义数据处理适配器。 +/// 该类可以组合多个适配器,并按顺序尝试解析请求信息。 +/// +/// 请求信息类型,必须实现 接口。 +public class CustomDataHandlingAdapterGroup : CustomDataHandlingAdapter + where TRequest : IRequestInfo +{ + private readonly List> m_dataHandlingAdapterSlims; + + /// + /// 初始化 类的新实例。 + /// + /// 适配器数组,用于组合多个数据处理适配器。 + public CustomDataHandlingAdapterGroup(params CustomDataHandlingAdapterGroup[] adapters) + { + this.m_dataHandlingAdapterSlims = new(adapters); + } + + /// + protected override FilterResult Filter(ref TReader reader, bool beCached, ref TRequest request) + { + throw new NotImplementedException(); + } + + /// + protected override bool ParseRequestCore(ref TReader reader, out TRequest request) + { + foreach (var item in this.m_dataHandlingAdapterSlims) + { + if (item.TryParseRequest(ref reader, out request)) + { + return true; + } + } + request = default; + return false; + } +} \ No newline at end of file diff --git a/src/TouchSocket.Core/DataAdapter/Custom/CustomFixedHeaderByteBlockDataHandlingAdapter.cs b/src/TouchSocket.Core/DataAdapter/Custom/CustomFixedHeaderByteBlockDataHandlingAdapter.cs deleted file mode 100644 index a08f49dc1..000000000 --- a/src/TouchSocket.Core/DataAdapter/Custom/CustomFixedHeaderByteBlockDataHandlingAdapter.cs +++ /dev/null @@ -1,149 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; -using TouchSocket.Resources; - -namespace TouchSocket.Core; - -/// -/// 用户自定义固定包头内存池解析器,使用该适配器时,接收方收到的数据中,将为,同时将实现为TFixedHeaderRequestInfo。 -/// -public abstract class CustomFixedHeaderByteBlockDataHandlingAdapter : CustomDataHandlingAdapter where TFixedHeaderRequestInfo : IFixedHeaderByteBlockRequestInfo -{ - /// - /// 固定包头的长度。 - /// - public abstract int HeaderLength { get; } - - /// - /// 筛选解析数据。实例化的TRequest会一直保存,直至解析成功,或手动清除。 - /// 当不满足解析条件时,请返回,此时会保存的数据 - /// 当数据部分异常时,请移动到指定位置,然后返回 - /// 当完全满足解析条件时,请返回最后将移至指定位置。 - /// - /// 字节块 - /// 是否为上次遗留对象,当该参数为时,request也将是上次实例化的对象。 - /// 对象。 - /// 缓存容量。当需要首次缓存时,指示申请的ByteBlock的容量。合理的值可避免ByteBlock扩容带来的性能消耗。 - /// - protected override FilterResult Filter(ref TByteBlock byteBlock, bool beCached, ref TFixedHeaderRequestInfo request, ref int tempCapacity) - { - if (beCached) - { - if (request.BodyLength > byteBlock.CanReadLength)//body不满足解析,开始缓存,然后保存对象 - { - tempCapacity = request.BodyLength + this.HeaderLength; - this.SurLength = request.BodyLength - byteBlock.CanReadLength; - return FilterResult.Cache; - } - - - var block = new ByteBlock(request.BodyLength); - - block.Write(byteBlock.Span.Slice(byteBlock.Position, request.BodyLength)); - //block.Write(byteBlock.Buffer, byteBlock.Position, request.BodyLength); - block.SeekToStart(); - if (request.OnParsingBody(block)) - { - byteBlock.Position += request.BodyLength; - return FilterResult.Success; - } - else - { - byteBlock.Position += 1; - return FilterResult.GoOn; - } - } - else - { - if (this.HeaderLength > byteBlock.CanReadLength) - { - this.SurLength = this.HeaderLength - byteBlock.CanReadLength; - return FilterResult.Cache; - } - - var requestInfo = this.GetInstance(); - var header = byteBlock.Span.Slice(byteBlock.Position, this.HeaderLength); - if (requestInfo.OnParsingHeader(header)) - { - byteBlock.Position += this.HeaderLength; - if (requestInfo.BodyLength > this.MaxPackageSize) - { - this.OnError(default, TouchSocketCoreResource.ValueMoreThan.Format(nameof(requestInfo.BodyLength), requestInfo.BodyLength, this.MaxPackageSize), true, true); - return FilterResult.GoOn; - } - request = requestInfo; - if (requestInfo.BodyLength > byteBlock.CanReadLength)//body不满足解析,开始缓存,然后保存对象 - { - tempCapacity = requestInfo.BodyLength + this.HeaderLength; - this.SurLength = requestInfo.BodyLength - byteBlock.CanReadLength; - return FilterResult.Cache; - } - - var block = new ByteBlock(request.BodyLength); - block.Write(byteBlock.Span.Slice(byteBlock.Position, request.BodyLength)); - block.SeekToStart(); - - if (requestInfo.OnParsingBody(block)) - { - byteBlock.Position += request.BodyLength; - return FilterResult.Success; - } - else - { - byteBlock.Position += 1; - return FilterResult.GoOn; - } - } - else - { - byteBlock.Position += 1; - return FilterResult.GoOn; - } - } - } - - /// - /// 获取泛型实例。 - /// - /// - protected abstract TFixedHeaderRequestInfo GetInstance(); -} - -/// -/// 用户自定义固定包头请求 -/// -public interface IFixedHeaderByteBlockRequestInfo : IRequestInfo -{ - /// - /// 数据体长度 - /// - int BodyLength { get; } - - /// - /// 当收到数据,由框架封送固定协议头。 - /// 您需要在此函数中,解析自己的固定包头,并且对赋值后续数据的长度,然后返回True。 - /// 如果返回,则意味着放弃本次解析 - /// - /// - /// - bool OnParsingHeader(ReadOnlySpan header); - - /// - /// 当收到数据,由框架封送有效载荷数据。 - /// 如果返回,意味着放弃本次解析的所有数据,包括已经解析完成的Header - /// - /// 载荷数据,注意:该字节块生命周期不受框架控制,请一定自行调用Dispose - /// 是否成功有效 - bool OnParsingBody(ByteBlock byteBlock); -} \ No newline at end of file diff --git a/src/TouchSocket.Core/DataAdapter/Custom/CustomFixedHeaderDataHandlingAdapter.cs b/src/TouchSocket.Core/DataAdapter/Custom/CustomFixedHeaderDataHandlingAdapter.cs index ac5491c0e..b494e0bec 100644 --- a/src/TouchSocket.Core/DataAdapter/Custom/CustomFixedHeaderDataHandlingAdapter.cs +++ b/src/TouchSocket.Core/DataAdapter/Custom/CustomFixedHeaderDataHandlingAdapter.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// @@ -30,70 +28,64 @@ public abstract class CustomFixedHeaderDataHandlingAdapter当数据部分异常时,请移动到指定位置,然后返回 /// 当完全满足解析条件时,请返回最后将移至指定位置。 /// - /// 字节块 + /// 字节块 /// 是否为上次遗留对象,当该参数为时,request也将是上次实例化的对象。 /// 对象。 - /// 缓存容量。当需要首次缓存时,指示申请的ByteBlock的容量。合理的值可避免ByteBlock扩容带来的性能消耗。 /// - protected override FilterResult Filter(ref TByteBlock byteBlock, bool beCached, ref TFixedHeaderRequestInfo request, ref int tempCapacity) + protected override FilterResult Filter(ref TReader reader, bool beCached, ref TFixedHeaderRequestInfo request) { if (beCached) { - if (request.BodyLength > byteBlock.CanReadLength)//body不满足解析,开始缓存,然后保存对象 + if (request.BodyLength > reader.BytesRemaining)//body不满足解析,开始缓存,然后保存对象 { - tempCapacity = request.BodyLength + this.HeaderLength; - this.SurLength = request.BodyLength - byteBlock.CanReadLength; return FilterResult.Cache; } //var body = byteBlock.ToArray(byteBlock.Position, request.BodyLength); - if (request.OnParsingBody(byteBlock.Span.Slice(byteBlock.Position, request.BodyLength))) + if (request.OnParsingBody(reader.GetSpan(request.BodyLength))) { - byteBlock.Position += request.BodyLength; + reader.Advance(request.BodyLength); return FilterResult.Success; } else { - byteBlock.Position += 1; + reader.Advance(1); return FilterResult.GoOn; } } else { - if (this.HeaderLength > byteBlock.CanReadLength) + if (this.HeaderLength > reader.BytesRemaining) { - this.SurLength = this.HeaderLength - byteBlock.CanReadLength; return FilterResult.Cache; } var requestInfo = this.GetInstance(); //var header = byteBlock.ToArray(byteBlock.Position, this.HeaderLength); - if (requestInfo.OnParsingHeader(byteBlock.Span.Slice(byteBlock.Position, this.HeaderLength))) + if (requestInfo.OnParsingHeader(reader.GetSpan(this.HeaderLength))) { - byteBlock.Position += this.HeaderLength; + reader.Advance(this.HeaderLength); request = requestInfo; - if (request.BodyLength > byteBlock.CanReadLength)//body不满足解析,开始缓存,然后保存对象 + if (request.BodyLength > reader.BytesRemaining)//body不满足解析,开始缓存,然后保存对象 { - tempCapacity = request.BodyLength + this.HeaderLength; - this.SurLength = request.BodyLength - byteBlock.CanReadLength; return FilterResult.Cache; } //var body = byteBlock.ToArray(byteBlock.Position, request.BodyLength); - if (request.OnParsingBody(byteBlock.Span.Slice(byteBlock.Position, request.BodyLength))) + if (request.OnParsingBody(reader.GetSpan(request.BodyLength))) { - byteBlock.Position += request.BodyLength; + reader.Advance(request.BodyLength); return FilterResult.Success; } else { - byteBlock.Position += 1; + reader.Advance(1); return FilterResult.GoOn; } } else { - byteBlock.Position += 1; + reader.Advance(1); return FilterResult.GoOn; } } diff --git a/src/TouchSocket.Core/DataAdapter/Custom/CustomJsonDataHandlingAdapter.cs b/src/TouchSocket.Core/DataAdapter/Custom/CustomJsonDataHandlingAdapter.cs index 99d4172ab..620bccafe 100644 --- a/src/TouchSocket.Core/DataAdapter/Custom/CustomJsonDataHandlingAdapter.cs +++ b/src/TouchSocket.Core/DataAdapter/Custom/CustomJsonDataHandlingAdapter.cs @@ -10,8 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Text; +using System.Buffers; namespace TouchSocket.Core; @@ -73,9 +72,9 @@ public abstract class CustomJsonDataHandlingAdapter : CustomDa public Encoding Encoding { get; } /// - protected override FilterResult Filter(ref TByteBlock byteBlock, bool beCached, ref TJsonRequestInfo request, ref int tempCapacity) + protected override FilterResult Filter(ref TByteBlock byteBlock, bool beCached, ref TJsonRequestInfo request) { - var stringSpan = byteBlock.Span.Slice(byteBlock.Position); + var stringSpan = byteBlock.Sequence; var myEnum = this.GetJsonPackageKind(stringSpan); if (myEnum == JsonPackageKind.None) @@ -113,7 +112,7 @@ public abstract class CustomJsonDataHandlingAdapter : CustomDa this.m_startCount += GetIndexCount(searchSpan, startSpan); this.m_endCount++; - index += (endIndex + endSpan.Length); + index += (int)(endIndex + endSpan.Length); if (this.m_startCount == this.m_endCount) { @@ -121,13 +120,13 @@ public abstract class CustomJsonDataHandlingAdapter : CustomDa this.m_startCount = 0; this.m_endCount = 0; - var memory = byteBlock.Memory.Slice(byteBlock.Position, index); + var memory = byteBlock.GetMemory(index); var r = myEnum == JsonPackageKind.Object ? memory.Span.IndexOf(this.m_openBrace) : memory.Span.IndexOf(this.m_leftSquareBracket); var dataMemory = memory.Slice(r); var impurityMemory = memory.Slice(0, r); request = this.GetInstance(myEnum, this.Encoding, dataMemory, impurityMemory); - byteBlock.Position += index; + byteBlock.Advance(index); return FilterResult.Success; } } @@ -143,35 +142,30 @@ public abstract class CustomJsonDataHandlingAdapter : CustomDa /// 请求信息实例。 protected abstract TJsonRequestInfo GetInstance(JsonPackageKind packageKind, Encoding encoding, ReadOnlyMemory dataMemory, ReadOnlyMemory impurityMemory); - private static int GetIndexCount(ReadOnlySpan span, ReadOnlySpan searchSpan) + private static int GetIndexCount(ReadOnlySequence sequence, ReadOnlySpan searchSpan) { var count = 0; while (true) { - var index = span.IndexOf(searchSpan); + var index = sequence.IndexOf(searchSpan); if (index < 0) { return count; } count++; - span = span.Slice(index + searchSpan.Length); + sequence = sequence.Slice(index + searchSpan.Length); } } - private JsonPackageKind GetJsonPackageKind(ReadOnlySpan span) + private JsonPackageKind GetJsonPackageKind(ReadOnlySequence sequence) { - var openBraceIndex = span.IndexOf(this.m_openBrace); - var leftSquareBracketIndex = span.IndexOf(this.m_leftSquareBracket); + var openBraceIndex = sequence.IndexOf(this.m_openBrace); + var leftSquareBracketIndex = sequence.IndexOf(this.m_leftSquareBracket); if (openBraceIndex < 0) { - if (leftSquareBracketIndex < 0) - { - return JsonPackageKind.None; - } - - return JsonPackageKind.Array; + return leftSquareBracketIndex < 0 ? JsonPackageKind.None : JsonPackageKind.Array; } else { @@ -179,13 +173,9 @@ public abstract class CustomJsonDataHandlingAdapter : CustomDa { return JsonPackageKind.Object; } - else if (openBraceIndex < leftSquareBracketIndex) - { - return JsonPackageKind.Object; - } else { - return JsonPackageKind.Array; + return openBraceIndex < leftSquareBracketIndex ? JsonPackageKind.Object : JsonPackageKind.Array; } } } diff --git a/src/TouchSocket.Core/DataAdapter/Custom/CustomUnfixedHeaderDataHandlingAdapter.cs b/src/TouchSocket.Core/DataAdapter/Custom/CustomUnfixedHeaderDataHandlingAdapter.cs index e07e60f1f..6525d6ec5 100644 --- a/src/TouchSocket.Core/DataAdapter/Custom/CustomUnfixedHeaderDataHandlingAdapter.cs +++ b/src/TouchSocket.Core/DataAdapter/Custom/CustomUnfixedHeaderDataHandlingAdapter.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// @@ -43,9 +41,9 @@ public interface IUnfixedHeaderRequestInfo : IRequestInfo /// 如果返回,意味着缓存剩余数据,此时如果仅仅是因为长度不足,则不必修改其他。 /// 但是如果是因为数据错误,则需要修改到正确位置,如果都不正确,则设置等于 /// - /// + /// /// 是否满足解析包头 - bool OnParsingHeader(ref TByteBlock byteBlock) where TByteBlock : IByteBlock; + bool OnParsingHeader(ref TReader reader) where TReader : IBytesReader; } /// @@ -59,61 +57,56 @@ public abstract class CustomUnfixedHeaderDataHandlingAdapter当数据部分异常时,请移动到指定位置,然后返回 /// 当完全满足解析条件时,请返回最后将移至指定位置。 /// - /// 字节块 + /// 字节块 /// 是否为上次遗留对象,当该参数为时,request也将是上次实例化的对象。 /// 对象。 - /// 缓存容量。当需要首次缓存时,指示申请的ByteBlock的容量。合理的值可避免ByteBlock扩容带来的性能消耗。 /// - protected override FilterResult Filter(ref TByteBlock byteBlock, bool beCached, ref TUnfixedHeaderRequestInfo request, ref int tempCapacity) + protected override FilterResult Filter(ref TReader reader, bool beCached, ref TUnfixedHeaderRequestInfo request) { if (beCached) { - if (request.BodyLength > byteBlock.CanReadLength)//body不满足解析,开始缓存,然后保存对象 + if (request.BodyLength > reader.BytesRemaining)//body不满足解析,开始缓存,然后保存对象 { - this.SurLength = request.BodyLength - byteBlock.CanReadLength; return FilterResult.Cache; } - var body = byteBlock.Span.Slice(byteBlock.Position, request.BodyLength); + var body = reader.GetSpan(request.BodyLength); if (request.OnParsingBody(body)) { - byteBlock.Position += request.BodyLength; + reader.Advance(request.BodyLength); return FilterResult.Success; } else { - byteBlock.Position += 1; + reader.Advance(1); return FilterResult.GoOn; } } else { var requestInfo = this.GetInstance(); - if (requestInfo.OnParsingHeader(ref byteBlock)) + if (requestInfo.OnParsingHeader(ref reader)) { request = requestInfo; - if (request.BodyLength > byteBlock.CanReadLength)//body不满足解析,开始缓存,然后保存对象 + if (request.BodyLength > reader.BytesRemaining)//body不满足解析,开始缓存,然后保存对象 { - tempCapacity = request.BodyLength + request.HeaderLength; - this.SurLength = request.BodyLength - byteBlock.CanReadLength; return FilterResult.Cache; } - var body = byteBlock.Span.Slice(byteBlock.Position, request.BodyLength); + var body = reader.GetSpan(request.BodyLength); if (request.OnParsingBody(body)) { - byteBlock.Position += request.BodyLength; + reader.Advance(request.BodyLength); return FilterResult.Success; } else { - byteBlock.Position += 1; + reader.Advance(1); return FilterResult.GoOn; } } else { - this.SurLength = int.MaxValue; return FilterResult.Cache; } } diff --git a/src/TouchSocket.Core/DataAdapter/DataHandlingAdapter.cs b/src/TouchSocket.Core/DataAdapter/DataHandlingAdapter.cs index 399deb65e..88d3b862b 100644 --- a/src/TouchSocket.Core/DataAdapter/DataHandlingAdapter.cs +++ b/src/TouchSocket.Core/DataAdapter/DataHandlingAdapter.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Runtime.CompilerServices; using TouchSocket.Resources; @@ -19,27 +18,22 @@ namespace TouchSocket.Core; /// /// 数据处理适配器 /// -public abstract class DataHandlingAdapter : DisposableObject +public abstract class DataHandlingAdapter : SafetyDisposableObject { /// /// 是否允许发送对象。 /// public abstract bool CanSendRequestInfo { get; } - /// - /// 拼接发送 - /// - public abstract bool CanSplicingSend { get; } - /// /// 日志记录器。 /// - public ILog Logger { get; set; } + public ILog Logger { get; private set; } /// /// 获取或设置适配器能接收的最大数据包长度。默认1024*1024 Byte。 /// - public int MaxPackageSize { get; set; } = 1024 * 1024 * 10; + public long MaxPackageSize { get; set; } = 1024 * 1024 * 10; /// /// 如果指定的长度超过最大包大小,则抛出异常。 @@ -50,7 +44,7 @@ public abstract class DataHandlingAdapter : DisposableObject /// 以避免处理过大的数据包导致的性能问题或内存溢出等问题。 /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected void ThrowIfMoreThanMaxPackageSize(int length) + protected void ThrowIfMoreThanMaxPackageSize(long length) { if (length > this.MaxPackageSize) { @@ -74,30 +68,16 @@ public abstract class DataHandlingAdapter : DisposableObject { throw new Exception(TouchSocketCoreResource.AdapterAlreadyUsed); } + + if (owner is ILoggerObject loggerObject) + { + this.Logger = loggerObject.Logger; + } this.Owner = owner; } /// - /// 在解析时发生错误。 - /// - /// 异常 - /// 错误异常 - /// 是否调用 - /// 是否记录日志 - protected virtual void OnError(Exception ex, string error, bool reset, bool log) - { - if (reset) - { - this.Reset(); - } - if (log) - { - this.Logger?.Exception(this, error, ex); - } - } - - /// - /// 重置解析器到初始状态,一般在被触发时,由返回值指示是否调用。 + /// 重置解析器到初始状态。 /// protected abstract void Reset(); } \ No newline at end of file diff --git a/src/TouchSocket.Core/DataAdapter/DataHandlingAdapterExtension.cs b/src/TouchSocket.Core/DataAdapter/DataHandlingAdapterExtension.cs index e6519bc69..318c8fb16 100644 --- a/src/TouchSocket.Core/DataAdapter/DataHandlingAdapterExtension.cs +++ b/src/TouchSocket.Core/DataAdapter/DataHandlingAdapterExtension.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// @@ -44,12 +42,29 @@ public static class DataHandlingAdapterExtension { adapter.CacheTimeoutEnable = option.CacheTimeoutEnable.Value; } - - if (option.UpdateCacheTimeWhenRev.HasValue) - { - adapter.UpdateCacheTimeWhenRev = option.UpdateCacheTimeWhenRev.Value; - } } + // public static void Config(this DataHandlingAdapterSlim adapter, TouchSocketConfig config) + //#if NET9_0_OR_GREATER + // where TRequest : allows ref struct + //#endif + // { + // var option = config.GetValue(AdapterOptionProperty) ?? throw new ArgumentNullException(nameof(AdapterOptionProperty)); + + // if (option.MaxPackageSize.HasValue) + // { + // adapter.MaxPackageSize = option.MaxPackageSize.Value; + // } + + // if (option.CacheTimeout.HasValue) + // { + // adapter.CacheTimeout = option.CacheTimeout.Value; + // } + + // if (option.CacheTimeoutEnable.HasValue) + // { + // adapter.CacheTimeoutEnable = option.CacheTimeoutEnable.Value; + // } + // } /// /// 将中的配置,装载在上。 @@ -71,40 +86,6 @@ public static class DataHandlingAdapterExtension /// /// 设置适配器相关的配置 /// + [GeneratorProperty(TargetType = typeof(TouchSocketConfig), ActionMode = true)] public static readonly DependencyProperty AdapterOptionProperty = new("AdapterOption", new AdapterOption()); - - /// - /// 设置适配器相关的配置 - /// - /// - /// - /// - public static TouchSocketConfig SetAdapterOption(this TouchSocketConfig config, AdapterOption value) - { - config.SetValue(AdapterOptionProperty, value); - return config; - } - - #region BuildAsBytes - - /// - /// 将对象构建到字节数组 - /// - /// - /// - public static byte[] BuildAsBytes(this IRequestInfoBuilder requestInfo) - { - var byteBlock = new ByteBlock(requestInfo.MaxLength); - try - { - requestInfo.Build(ref byteBlock); - return byteBlock.ToArray(); - } - finally - { - byteBlock.Dispose(); - } - } - - #endregion BuildAsBytes } \ No newline at end of file diff --git a/src/TouchSocket.Core/DataAdapter/IRequestInfoBuilder.cs b/src/TouchSocket.Core/DataAdapter/IRequestInfoBuilder.cs index 5fc3598fd..c148253e3 100644 --- a/src/TouchSocket.Core/DataAdapter/IRequestInfoBuilder.cs +++ b/src/TouchSocket.Core/DataAdapter/IRequestInfoBuilder.cs @@ -15,7 +15,7 @@ namespace TouchSocket.Core; /// /// 指示应当如何构建 /// -public interface IRequestInfoBuilder : IRequestInfo, IByteBlockBuilder +public interface IRequestInfoBuilder : IRequestInfo, IBytesBuilder { } \ No newline at end of file diff --git a/src/TouchSocket.Core/DataAdapter/NormalDataHandlingAdapter.cs b/src/TouchSocket.Core/DataAdapter/NormalDataHandlingAdapter.cs deleted file mode 100644 index f570116fc..000000000 --- a/src/TouchSocket.Core/DataAdapter/NormalDataHandlingAdapter.cs +++ /dev/null @@ -1,60 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; -using System.Threading.Tasks; - -namespace TouchSocket.Core; - -/// -/// 普通Tcp数据处理器,该适配器不对数据做任何处理。 -/// -public sealed class NormalDataHandlingAdapter : SingleStreamDataHandlingAdapter -{ - /// - public override bool CanSplicingSend => false; - - /// - public override bool CanSendRequestInfo => false; - - /// - /// 当接收到数据时处理数据 - /// - /// 数据流 - protected override async Task PreviewReceivedAsync(ByteBlock byteBlock) - { - await this.GoReceivedAsync(byteBlock, null).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - - ///// - ///// - ///// - ///// 数据 - ///// 偏移 - ///// 长度 - //protected override void PreviewSend(byte[] buffer, int offset, int length) - //{ - // this.GoSend(buffer, offset, length); - //} - - /// - protected override Task PreviewSendAsync(ReadOnlyMemory memory) - { - return this.GoSendAsync(memory); - } - - /// - protected override void Reset() - { - base.Reset(); - } -} \ No newline at end of file diff --git a/src/TouchSocket.Core/DataAdapter/PackageAdapters/FixedHeaderPackageAdapter.cs b/src/TouchSocket.Core/DataAdapter/PackageAdapters/FixedHeaderPackageAdapter.cs index 63b8ae4d9..785f2b0b6 100644 --- a/src/TouchSocket.Core/DataAdapter/PackageAdapters/FixedHeaderPackageAdapter.cs +++ b/src/TouchSocket.Core/DataAdapter/PackageAdapters/FixedHeaderPackageAdapter.cs @@ -10,92 +10,130 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; using System.ComponentModel; -using System.Threading.Tasks; using TouchSocket.Resources; namespace TouchSocket.Core; /// -/// 固定包头数据包处理适配器,支持Byte、UShort、Int三种类型作为包头。使用大小端设置。 +/// 固定包头适配器。 +/// 按照指定的包头类型(Byte、Ushort、Int)进行数据包的长度解析和组包。 +/// 支持最小包长度校验,自动处理半包、粘包等情况。 /// public class FixedHeaderPackageAdapter : SingleStreamDataHandlingAdapter { - private byte[] m_agreementTempBytes; - private int m_surPlusLength = 0; - - //包剩余长度 - private ByteBlock m_tempByteBlock; - - /// - public override bool CanSendRequestInfo => false; - - /// - public override bool CanSplicingSend => true; - /// - /// 设置包头类型,默认为int + /// 固定包头类型,决定包头长度(1/2/4字节),默认Int。 /// public FixedHeaderType FixedHeaderType { get; set; } = FixedHeaderType.Int; /// - /// 获取或设置包数据的最小值(默认为0) + /// 获取或设置包数据的最小值(默认为0)。用于校验包体长度。 /// public int MinPackageSize { get; set; } = 0; - /// - /// 当接收到数据时处理数据 - /// - /// 数据流 - protected override async Task PreviewReceivedAsync(ByteBlock byteBlock) + /// + public override void SendInput(ref TWriter writer, in ReadOnlyMemory memory) { - var array = byteBlock.Memory.GetArray(); - var buffer = array.Array; - var r = byteBlock.Length; + this.ThrowIfLengthValidationFailed(memory.Length); + Span lenBytes = stackalloc byte[4]; - if (this.CacheTimeoutEnable && DateTimeOffset.UtcNow - this.LastCacheTime > this.CacheTimeout) + switch (this.FixedHeaderType) { - this.Reset(); - } - - if (this.m_agreementTempBytes != null) - { - await this.SeamPackage(buffer, r).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - else if (this.m_tempByteBlock == null) - { - await this.SplitPackage(buffer, 0, r).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - else - { - if (this.m_surPlusLength == r) - { - this.m_tempByteBlock.Write(new ReadOnlySpan(buffer, 0, this.m_surPlusLength)); - await this.PreviewHandle(this.m_tempByteBlock).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.m_tempByteBlock = null; - this.m_surPlusLength = 0; - } - else if (this.m_surPlusLength < r) - { - this.m_tempByteBlock.Write(new ReadOnlySpan(buffer, 0, this.m_surPlusLength)); - await this.PreviewHandle(this.m_tempByteBlock).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.m_tempByteBlock = null; - await this.SplitPackage(buffer, this.m_surPlusLength, r).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - else - { - this.m_tempByteBlock.Write(new ReadOnlySpan(buffer, 0, r)); - this.m_surPlusLength -= r; - if (this.UpdateCacheTimeWhenRev) + case FixedHeaderType.Byte: { - this.LastCacheTime = DateTimeOffset.UtcNow; + var dataLen = (byte)(memory.Length); + lenBytes[0] = dataLen; + lenBytes = lenBytes.Slice(0, 1); + break; } + case FixedHeaderType.Ushort: + { + var dataLen = (ushort)(memory.Length); + TouchSocketBitConverter.Default.WriteBytes(lenBytes, dataLen); + lenBytes = lenBytes.Slice(0, 2); + break; + } + case FixedHeaderType.Int: + { + var dataLen = memory.Length; + TouchSocketBitConverter.Default.WriteBytes(lenBytes, dataLen); + break; + } + default: throw new InvalidEnumArgumentException(TouchSocketCoreResource.InvalidParameter.Format(nameof(this.FixedHeaderType))); + } + + writer.Write(lenBytes); + writer.Write(memory.Span); + } + + /// + /// 收到数据的预处理入口。自动处理缓存合并、半包等情况。 + /// + /// 数据读取器 + protected override async Task PreviewReceivedAsync(TReader reader) + { + while (reader.BytesRemaining > 0) + { + var index = 0; + int length; + switch (this.FixedHeaderType) + { + case FixedHeaderType.Byte: + { + var headerSpan = reader.GetSpan(1); + length = headerSpan[index]; + index += 1; + break; + } + + case FixedHeaderType.Ushort: + { + if (reader.BytesRemaining < 2) + { + return; + } + var headerSpan = reader.GetSpan(2); + length = TouchSocketBitConverter.Default.To(headerSpan); + index += 2; + break; + } + + case FixedHeaderType.Int: + { + if (reader.BytesRemaining < 4) + { + return; + } + var headerSpan = reader.GetSpan(4); + length = TouchSocketBitConverter.Default.To(headerSpan); + index += 4; + break; + } + + default: + throw ThrowHelper.CreateInvalidEnumArgumentException(this.FixedHeaderType); + } + + this.ThrowIfLengthValidationFailed(length); + var recedSurPlusLength = (int)(reader.BytesRemaining - index); + if (recedSurPlusLength >= length) + { + var memory = reader.GetMemory(index + length).Slice(index, length); + await this.GoReceivedAsync(memory, null).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + reader.Advance(index + length); + } + else //半包 + { + return; } } } + /// + /// 校验包体长度是否合法,超出范围则抛出异常。 + /// + /// 包体长度 private void ThrowIfLengthValidationFailed(int length) { if (length < this.MinPackageSize || length > this.MaxPackageSize) @@ -103,219 +141,4 @@ public class FixedHeaderPackageAdapter : SingleStreamDataHandlingAdapter ThrowHelper.ThrowArgumentOutOfRangeException_BetweenAnd(nameof(length), length, this.MinPackageSize, this.MaxPackageSize); } } - - - /// - protected override Task PreviewSendAsync(IRequestInfo requestInfo) - { - throw new NotImplementedException(); - } - - /// - protected override async Task PreviewSendAsync(ReadOnlyMemory memory) - { - this.ThrowIfLengthValidationFailed(memory.Length); - - ByteBlock byteBlock; - byte[] lenBytes; - - switch (this.FixedHeaderType) - { - case FixedHeaderType.Byte: - { - var dataLen = (byte)(memory.Length); - byteBlock = new ByteBlock(dataLen + 1); - lenBytes = new byte[] { dataLen }; - break; - } - case FixedHeaderType.Ushort: - { - var dataLen = (ushort)(memory.Length); - byteBlock = new ByteBlock(dataLen + 2); - lenBytes = TouchSocketBitConverter.Default.GetBytes(dataLen); - break; - } - case FixedHeaderType.Int: - { - var dataLen = memory.Length; - byteBlock = new ByteBlock(dataLen + 4); - lenBytes = TouchSocketBitConverter.Default.GetBytes(dataLen); - break; - } - default: throw new InvalidEnumArgumentException(TouchSocketCoreResource.InvalidParameter.Format(nameof(this.FixedHeaderType))); - } - - try - { - byteBlock.Write(lenBytes); - byteBlock.Write(memory.Span); - await this.GoSendAsync(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - finally - { - byteBlock.Dispose(); - } - } - - /// - protected override async Task PreviewSendAsync(IList> transferBytes) - { - if (transferBytes.Count == 0) - { - return; - } - - var length = 0; - foreach (var item in transferBytes) - { - length += item.Count; - } - - this.ThrowIfLengthValidationFailed(length); - - ByteBlock byteBlock; - byte[] lenBytes; - - switch (this.FixedHeaderType) - { - case FixedHeaderType.Byte: - { - var dataLen = (byte)length; - byteBlock = new ByteBlock(dataLen + 1); - lenBytes = new byte[] { dataLen }; - break; - } - case FixedHeaderType.Ushort: - { - var dataLen = (ushort)length; - byteBlock = new ByteBlock(dataLen + 2); - lenBytes = TouchSocketBitConverter.Default.GetBytes(dataLen); - break; - } - case FixedHeaderType.Int: - { - byteBlock = new ByteBlock(length + 4); - lenBytes = TouchSocketBitConverter.Default.GetBytes(length); - break; - } - default: throw new InvalidEnumArgumentException(TouchSocketCoreResource.InvalidParameter.Format(nameof(this.FixedHeaderType))); - } - - try - { - byteBlock.Write(lenBytes); - foreach (var item in transferBytes) - { - byteBlock.Write(new ReadOnlySpan(item.Array, item.Offset, item.Count)); - } - await this.GoSendAsync(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - finally - { - byteBlock.Dispose(); - } - } - - /// - protected override void Reset() - { - this.m_agreementTempBytes = null; - this.m_surPlusLength = default; - this.m_tempByteBlock?.Dispose(); - this.m_tempByteBlock = null; - base.Reset(); - } - - private async Task PreviewHandle(ByteBlock byteBlock) - { - try - { - byteBlock.Position = 0; - await this.GoReceivedAsync(byteBlock, null).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - finally - { - byteBlock.Dispose(); - } - } - - /// - /// 缝合包 - /// - /// - /// - private async Task SeamPackage(byte[] buffer, int r) - { - var byteBlock = new ByteBlock(r + this.m_agreementTempBytes.Length); - byteBlock.Write(this.m_agreementTempBytes); - byteBlock.Write(new ReadOnlySpan(buffer, 0, r)); - r += this.m_agreementTempBytes.Length; - this.m_agreementTempBytes = null; - - var array = byteBlock.Memory.GetArray(); - var buffer2 = array.Array; - await this.SplitPackage(buffer2, 0, r).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - byteBlock.Dispose(); - } - - /// - /// 分解包 - /// - /// - /// - /// - private async Task SplitPackage(byte[] dataBuffer, int index, int r) - { - while (index < r) - { - if (r - index <= (byte)this.FixedHeaderType) - { - this.m_agreementTempBytes = new byte[r - index]; - Array.Copy(dataBuffer, index, this.m_agreementTempBytes, 0, this.m_agreementTempBytes.Length); - if (this.UpdateCacheTimeWhenRev) - { - this.LastCacheTime = DateTimeOffset.UtcNow; - } - return; - } - var length = 0; - - switch (this.FixedHeaderType) - { - case FixedHeaderType.Byte: - length = dataBuffer[index]; - break; - - case FixedHeaderType.Ushort: - length = TouchSocketBitConverter.Default.ToUInt16(dataBuffer, index); - break; - - case FixedHeaderType.Int: - length = TouchSocketBitConverter.Default.ToInt32(dataBuffer, index); - break; - } - - this.ThrowIfLengthValidationFailed(length); - - var recedSurPlusLength = r - index - (byte)this.FixedHeaderType; - if (recedSurPlusLength >= length) - { - var byteBlock = new ByteBlock(length); - byteBlock.Write(new ReadOnlySpan(dataBuffer, index + (byte)this.FixedHeaderType, length)); - await this.PreviewHandle(byteBlock).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.m_surPlusLength = 0; - } - else//半包 - { - this.m_tempByteBlock = new ByteBlock(length); - this.m_surPlusLength = length - recedSurPlusLength; - this.m_tempByteBlock.Write(new ReadOnlySpan(dataBuffer, index + (byte)this.FixedHeaderType, recedSurPlusLength)); - if (this.UpdateCacheTimeWhenRev) - { - this.LastCacheTime = DateTimeOffset.UtcNow; - } - } - index += (length + (byte)this.FixedHeaderType); - } - } } \ No newline at end of file diff --git a/src/TouchSocket.Core/DataAdapter/PackageAdapters/FixedSizePackageAdapter.cs b/src/TouchSocket.Core/DataAdapter/PackageAdapters/FixedSizePackageAdapter.cs index 6743d166e..5c2b3a2a3 100644 --- a/src/TouchSocket.Core/DataAdapter/PackageAdapters/FixedSizePackageAdapter.cs +++ b/src/TouchSocket.Core/DataAdapter/PackageAdapters/FixedSizePackageAdapter.cs @@ -10,191 +10,57 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; using TouchSocket.Resources; namespace TouchSocket.Core; /// -/// 固定长度数据包处理适配器。 +/// 固定长度包适配器。 +/// 用于处理每个数据包长度固定的场景。 /// public class FixedSizePackageAdapter : SingleStreamDataHandlingAdapter { /// - /// 包剩余长度 + /// 构造函数,指定固定包长度。 /// - private int m_surPlusLength = 0; - - /// - /// 临时包 - /// - private ByteBlock m_tempByteBlock; - - /// - /// 构造函数 - /// - /// 数据包的长度 + /// 每个包的固定长度。 public FixedSizePackageAdapter(int fixedSize) { this.FixedSize = fixedSize; } - /// - public override bool CanSendRequestInfo => false; - - /// - public override bool CanSplicingSend => true; - /// - /// 获取已设置的数据包的长度 + /// 获取固定包长度。 /// public int FixedSize { get; private set; } /// - /// 预处理 + /// 预处理接收到的数据。 /// - /// - protected override async Task PreviewReceivedAsync(ByteBlock byteBlock) + /// 数据读取器。 + protected override async Task PreviewReceivedAsync(TReader reader) { - if (this.CacheTimeoutEnable && DateTimeOffset.UtcNow - this.LastCacheTime > this.CacheTimeout) + while (reader.BytesRemaining > 0) { - this.Reset(); - } - var array = byteBlock.Memory.GetArray(); - var buffer = array.Array; - var r = byteBlock.Length; - if (this.m_tempByteBlock == null) - { - await this.SplitPackage(buffer, 0, r).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - else - { - if (this.m_surPlusLength == r) + if (reader.BytesRemaining < this.FixedSize) { - this.m_tempByteBlock.Write(new ReadOnlySpan(buffer, 0, this.m_surPlusLength)); - await this.PreviewHandle(this.m_tempByteBlock).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.m_tempByteBlock = null; - this.m_surPlusLength = 0; - } - else if (this.m_surPlusLength < r) - { - this.m_tempByteBlock.Write(new ReadOnlySpan(buffer, 0, this.m_surPlusLength)); - await this.PreviewHandle(this.m_tempByteBlock).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.m_tempByteBlock = null; - await this.SplitPackage(buffer, this.m_surPlusLength, r).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - else - { - this.m_tempByteBlock.Write(new ReadOnlySpan(buffer, 0, r)); - this.m_surPlusLength -= r; - if (this.UpdateCacheTimeWhenRev) - { - this.LastCacheTime = DateTimeOffset.UtcNow; - } + return; } + + var memory = reader.GetMemory(this.FixedSize); + await this.GoReceivedAsync(memory, null).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + reader.Advance(this.FixedSize); } } /// - protected override async Task PreviewSendAsync(ReadOnlyMemory memory) + public override void SendInput(ref TWriter writer, in ReadOnlyMemory memory) { var dataLen = memory.Length; if (dataLen != this.FixedSize) { throw new OverlengthException(TouchSocketCoreResource.ValueMoreThan.Format(nameof(memory.Length), this.FixedSize)); } - var byteBlock = new ByteBlock(this.FixedSize); - - byteBlock.Write(memory.Span); - - byteBlock.SetLength(this.FixedSize); - try - { - await this.GoSendAsync(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - finally - { - byteBlock.Dispose(); - } - } - - /// - protected override async Task PreviewSendAsync(IList> transferBytes) - { - var length = 0; - foreach (var item in transferBytes) - { - length += item.Count; - } - - if (length != this.FixedSize) - { - throw new OverlengthException(TouchSocketCoreResource.ValueMoreThan.Format(nameof(length), this.FixedSize)); - } - var byteBlock = new ByteBlock(this.FixedSize); - - foreach (var item in transferBytes) - { - byteBlock.Write(new ReadOnlySpan(item.Array, item.Offset, item.Count)); - } - - byteBlock.SetLength(this.FixedSize); - try - { - await this.GoSendAsync(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - finally - { - byteBlock.Dispose(); - } - } - - /// - protected override void Reset() - { - this.m_tempByteBlock.SafeDispose(); - this.m_tempByteBlock = null; - this.m_surPlusLength = 0; - base.Reset(); - } - - private async Task PreviewHandle(ByteBlock byteBlock) - { - try - { - byteBlock.Position = 0; - await this.GoReceivedAsync(byteBlock, null).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - finally - { - byteBlock.Dispose(); - } - } - - private async Task SplitPackage(byte[] dataBuffer, int index, int r) - { - while (index < r) - { - if (r - index >= this.FixedSize) - { - var byteBlock = new ByteBlock(this.FixedSize); - byteBlock.Write(new ReadOnlySpan(dataBuffer, index, this.FixedSize)); - await this.PreviewHandle(byteBlock).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.m_surPlusLength = 0; - } - else//半包 - { - this.m_tempByteBlock = new ByteBlock(this.FixedSize); - this.m_surPlusLength = this.FixedSize - (r - index); - this.m_tempByteBlock.Write(new ReadOnlySpan(dataBuffer, index, r - index)); - if (this.UpdateCacheTimeWhenRev) - { - this.LastCacheTime = DateTimeOffset.UtcNow; - } - } - index += this.FixedSize; - } + base.SendInput(ref writer, memory); } } \ No newline at end of file diff --git a/src/TouchSocket.Core/DataAdapter/PackageAdapters/JsonPackageAdapter.cs b/src/TouchSocket.Core/DataAdapter/PackageAdapters/JsonPackageAdapter.cs index eac5be786..b5134600c 100644 --- a/src/TouchSocket.Core/DataAdapter/PackageAdapters/JsonPackageAdapter.cs +++ b/src/TouchSocket.Core/DataAdapter/PackageAdapters/JsonPackageAdapter.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Text; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/DataAdapter/PackageAdapters/PeriodPackageAdapter.cs b/src/TouchSocket.Core/DataAdapter/PackageAdapters/PeriodPackageAdapter.cs index cc0137c20..ce18a2930 100644 --- a/src/TouchSocket.Core/DataAdapter/PackageAdapters/PeriodPackageAdapter.cs +++ b/src/TouchSocket.Core/DataAdapter/PackageAdapters/PeriodPackageAdapter.cs @@ -10,10 +10,8 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Collections.Concurrent; -using System.Threading; -using System.Threading.Tasks; +using System.Runtime.ExceptionServices; namespace TouchSocket.Core; @@ -23,15 +21,26 @@ namespace TouchSocket.Core; public class PeriodPackageAdapter : SingleStreamDataHandlingAdapter { private readonly ConcurrentQueue m_bytes = new ConcurrentQueue(); - private long m_fireCount; + private readonly CancellationTokenSource m_cts = new CancellationTokenSource(); + private readonly SemaphoreSlim m_semaphoreSlim = new SemaphoreSlim(1, 1); private int m_dataCount; + private ExceptionDispatchInfo m_exceptionDispatchInfo; + private long m_fireCount; /// - protected override Task PreviewReceivedAsync(ByteBlock byteBlock) + protected override Task PreviewReceivedAsync(TReader reader) { - var dataLength = byteBlock.Length; + this.m_exceptionDispatchInfo?.Throw(); + + var dataLength = (int)reader.Sequence.Length; var valueByteBlock = new ValueByteBlock(dataLength); - valueByteBlock.Write(byteBlock.Span); + + foreach (var item in reader.Sequence) + { + valueByteBlock.Write(item.Span); + reader.Advance(item.Length); + } + this.m_bytes.Enqueue(valueByteBlock); Interlocked.Increment(ref this.m_fireCount); Interlocked.Add(ref this.m_dataCount, dataLength); @@ -42,12 +51,23 @@ public class PeriodPackageAdapter : SingleStreamDataHandlingAdapter return EasyTask.CompletedTask; } + /// + protected override void SafetyDispose(bool disposing) + { + if (disposing) + { + this.m_cts.SafeCancel(); + this.m_cts.SafeDispose(); + } + base.SafetyDispose(disposing); + } + private async Task DelayGo() { await Task.Delay(this.CacheTimeout).ConfigureAwait(EasyTask.ContinueOnCapturedContext); if (Interlocked.Decrement(ref this.m_fireCount) == 0) { - using (var byteBlock = new ByteBlock(this.m_dataCount)) + using (var byteBlock = new ValueByteBlock(this.m_dataCount)) { while (this.m_bytes.TryDequeue(out var valueByteBlock)) { @@ -60,13 +80,18 @@ public class PeriodPackageAdapter : SingleStreamDataHandlingAdapter byteBlock.SeekToStart(); + await this.m_semaphoreSlim.WaitAsync(this.m_cts.Token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); try { - await this.GoReceivedAsync(byteBlock, default).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.GoReceivedAsync(byteBlock.Memory, default).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } catch (Exception ex) { - this.OnError(ex, ex.Message, true, true); + this.m_exceptionDispatchInfo = ExceptionDispatchInfo.Capture(ex); + } + finally + { + this.m_semaphoreSlim.Release(); } } } diff --git a/src/TouchSocket.Core/DataAdapter/PackageAdapters/TerminatorPackageAdapter.cs b/src/TouchSocket.Core/DataAdapter/PackageAdapters/TerminatorPackageAdapter.cs index 2f586c510..d62f4626c 100644 --- a/src/TouchSocket.Core/DataAdapter/PackageAdapters/TerminatorPackageAdapter.cs +++ b/src/TouchSocket.Core/DataAdapter/PackageAdapters/TerminatorPackageAdapter.cs @@ -10,202 +10,91 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; using TouchSocket.Resources; namespace TouchSocket.Core; /// -/// 终止字符数据包处理适配器,支持以任意字符、字节数组结尾的数据包。 +/// 终止符数据包处理适配器,支持以指定终止符(字符或字节数组)结尾的数据包解析。 +/// 适用于流式数据协议,自动分包并处理缓存,支持保留终止符选项。 /// public class TerminatorPackageAdapter : SingleStreamDataHandlingAdapter { - private readonly byte[] m_terminatorCode; - private ByteBlock m_tempByteBlock; + /// + /// 终止符字节序列。 + /// + private readonly ReadOnlyMemory m_terminatorCode; /// - /// 构造函数 + /// 使用UTF8编码的终止符构造适配器。 /// - /// + /// 终止符字符串。 public TerminatorPackageAdapter(string terminator) : this(0, Encoding.UTF8.GetBytes(terminator)) { } /// - /// 构造函数 + /// 使用指定编码的终止符构造适配器。 /// - /// - /// + /// 终止符字符串。 + /// 编码方式。 public TerminatorPackageAdapter(string terminator, Encoding encoding) : this(0, encoding.GetBytes(terminator)) { } /// - /// 构造函数 + /// 使用指定最小包长度和终止符字节数组构造适配器。 /// - /// - /// + /// 最小包长度。 + /// 终止符字节数组。 public TerminatorPackageAdapter(int minSize, byte[] terminatorCode) { this.MinSize = minSize; this.m_terminatorCode = terminatorCode; } - /// - public override bool CanSendRequestInfo => false; - - /// - public override bool CanSplicingSend => true; - /// - /// 即使找到了终止因子,也不会结束,默认0 + /// 最小包长度,默认为0。 /// - public int MinSize { get; set; } = 0; + public int MinSize { get; } /// - /// 保留终止因子 + /// 是否保留终止符在解析后的数据包中。 /// public bool ReserveTerminatorCode { get; set; } - /// - /// 预处理 - /// - /// - protected override async Task PreviewReceivedAsync(ByteBlock byteBlock) + + /// + protected override async Task PreviewReceivedAsync(TReader reader) { - if (this.CacheTimeoutEnable && DateTimeOffset.UtcNow - this.LastCacheTime > this.CacheTimeout) + while (reader.BytesRemaining > 0) { - this.Reset(); - } - var array = byteBlock.Memory.GetArray(); - var buffer = array.Array; - var cacheLength = byteBlock.Length; - if (this.m_tempByteBlock != null) - { - this.m_tempByteBlock.Write(new ReadOnlySpan(buffer, 0, cacheLength)); - buffer = this.m_tempByteBlock.Memory.GetArray().Array; - cacheLength = this.m_tempByteBlock.Position; - } - - var indexes = buffer.IndexOfInclude(0, cacheLength, this.m_terminatorCode); - if (indexes.Count == 0) - { - if (cacheLength > this.MaxPackageSize) + var sequence = reader.Sequence; + var index = sequence.IndexOf(this.m_terminatorCode.Span); + if (index < 0) { - throw new Exception(TouchSocketCoreResource.ValueMoreThan.Format(nameof(cacheLength), this.MaxPackageSize)); + return; } - else if (this.m_tempByteBlock == null) + var length = (int)(index + this.m_terminatorCode.Length); + var memory = reader.GetMemory(length); + if (!this.ReserveTerminatorCode) { - this.m_tempByteBlock = new ByteBlock(cacheLength * 2); - this.m_tempByteBlock.Write(new ReadOnlySpan(buffer, 0, cacheLength)); - if (this.UpdateCacheTimeWhenRev) - { - this.LastCacheTime = DateTimeOffset.UtcNow; - } - } - } - else - { - var startIndex = 0; - foreach (var lastIndex in indexes) - { - var length = this.ReserveTerminatorCode ? lastIndex - startIndex + 1 : lastIndex - startIndex - this.m_terminatorCode.Length + 1; - var packageByteBlock = new ByteBlock(length); - packageByteBlock.Write(new ReadOnlySpan(buffer, startIndex, length)); - - //var mes = Encoding.UTF8.GetString(packageByteBlock.Buffer, 0, packageByteBlock.Position); - - await this.PreviewHandle(packageByteBlock).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - startIndex = lastIndex + 1; - } - this.Reset(); - if (startIndex < cacheLength) - { - this.m_tempByteBlock = new ByteBlock((cacheLength - startIndex) * 2); - this.m_tempByteBlock.Write(new ReadOnlySpan(buffer, startIndex, cacheLength - startIndex)); - if (this.UpdateCacheTimeWhenRev) - { - this.LastCacheTime = DateTimeOffset.UtcNow; - } + memory = memory.Slice(0, (int)index); } + await this.GoReceivedAsync(memory, default).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + reader.Advance(length); } } /// - protected override async Task PreviewSendAsync(ReadOnlyMemory memory) + public override void SendInput(ref TWriter writer, in ReadOnlyMemory memory) { if (memory.Length > this.MaxPackageSize) { throw new Exception(TouchSocketCoreResource.ValueMoreThan.Format(nameof(memory.Length), this.MaxPackageSize)); } - var dataLen = memory.Length + this.m_terminatorCode.Length; - var byteBlock = new ByteBlock(dataLen); - byteBlock.Write(memory.Span); - byteBlock.Write(this.m_terminatorCode); - - try - { - await this.GoSendAsync(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - finally - { - byteBlock.Dispose(); - } - } - - /// - protected override async Task PreviewSendAsync(IList> transferBytes) - { - var length = 0; - foreach (var item in transferBytes) - { - length += item.Count; - } - if (length > this.MaxPackageSize) - { - throw new Exception(TouchSocketCoreResource.ValueMoreThan.Format(nameof(length), this.MaxPackageSize)); - } - var dataLen = length + this.m_terminatorCode.Length; - var byteBlock = new ByteBlock(dataLen); - foreach (var item in transferBytes) - { - byteBlock.Write(new ReadOnlySpan(item.Array, item.Offset, item.Count)); - } - - byteBlock.Write(this.m_terminatorCode); - - try - { - await this.GoSendAsync(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - finally - { - byteBlock.Dispose(); - } - } - - /// - protected override void Reset() - { - this.m_tempByteBlock.SafeDispose(); - this.m_tempByteBlock = null; - base.Reset(); - } - - private async Task PreviewHandle(ByteBlock byteBlock) - { - try - { - byteBlock.Position = 0; - await this.GoReceivedAsync(byteBlock, null).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - finally - { - byteBlock.Dispose(); - } + writer.Write(memory.Span); + writer.Write(this.m_terminatorCode.Span); } } \ No newline at end of file diff --git a/src/TouchSocket.Core/DataAdapter/SingleStreamDataHandlingAdapter.cs b/src/TouchSocket.Core/DataAdapter/SingleStreamDataHandlingAdapter.cs index 095ee69cc..1e3f6c3f9 100644 --- a/src/TouchSocket.Core/DataAdapter/SingleStreamDataHandlingAdapter.cs +++ b/src/TouchSocket.Core/DataAdapter/SingleStreamDataHandlingAdapter.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - namespace TouchSocket.Core; /// @@ -21,40 +17,27 @@ namespace TouchSocket.Core; /// public abstract class SingleStreamDataHandlingAdapter : DataHandlingAdapter { + private long m_cacheSize; + + private volatile bool m_needReset = false; + /// /// 缓存超时时间。默认1秒。 /// public TimeSpan CacheTimeout { get; set; } = TimeSpan.FromSeconds(1); /// - /// 是否启用缓存超时。默认false。 + /// 是否启用缓存超时。默认。 /// public bool CacheTimeoutEnable { get; set; } = false; /// public override bool CanSendRequestInfo => false; - /// - public override bool CanSplicingSend => false; - /// /// 当接收数据处理完成后,回调该函数执行接收 /// - public Func ReceivedAsyncCallBack { get; set; } - - /// - /// 当发送数据处理完成后,回调该函数执行异步发送 - /// - public Func, Task> SendAsyncCallBack { get; set; } - - /// - /// 是否在收到数据时,即刷新缓存时间。默认true。 - /// - /// 当设为true时,将弱化的作用,只要一直有数据,则缓存不会过期。 - /// 当设为false时,则在的时效内。必须完成单个缓存的数据。 - /// - /// - public bool UpdateCacheTimeWhenRev { get; set; } = true; + public Func, IRequestInfo, Task> ReceivedAsyncCallBack { get; set; } /// /// 最后缓存的时间 @@ -64,128 +47,99 @@ public abstract class SingleStreamDataHandlingAdapter : DataHandlingAdapter /// /// 收到数据的切入点,该方法由框架自动调用。 /// - /// - public async Task ReceivedInputAsync(ByteBlock byteBlock) + /// + public async Task ReceivedInputAsync(TReader reader) + where TReader : class, IBytesReader { - try + this.CacheVerify(ref reader); + await this.PreviewReceivedAsync(reader).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + this.m_cacheSize = reader.BytesRemaining; + this.LastCacheTime = DateTimeOffset.UtcNow; + } + + /// + /// 校验并处理缓存数据的有效性。 + /// + /// 实现了 接口的类型。 + /// 字节读取器的引用。 + protected void CacheVerify(ref TReader reader) + where TReader : IBytesReader + { + if (this.m_cacheSize > 0) { - await this.PreviewReceivedAsync(byteBlock).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - catch (Exception ex) - { - this.OnError(ex, ex.Message, true, true); + if (this.CacheTimeoutEnable && this.LastCacheTime + this.CacheTimeout <= DateTimeOffset.UtcNow) + { + // 缓存已超时,跳过旧的缓存数据 + reader.Advance((int)this.m_cacheSize); + } + else if (this.m_needReset) + { + this.m_needReset = false; + reader.Advance((int)this.m_cacheSize); + } } } #region SendInput /// - /// 发送数据的切入点,该方法由框架自动调用。 + /// 发送输入数据到指定的写入器。 /// - /// - /// - public Task SendInputAsync(IRequestInfo requestInfo) + /// 实现了 接口的写入器类型。 + /// 写入器的引用。 + /// 要写入的数据内存块。 + public virtual void SendInput(ref TWriter writer, in ReadOnlyMemory memory) + where TWriter : IBytesWriter { - return this.PreviewSendAsync(requestInfo); + writer.Write(memory.Span); } /// - /// 发送数据的切入点,该方法由框架自动调用。 + /// 发送输入数据到指定的写入器。 + /// 如果 实现了 ,则调用其 Build 方法写入数据。 + /// 否则抛出异常。 /// - public Task SendInputAsync(ReadOnlyMemory memory) + /// 实现了 接口的写入器类型。 + /// 写入器的引用。 + /// 要写入的请求信息。 + public virtual void SendInput(ref TWriter writer, IRequestInfo requestInfo) + where TWriter : IBytesWriter { - return this.PreviewSendAsync(memory); - } - - /// - /// 发送数据的切入点,该方法由框架自动调用。 - /// - /// - public Task SendInputAsync(IList> transferBytes) - { - return this.PreviewSendAsync(transferBytes); - } - - /// - /// 当发送数据前预先处理数据 - /// - /// - protected virtual async Task PreviewSendAsync(IRequestInfo requestInfo) - { - ThrowHelper.ThrowArgumentNullExceptionIf(requestInfo, nameof(requestInfo)); - - var requestInfoBuilder = (IRequestInfoBuilder)requestInfo; - - var byteBlock = new ValueByteBlock(requestInfoBuilder.MaxLength); - try + if (requestInfo is not IRequestInfoBuilder requestInfoBuilder) { - requestInfoBuilder.Build(ref byteBlock); - await this.GoSendAsync(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + throw new Exception(); } - finally - { - byteBlock.Dispose(); - } - } - - /// - /// 当发送数据前预先处理数据 - /// - protected virtual Task PreviewSendAsync(ReadOnlyMemory memory) - { - return this.GoSendAsync(memory); - } - - /// - /// 组合发送预处理数据, - /// 当属性SplicingSend实现为时,系统才会调用该方法。 - /// - /// 代发送数据组合 - protected virtual Task PreviewSendAsync(IList> transferBytes) - { - throw new NotImplementedException(); + requestInfoBuilder.Build(ref writer); } #endregion SendInput - /// - /// - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - } - /// /// 处理已经经过预先处理后的数据 /// - /// 以二进制形式传递 + /// 以二进制形式传递 /// 以解析实例传递 - protected virtual Task GoReceivedAsync(ByteBlock byteBlock, IRequestInfo requestInfo) + protected virtual Task GoReceivedAsync(ReadOnlyMemory memory, IRequestInfo requestInfo) { - return this.ReceivedAsyncCallBack == null ? EasyTask.CompletedTask : this.ReceivedAsyncCallBack.Invoke(byteBlock, requestInfo); + return this.ReceivedAsyncCallBack == null ? EasyTask.CompletedTask : this.ReceivedAsyncCallBack.Invoke(memory, requestInfo); } /// - /// 异步发送已经经过预先处理后的数据 + /// 当接收到数据后预先处理数据,然后调用处理数据 /// - /// - /// - protected virtual Task GoSendAsync(ReadOnlyMemory memory) - { - return this.SendAsyncCallBack == null ? EasyTask.CompletedTask : this.SendAsyncCallBack.Invoke(memory); - } + /// + protected abstract Task PreviewReceivedAsync(TReader reader) + where TReader : class, IBytesReader; - /// - /// 当接收到数据后预先处理数据,然后调用处理数据 - /// - /// - protected abstract Task PreviewReceivedAsync(ByteBlock byteBlock); - /// - /// 重置解析器到初始状态,一般在被触发时,由返回值指示是否调用。 - /// + /// protected override void Reset() { - this.LastCacheTime = DateTimeOffset.UtcNow; + this.m_needReset = true; + } + + /// + protected override void SafetyDispose(bool disposing) + { } } \ No newline at end of file diff --git a/src/TouchSocket.Core/DataAdapter/Test/MultithreadingDataAdapterTester.cs b/src/TouchSocket.Core/DataAdapter/Test/MultithreadingDataAdapterTester.cs new file mode 100644 index 000000000..3e77f01a2 --- /dev/null +++ b/src/TouchSocket.Core/DataAdapter/Test/MultithreadingDataAdapterTester.cs @@ -0,0 +1,168 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.Diagnostics; +using System.Net; + +namespace TouchSocket.Core; + +/// +/// 多线程数据适配器测试器,用于在多线程环境下测试 的性能和正确性。 +/// 该类提供了高并发场景下的数据处理适配器测试功能,可以模拟多线程发送和接收数据。 +/// +/// +/// 测试器通过内部智能队列管理数据传输,支持异步操作和性能统计。 +/// 可用于验证数据适配器在高负载情况下的稳定性和性能表现。 +/// +public class MultithreadingDataAdapterTester : SafetyDisposableObject +{ + private readonly IntelligentDataQueue m_asyncBytes; + + private UdpDataHandlingAdapter m_adapter; + + private volatile int m_count; + + private int m_expectedCount; + + private int m_millisecondsTimeout; + + private Func, IRequestInfo, Task> m_receivedCallBack; + + private Stopwatch m_stopwatch; + + /// + /// 初始化 类的新实例。 + /// + /// 并发多线程数量,用于确定同时处理数据的线程数。 + protected MultithreadingDataAdapterTester(int multiThread) + { + this.m_asyncBytes = new IntelligentDataQueue(1024 * 1024 * 10); + for (var i = 0; i < multiThread; i++) + { + _ = EasyTask.SafeRun(this.BeginSend); + } + } + + /// + /// 创建数据适配器测试器实例。 + /// + /// 待测试的 适配器。 + /// 并发多线程数量,决定测试的并发程度。 + /// 收到数据时的回调函数,可选参数。如果为 ,则不执行额外的回调处理。 + /// 返回配置完成的 实例。 + /// + /// 该方法会自动配置适配器的发送和接收回调函数,建立测试器与适配器之间的数据流转机制。 + /// + public static MultithreadingDataAdapterTester CreateTester(UdpDataHandlingAdapter adapter, int multiThread, Func, IRequestInfo, Task> receivedCallBack = default) + { + var tester = new MultithreadingDataAdapterTester(multiThread); + tester.m_adapter = adapter; + adapter.SendCallBackAsync = tester.SendCallback; + adapter.ReceivedCallBack = tester.OnReceived; + tester.m_receivedCallBack = receivedCallBack; + return tester; + } + + /// + /// 异步执行模拟测试运行,发送指定的数据并统计处理时间。 + /// + /// 待测试的内存数据块,包含要发送的字节数据。 + /// 测试发送的总次数。 + /// 期望接收到的数据包数量,用于判断测试是否完成。 + /// 测试超时时间,单位为毫秒。如果在指定时间内未完成测试,将抛出超时异常。 + /// 返回一个 ,表示测试完成所用的时间。 + /// 当测试在指定超时时间内未完成时抛出。 + /// + /// 该方法会启动计时器,发送指定次数的数据包,然后等待接收到期望数量的响应。 + /// 测试过程是异步的,支持高并发数据发送和处理。 + /// + public async Task RunAsync(ReadOnlyMemory memory, int testCount, int expectedCount, int millisecondsTimeout) + { + this.m_count = 0; + this.m_expectedCount = expectedCount; + this.m_millisecondsTimeout = millisecondsTimeout; + this.m_stopwatch = new Stopwatch(); + this.m_stopwatch.Start(); + for (var i = 0; i < testCount; i++) + { + await this.m_adapter.SendInputAsync(null, memory, CancellationToken.None).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + if (SpinWait.SpinUntil(() => this.m_count == this.m_expectedCount, this.m_millisecondsTimeout)) + { + this.m_stopwatch.Stop(); + return this.m_stopwatch.Elapsed; + } + + throw new TimeoutException(); + } + + /// + protected override void SafetyDispose(bool disposing) + { + } + + private async Task BeginSend() + { + while (!this.DisposedValue) + { + if (this.TryGet(out var byteBlocks)) + { + foreach (var block in byteBlocks) + { + try + { + await this.m_adapter.ReceivedInputAsync(null, block.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + finally + { + block.Dispose(); + } + } + } + else + { + await Task.Delay(1).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + } + } + + private async Task OnReceived(EndPoint endPoint, ReadOnlyMemory memory, IRequestInfo requestInfo) + { + if (this.m_receivedCallBack != null) + { + await this.m_receivedCallBack(memory, requestInfo).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + Interlocked.Increment(ref this.m_count); + } + + private Task SendCallback(EndPoint endPoint, ReadOnlyMemory memory, CancellationToken cancellationToken) + { + var array = memory.ToArray(); + var asyncByte = new QueueDataBytes(array, 0, array.Length); + //Array.Copy(buffer, offset, asyncByte.Buffer, 0, length); + this.m_asyncBytes.Enqueue(asyncByte); + return EasyTask.CompletedTask; + } + + private bool TryGet(out List byteBlocks) + { + byteBlocks = new List(); + + while (this.m_asyncBytes.TryDequeue(out var asyncByte)) + { + var block = new ByteBlock(asyncByte.Length); + block.Write(new ReadOnlySpan(asyncByte.Buffer, asyncByte.Offset, asyncByte.Length)); + byteBlocks.Add(block); + } + return byteBlocks.Count > 0; + } +} \ No newline at end of file diff --git a/src/TouchSocket.Core/DataAdapter/Test/SingleStreamDataAdapterTester.cs b/src/TouchSocket.Core/DataAdapter/Test/SingleStreamDataAdapterTester.cs index 8a96ca0bf..5f42516f3 100644 --- a/src/TouchSocket.Core/DataAdapter/Test/SingleStreamDataAdapterTester.cs +++ b/src/TouchSocket.Core/DataAdapter/Test/SingleStreamDataAdapterTester.cs @@ -10,205 +10,183 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; +using System.IO.Pipelines; namespace TouchSocket.Core; /// -///单线程状况的流式数据处理适配器测试 +/// 单线程状况的流式数据处理适配器测试。 /// public class SingleStreamDataAdapterTester : DisposableObject { - private readonly IntelligentDataQueue m_asyncBytes; + private readonly Pipe m_pipe = new Pipe(new PipeOptions(default, default, default, 1024 * 1024 * 1024, 1024 * 1024 * 512, -1)); private SingleStreamDataHandlingAdapter m_adapter; - private int m_bufferLength; private int m_count; private int m_expectedCount; - private Func m_receivedCallBack; - private Stopwatch m_stopwatch; - private int m_timeout; + private int m_bufferLength; + private Func, IRequestInfo, Task> m_receivedCallBack; /// - /// Tcp数据处理适配器测试 + /// Tcp数据处理适配器测试。 /// protected SingleStreamDataAdapterTester() { - this.m_asyncBytes = new IntelligentDataQueue(1024 * 1024 * 10); - - Task.Run(this.BeginSend); } /// - /// 获取测试器 + /// 获取测试器。 /// - /// 待测试适配器 - /// 收到数据回调 - /// 缓存数据长度 - /// - public static SingleStreamDataAdapterTester CreateTester(SingleStreamDataHandlingAdapter adapter, int bufferLength = 1024, Func receivedCallBack = default) + /// 待测试适配器。 + /// 收到数据回调。 + /// 返回实例。 + public static SingleStreamDataAdapterTester CreateTester(SingleStreamDataHandlingAdapter adapter, Func, IRequestInfo, Task> receivedCallBack = default) { var tester = new SingleStreamDataAdapterTester { - m_adapter = adapter, - m_bufferLength = bufferLength + m_adapter = adapter }; - adapter.SendAsyncCallBack = tester.SendCallback; adapter.ReceivedAsyncCallBack = tester.OnReceived; tester.m_receivedCallBack = receivedCallBack; return tester; } /// - /// 模拟测试运行发送 + /// 异步运行数据适配器测试。 /// - /// - /// - /// - /// 测试次数 - /// 期待测试次数 - /// 超时 - /// - public TimeSpan Run(byte[] buffer, int offset, int length, int testCount, int expectedCount, int millisecondsTimeout) + /// 要发送的数据内存块。 + /// 测试发送次数。 + /// 预期接收次数。 + /// 每次写入的缓冲区长度。 + /// 取消令牌。 + /// 返回测试所用的时间。 + public async Task RunAsync(ReadOnlyMemory memory, int testCount, int expectedCount, int bufferLength, CancellationToken cancellationToken) { this.m_count = 0; this.m_expectedCount = expectedCount; - this.m_timeout = millisecondsTimeout; - this.m_stopwatch = new Stopwatch(); - this.m_stopwatch.Start(); - Task.Run(async () => - { - for (var i = 0; i < testCount; i++) - { - await this.m_adapter.SendInputAsync(new Memory(buffer, offset, length)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - }); + this.m_bufferLength = bufferLength; - if (SpinWait.SpinUntil(() => this.m_count == this.m_expectedCount, this.m_timeout)) + var receivedTask = EasyTask.SafeRun(this.ReceivedLoopAsync, cancellationToken); + + var m_stopwatch = new Stopwatch(); + m_stopwatch.Start(); + + for (var i = 0; i < testCount; i++) { - this.m_stopwatch.Stop(); - return this.m_stopwatch.Elapsed; + cancellationToken.ThrowIfCancellationRequested(); + + var valueByteBlock = new ValueByteBlock(memory.Length + 1024); + try + { + this.m_adapter.SendInput(ref valueByteBlock, memory); + + await this.SendCallback(valueByteBlock.Memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + finally + { + valueByteBlock.Dispose(); + } + } - throw new TimeoutException(); + + await this.m_pipe.Writer.CompleteAsync(); + + await receivedTask.WithCancellation(cancellationToken); + + if (this.m_count != this.m_expectedCount) + { + throw new Exception($"实际接收次数:{this.m_count},预期接收次数:{this.m_expectedCount}"); + } + + m_stopwatch.Stop(); + + await this.m_pipe.Reader.CompleteAsync(); + this.m_pipe.Reset(); + + var elapsed = m_stopwatch.Elapsed; + return elapsed; } /// - /// 模拟发送 + /// 以指定的超时时间异步运行数据适配器测试。 /// - /// - /// 测试次数 - /// 期待测试次数 - /// 超时 - public TimeSpan Run(byte[] buffer, int testCount, int expectedCount, int millisecondsTimeout) + /// 要发送的数据内存块。 + /// 测试发送次数。 + /// 预期接收次数。 + /// 每次写入的缓冲区长度。 + /// 超时时间(毫秒)。 + /// 返回测试所用的时间。 + /// 超时抛出异常。 + public async Task RunAsync(ReadOnlyMemory memory, int testCount, int expectedCount, int bufferLength, int millisecondsTimeout) { - return this.Run(buffer, 0, buffer.Length, testCount, expectedCount, millisecondsTimeout); - } - - private async Task BeginSend() - { - while (!this.DisposedValue) + using (var cancellationToken = new CancellationTokenSource(millisecondsTimeout)) { - if (this.TryGet(out var byteBlocks)) + try { - foreach (var block in byteBlocks) - { - try - { - await this.m_adapter.ReceivedInputAsync(block).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - finally - { - block.Dispose(); - } - } + return await this.RunAsync(memory, testCount, expectedCount, bufferLength, cancellationToken.Token); } - else + catch (OperationCanceledException) { - await Task.Delay(1).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + throw new TimeoutException(); + } + catch (Exception) + { + throw; } } } - private async Task OnReceived(ByteBlock byteBlock, IRequestInfo requestInfo) + private async Task OnReceived(ReadOnlyMemory memory, IRequestInfo requestInfo) { this.m_count++; if (this.m_receivedCallBack != null) { - await this.m_receivedCallBack.Invoke(byteBlock, requestInfo).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.m_receivedCallBack.Invoke(memory, requestInfo).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } - private Task SendCallback(ReadOnlyMemory memory) + private async Task ReceivedLoopAsync(CancellationToken cancellationToken) { - var array = memory.ToArray(); - var asyncByte = new QueueDataBytes(array, 0, array.Length); - this.m_asyncBytes.Enqueue(asyncByte); - return EasyTask.CompletedTask; + using var reader = new PooledBytesReader(); + while (!cancellationToken.IsCancellationRequested) + { + var readResult = await this.m_pipe.Reader.ReadAsync(cancellationToken); + var sequence = readResult.Buffer; + reader.Reset(sequence); + await this.m_adapter.ReceivedInputAsync(reader).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + + var position = sequence.GetPosition(reader.BytesRead); + + // 通知PipeReader已消费 + this.m_pipe.Reader.AdvanceTo(position, sequence.End); + + // 如果本次ReadAsync已完成或被取消,直接返回 + if (readResult.IsCanceled || readResult.IsCompleted) + { + return; + } + + reader.Clear(); + } } - private bool TryGet(out List byteBlocks) + private async Task SendCallback(ReadOnlyMemory memory, CancellationToken cancellationToken) { - byteBlocks = new List(); - ByteBlock block = null; + var offset = 0; while (true) { - if (this.m_asyncBytes.TryDequeue(out var asyncByte)) - { - if (block == null) - { - block = new ByteBlock(this.m_bufferLength); - byteBlocks.Add(block); - } - var surLen = this.m_bufferLength - block.Position; - if (surLen < asyncByte.Length)//不能完成写入 - { - block.Write(new ReadOnlySpan(asyncByte.Buffer, asyncByte.Offset, surLen)); - var offset = surLen; - while (offset < asyncByte.Length) - { - block = this.Write(asyncByte, ref offset); - byteBlocks.Add(block); - } + cancellationToken.ThrowIfCancellationRequested(); - if (byteBlocks.Count > 10) - { - break; - } - } - else//本次能完成写入 - { - block.Write(new ReadOnlySpan(asyncByte.Buffer, asyncByte.Offset, asyncByte.Length)); - if (byteBlocks.Count > 10) - { - break; - } - } - } - else + var remainingLength = memory.Length - offset; + if (remainingLength <= 0) { - if (byteBlocks.Count > 0) - { - break; - } - else - { - return false; - } + break; } + var sliceMemory = memory.Slice(offset, Math.Min(remainingLength, this.m_bufferLength)); + await this.m_pipe.Writer.WriteAsync(sliceMemory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + offset += sliceMemory.Length; } - return true; - } - private ByteBlock Write(QueueDataBytes transferByte, ref int offset) - { - var block = new ByteBlock(this.m_bufferLength); - var len = Math.Min(transferByte.Length - offset, this.m_bufferLength); - block.Write(new ReadOnlySpan(transferByte.Buffer, offset, len)); - offset += len; - - return block; } } \ No newline at end of file diff --git a/src/TouchSocket.Core/DataAdapter/Test/SingleStreamDataAdapterTester2.cs b/src/TouchSocket.Core/DataAdapter/Test/SingleStreamDataAdapterTester2.cs index 7a639f779..329479b0b 100644 --- a/src/TouchSocket.Core/DataAdapter/Test/SingleStreamDataAdapterTester2.cs +++ b/src/TouchSocket.Core/DataAdapter/Test/SingleStreamDataAdapterTester2.cs @@ -10,194 +10,151 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; +using System.IO.Pipelines; namespace TouchSocket.Core; /// ///单线程状况的流式数据处理适配器测试 /// -public class SingleStreamDataAdapterTester : DisposableObject where TAdapter : CustomDataHandlingAdapter +public class SingleStreamDataAdapterTester + where TAdapter : CustomDataHandlingAdapter where TRequest : class, IRequestInfo { - private readonly IntelligentDataQueue m_asyncBytes; private readonly TAdapter m_adapter; - private readonly int m_bufferLength; + private readonly Pipe m_pipe = new Pipe(new PipeOptions(default, default, default, 1024 * 1024 * 1024, 1024 * 1024 * 512, -1)); private readonly Action m_receivedCallBack; + private int m_bufferLength; private int m_count; private int m_expectedCount; - private Stopwatch m_stopwatch; - private int m_timeout; /// /// Tcp数据处理适配器测试 /// - public SingleStreamDataAdapterTester(TAdapter adapter, int bufferLength = 1024, Action receivedCallBack = default) + public SingleStreamDataAdapterTester(TAdapter adapter, Action receivedCallBack = default) { this.m_adapter = adapter; - this.m_asyncBytes = new IntelligentDataQueue(1024 * 1024 * 10); - this.m_bufferLength = bufferLength; this.m_receivedCallBack = receivedCallBack; - adapter.SendAsyncCallBack = this.SendCallback; - Task.Run(this.BeginSend); } - - /// - /// 模拟测试运行发送 - /// - /// - /// - /// - /// 测试次数 - /// 期待测试次数 - /// 超时 - /// - public TimeSpan Run(byte[] buffer, int offset, int length, int testCount, int expectedCount, int millisecondsTimeout) + public async Task RunAsync(ReadOnlyMemory memory, int testCount, int expectedCount, int bufferLength, CancellationToken cancellationToken) { this.m_count = 0; this.m_expectedCount = expectedCount; - this.m_timeout = millisecondsTimeout; - this.m_stopwatch = new Stopwatch(); - this.m_stopwatch.Start(); - Task.Run(async () => - { - for (var i = 0; i < testCount; i++) - { - await this.m_adapter.SendInputAsync(new Memory(buffer, offset, length)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - }); + this.m_bufferLength = bufferLength; + var receivedTask = EasyTask.SafeRun(this.ReceivedLoopAsync, cancellationToken); - if (SpinWait.SpinUntil(() => this.m_count == this.m_expectedCount, this.m_timeout)) + var m_stopwatch = new Stopwatch(); + m_stopwatch.Start(); + + for (var i = 0; i < testCount; i++) { - this.m_stopwatch.Stop(); - return this.m_stopwatch.Elapsed; + cancellationToken.ThrowIfCancellationRequested(); + var valueByteBlock = new ValueByteBlock(memory.Length + 1024); + try + { + this.m_adapter.SendInput(ref valueByteBlock, memory); + + await this.SendCallback(valueByteBlock.Memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + finally + { + valueByteBlock.Dispose(); + } } - throw new TimeoutException(); + + await this.m_pipe.Writer.CompleteAsync(); + + await receivedTask.WithCancellation(cancellationToken); + + if (this.m_count != this.m_expectedCount) + { + throw new Exception($"实际接收次数:{this.m_count},预期接收次数:{this.m_expectedCount}"); + } + + m_stopwatch.Stop(); + + await this.m_pipe.Reader.CompleteAsync(); + this.m_pipe.Reset(); + + var elapsed = m_stopwatch.Elapsed; + return elapsed; } /// - /// 模拟发送 + /// 以指定的超时时间异步运行测试。 /// - /// - /// 测试次数 - /// 期待测试次数 - /// 超时 - public TimeSpan Run(byte[] buffer, int testCount, int expectedCount, int millisecondsTimeout) + /// 要发送的数据内存块。 + /// 测试发送次数。 + /// 预期接收次数。 + /// 每次写入的缓冲区长度。 + /// 超时时间(毫秒)。 + /// 返回测试所用的时间。 + /// 超时未完成时抛出。 + public async Task RunAsync(ReadOnlyMemory memory, int testCount, int expectedCount, int bufferLength, int millisecondsTimeout) { - return this.Run(buffer, 0, buffer.Length, testCount, expectedCount, millisecondsTimeout); - } - - private async Task BeginSend() - { - while (!this.DisposedValue) + using (var cancellationToken = new CancellationTokenSource(millisecondsTimeout)) { - if (this.TryGet(out var byteBlocks)) + try { - foreach (var block in byteBlocks) - { - block.SeekToStart(); - try - { - var byteBlock = block; - while (byteBlock.CanRead) - { - if (this.m_adapter.TryParseRequest(ref byteBlock, out var request)) - { - this.m_count++; - this.m_receivedCallBack?.Invoke(request); - } - } - } - catch - { - - } - finally - { - block.Dispose(); - } - } + return await this.RunAsync(memory, testCount, expectedCount, bufferLength, cancellationToken.Token); } - else + catch (OperationCanceledException) { - await Task.Delay(1).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + throw new TimeoutException(); + } + catch (Exception) + { + throw; } } } - private Task SendCallback(ReadOnlyMemory memory) + private async Task ReceivedLoopAsync(CancellationToken cancellationToken) { - var array = memory.ToArray(); - var asyncByte = new QueueDataBytes(array, 0, array.Length); - this.m_asyncBytes.Enqueue(asyncByte); - return EasyTask.CompletedTask; - } - - private bool TryGet(out List byteBlocks) - { - byteBlocks = new List(); - ByteBlock block = null; - while (true) + while (!cancellationToken.IsCancellationRequested) { - if (this.m_asyncBytes.TryDequeue(out var asyncByte)) - { - if (block == null) - { - block = new ByteBlock(this.m_bufferLength); - byteBlocks.Add(block); - } - var surLen = this.m_bufferLength - block.Position; - if (surLen < asyncByte.Length)//不能完成写入 - { - block.Write(new ReadOnlySpan(asyncByte.Buffer, asyncByte.Offset, surLen)); - var offset = surLen; - while (offset < asyncByte.Length) - { - block = this.Write(asyncByte, ref offset); - byteBlocks.Add(block); - } + var readResult = await this.m_pipe.Reader.ReadAsync(cancellationToken); - if (byteBlocks.Count > 10) - { - break; - } - } - else//本次能完成写入 - { - block.Write(new ReadOnlySpan(asyncByte.Buffer, asyncByte.Offset, asyncByte.Length)); - if (byteBlocks.Count > 10) - { - break; - } - } - } - else + var reader = new BytesReader(readResult.Buffer); + + while (reader.BytesRemaining > 0) { - if (byteBlocks.Count > 0) + if (!this.m_adapter.TryParseRequest(ref reader, out var request)) { break; } - else - { - return false; - } + + this.m_count++; + this.m_receivedCallBack?.Invoke(request); + } + + var position = readResult.Buffer.GetPosition(reader.BytesRead); + this.m_pipe.Reader.AdvanceTo(position, readResult.Buffer.End); + + if (readResult.IsCanceled || readResult.IsCompleted) + { + return; } } - return true; } - private ByteBlock Write(QueueDataBytes transferByte, ref int offset) + private async Task SendCallback(ReadOnlyMemory memory, CancellationToken cancellationToken) { - var block = new ByteBlock(this.m_bufferLength); - var len = Math.Min(transferByte.Length - offset, this.m_bufferLength); - block.Write(new ReadOnlySpan(transferByte.Buffer, offset, len)); - offset += len; + var offset = 0; + while (true) + { + cancellationToken.ThrowIfCancellationRequested(); - return block; + var remainingLength = memory.Length - offset; + if (remainingLength <= 0) + { + break; + } + var sliceMemory = memory.Slice(offset, Math.Min(remainingLength, this.m_bufferLength)); + await this.m_pipe.Writer.WriteAsync(sliceMemory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + offset += sliceMemory.Length; + } } } \ No newline at end of file diff --git a/src/TouchSocket/DataAdapter/Test/TcpDataAdapterTester.cs b/src/TouchSocket.Core/DataAdapter/Test/TcpDataAdapterTester.cs similarity index 93% rename from src/TouchSocket/DataAdapter/Test/TcpDataAdapterTester.cs rename to src/TouchSocket.Core/DataAdapter/Test/TcpDataAdapterTester.cs index 136da2ed9..0088c095b 100644 --- a/src/TouchSocket/DataAdapter/Test/TcpDataAdapterTester.cs +++ b/src/TouchSocket.Core/DataAdapter/Test/TcpDataAdapterTester.cs @@ -10,9 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - -namespace TouchSocket.Sockets; +namespace TouchSocket.Core; /// /// Tcp数据处理适配器测试 diff --git a/src/TouchSocket.Core/Container/InternalScopedResolver.cs b/src/TouchSocket.Core/DataAdapter/Test/UdpDataAdapterTester.cs similarity index 78% rename from src/TouchSocket.Core/Container/InternalScopedResolver.cs rename to src/TouchSocket.Core/DataAdapter/Test/UdpDataAdapterTester.cs index 6f9e12bc3..7ae1613f0 100644 --- a/src/TouchSocket.Core/Container/InternalScopedResolver.cs +++ b/src/TouchSocket.Core/DataAdapter/Test/UdpDataAdapterTester.cs @@ -12,12 +12,12 @@ namespace TouchSocket.Core; -internal class InternalScopedResolver : DisposableObject, IScopedResolver +/// +/// Udp数据处理适配器测试 +/// +public class UdpDataAdapterTester : MultithreadingDataAdapterTester { - public IResolver Resolver { get; set; } - - public InternalScopedResolver(IResolver resolver) + protected UdpDataAdapterTester(int multiThread) : base(multiThread) { - this.Resolver = resolver; } } \ No newline at end of file diff --git a/src/TouchSocket/DataAdapter/Udp/UdpDataHandlingAdapter.cs b/src/TouchSocket.Core/DataAdapter/Udp/UdpDataHandlingAdapter.cs similarity index 62% rename from src/TouchSocket/DataAdapter/Udp/UdpDataHandlingAdapter.cs rename to src/TouchSocket.Core/DataAdapter/Udp/UdpDataHandlingAdapter.cs index 32b8dd61b..3a36510c5 100644 --- a/src/TouchSocket/DataAdapter/Udp/UdpDataHandlingAdapter.cs +++ b/src/TouchSocket.Core/DataAdapter/Udp/UdpDataHandlingAdapter.cs @@ -10,13 +10,9 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; using System.Net; -using System.Threading.Tasks; -using TouchSocket.Core; -namespace TouchSocket.Sockets; +namespace TouchSocket.Core; /// /// Udp数据处理适配器 @@ -26,34 +22,24 @@ public abstract class UdpDataHandlingAdapter : DataHandlingAdapter /// public override bool CanSendRequestInfo => false; - /// - public override bool CanSplicingSend => false; - /// /// 当接收数据处理完成后,回调该函数执行接收 /// - public Func ReceivedCallBack { get; set; } + public Func, IRequestInfo, Task> ReceivedCallBack { get; set; } /// /// 当接收数据处理完成后,异步回调该函数执行发送 /// - public Func, Task> SendCallBackAsync { get; set; } + public Func, CancellationToken, Task> SendCallBackAsync { get; set; } /// /// 收到数据的切入点,该方法由框架自动调用。 /// /// - /// - public virtual async Task ReceivedInput(EndPoint remoteEndPoint, ByteBlock byteBlock) + /// + public Task ReceivedInputAsync(EndPoint remoteEndPoint, ReadOnlyMemory memory) { - try - { - await this.PreviewReceived(remoteEndPoint, byteBlock).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - catch (Exception ex) - { - this.OnError(ex, ex.Message, true, true); - } + return this.PreviewReceivedAsync(remoteEndPoint, memory); } #region SendInputAsync @@ -63,14 +49,15 @@ public abstract class UdpDataHandlingAdapter : DataHandlingAdapter /// /// 要发送数据的端点。 /// 包含要发送的数据的只读内存。 + /// 可取消令箭 /// 返回一个任务,表示发送操作。 /// /// 此方法是一个异步操作,用于向指定的端点发送输入数据。 /// 它使用PreviewSendAsync方法来执行实际的发送操作。 /// - public Task SendInputAsync(EndPoint endPoint, ReadOnlyMemory memory) + public Task SendInputAsync(EndPoint endPoint, ReadOnlyMemory memory, CancellationToken cancellationToken) { - return this.PreviewSendAsync(endPoint, memory); + return this.PreviewSendAsync(endPoint, memory, cancellationToken); } /// @@ -78,34 +65,24 @@ public abstract class UdpDataHandlingAdapter : DataHandlingAdapter /// /// /// - public Task SendInputAsync(EndPoint endPoint, IRequestInfo requestInfo) + /// 可取消令箭 + public Task SendInputAsync(EndPoint endPoint, IRequestInfo requestInfo, CancellationToken cancellationToken) { - return this.PreviewSendAsync(endPoint, requestInfo); + return this.PreviewSendAsync(endPoint, requestInfo, cancellationToken); } - - /// - /// 发送数据的切入点,该方法由框架自动调用。 - /// - /// - /// - public Task SendInputAsync(EndPoint endPoint, IList> transferBytes) - { - return this.PreviewSendAsync(endPoint, transferBytes); - } - #endregion SendInputAsync /// /// 处理已经经过预先处理后的数据 /// /// 远程端点,标识数据来源 - /// 接收到的二进制数据块 + /// 接收到的二进制数据块 /// 解析后的请求信息 /// 一个异步任务,代表处理过程 - protected Task GoReceived(EndPoint remoteEndPoint, ByteBlock byteBlock, IRequestInfo requestInfo) + protected Task GoReceived(EndPoint remoteEndPoint, ReadOnlyMemory memory, IRequestInfo requestInfo) { // 调用接收回调,继续处理接收到的数据 - return this.ReceivedCallBack.Invoke(remoteEndPoint, byteBlock, requestInfo); + return this.ReceivedCallBack.Invoke(remoteEndPoint, memory, requestInfo); } /// @@ -113,19 +90,20 @@ public abstract class UdpDataHandlingAdapter : DataHandlingAdapter /// /// 目标端点,表示数据发送的目的地址 /// 已经经过预先处理的字节数据,以 ReadOnlyMemory 方式传递以提高性能 + /// 可取消令箭 /// 返回一个 Task 对象,表示异步操作的完成 - protected Task GoSendAsync(EndPoint endPoint, ReadOnlyMemory memory) + protected Task GoSendAsync(EndPoint endPoint, ReadOnlyMemory memory, CancellationToken cancellationToken) { - return this.SendCallBackAsync.Invoke(endPoint, memory); + return this.SendCallBackAsync.Invoke(endPoint, memory, cancellationToken); } /// - /// 当接收到数据后预先处理数据,然后调用处理数据 + /// 当接收到数据后预先处理数据,然后调用处理数据 /// /// - /// - protected virtual async Task PreviewReceived(EndPoint remoteEndPoint, ByteBlock byteBlock) + /// + protected virtual async Task PreviewReceivedAsync(EndPoint remoteEndPoint, ReadOnlyMemory memory) { - await this.GoReceived(remoteEndPoint, byteBlock, default).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.GoReceived(remoteEndPoint, memory, default).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// @@ -133,9 +111,10 @@ public abstract class UdpDataHandlingAdapter : DataHandlingAdapter /// /// /// - protected virtual async Task PreviewSendAsync(EndPoint endPoint, IRequestInfo requestInfo) + /// 可取消令箭 + protected virtual async Task PreviewSendAsync(EndPoint endPoint, IRequestInfo requestInfo, CancellationToken cancellationToken) { - ThrowHelper.ThrowArgumentNullExceptionIf(requestInfo, nameof(requestInfo)); + ThrowHelper.ThrowIfNull(requestInfo, nameof(requestInfo)); var requestInfoBuilder = (IRequestInfoBuilder)requestInfo; @@ -143,7 +122,7 @@ public abstract class UdpDataHandlingAdapter : DataHandlingAdapter try { requestInfoBuilder.Build(ref byteBlock); - await this.GoSendAsync(endPoint, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.GoSendAsync(endPoint, byteBlock.Memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } finally { @@ -156,22 +135,11 @@ public abstract class UdpDataHandlingAdapter : DataHandlingAdapter /// /// 数据发送的目标端点。 /// 待发送的字节数据内存。 + /// 可取消令箭 /// 一个表示异步操作完成的 对象。 - protected virtual Task PreviewSendAsync(EndPoint endPoint, ReadOnlyMemory memory) + protected virtual Task PreviewSendAsync(EndPoint endPoint, ReadOnlyMemory memory, CancellationToken cancellationToken) { - return this.GoSendAsync(endPoint, memory); - } - - /// - /// 组合发送预处理数据, - /// 当属性SplicingSend实现为时,系统才会调用该方法。 - /// - /// - /// - /// - protected virtual Task PreviewSendAsync(EndPoint endPoint, IList> transferBytes) - { - throw new NotImplementedException(); + return this.GoSendAsync(endPoint, memory, cancellationToken); } /// @@ -179,4 +147,10 @@ public abstract class UdpDataHandlingAdapter : DataHandlingAdapter { } + + /// + protected override void SafetyDispose(bool disposing) + { + + } } \ No newline at end of file diff --git a/src/TouchSocket/DataAdapter/Udp/UdpPackage/UdpFrame.cs b/src/TouchSocket.Core/DataAdapter/Udp/UdpPackage/UdpFrame.cs similarity index 97% rename from src/TouchSocket/DataAdapter/Udp/UdpPackage/UdpFrame.cs rename to src/TouchSocket.Core/DataAdapter/Udp/UdpPackage/UdpFrame.cs index a99a1bcd5..d0040ef35 100644 --- a/src/TouchSocket/DataAdapter/Udp/UdpPackage/UdpFrame.cs +++ b/src/TouchSocket.Core/DataAdapter/Udp/UdpPackage/UdpFrame.cs @@ -10,11 +10,9 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Runtime.CompilerServices; -using TouchSocket.Core; -namespace TouchSocket.Sockets; +namespace TouchSocket.Core; /// /// UDP数据帧 diff --git a/src/TouchSocket/DataAdapter/Udp/UdpPackage/UdpPackage.cs b/src/TouchSocket.Core/DataAdapter/Udp/UdpPackage/UdpPackage.cs similarity index 97% rename from src/TouchSocket/DataAdapter/Udp/UdpPackage/UdpPackage.cs rename to src/TouchSocket.Core/DataAdapter/Udp/UdpPackage/UdpPackage.cs index 56538e6e8..2bdbd5382 100644 --- a/src/TouchSocket/DataAdapter/Udp/UdpPackage/UdpPackage.cs +++ b/src/TouchSocket.Core/DataAdapter/Udp/UdpPackage/UdpPackage.cs @@ -11,10 +11,8 @@ //------------------------------------------------------------------------------ using System.Collections.Concurrent; -using System.Threading; -using TouchSocket.Core; -namespace TouchSocket.Sockets; +namespace TouchSocket.Core; /// /// UDP数据包 diff --git a/src/TouchSocket.Core/DataAdapter/Udp/UdpPackage/UdpPackageAdapter.cs b/src/TouchSocket.Core/DataAdapter/Udp/UdpPackage/UdpPackageAdapter.cs new file mode 100644 index 000000000..e24967594 --- /dev/null +++ b/src/TouchSocket.Core/DataAdapter/Udp/UdpPackage/UdpPackageAdapter.cs @@ -0,0 +1,164 @@ +//------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +//------------------------------------------------------------------------------ + +using System.Collections.Concurrent; +using System.Net; + +namespace TouchSocket.Core; + +/// +/// UDP数据包的适配器 +/// +public class UdpPackageAdapter : UdpDataHandlingAdapter +{ + private readonly ConcurrentDictionary m_revStore; + private int m_mtu = 1472; + + /// + /// 构造函数 + /// + public UdpPackageAdapter() + { + this.m_revStore = new ConcurrentDictionary(); + } + + /// + public override bool CanSendRequestInfo => false; + + /// + /// 最大传输单元 + /// + public int MTU + { + get => this.m_mtu + 11; + set => this.m_mtu = value > 11 ? value : 1472; + } + + /// + /// 接收超时时间,默认5000ms + /// + public int Timeout { get; set; } = 5000; + + /// + protected override async Task PreviewReceivedAsync(EndPoint remoteEndPoint, ReadOnlyMemory memory) + { + var udpFrame = new UdpFrame(); + if (udpFrame.Parse(memory.Span)) + { + var udpPackage = this.m_revStore.GetOrAdd(udpFrame.Id, (i) => new UdpPackage(i, this.Timeout, this.m_revStore)); + udpPackage.Add(udpFrame); + if (udpPackage.Length > this.MaxPackageSize) + { + this.m_revStore.TryRemove(udpPackage.Id, out _); + + ThrowHelper.ThrowArgumentOutOfRangeException_MoreThan(nameof(udpPackage.Length), udpPackage.Length, this.MaxPackageSize); + return; + } + if (udpPackage.IsComplated) + { + if (this.m_revStore.TryRemove(udpPackage.Id, out _)) + { + using (var block = new ByteBlock(udpPackage.Length)) + { + if (udpPackage.TryGetData(block)) + { + await this.GoReceived(remoteEndPoint, block.Memory, null); + } + } + } + } + } + } + + /// + protected override async Task PreviewSendAsync(EndPoint endPoint, ReadOnlyMemory memory, CancellationToken cancellationToken = default) + { + this.ThrowIfMoreThanMaxPackageSize(memory.Length); + + var id = TouchSocketCoreUtility.GenerateRandomInt64(); + var off = 0; + var surLen = memory.Length; + var freeRoom = this.m_mtu - 11; + ushort sn = 0; + /*|********|**|*|n|*/ + /*|********|**|*|**|*/ + while (surLen > 0) + { + var byteBlock = new ByteBlock(this.m_mtu); + try + { + WriterExtension.WriteValue(ref byteBlock, id); + WriterExtension.WriteValue(ref byteBlock, sn++); + + if (surLen > freeRoom)//有余 + { + WriterExtension.WriteValue(ref byteBlock, 0); + byteBlock.Write(memory.Span.Slice(off, freeRoom)); + //Buffer.BlockCopy(buffer, off, data, 11, freeRoom); + off += freeRoom; + surLen -= freeRoom; + await this.GoSendAsync(endPoint, byteBlock.Memory, cancellationToken); + } + else if (surLen + 2 <= freeRoom)//结束且能容纳Crc + { + + var flag = ((byte)0).SetBit(7, true);//设置终结帧 + WriterExtension.WriteValue(ref byteBlock, flag); + + byteBlock.Write(memory.Span.Slice(off, surLen)); + //Buffer.BlockCopy(buffer, off, data, 11, surLen); + + WriterExtension.WriteValue(ref byteBlock, Crc.Crc16Value(memory.Span), EndianType.Big); + //Buffer.BlockCopy(Crc.Crc16(buffer, offset, length), 0, data, 11 + surLen, 2); + + //Buffer.BlockCopy(Crc.Crc16(memory.Span), 0, data, 11 + surLen, 2); + + //await this.GoSendAsync(endPoint, data, 0, surLen + 11 + 2); + await this.GoSendAsync(endPoint, byteBlock.Memory, cancellationToken); + + off += surLen; + surLen -= surLen; + } + else//结束但不能容纳Crc + { + WriterExtension.WriteValue(ref byteBlock, 0); + byteBlock.Write(memory.Span.Slice(off, surLen)); + //Buffer.BlockCopy(buffer, off, data, 11, surLen); + await this.GoSendAsync(endPoint, byteBlock.Memory, cancellationToken); + + byteBlock.Reset(); + + off += surLen; + surLen -= surLen; + + //var finData = new byte[13]; + + WriterExtension.WriteValue(ref byteBlock, id); + WriterExtension.WriteValue(ref byteBlock, sn++); + WriterExtension.WriteValue(ref byteBlock, ((byte)0).SetBit(7, true)); + WriterExtension.WriteValue(ref byteBlock, Crc.Crc16Value(memory.Span), EndianType.Big); + + //Buffer.BlockCopy(TouchSocketBitConverter.Default.GetBytes(id), 0, finData, 0, 8); + //Buffer.BlockCopy(TouchSocketBitConverter.Default.GetBytes(sn++), 0, finData, 8, 2); + //byte flag = 0; + //finData[10] = flag.SetBit(7, 1); + //Buffer.BlockCopy(Crc.Crc16(buffer, offset, length), 0, finData, 11, 2); + await this.GoSendAsync(endPoint, byteBlock.Memory, cancellationToken); + } + } + finally + { + byteBlock.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/src/TouchSocket.Core/Dependency/Attribute/DataValidationAttribute.cs b/src/TouchSocket.Core/Dependency/Attribute/DataValidationAttribute.cs index 5e73581a5..6b22dee0c 100644 --- a/src/TouchSocket.Core/Dependency/Attribute/DataValidationAttribute.cs +++ b/src/TouchSocket.Core/Dependency/Attribute/DataValidationAttribute.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/Dependency/DependencyObject.cs b/src/TouchSocket.Core/Dependency/DependencyObject.cs index f9c77bffa..be0b4bc93 100644 --- a/src/TouchSocket.Core/Dependency/DependencyObject.cs +++ b/src/TouchSocket.Core/Dependency/DependencyObject.cs @@ -10,10 +10,8 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Diagnostics; using System.Runtime.CompilerServices; -using System.Threading; namespace TouchSocket.Core; @@ -21,7 +19,7 @@ namespace TouchSocket.Core; /// 依赖项对象. 线程安全。 /// -public class DependencyObject : DisposableObject, IDependencyObject +public class DependencyObject : SafetyDisposableObject, IDependencyObject { private DependencyProperties m_dp; private SpinLock m_lock = new SpinLock(Debugger.IsAttached); @@ -29,11 +27,7 @@ public class DependencyObject : DisposableObject, IDependencyObject /// public TValue GetValue(DependencyProperty dp) { - if (this.TryGetValue(dp, out var value)) - { - return value; - } - return dp.OnFailedToGetTheValue.Invoke(this); + return this.TryGetValue(dp, out var value) ? value : dp.OnFailedToGetTheValue.Invoke(this); } /// @@ -157,7 +151,7 @@ public class DependencyObject : DisposableObject, IDependencyObject this.m_lock.Enter(ref lockTakenFotThis); dependencyObject.m_lock.Enter(ref lockTakenFotOther); - ThrowHelper.ThrowArgumentNullExceptionIf(dependencyObject, nameof(dependencyObject)); + ThrowHelper.ThrowIfNull(dependencyObject, nameof(dependencyObject)); ThrowHelper.ThrowObjectDisposedExceptionIf(dependencyObject); @@ -194,7 +188,7 @@ public class DependencyObject : DisposableObject, IDependencyObject } /// - protected override void Dispose(bool disposing) + protected override void SafetyDispose(bool disposing) { if (disposing) { @@ -212,7 +206,6 @@ public class DependencyObject : DisposableObject, IDependencyObject } } } - base.Dispose(disposing); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/TouchSocket.Core/Dependency/DependencyProperties.cs b/src/TouchSocket.Core/Dependency/DependencyProperties.cs index 59d022031..dda5fb72c 100644 --- a/src/TouchSocket.Core/Dependency/DependencyProperties.cs +++ b/src/TouchSocket.Core/Dependency/DependencyProperties.cs @@ -10,43 +10,235 @@ // 感谢您的下载和使用 // ------------------------------------------------------------------------------ -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; +using System.Runtime.CompilerServices; namespace TouchSocket.Core; -[DebuggerTypeProxy(typeof(DependencyObjectDebugView))] +/// +/// 依赖属性集合,继承自字典,用于存储依赖属性的键值对。 +/// 提供了增强的调试视图和便捷的操作方法。 +/// +[DebuggerTypeProxy(typeof(DependencyPropertiesDebugView))] internal class DependencyProperties : Dictionary { - #region DebugView - - private sealed class DependencyObjectDebugView + /// + /// 添加或更新依赖属性值。 + /// + /// 属性键 + /// 属性值 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AddOrUpdate(int key, object value) { - private readonly DependencyProperties m_instance; + this[key] = value; + } - public DependencyObjectDebugView(DependencyProperties instance) => this.m_instance = instance; + /// + /// 获取依赖属性值,如果不存在则返回默认值。 + /// + /// 属性键 + /// 默认值 + /// 属性值或默认值 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public object GetValueOrDefault(int key, object defaultValue = null) + { + return this.TryGetValue(key, out var value) ? value : defaultValue; + } - // 将字典转换为映射后的属性形式 + /// + /// 获取强类型的依赖属性值,如果不存在或类型不匹配则返回默认值。 + /// + /// 属性值类型 + /// 属性键 + /// 默认值 + /// 属性值或默认值 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T GetValueOrDefault(int key, T defaultValue = default) + { + if (this.TryGetValue(key, out var value) && value is T typedValue) + { + return typedValue; + } + return defaultValue; + } + + /// + /// 尝试移除指定键的值并返回被移除的值。 + /// + /// 要移除的键 + /// 被移除的值 + /// 如果成功移除返回true,否则返回false + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryRemove(int key, out object value) + { + if (this.TryGetValue(key, out value)) + { + this.Remove(key); + return true; + } + value = null; + return false; + } + + /// + /// 检查是否包含指定的依赖属性。 + /// + /// 属性ID + /// 如果包含返回true,否则返回false + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool HasProperty(int propertyId) + { + return this.ContainsKey(propertyId); + } + + /// + /// 获取所有属性的键值对集合,以便于调试和枚举。 + /// + /// 包含属性名称和值的键值对数组 + public KeyValuePair[] GetNamedProperties() + { + if (this.Count == 0) + { + return Array.Empty>(); + } + + var result = new KeyValuePair[this.Count]; + var index = 0; + + foreach (var kvp in this) + { + var propertyName = DependencyPropertyBase.s_keyNameMap.TryGetValue(kvp.Key, out var name) + ? name + : $"UnknownProperty_{kvp.Key}"; + + result[index++] = new KeyValuePair(propertyName, kvp.Value); + } + + return result; + } + + /// + /// 清空所有属性并释放相关资源。 + /// + public new void Clear() + { + // 对于实现了IDisposable的值,进行资源释放 + foreach (var value in this.Values) + { + if (value is IDisposable disposable) + { + try + { + disposable.Dispose(); + } + catch + { + // 忽略释放过程中的异常,避免影响清理流程 + } + } + } + + base.Clear(); + } + + #region 调试视图 + + /// + /// 依赖属性调试视图,用于在调试器中更友好地显示属性信息。 + /// + private sealed class DependencyPropertiesDebugView + { + private readonly DependencyProperties _instance; + + /// + /// 初始化调试视图。 + /// + /// 依赖属性实例 + public DependencyPropertiesDebugView(DependencyProperties instance) + { + this._instance = instance ?? throw new ArgumentNullException(nameof(instance)); + } + + /// + /// 获取用于调试显示的属性项数组。 + /// 将依赖属性的ID转换为可读的属性名称。 + /// [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] - public KeyValuePair[] Items + public DependencyPropertyDebugItem[] Items { get { - if (this.m_instance is null) + if (this._instance.Count == 0) { - return []; + return Array.Empty(); } - return this.m_instance - .Select(kvp => new KeyValuePair( - DependencyPropertyBase.s_keyNameMap.TryGetValue(kvp.Key, out var name) - ? name - : $"UnknownKey_{kvp.Key}", - kvp.Value)) - .ToArray(); + + var items = new DependencyPropertyDebugItem[this._instance.Count]; + var index = 0; + + foreach (var kvp in this._instance) + { + var propertyName = DependencyPropertyBase.s_keyNameMap.TryGetValue(kvp.Key, out var name) + ? name + : $"UnknownProperty_{kvp.Key}"; + + items[index++] = new DependencyPropertyDebugItem( + propertyName, + kvp.Key, + kvp.Value + ); + } + + return items; } } + + /// + /// 获取属性总数。 + /// + public int Count => this._instance.Count; } - #endregion DebugView + /// + /// 用于调试显示的依赖属性项。 + /// + [DebuggerDisplay("{PropertyName} (ID: {PropertyId}) = {Value}")] + private readonly struct DependencyPropertyDebugItem + { + /// + /// 初始化依赖属性调试项。 + /// + /// 属性名称 + /// 属性ID + /// 属性值 + public DependencyPropertyDebugItem(string propertyName, int propertyId, object value) + { + this.PropertyName = propertyName; + this.PropertyId = propertyId; + this.Value = value; + this.ValueType = value?.GetType()?.Name ?? "null"; + } + + /// + /// 属性名称 + /// + public string PropertyName { get; } + + /// + /// 属性ID + /// + public int PropertyId { get; } + + /// + /// 属性值 + /// + public object Value { get; } + + /// + /// 值类型名称 + /// + public string ValueType { get; } + } + + #endregion } \ No newline at end of file diff --git a/src/TouchSocket.Core/Dependency/DependencyProperty.cs b/src/TouchSocket.Core/Dependency/DependencyProperty.cs index e21f6e2ad..b33598ce7 100644 --- a/src/TouchSocket.Core/Dependency/DependencyProperty.cs +++ b/src/TouchSocket.Core/Dependency/DependencyProperty.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Diagnostics; namespace TouchSocket.Core; @@ -36,7 +35,8 @@ public sealed class DependencyProperty : DependencyPropertyBase, IEquata : base(propertyName) { this.m_name = propertyName; - this.OnFailedToGetTheValue = ThrowHelper.ThrowArgumentNullExceptionIf(onFailedToGetTheValue, nameof(onFailedToGetTheValue)); + ThrowHelper.ThrowIfNull(onFailedToGetTheValue, nameof(onFailedToGetTheValue)); + this.OnFailedToGetTheValue = onFailedToGetTheValue; } /// diff --git a/src/TouchSocket.Core/Dependency/DependencyPropertyBase.cs b/src/TouchSocket.Core/Dependency/DependencyPropertyBase.cs index b53b5b832..c4adb3345 100644 --- a/src/TouchSocket.Core/Dependency/DependencyPropertyBase.cs +++ b/src/TouchSocket.Core/Dependency/DependencyPropertyBase.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Collections.Generic; -using System.Threading; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/Diagnostics/TimeMeasurer.cs b/src/TouchSocket.Core/Diagnostics/TimeMeasurer.cs index 52759c7e8..098a4f3cf 100644 --- a/src/TouchSocket.Core/Diagnostics/TimeMeasurer.cs +++ b/src/TouchSocket.Core/Diagnostics/TimeMeasurer.cs @@ -10,9 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Diagnostics; -using System.Threading.Tasks; namespace TouchSocket.Core; diff --git a/src/TouchSocket.Core/Disposable/DisposableObject.cs b/src/TouchSocket.Core/Disposable/DisposableObject.cs index f77e6324b..93a99245b 100644 --- a/src/TouchSocket.Core/Disposable/DisposableObject.cs +++ b/src/TouchSocket.Core/Disposable/DisposableObject.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Runtime.CompilerServices; namespace TouchSocket.Core; @@ -28,6 +27,29 @@ public abstract partial class DisposableObject : IDisposableObject /// public bool DisposedValue => this.m_disposedValue; + /// + /// 释放资源。内部已经处理了 + /// + public void Dispose() + { + if (this.m_disposedValue) + { + return; + } + this.Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + /// + /// 处置资源 + /// + /// 一个值,表示是否释放托管资源 + protected virtual void Dispose(bool disposing) + { + // 标记当前对象为已处置状态 + this.m_disposedValue = true; + } + /// /// 判断当前对象是否已经被释放。 /// 如果已经被释放,则抛出异常。 @@ -43,24 +65,4 @@ public abstract partial class DisposableObject : IDisposableObject ThrowHelper.ThrowObjectDisposedException(this); } } - - - /// - /// 处置资源 - /// - /// 一个值,表示是否释放托管资源 - protected virtual void Dispose(bool disposing) - { - // 标记当前对象为已处置状态 - this.m_disposedValue = true; - } - - /// - /// 释放资源。内部已经处理了 - /// - public void Dispose() - { - this.Dispose(disposing: true); - GC.SuppressFinalize(this); - } } \ No newline at end of file diff --git a/src/TouchSocket.Core/Disposable/IDisposableObject.cs b/src/TouchSocket.Core/Disposable/IDisposableObject.cs index ab61e99f3..7a2e41adb 100644 --- a/src/TouchSocket.Core/Disposable/IDisposableObject.cs +++ b/src/TouchSocket.Core/Disposable/IDisposableObject.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/Disposable/SafetyDisposableObject.cs b/src/TouchSocket.Core/Disposable/SafetyDisposableObject.cs index a3fa3c7fc..e43359eea 100644 --- a/src/TouchSocket.Core/Disposable/SafetyDisposableObject.cs +++ b/src/TouchSocket.Core/Disposable/SafetyDisposableObject.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading; - namespace TouchSocket.Core; /// @@ -20,7 +17,7 @@ namespace TouchSocket.Core; /// public abstract class SafetyDisposableObject : DisposableObject { - private int m_count = 0; + private int m_disposed = 0; /// protected sealed override void Dispose(bool disposing) @@ -30,12 +27,11 @@ public abstract class SafetyDisposableObject : DisposableObject return; } - base.Dispose(disposing); - - if (Interlocked.Increment(ref this.m_count) == 1) + if (Interlocked.Exchange(ref this.m_disposed, 1) == 0) { this.SafetyDispose(disposing); } + base.Dispose(disposing); } /// diff --git a/src/TouchSocket.Core/EventArgs/TouchSocketEventArgs.cs b/src/TouchSocket.Core/EventArgs/TouchSocketEventArgs.cs index 918ad2ca4..3729e0c10 100644 --- a/src/TouchSocket.Core/EventArgs/TouchSocketEventArgs.cs +++ b/src/TouchSocket.Core/EventArgs/TouchSocketEventArgs.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; diff --git a/src/TouchSocket.Core/Exceptions/MessageNotFoundException.cs b/src/TouchSocket.Core/Exceptions/MessageNotFoundException.cs index bb2001930..54b45e108 100644 --- a/src/TouchSocket.Core/Exceptions/MessageNotFoundException.cs +++ b/src/TouchSocket.Core/Exceptions/MessageNotFoundException.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/Exceptions/MessageRegisteredException.cs b/src/TouchSocket.Core/Exceptions/MessageRegisteredException.cs index cd9055828..dcec5035b 100644 --- a/src/TouchSocket.Core/Exceptions/MessageRegisteredException.cs +++ b/src/TouchSocket.Core/Exceptions/MessageRegisteredException.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/Exceptions/OverlengthException.cs b/src/TouchSocket.Core/Exceptions/OverlengthException.cs index 6fe1ea578..7d9014da7 100644 --- a/src/TouchSocket.Core/Exceptions/OverlengthException.cs +++ b/src/TouchSocket.Core/Exceptions/OverlengthException.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/Exceptions/UnknownErrorException.cs b/src/TouchSocket.Core/Exceptions/UnknownErrorException.cs index 4c0ec84fe..25a20b253 100644 --- a/src/TouchSocket.Core/Exceptions/UnknownErrorException.cs +++ b/src/TouchSocket.Core/Exceptions/UnknownErrorException.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using TouchSocket.Resources; namespace TouchSocket.Core; diff --git a/src/TouchSocket.Core/Extensions/CollectionsExtension.cs b/src/TouchSocket.Core/Extensions/CollectionsExtension.cs index 0f7f003d9..5bfa8a73b 100644 --- a/src/TouchSocket.Core/Extensions/CollectionsExtension.cs +++ b/src/TouchSocket.Core/Extensions/CollectionsExtension.cs @@ -10,10 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Threading.Tasks; namespace TouchSocket.Core; @@ -59,7 +56,7 @@ public static class CollectionsExtension return count; } -#if NET45_OR_GREATER || NETSTANDARD2_0 +#if NET462_OR_GREATER || NETSTANDARD2_0 /// /// 尝试向字典中添加键值对。 @@ -72,12 +69,10 @@ public static class CollectionsExtension /// 如果添加成功则返回,否则返回 public static bool TryAdd(this IDictionary dictionary, TKey key, TValue value) { - // 如果字典中已经包含此键,则不添加,并返回 if (dictionary.ContainsKey(key)) { return false; } - // 向字典中添加键值对 dictionary.Add(key, value); return true; } diff --git a/src/TouchSocket.Core/Extensions/RangeExtension.cs b/src/TouchSocket.Core/Extensions/RangeExtension.cs index 171d03ba9..86ca2dae6 100644 --- a/src/TouchSocket.Core/Extensions/RangeExtension.cs +++ b/src/TouchSocket.Core/Extensions/RangeExtension.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; #if NET6_0_OR_GREATER diff --git a/src/TouchSocket.Core/Extensions/ReflectionExtension.cs b/src/TouchSocket.Core/Extensions/ReflectionExtension.cs index 7bfc6f634..6a01db6dd 100644 --- a/src/TouchSocket.Core/Extensions/ReflectionExtension.cs +++ b/src/TouchSocket.Core/Extensions/ReflectionExtension.cs @@ -10,11 +10,9 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; using System.ComponentModel; -using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; namespace TouchSocket.Core; @@ -71,8 +69,7 @@ public static class ReflectionExtension /// public static IEnumerable GetTupleElementNames(this ParameterInfo parameter) { - return (IEnumerable)DynamicMethodMemberAccessor.Default.GetValue(parameter.GetCustomAttribute(Type.GetType("System.Runtime.CompilerServices.TupleElementNamesAttribute")), "TransformNames"); - //return ((dynamic)parameter.GetCustomAttribute(Type.GetType("System.Runtime.CompilerServices.TupleElementNamesAttribute")))?.TransformNames; + return parameter.GetCustomAttribute().TransformNames; } /// @@ -109,9 +106,7 @@ public static class ReflectionExtension /// public static IEnumerable GetTupleElementNames(this MemberInfo memberInfo) { - return (IEnumerable)DynamicMethodMemberAccessor.Default.GetValue(memberInfo.GetCustomAttribute(Type.GetType("System.Runtime.CompilerServices.TupleElementNamesAttribute")), "TransformNames"); - - //return ((dynamic)memberInfo.GetCustomAttribute(Type.GetType("System.Runtime.CompilerServices.TupleElementNamesAttribute")))?.TransformNames; + return memberInfo.GetCustomAttribute().TransformNames; } /// @@ -189,12 +184,7 @@ public static class ReflectionExtension } // 判断属性的获取方法是否为公共的且无参数 - if (propertyInfo.GetMethod.IsPublic && propertyInfo.GetMethod.GetParameters().Length == 0) - { - return true; - } - - return false; + return propertyInfo.GetMethod.IsPublic && propertyInfo.GetMethod.GetParameters().Length == 0; } #endregion FieldInfo diff --git a/src/TouchSocket.Core/Extensions/StringExtension.cs b/src/TouchSocket.Core/Extensions/StringExtension.cs index 8b1012dd3..075b1f7d8 100644 --- a/src/TouchSocket.Core/Extensions/StringExtension.cs +++ b/src/TouchSocket.Core/Extensions/StringExtension.cs @@ -10,13 +10,9 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Runtime.CompilerServices; using System.Security.Cryptography; -using System.Text; using System.Text.RegularExpressions; using TouchSocket.Resources; @@ -300,8 +296,9 @@ public static class StringExtension /// /// /// - public static byte[] ToUtf8Bytes(this string value) + public static ReadOnlyMemory ToUtf8Bytes(this string value) { + ThrowHelper.ThrowIfNull(value, nameof(value)); return Encoding.UTF8.GetBytes(value); } diff --git a/src/TouchSocket.Core/Extensions/SystemExtension.cs b/src/TouchSocket.Core/Extensions/SystemExtension.cs index c99105719..ba0840504 100644 --- a/src/TouchSocket.Core/Extensions/SystemExtension.cs +++ b/src/TouchSocket.Core/Extensions/SystemExtension.cs @@ -10,19 +10,14 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Buffers; using System.Collections; -using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; -using System.IO; using System.Net; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; namespace TouchSocket.Core; @@ -117,89 +112,61 @@ public static class SystemExtension /// /// 对于给定的无符号长整型数值,设置指定索引位置的位值为指定的布尔值。 /// - /// 原始数值。 + /// 原始数值。 /// 位索引,范围为0到63。 /// 要设置的位值(true为1,false为0)。 /// 修改后的数值。 /// 当索引值不在有效范围内时抛出异常。 - public static ulong SetBit(this ulong b, int index, bool bitvalue) + public static ulong SetBit(this ulong value, int index, bool bitvalue) { - // 检查索引范围是否有效 - if (index < 0 || index > 63) - { - ThrowHelper.ThrowArgumentOutOfRangeException_BetweenAnd(nameof(index), index, 0, 63); - } - - // 构造一个基数,用于在指定索引位置设置位值 - ulong baseNumber = 1; - // 根据bitvalue的值,设置位 - return bitvalue ? b | baseNumber << index : b & ~(baseNumber << index); + var accessor = new BitAccessor(ref value); + accessor.Set(index, bitvalue); + return value; } /// /// 对于给定的无符号整型数值,设置指定索引位置的位值为指定的布尔值。 /// - /// 原始数值。 + /// 原始数值。 /// 位索引,范围为0到31。 /// 要设置的位值(true为1,false为0)。 /// 修改后的数值。 /// 当索引值不在有效范围内时抛出异常。 - public static uint SetBit(this uint b, int index, bool bitvalue) + public static uint SetBit(this uint value, int index, bool bitvalue) { - // 检查索引范围是否有效 - if (index < 0 || index > 31) - { - ThrowHelper.ThrowArgumentOutOfRangeException_BetweenAnd(nameof(index), index, 0, 31); - } - - // 构造一个基数,用于在指定索引位置设置位值 - uint baseNumber = 1; - // 根据bitvalue的值,设置位 - return bitvalue ? b | baseNumber << index : b & ~(baseNumber << index); + var accessor = new BitAccessor(ref value); + accessor.Set(index, bitvalue); + return value; } /// /// 对于给定的无符号短整型数值,设置指定索引位置的位值为指定的布尔值。 /// - /// 原始数值。 + /// 原始数值。 /// 位索引,范围为0到15。 /// 要设置的位值(true为1,false为0)。 /// 修改后的数值。 /// 当索引值不在有效范围内时抛出异常。 - public static ushort SetBit(this ushort b, int index, bool bitvalue) + public static ushort SetBit(this ushort value, int index, bool bitvalue) { - // 检查索引范围是否有效 - if (index < 0 || index > 15) - { - ThrowHelper.ThrowArgumentOutOfRangeException_BetweenAnd(nameof(index), index, 0, 15); - } - - // 构造一个基数,用于在指定索引位置设置位值 - ushort baseNumber = 1; - // 根据bitvalue的值,设置位 - return bitvalue ? (ushort)(b | baseNumber << index) : (ushort)(b & ~(baseNumber << index)); + var accessor = new BitAccessor(ref value); + accessor.Set(index, bitvalue); + return value; } /// /// 对于给定的无符号字节型数值,设置指定索引位置的位值为指定的布尔值。 /// - /// 原始数值。 + /// 原始数值。 /// 位索引,范围为0到7。 /// 要设置的位值(true为1,false为0)。 /// 修改后的数值。 /// 当索引值不在有效范围内时抛出异常。 - public static byte SetBit(this byte b, int index, bool bitvalue) + public static byte SetBit(this byte value, int index, bool bitvalue) { - // 检查索引范围是否有效 - if (index < 0 || index > 7) - { - ThrowHelper.ThrowArgumentOutOfRangeException_BetweenAnd(nameof(index), index, 0, 7); - } - - // 构造一个基数,用于在指定索引位置设置位值 - byte baseNumber = 1; - // 根据bitvalue的值,设置位 - return bitvalue ? (byte)(b | baseNumber << index) : (byte)(b & ~(baseNumber << index)); + var accessor = new BitAccessor(ref value); + accessor.Set(index, bitvalue); + return value; } #endregion @@ -208,65 +175,53 @@ public static class SystemExtension /// /// 获取无符号长整型数值中的指定位置的位是否为1。 /// - /// 要检查的无符号长整型数值。 + /// 要检查的无符号长整型数值。 /// 要检查的位的位置,从0到63。 /// 如果指定位置的位为1,则返回;否则返回 /// 当索引值不在0到63之间时,抛出此异常。 - public static bool GetBit(this ulong b, int index) + public static bool GetBit(this ulong value, int index) { - if (index > 63 || index < 0) - { - ThrowHelper.ThrowArgumentOutOfRangeException_BetweenAnd(nameof(index), index, 0, 63); - } - return (b & (ulong)1 << index) != 0; + var accessor = new BitAccessor(ref value); + return accessor.Get(index); } /// /// 获取无符号整型数值中的指定位置的位是否为1。 /// - /// 要检查的无符号整型数值。 + /// 要检查的无符号整型数值。 /// 要检查的位的位置,从0到31。 /// 如果指定位置的位为1,则返回;否则返回 /// 当索引值不在0到31之间时,抛出此异常。 - public static bool GetBit(this uint b, int index) + public static bool GetBit(this uint value, int index) { - if (index > 31 || index < 0) - { - ThrowHelper.ThrowArgumentOutOfRangeException_BetweenAnd(nameof(index), index, 0, 31); - } - return (b & (uint)1 << index) != 0; + var accessor = new BitAccessor(ref value); + return accessor.Get(index); } /// /// 获取无符号短整型数值中的指定位置的位是否为1。 /// - /// 要检查的无符号短整型数值。 + /// 要检查的无符号短整型数值。 /// 要检查的位的位置,从0到15。 /// 如果指定位置的位为1,则返回;否则返回 /// 当索引值不在0到15之间时,抛出此异常。 - public static bool GetBit(this ushort b, int index) + public static bool GetBit(this ushort value, int index) { - if (index > 15 || index < 0) - { - ThrowHelper.ThrowArgumentOutOfRangeException_BetweenAnd(nameof(index), index, 0, 15); - } - return (b & 1 << index) != 0; + var accessor = new BitAccessor(ref value); + return accessor.Get(index); } /// /// 获取字节型数值中的指定位置的位是否为1。 /// - /// 要检查的字节型数值。 + /// 要检查的字节型数值。 /// 要检查的位的位置,从0到7。 /// 如果指定位置的位为1,则返回;否则返回 /// 当索引值不在0到7之间时,抛出此异常。 - public static bool GetBit(this byte b, int index) + public static bool GetBit(this byte value, int index) { - if (index > 7 || index < 0) - { - ThrowHelper.ThrowArgumentOutOfRangeException_BetweenAnd(nameof(index), index, 0, 7); - } - return (b & 1 << index) != 0; + var accessor = new BitAccessor(ref value); + return accessor.Get(index); } #endregion @@ -382,7 +337,7 @@ public static class SystemExtension /// /// /// - public static List IndexOfInclude(this ReadOnlySpan srcByteArray, int offset, int length, Span subByteArray) + public static List IndexOfInclude(this ReadOnlySpan srcByteArray, int offset, int length, ReadOnlySpan subByteArray) { var subByteArrayLen = subByteArray.Length; var indexes = new List(); @@ -456,7 +411,7 @@ public static class SystemExtension /// /// /// - public static object GetDefault(this Type type) + public static object GetDefault([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] this Type type) { return type.IsValueType ? Activator.CreateInstance(type) : null; } @@ -576,9 +531,29 @@ public static class SystemExtension /// public static bool IsNullableType(this Type type) { - return (type.IsGenericType && type. - GetGenericTypeDefinition().Equals - (TouchSocketCoreUtility.NullableType)); + return type != null + && type.IsGenericType + && type.GetGenericTypeDefinition() == typeof(Nullable<>); + } + + /// + /// 判断该类型是否为可空类型,并返回实际类型 + /// + /// 要检查的类型 + /// 当类型是可空类型时,返回其实际类型;否则返回原类型 + /// 如果是可空类型返回,否则返回 + public static bool IsNullableType(this Type type, out Type actualType) + { + if (type != null + && type.IsGenericType + && type.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + actualType = type.GetGenericArguments()[0]; + return true; + } + + actualType = type; + return false; } /// @@ -643,20 +618,9 @@ public static class SystemExtension /// public static string GetDeterminantName(this Type type) { - IEnumerable types; - if (type.IsGenericType) - { - types = type.GetGenericArguments(); - } - else if (type.IsArray) - { - types = new Type[] { type.GetElementType() }; - } - else - { - types = []; - } - + var types = type.IsGenericType + ? type.GetGenericArguments() + : type.IsArray ? (new Type[] { type.GetElementType() }) : (IEnumerable)[]; var stringBuilder = new StringBuilder(); stringBuilder.Append(type.Namespace); stringBuilder.Append(type.Name); @@ -671,64 +635,6 @@ public static class SystemExtension #endregion Type - #region Memory - - /// - /// 从指定的 对象中获取内部数组。 - /// - /// 要获取内部数组的内存对象。 - /// 一个表示内存内部数组的 对象。 - /// - /// 此方法通过将 对象转换为 对象, - /// 然后调用 方法来获取内部数组。 - /// - public static ArraySegment GetArray(this Memory memory) - { - return ((ReadOnlyMemory)memory).GetArray(); - } - - /// - /// 从指定的 对象中获取内部数组。 - /// - /// 要获取内部数组的只读内存对象。 - /// 一个表示内存内部数组的 对象。 - /// - /// 此方法尝试通过 方法获取内存的内部数组。 - /// 如果成功,直接返回结果;如果失败(即内存不是由数组支持的),则将内存复制到数组并返回该数组的段。 - /// - public static ArraySegment GetArray(this ReadOnlyMemory memory) - { - return MemoryMarshal.TryGetArray(memory, out var result) ? result : new ArraySegment(memory.ToArray()); - } - - #endregion Memory - - #region EndPoint - - /// - /// 从中获得IP地址。 - /// - /// - /// - public static string GetIP(this EndPoint endPoint) - { - var r = endPoint.ToString().LastIndexOf(":"); - return endPoint.ToString().Substring(0, r); - } - - /// - /// 从中获得Port。 - /// - /// - /// - public static int GetPort(this EndPoint endPoint) - { - var r = endPoint.ToString().LastIndexOf(":"); - return Convert.ToInt32(endPoint.ToString().Substring(r + 1, endPoint.ToString().Length - (r + 1))); - } - - #endregion EndPoint - #region Span /// /// 将字节的连续内存表示形式转换为字符串。 @@ -754,6 +660,16 @@ public static class SystemExtension #endif } + /// + /// 将只读的字节连续内存表示形式按 UTF-8 编码转换为字符串。 + /// + /// 要转换为字符串的只读字节范围。 + /// 转换后的字符串。 + public static string ToUtf8String(this ReadOnlySpan span) + { + return ToString(span, Encoding.UTF8); + } + /// /// 将只读的字节连续内存表示形式转换为字符串。 /// @@ -810,8 +726,104 @@ public static class SystemExtension /// 检查字节是否为 HTTP 规范允许的空白字符(空格或制表符) /// private static bool IsWhitespace(byte b) => b == 0x20 || b == 0x09; + + /// + /// 判断指定的 是否包含指定的字节值。 + /// + /// 要检查的字节范围。 + /// 要查找的字节值。 + /// 如果包含指定字节值,则返回 ;否则返回 + public static bool Contains(this ReadOnlySpan span, byte value) + { + if (span.IsEmpty) + { + return false; + } + + for (var i = 0; i < span.Length; i++) + { + if (span[i] == value) + { + return true; + } + } + return false; + } #endregion + #region Memory + + /// + /// 从指定的 对象中获取内部数组。 + /// + /// 要获取内部数组的内存对象。 + /// 一个表示内存内部数组的 对象。 + /// + /// 此方法通过将 对象转换为 对象, + /// 然后调用 方法来获取内部数组。 + /// + public static ArraySegment GetArray(this Memory memory) + { + return ((ReadOnlyMemory)memory).GetArray(); + } + + /// + /// 从指定的 对象中获取内部数组。 + /// + /// 要获取内部数组的只读内存对象。 + /// 一个表示内存内部数组的 对象。 + /// + /// 此方法尝试通过 方法获取内存的内部数组。 + /// 如果成功,直接返回结果;如果失败(即内存不是由数组支持的),则将内存复制到数组并返回该数组的段。 + /// + public static ArraySegment GetArray(this ReadOnlyMemory memory) + { + return MemoryMarshal.TryGetArray(memory, out var result) ? result : new ArraySegment(memory.ToArray()); + } + + /// + /// 获取 的第一个元素。 + /// + /// 元素类型。 + /// 要获取第一个元素的只读内存。 + /// 第一个元素。 + public static T First(this ReadOnlyMemory memory) + { + if (memory.IsEmpty) + { + ThrowHelper.ThrowArgumentNullException(nameof(memory)); + } + return memory.Span[0]; + } + + #endregion Memory + + #region EndPoint + + /// + /// 从中获得IP地址。 + /// + /// + /// + public static string GetIP(this EndPoint endPoint) + { + var r = endPoint.ToString().LastIndexOf(":"); + return endPoint.ToString().Substring(0, r); + } + + /// + /// 从中获得Port。 + /// + /// + /// + public static int GetPort(this EndPoint endPoint) + { + var r = endPoint.ToString().LastIndexOf(":"); + return Convert.ToInt32(endPoint.ToString().Substring(r + 1, endPoint.ToString().Length - (r + 1))); + } + + #endregion EndPoint + #region DateTime private static readonly DateTime s_utc_time = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); @@ -923,14 +935,14 @@ public static class SystemExtension /// /// 此方法扩展的流对象,表示要写入的流。 /// 只读内存块,其中包含要写入的字节数据。 - /// 用于取消异步写入操作的取消令牌。 + /// 用于取消异步写入操作的取消令牌。 /// /// 此方法利用内存块的 GetArray 方法获取数组段信息,然后使用现有的 WriteAsync 方法异步地将内容写入流中,提高了写入操作的效率和灵活性。 /// - public static async Task WriteAsync(this Stream stream, ReadOnlyMemory memory, CancellationToken token) + public static async ValueTask WriteAsync(this Stream stream, ReadOnlyMemory memory, CancellationToken cancellationToken) { var segment = memory.GetArray(); - await stream.WriteAsync(segment.Array, segment.Offset, segment.Count, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await stream.WriteAsync(segment.Array, segment.Offset, segment.Count, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// @@ -986,11 +998,7 @@ public static class SystemExtension { var buffer = new byte[stream.Length]; var bytesRead = stream.Read(buffer, 0, buffer.Length); - if (bytesRead != buffer.Length) - { - throw new IOException("读取的字节数与流的长度不匹配。"); - } - return buffer; + return bytesRead != buffer.Length ? throw new IOException("读取的字节数与流的长度不匹配。") : buffer; } // 如果流不支持长度属性或位置不在起始位置,使用 MemoryStream 来读取数据 @@ -1013,12 +1021,126 @@ public static class SystemExtension /// public static IEnumerable GetSafeEnumerator(this IEnumerable enumerator) { - if (enumerator is null) + return enumerator is null ? [] : enumerator; + } + #endregion + + #region ReadOnlySequence + + /// + /// 将 按 UTF-8 编码转换为字符串。 + /// + /// 要转换的字节序列。 + /// 转换后的字符串。 + public static string ToUtf8String(this ReadOnlySequence sequence) + { + return ToString(sequence, Encoding.UTF8); + } + + /// + /// 将 按指定编码转换为字符串。 + /// + /// 要转换的字节序列。 + /// 用于解码字节的编码。 + /// 转换后的字符串。 + public static string ToString(this ReadOnlySequence sequence, Encoding encoding) + { +#if NET6_0_OR_GREATER + return encoding.GetString(sequence); +#else + using (var buffer = new ContiguousMemoryBuffer(sequence)) { - return []; + return buffer.Memory.Span.ToString(encoding); + } +#endif + } + + /// + /// 在 中查找第一个与指定 匹配的子序列的起始索引。 + /// 如果未找到则返回 -1。 + /// + /// 要搜索的字节序列。 + /// 要查找的字节子序列。 + /// 匹配子序列的起始索引,未找到则返回 -1。 + public static long IndexOf(this ReadOnlySequence sequence, ReadOnlySpan value) + { + // 处理空值或空序列 + if (value.Length == 0) + { + return 0; } - return enumerator; + if (sequence.Length < value.Length) + { + return -1; + } + + var firstByte = value[0]; + long globalPosition = 0; + var enumerator = sequence.GetEnumerator(); + + // 遍历每个内存段 + while (enumerator.MoveNext()) + { + var currentSpan = enumerator.Current.Span; + var localIndex = 0; + + // 在当前段中搜索首字节 + while (localIndex < currentSpan.Length) + { + // 查找首字节匹配位置 + var matchIndex = currentSpan.Slice(localIndex).IndexOf(firstByte); + if (matchIndex == -1) + { + break; + } + + localIndex += matchIndex; + var globalIndex = globalPosition + localIndex; + + // 检查剩余长度是否足够 + if (sequence.Length - globalIndex < value.Length) + { + return -1; + } + + // 检查完整匹配 + if (IsMatch(sequence, globalIndex, value)) + { + return globalIndex; + } + + localIndex++; // 继续搜索下一个位置 + } + globalPosition += currentSpan.Length; + } + return -1; + } + + private static bool IsMatch(ReadOnlySequence sequence, long start, ReadOnlySpan value) + { + // 切片目标长度的子序列 + var slice = sequence.Slice(start, value.Length); + var valueIndex = 0; + + // 遍历子序列的所有段 + foreach (var segment in slice) + { + var segmentSpan = segment.Span; + for (var i = 0; i < segmentSpan.Length; i++) + { + if (segmentSpan[i] != value[valueIndex++]) + { + return false; + } + + if (valueIndex >= value.Length) + { + return true; // 已匹配所有字节 + } + } + } + return valueIndex == value.Length; } #endregion } \ No newline at end of file diff --git a/src/TouchSocket.Core/Extensions/SystemThreadingExtension.cs b/src/TouchSocket.Core/Extensions/SystemThreadingExtension.cs index 153535ec4..4ac52ca91 100644 --- a/src/TouchSocket.Core/Extensions/SystemThreadingExtension.cs +++ b/src/TouchSocket.Core/Extensions/SystemThreadingExtension.cs @@ -10,10 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; namespace TouchSocket.Core; @@ -53,13 +50,13 @@ public static class SystemThreadingExtension /// /// 要等待的信号量。 /// 等待的超时时间(以毫秒为单位)。 - /// 用于取消操作的取消令牌。 + /// 用于取消操作的取消令牌。 /// /// 如果信号量未在指定的超时时间内释放,则抛出超时异常。 /// - public static void WaitTime(this SemaphoreSlim semaphoreSlim, int millisecondsTimeout, CancellationToken token) + public static void WaitTime(this SemaphoreSlim semaphoreSlim, int millisecondsTimeout, CancellationToken cancellationToken) { - if (!semaphoreSlim.Wait(millisecondsTimeout, token)) + if (!semaphoreSlim.Wait(millisecondsTimeout, cancellationToken)) { ThrowHelper.ThrowTimeoutException(); } @@ -70,14 +67,14 @@ public static class SystemThreadingExtension /// /// 要等待的信号量。 /// 等待的超时时间(以毫秒为单位)。 - /// 用于取消操作的取消令牌。 + /// 用于取消操作的取消令牌。 /// 一个Task对象,表示异步等待操作。 /// /// 如果信号量未在指定的超时时间内释放,则抛出超时异常。 /// - public static async Task WaitTimeAsync(this SemaphoreSlim semaphoreSlim, int millisecondsTimeout, CancellationToken token) + public static async Task WaitTimeAsync(this SemaphoreSlim semaphoreSlim, int millisecondsTimeout, CancellationToken cancellationToken) { - if (!await semaphoreSlim.WaitAsync(millisecondsTimeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) + if (!await semaphoreSlim.WaitAsync(millisecondsTimeout, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) { ThrowHelper.ThrowTimeoutException(); } @@ -87,13 +84,13 @@ public static class SystemThreadingExtension /// 异步等待信号量并返回结果,支持取消令牌。 /// /// 要等待的信号量。 - /// 用于取消操作的取消令牌。 + /// 用于取消操作的取消令牌。 /// 一个 对象,表示操作的结果。 - public static async Task WaitResultAsync(this SemaphoreSlim semaphoreSlim, CancellationToken token) + public static async ValueTask WaitResultAsync(this SemaphoreSlim semaphoreSlim, CancellationToken cancellationToken) { try { - await semaphoreSlim.WaitAsync(token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await semaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return Result.Success; } catch (OperationCanceledException) @@ -182,7 +179,7 @@ public static class SystemThreadingExtension } /// - /// 配置ConfigureAwait为false。 + /// 配置ConfigureAwait为。 /// /// /// @@ -194,7 +191,7 @@ public static class SystemThreadingExtension } /// - /// 配置ConfigureAwait为false。 + /// 配置ConfigureAwait为。 /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -228,7 +225,7 @@ public static class SystemThreadingExtension } /// - /// 同步获取配置ConfigureAwait为false时的结果。 + /// 同步获取配置ConfigureAwait为时的结果。 /// /// /// @@ -240,7 +237,7 @@ public static class SystemThreadingExtension } /// - /// 同步配置ConfigureAwait为false时的执行。 + /// 同步配置ConfigureAwait为时的执行。 /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -332,4 +329,239 @@ public static class SystemThreadingExtension } #endif #endregion Task + + #region CancellationTokenSource + /// + /// 获取 ,如果 为 null,则返回一个已取消的 。 + /// + /// 用于生成 。 + /// 返回一个 + public static CancellationToken GetTokenOrCanceled(this CancellationTokenSource tokenSource) + { + if (tokenSource is null) + { + // 如果 tokenSource 为 null,则返回一个已取消的 CancellationToken + return new CancellationToken(canceled: true); + } + // 如果 tokenSource 不为 null,则返回其 Token 属性 + return tokenSource.Token; + } + + + /// + /// 安全地取消 ,并返回操作结果。 + /// + /// 要取消的 。 + /// 一个 对象,表示操作的结果。 + public static Result SafeCancel(this CancellationTokenSource tokenSource) + { + if (tokenSource is null) + { + return Result.Success; + } + try + { + tokenSource.Cancel(); + return Result.Success; + } + catch (ObjectDisposedException) + { + return Result.Disposed; + } + catch (Exception ex) + { + return Result.FromException(ex); + } + } + #endregion + + #region MyRegion + /// + /// Optimistically performs some value transformation based on some field and tries to apply it back to the field, + /// retrying as many times as necessary until no other thread is manipulating the same field. + /// + /// The type of data. + /// The field that may be manipulated by multiple threads. + /// A function that receives the unchanged value and returns the changed value. + /// + /// if the location's value is changed by applying the result of the function; + /// if the location's value remained the same because the last invocation of returned the existing value. + /// + public static bool ApplyChangeOptimistically(ref T hotLocation, Func applyChange) + where T : class + { + + bool successful; + do + { + var oldValue = Volatile.Read(ref hotLocation); + var newValue = applyChange(oldValue); + if (object.ReferenceEquals(oldValue, newValue)) + { + // No change was actually required. + return false; + } + + var actualOldValue = Interlocked.CompareExchange(ref hotLocation, newValue, oldValue); + successful = object.ReferenceEquals(oldValue, actualOldValue); + } + while (!successful); + + return true; + } + + /// + /// Optimistically performs some value transformation based on some field and tries to apply it back to the field, + /// retrying as many times as necessary until no other thread is manipulating the same field. + /// + /// + /// Use this overload when requires a single item, as is common when updating immutable + /// collection types. By passing the item as a method operand, the caller may be able to avoid allocating a closure + /// object for every call. + /// + /// The type of data to apply the change to. + /// The type of argument passed to the . + /// The field that may be manipulated by multiple threads. + /// An argument to pass to . + /// A function that receives both the unchanged value and , then returns the changed value. + /// + /// if the location's value is changed by applying the result of the function; + /// if the location's value remained the same because the last invocation of returned the existing value. + /// + public static bool ApplyChangeOptimistically(ref T hotLocation, TArg applyChangeArgument, Func applyChange) + where T : class + { + bool successful; + do + { + var oldValue = Volatile.Read(ref hotLocation); + var newValue = applyChange(oldValue, applyChangeArgument); + if (object.ReferenceEquals(oldValue, newValue)) + { + // No change was actually required. + return false; + } + + var actualOldValue = Interlocked.CompareExchange(ref hotLocation, newValue, oldValue); + successful = object.ReferenceEquals(oldValue, actualOldValue); + } + while (!successful); + + return true; + } + + /// + /// Wraps a task with one that will complete as cancelled based on a cancellation cancellationToken, + /// allowing someone to await a task but be able to break out early by cancelling the cancellationToken. + /// + /// The type of value returned by the task. + /// The task to wrap. + /// The cancellationToken that can be canceled to break out of the await. + /// The wrapping task. + public static Task WithCancellation(this Task task, CancellationToken cancellationToken) + { + + if (!cancellationToken.CanBeCanceled || task.IsCompleted) + { + return task; + } + + return cancellationToken.IsCancellationRequested + ? Task.FromCanceled(cancellationToken) + : WithCancellationSlow(task, cancellationToken); + } + + /// + /// Wraps a task with one that will complete as cancelled based on a cancellation cancellationToken, + /// allowing someone to await a task but be able to break out early by cancelling the cancellationToken. + /// + /// The task to wrap. + /// The cancellationToken that can be canceled to break out of the await. + /// The wrapping task. + public static Task WithCancellation(this Task task, CancellationToken cancellationToken) + { + + if (!cancellationToken.CanBeCanceled || task.IsCompleted) + { + return task; + } + + return cancellationToken.IsCancellationRequested + ? Task.FromCanceled(cancellationToken) + : WithCancellationSlow(task, continueOnCapturedContext: false, cancellationToken: cancellationToken); + } + + /// + /// Wraps a task with one that will complete as cancelled based on a cancellation cancellationToken, + /// allowing someone to await a task but be able to break out early by cancelling the cancellationToken. + /// + /// The task to wrap. + /// A value indicating whether *internal* continuations required to respond to cancellation should run on the current . + /// The cancellationToken that can be canceled to break out of the await. + /// The wrapping task. + internal static Task WithCancellation(this Task task, bool continueOnCapturedContext, CancellationToken cancellationToken) + { + if (!cancellationToken.CanBeCanceled || task.IsCompleted) + { + return task; + } + + return cancellationToken.IsCancellationRequested + ? Task.FromCanceled(cancellationToken) + : WithCancellationSlow(task, continueOnCapturedContext, cancellationToken); + } + + + /// + /// Wraps a task with one that will complete as cancelled based on a cancellation cancellationToken, + /// allowing someone to await a task but be able to break out early by cancelling the cancellationToken. + /// + /// The type of value returned by the task. + /// The task to wrap. + /// The cancellationToken that can be canceled to break out of the await. + /// The wrapping task. + private static async Task WithCancellationSlow(Task task, CancellationToken cancellationToken) + { + var tcs = new TaskCompletionSource(); + using (cancellationToken.Register(s => ((TaskCompletionSource)s!).TrySetResult(true), tcs)) + { + if (task != await Task.WhenAny(task, tcs.Task).ConfigureAwait(false)) + { + cancellationToken.ThrowIfCancellationRequested(); + } + } + + // Rethrow any fault/cancellation exception, even if we awaited above. + // But if we skipped the above if branch, this will actually yield + // on an incompleted task. + return await task.ConfigureAwait(false); + } + + /// + /// Wraps a task with one that will complete as cancelled based on a cancellation cancellationToken, + /// allowing someone to await a task but be able to break out early by cancelling the cancellationToken. + /// + /// The task to wrap. + /// A value indicating whether *internal* continuations required to respond to cancellation should run on the current . + /// The cancellationToken that can be canceled to break out of the await. + /// The wrapping task. + private static async Task WithCancellationSlow(this Task task, bool continueOnCapturedContext, CancellationToken cancellationToken) + { + var tcs = new TaskCompletionSource(); + using (cancellationToken.Register(s => ((TaskCompletionSource)s!).TrySetResult(true), tcs)) + { + if (task != await Task.WhenAny(task, tcs.Task).ConfigureAwait(continueOnCapturedContext)) + { + cancellationToken.ThrowIfCancellationRequested(); + } + } + + // Rethrow any fault/cancellation exception, even if we awaited above. + // But if we skipped the above if branch, this will actually yield + // on an incompleted task. + await task.ConfigureAwait(continueOnCapturedContext); + } + + + #endregion } \ No newline at end of file diff --git a/src/TouchSocket.Core/Flow/Counter.cs b/src/TouchSocket.Core/Flow/Counter.cs index d3fcea18a..cd8d4a8b8 100644 --- a/src/TouchSocket.Core/Flow/Counter.cs +++ b/src/TouchSocket.Core/Flow/Counter.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/Flow/FlowGate.cs b/src/TouchSocket.Core/Flow/FlowGate.cs index ec128dabb..beb31445a 100644 --- a/src/TouchSocket.Core/Flow/FlowGate.cs +++ b/src/TouchSocket.Core/Flow/FlowGate.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading; -using System.Threading.Tasks; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/Flow/FlowOperator.cs b/src/TouchSocket.Core/Flow/FlowOperator.cs index 695cac786..e87dd2bfd 100644 --- a/src/TouchSocket.Core/Flow/FlowOperator.cs +++ b/src/TouchSocket.Core/Flow/FlowOperator.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading; -using System.Threading.Tasks; - namespace TouchSocket.Core; /// @@ -79,11 +75,6 @@ public abstract class FlowOperator /// public Result Result { get; protected set; } - /// - /// 超时时间,默认10*1000ms。 - /// - public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(10); - /// /// 可取消令箭 /// diff --git a/src/TouchSocket.Core/Flow/ValueCounter.cs b/src/TouchSocket.Core/Flow/ValueCounter.cs index d32199401..c37784e2b 100644 --- a/src/TouchSocket.Core/Flow/ValueCounter.cs +++ b/src/TouchSocket.Core/Flow/ValueCounter.cs @@ -10,51 +10,78 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading; - namespace TouchSocket.Core; /// -/// 值类型计数器。 +/// 值类型计数器,提供基于时间周期的计数功能。 +/// 支持线程安全的计数操作,可在达到指定周期时触发回调。 /// +/// +/// 该计数器使用原子操作确保线程安全性,适用于高并发场景下的统计需求。 +/// 计数器会跟踪周期内的累计计数和总计数,并在周期结束时自动重置周期计数。 +/// public struct ValueCounter { - /// - /// 周期内的累计计数值。 - /// private long m_count; /// - /// 最后一次递增时间 + /// 最后一次递增时间。 /// private DateTimeOffset m_lastIncrement; + private long m_totalCount; + /// - /// 周期内的累计计数值。 + /// 初始化 结构的新实例。 /// + /// 计数周期时间间隔。 + /// 当达到一个周期时的回调函数,参数为周期内的累计计数值。 + public ValueCounter(TimeSpan period, Action onPeriod = default) : this() + { + this.OnPeriod = onPeriod; + this.Period = period; + } + + /// + /// 获取当前周期内的累计计数值。 + /// + /// 返回当前周期内的计数值,当周期结束时会重置为0。 public readonly long Count => this.m_count; /// - /// 最后一次递增时间 + /// 获取最后一次递增的时间。 /// + /// 返回最后一次调用递增操作的UTC时间。 public readonly DateTimeOffset LastIncrement => this.m_lastIncrement; /// - /// 当达到一个周期时触发。 + /// 获取或设置当达到一个周期时的回调函数。 /// + /// 当周期结束时执行的回调函数,参数为该周期内的累计计数值。如果为 ,则不执行任何操作。 public Action OnPeriod { get; set; } /// - /// 计数周期。 + /// 获取或设置计数周期的时间间隔。 /// + /// 计数器的周期时间间隔,用于判断是否进入新的计数周期。 public TimeSpan Period { get; set; } /// - /// 累计增加计数 + /// 获取从开始到现在的总计数值。 /// - /// 要增加的值 - /// 返回值表示当前递增的是否在一个新的周期内。 + /// 返回计数器的累计总数,包括所有周期的计数值,不会因周期重置而清零。 + public readonly long TotalCount => this.m_totalCount; + + /// + /// 以线程安全的方式增加指定的计数值。 + /// + /// 要增加的计数值。 + /// 如果当前递增在同一周期内,返回 ;如果进入了新的周期,返回 + /// + /// 此方法会检查自上次递增以来是否超过了设定的周期时间。 + /// 如果超过周期时间,会触发 回调并重置当前周期计数。 + /// 所有计数操作都使用原子操作确保线程安全。 + /// public bool Increment(long value) { // 用于判断是否在一个新的周期内 @@ -84,15 +111,19 @@ public struct ValueCounter // 原子性地增加计数器的值 Interlocked.Add(ref this.m_count, value); + Interlocked.Add(ref this.m_totalCount, value); // 返回是否在周期内的标志 return isPeriod; } /// - /// 累计增加一个计数 + /// 以线程安全的方式增加1个计数。 /// - /// 返回是否成功增加计数 + /// 如果当前递增在同一周期内,返回 ;如果进入了新的周期,返回 + /// + /// 这是 方法的便捷重载,等效于调用 Increment(1)。 + /// public bool Increment() { // 调用重载的Increment方法,增量为1 @@ -100,11 +131,17 @@ public struct ValueCounter } /// - /// 重置 + /// 重置计数器的所有状态,包括当前周期计数、总计数和最后递增时间。 /// + /// + /// 此操作会将 重置为0, + /// 并将 重置为默认值。 + /// 重置操作是线程安全的。 + /// public void Reset() { - this.m_count = 0; + Interlocked.Exchange(ref this.m_count, 0); + Interlocked.Exchange(ref this.m_totalCount, 0); this.m_lastIncrement = default; } } \ No newline at end of file diff --git a/src/TouchSocket.Core/IO/BlockSegment.cs b/src/TouchSocket.Core/IO/BlockSegment.cs deleted file mode 100644 index 49e060ded..000000000 --- a/src/TouchSocket.Core/IO/BlockSegment.cs +++ /dev/null @@ -1,376 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; -using System.Threading; -using System.Threading.Tasks; -using System.Threading.Tasks.Sources; - -namespace TouchSocket.Core; - -/// -/// 表示一个块段,用于异步操作中作为值任务的源,提供 类型的结果。 -/// -/// 块段中元素的类型,必须实现 接口。 -public abstract class BlockSegment : DisposableObject, IValueTaskSource - where TBlockResult : IBlockResult -{ - #region 字段 - - private readonly SemaphoreSlim m_resetEventForCompleteRead = new(0, 1); - private CancellationTokenRegistration m_tokenRegistration; - private ManualResetValueTaskSourceCore m_core; - private readonly TBlockResult m_blockResult; - - #endregion 字段 - - /// - /// 初始化 类的新实例。 - /// - /// 指示是否异步运行延续。 - public BlockSegment(bool runContinuationsAsynchronously = false) - { - this.m_core = new ManualResetValueTaskSourceCore() - { - RunContinuationsAsynchronously = runContinuationsAsynchronously - }; - - this.m_blockResult = this.CreateResult(this.CompleteRead); - } - - /// - /// 取消当前操作。 - /// - protected void Cancel() - { - this.m_core.SetException(new OperationCanceledException()); - } - - /// - /// 完成读取操作。 - /// - protected virtual void CompleteRead() - { - this.m_core.Reset(); - this.m_resetEventForCompleteRead.Release(); - } - - /// - /// 创建块段结果。 - /// - /// 用于释放的操作。 - /// 块段结果。 - protected abstract TBlockResult CreateResult(Action actionForDispose); - - /// - protected override void Dispose(bool disposing) - { - if (this.DisposedValue) - { - return; - } - - if (disposing) - { - this.m_resetEventForCompleteRead.SafeDispose(); - } - base.Dispose(disposing); - } - - /// - /// 异步读取块段。 - /// - /// 取消令牌。 - /// 值任务,表示异步读取操作。 - protected ValueTask ProtectedReadAsync(CancellationToken token) - { - this.ThrowIfDisposed(); - token.ThrowIfCancellationRequested(); - - if (this.m_blockResult.IsCompleted) - { - return EasyValueTask.FromResult(this.m_blockResult); - } - - if (token.CanBeCanceled) - { - if (this.m_tokenRegistration == default) - { - this.m_tokenRegistration = token.Register(this.Cancel); - } - else - { - this.m_tokenRegistration.Dispose(); - this.m_tokenRegistration = token.Register(this.Cancel); - } - } - - return new ValueTask(this, this.m_core.Version); - } - - /// - /// 设置异常。 - /// - /// 异常实例。 - protected void SetException(Exception ex) - { - this.m_core.SetException(ex); - } - - /// - /// 触发异步操作。 - /// - /// 表示异步操作的任务。 - protected async Task TriggerAsync() - { - this.m_core.SetResult(this.m_blockResult); - await this.m_resetEventForCompleteRead.WaitAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - - #region IValueTaskSource - - /// - TBlockResult IValueTaskSource.GetResult(short token) - { - return this.m_core.GetResult(token); - } - - /// - ValueTaskSourceStatus IValueTaskSource.GetStatus(short token) - { - return this.m_core.GetStatus(token); - } - - /// - void IValueTaskSource.OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) - { - try - { - this.m_core.OnCompleted(continuation, state, token, flags); - } - catch (Exception ex) - { - // 记录异常并尝试设置异常状态,防止continuation执行异常影响整个应用 - // 这里可以根据需要添加日志记录 - // Logger?.LogError(ex, "Error in OnCompleted continuation execution"); - - // 如果可能,尝试通知异常 - try - { - this.m_core.SetException(ex); - } - catch - { - // 忽略SetException的异常,避免递归异常 - } - } - } - - #endregion IValueTaskSource -} - -///// -///// 表示一个块段,用于异步操作中作为值任务的源,提供 类型的结果。 -///// -///// 块段中元素的类型。 -//public abstract class BlockSegment : DisposableObject, IValueTaskSource> -//{ -// public static readonly IBlockResult Completed = new InternalCompletedBlockResult(); - -// #region 字段 - -// private readonly AsyncAutoResetEvent m_resetEventForCompleteRead = new AsyncAutoResetEvent(false); -// private readonly InternalBlockResult m_result; -// private CancellationTokenRegistration m_tokenRegistration; -// private ManualResetValueTaskSourceCore> m_valueTaskSourceCore; - -// #endregion 字段 - -// /// -// /// 初始化BlockSegment类的新实例。 -// /// -// public BlockSegment(bool runContinuationsAsynchronously = false) -// { -// m_valueTaskSourceCore = new ManualResetValueTaskSourceCore>() -// { -// RunContinuationsAsynchronously = runContinuationsAsynchronously -// }; -// // 初始化块结果,与CompleteRead方法关联 -// this.m_result = new InternalBlockResult(this.CompleteRead); -// } - -// /// -// /// 获取当前块段的结果。 -// /// -// protected IBlockResult BlockResult => this.m_result; - -// public static IBlockResult FromResult(T result) -// { -// return new InternalBlockResult(() => { }) { IsCompleted = true }; -// } - -// protected void Cancel() -// { -// this.m_valueTaskSourceCore.SetException(new OperationCanceledException()); -// } - -// /// -// protected override void Dispose(bool disposing) -// { -// if (this.DisposedValue) -// { -// return; -// } - -// if (disposing) -// { -// this.m_resetEventForCompleteRead.Set(); -// this.m_resetEventForCompleteRead.SafeDispose(); -// } -// base.Dispose(disposing); -// } - -// protected async Task InputAsync(T result) -// { -// // 设置结果中的内存数据 -// this.m_result.Result = result; - -// this.m_valueTaskSourceCore.SetResult(this.m_result); -// // 等待读取完成 -// await this.m_resetEventForCompleteRead.WaitOneAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); -// } - -// protected async Task ProtectedComplete(string msg) -// { -// try -// { -// this.m_result.IsCompleted = true; -// this.m_result.Message = msg; -// await this.InputAsync(default).ConfigureAwait(EasyTask.ContinueOnCapturedContext); -// return Result.Success; -// } -// catch (Exception ex) -// { -// return Result.FromException(ex); -// } -// } - -// /// -// /// 重置块段的状态,为下一次使用做准备。 -// /// -// protected virtual void Reset() -// { -// // 重置等待读取完成的事件 -// this.m_resetEventForCompleteRead.Reset(); -// // 将块结果标记为未完成 -// this.m_result.IsCompleted = false; -// // 清除结果中的内存数据 -// this.m_result.Result = default; -// // 清除结果中的消息 -// this.m_result.Message = default; -// this.m_valueTaskSourceCore.Reset(); -// } - -// /// -// /// 值等待异步操作。 -// /// -// /// 取消令牌。 -// /// 值任务。 -// protected ValueTask> ValueWaitAsync(CancellationToken token) -// { -// this.ThrowIfDisposed(); -// token.ThrowIfCancellationRequested(); - -// if (this.m_result.IsCompleted) -// { -// return EasyValueTask.FromResult>(this.m_result); -// } - -// if (token.CanBeCanceled) -// { -// if (this.m_tokenRegistration == default) -// { -// this.m_tokenRegistration = token.Register(this.Cancel); -// } -// else -// { -// this.m_tokenRegistration.Dispose(); -// this.m_tokenRegistration = token.Register(this.Cancel); -// } -// } -// this.Reset(); -// return new ValueTask>(this, this.m_valueTaskSourceCore.Version); -// } - -// private void CompleteRead() -// { -// this.m_resetEventForCompleteRead.Set(); -// } - -// #region IValueTaskSource - -// IBlockResult IValueTaskSource>.GetResult(short token) -// { -// return this.m_valueTaskSourceCore.GetResult(token); -// } - -// ValueTaskSourceStatus IValueTaskSource>.GetStatus(short token) -// { -// return this.m_valueTaskSourceCore.GetStatus(token); -// } - -// void IValueTaskSource>.OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) -// { -// this.m_valueTaskSourceCore.OnCompleted(continuation, state, token, flags); -// } - -// #endregion IValueTaskSource - -// #region Class - -// internal sealed class InternalBlockResult : IBlockResult -// { -// private readonly Action m_disAction; - -// /// -// /// ReceiverResult -// /// -// /// -// public InternalBlockResult(Action disAction) -// { -// this.m_disAction = disAction; -// } - -// public bool IsCompleted { get; set; } -// public string Message { get; set; } - -// public T Result { get; set; } - -// public void Dispose() -// { -// this.m_disAction.Invoke(); -// } -// } - -// private sealed class InternalCompletedBlockResult : IBlockResult -// { -// public bool IsCompleted => true; -// public string Message => string.Empty; -// public T Result => default; - -// public void Dispose() -// { -// } -// } - -// #endregion Class -//} \ No newline at end of file diff --git a/src/TouchSocket.Core/IO/ConsoleAction.cs b/src/TouchSocket.Core/IO/ConsoleAction.cs index 9e36f843a..fc3499a6e 100644 --- a/src/TouchSocket.Core/IO/ConsoleAction.cs +++ b/src/TouchSocket.Core/IO/ConsoleAction.cs @@ -10,44 +10,14 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - namespace TouchSocket.Core; -/// -/// 控制台行为 -/// - -internal readonly struct VAction -{ - /// - /// 构造函数 - /// - /// - /// - /// - public VAction(string description, string fullOrder, Func action) - { - this.FullOrder = fullOrder; - this.Action = action ?? throw new ArgumentNullException(nameof(action)); - this.Description = description ?? throw new ArgumentNullException(nameof(description)); - } - - public Func Action { get; } - - public string Description { get; } - public string FullOrder { get; } -} - /// /// 控制台行为 /// public sealed class ConsoleAction { - private readonly Dictionary m_actions = new Dictionary(); + private readonly Dictionary m_actions = new Dictionary(); /// /// 构造函数 @@ -59,24 +29,7 @@ public sealed class ConsoleAction this.Add(helpOrder, "帮助信息", this.ShowAll); - var title = $@" - - _______ _ _____ _ _ - |__ __| | | / ____| | | | | - | | ___ _ _ ___ | |__ | (___ ___ ___ | | __ ___ | |_ - | | / _ \ | | | | / __|| '_ \ \___ \ / _ \ / __|| |/ // _ \| __| - | || (_) || |_| || (__ | | | | ____) || (_) || (__ | <| __/| |_ - |_| \___/ \__,_| \___||_| |_||_____/ \___/ \___||_|\_\\___| \__| - - ------------------------------------------------------------------- - Author : 若汝棋茗 - Version : {System.Reflection.Assembly.GetExecutingAssembly().GetName().Version} - Gitee : https://gitee.com/rrqm_home - Github : https://github.com/rrqm - API : https://touchsocket.net/ - ------------------------------------------------------------------- -"; - Console.WriteLine(title); + this.ShowBanner(); } /// @@ -87,12 +40,17 @@ public sealed class ConsoleAction /// /// 帮助信息指令 /// - public string HelpOrder { get; private set; } + public string HelpOrder { get; } + + /// + /// 所有命令信息 + /// + public IReadOnlyList AllActionInfos => this.m_actions.Values.Where(a => a.FullOrder != this.HelpOrder).ToList(); /// /// 添加 /// - /// 指令,多个指令用“|”分割 + /// 指令,多个指令用"|"分割 /// 描述 /// public void Add(string order, string description, Action action) @@ -108,7 +66,7 @@ public sealed class ConsoleAction /// /// 添加 /// - /// 指令,多个指令用“|”分割 + /// 指令,多个指令用"|"分割 /// 描述 /// public void Add(string order, string description, Func action) @@ -116,7 +74,7 @@ public sealed class ConsoleAction var orders = order.ToLower().Split('|'); foreach (var item in orders) { - this.m_actions.Add(item, new VAction(description, order, action)); + this.m_actions.Add(item, new ConsoleActionInfo(description, order, action)); } } @@ -135,6 +93,7 @@ public sealed class ConsoleAction } catch (Exception ex) { + this.WriteError($"执行命令时发生错误: {ex.Message}"); OnException?.Invoke(ex); } return true; @@ -152,10 +111,16 @@ public sealed class ConsoleAction { while (true) { + this.WritePrompt("请输入命令: "); var str = Console.ReadLine(); + if (string.IsNullOrWhiteSpace(str)) + { + continue; + } + if (!await this.RunAsync(str).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) { - Console.WriteLine($"没有这个指令。"); + this.WriteWarning($"没有找到命令 '{str}',输入 '{this.HelpOrder.Split('|')[0]}' 查看帮助信息。"); } } } @@ -165,21 +130,162 @@ public sealed class ConsoleAction /// public void ShowAll() { - var max = this.m_actions.Values.Max(a => a.FullOrder.Length) + 8; + this.WriteTitle("\n可用命令列表:"); + this.WriteLine(); - var s = new List(); - foreach (var item in this.m_actions) + var maxOrderLength = this.m_actions.Values.Max(a => a.FullOrder.Length); + var separator = new string('─', maxOrderLength + 4); + + var distinctActions = new List(); + foreach (var item in this.m_actions.OrderBy(a => a.Value.FullOrder)) { - if (!s.Contains(item.Value.FullOrder.ToLower())) + if (!distinctActions.Contains(item.Value.FullOrder.ToLower())) { - s.Add(item.Value.FullOrder.ToLower()); - Console.Write($"[{item.Value.FullOrder}]"); - for (var i = 0; i < max - item.Value.FullOrder.Length; i++) - { - Console.Write("-"); - } - Console.WriteLine(item.Value.Description); + distinctActions.Add(item.Value.FullOrder.ToLower()); + + this.WriteCommand($" [{item.Value.FullOrder}]"); + var padding = maxOrderLength - item.Value.FullOrder.Length + 2; + Console.Write(new string(' ', padding)); + this.WriteDescription(item.Value.Description); + this.WriteLine(); } } + + this.WriteLine(); + } + + /// + /// 显示Banner + /// + private void ShowBanner() + { + var originalColor = Console.ForegroundColor; + + Console.ForegroundColor = ConsoleColor.Cyan; + var title = @" + + _______ _ _____ _ _ + |__ __| | | / ____| | | | | + | | ___ _ _ ___ | |__ | (___ ___ ___ | | __ ___ | |_ + | | / _ \ | | | | / __|| '_ \ \___ \ / _ \ / __|| |/ // _ \| __| + | || (_) || |_| || (__ | | | | ____) || (_) || (__ | <| __/| |_ + |_| \___/ \__,_| \___||_| |_||_____/ \___/ \___||_|\_\\___| \__| + +"; + Console.WriteLine(title); + + Console.ForegroundColor = ConsoleColor.Gray; + Console.WriteLine(" " + new string('─', 67)); + + this.WriteInfo(" Author : "); + Console.ForegroundColor = ConsoleColor.White; + Console.WriteLine("若汝棋茗"); + + this.WriteInfo(" Version : "); + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version); + + this.WriteInfo(" Gitee : "); + Console.ForegroundColor = ConsoleColor.Blue; + Console.WriteLine("https://gitee.com/rrqm_home"); + + this.WriteInfo(" Github : "); + Console.ForegroundColor = ConsoleColor.Blue; + Console.WriteLine("https://github.com/rrqm"); + + this.WriteInfo(" API : "); + Console.ForegroundColor = ConsoleColor.Blue; + Console.WriteLine("https://touchsocket.net/"); + + Console.ForegroundColor = ConsoleColor.Gray; + Console.WriteLine(" " + new string('─', 67)); + + Console.ForegroundColor = originalColor; + Console.WriteLine(); + } + + /// + /// 写入标题 + /// + private void WriteTitle(string text) + { + var originalColor = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine(text); + Console.ForegroundColor = originalColor; + } + + /// + /// 写入命令 + /// + private void WriteCommand(string text) + { + var originalColor = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Green; + Console.Write(text); + Console.ForegroundColor = originalColor; + } + + /// + /// 写入描述 + /// + private void WriteDescription(string text) + { + var originalColor = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Gray; + Console.Write(text); + Console.ForegroundColor = originalColor; + } + + /// + /// 写入提示 + /// + private void WritePrompt(string text) + { + var originalColor = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Cyan; + Console.Write(text); + Console.ForegroundColor = originalColor; + } + + /// + /// 写入警告 + /// + private void WriteWarning(string text) + { + var originalColor = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine(text); + Console.ForegroundColor = originalColor; + } + + /// + /// 写入错误 + /// + private void WriteError(string text) + { + var originalColor = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(text); + Console.ForegroundColor = originalColor; + } + + /// + /// 写入信息 + /// + private void WriteInfo(string text) + { + var originalColor = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.DarkGray; + Console.Write(text); + Console.ForegroundColor = originalColor; + } + + /// + /// 写入空行 + /// + private void WriteLine() + { + Console.WriteLine(); } } \ No newline at end of file diff --git a/src/TouchSocket.Http/Common/HttpProxy.cs b/src/TouchSocket.Core/IO/ConsoleActionInfo.cs similarity index 51% rename from src/TouchSocket.Http/Common/HttpProxy.cs rename to src/TouchSocket.Core/IO/ConsoleActionInfo.cs index ed92227f5..2446bfea5 100644 --- a/src/TouchSocket.Http/Common/HttpProxy.cs +++ b/src/TouchSocket.Core/IO/ConsoleActionInfo.cs @@ -10,45 +10,38 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using TouchSocket.Sockets; - -namespace TouchSocket.Http; +namespace TouchSocket.Core; /// -/// Http代理 +/// 控制台行为信息。 /// -[Obsolete("此配置已被弃用,不再支持代理", true)] -public class HttpProxy +public readonly struct ConsoleActionInfo { /// - /// 不带基本验证的代理 + /// 初始化结构体。 /// - /// - public HttpProxy(IPHost host) + /// 行为描述。 + /// 完整命令。 + /// 执行的动作。 + public ConsoleActionInfo(string description, string fullOrder, Func action) { - this.Host = host; + this.FullOrder = fullOrder; + this.Action = action ?? throw new ArgumentNullException(nameof(action)); + this.Description = description ?? throw new ArgumentNullException(nameof(description)); } /// - /// 带基本验证的代理 + /// 获取控制台行为对应的动作。 /// - /// - /// - /// - public HttpProxy(IPHost host, string userName, string passWord) - { - this.Host = host; - this.Credential = new NetworkCredential(userName, passWord, $"{host.Authority}"); - } + public Func Action { get; } /// - /// 验证代理 + /// 获取控制台行为描述。 /// - public NetworkCredential Credential { get; set; } + public string Description { get; } /// - /// 代理的地址 + /// 获取完整命令。 /// - public IPHost Host { get; set; } -} \ No newline at end of file + public string FullOrder { get; } +} diff --git a/src/TouchSocket.Core/IO/DirectoryUtility.cs b/src/TouchSocket.Core/IO/DirectoryUtility.cs index 9d5cfd84e..b43462c26 100644 --- a/src/TouchSocket.Core/IO/DirectoryUtility.cs +++ b/src/TouchSocket.Core/IO/DirectoryUtility.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.IO; -using System.Linq; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/IO/FileIO/FilePool.cs b/src/TouchSocket.Core/IO/FileIO/FilePool.cs index e7c8924dc..526f1ee98 100644 --- a/src/TouchSocket.Core/IO/FileIO/FilePool.cs +++ b/src/TouchSocket.Core/IO/FileIO/FilePool.cs @@ -10,12 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Threading; -using System.Threading.Tasks; using TouchSocket.Resources; namespace TouchSocket.Core; @@ -226,7 +221,7 @@ public static partial class FilePool private static void DelayRunReleaseFile(string path, int time) { - Task.Run(async () => + _ = EasyTask.SafeRun(async () => { await Task.Delay(time).ConfigureAwait(EasyTask.ContinueOnCapturedContext); if (GetReferenceCount(path) == 0) diff --git a/src/TouchSocket.Core/IO/FileIO/FileStorage.cs b/src/TouchSocket.Core/IO/FileIO/FileStorage.cs index 3ed9d1a53..d589b77cf 100644 --- a/src/TouchSocket.Core/IO/FileIO/FileStorage.cs +++ b/src/TouchSocket.Core/IO/FileIO/FileStorage.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.IO; -using System.Threading; using TouchSocket.Resources; namespace TouchSocket.Core; diff --git a/src/TouchSocket.Core/IO/FileIO/FileStorageReader.cs b/src/TouchSocket.Core/IO/FileIO/FileStorageReader.cs index 94cf6a3d1..3b2fb8c60 100644 --- a/src/TouchSocket.Core/IO/FileIO/FileStorageReader.cs +++ b/src/TouchSocket.Core/IO/FileIO/FileStorageReader.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading; - namespace TouchSocket.Core; /// @@ -33,9 +30,8 @@ public partial class FileStorageReader : SafetyDisposableObject /// public FileStorageReader(FileStorage fileStorage) { - // 使用ThrowHelper提供的扩展方法验证fileStorage参数是否为, - // 并将fileStorage赋值给类成员变量FileStorage。 - this.FileStorage = ThrowHelper.ThrowArgumentNullExceptionIf(fileStorage, nameof(fileStorage)); + ThrowHelper.ThrowIfNull(fileStorage, nameof(fileStorage)); + this.FileStorage = fileStorage; } /// diff --git a/src/TouchSocket.Core/IO/FileIO/FileStorageStream.cs b/src/TouchSocket.Core/IO/FileIO/FileStorageStream.cs index 9f8812fa0..9ceb4805b 100644 --- a/src/TouchSocket.Core/IO/FileIO/FileStorageStream.cs +++ b/src/TouchSocket.Core/IO/FileIO/FileStorageStream.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.IO; -using System.Threading; - namespace TouchSocket.Core; /// @@ -29,7 +26,8 @@ public partial class FileStorageStream : Stream /// public FileStorageStream(FileStorage fileStorage) { - this.FileStorage = ThrowHelper.ThrowArgumentNullExceptionIf(fileStorage, nameof(fileStorage)); + ThrowHelper.ThrowIfNull(fileStorage, nameof(fileStorage)); + this.FileStorage = fileStorage; } /// diff --git a/src/TouchSocket.Core/IO/FileIO/FileStorageWriter.cs b/src/TouchSocket.Core/IO/FileIO/FileStorageWriter.cs index 667c1ee16..3aa147253 100644 --- a/src/TouchSocket.Core/IO/FileIO/FileStorageWriter.cs +++ b/src/TouchSocket.Core/IO/FileIO/FileStorageWriter.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading; - namespace TouchSocket.Core; /// @@ -29,8 +26,8 @@ public partial class FileStorageWriter : SafetyDisposableObject /// 文件存储服务的实例,用于后续的文件写入操作。 public FileStorageWriter(FileStorage fileStorage) { - // 当fileStorage为时,抛出带有参数名称的ArgumentException,确保fileStorage参数不是null - this.FileStorage = ThrowHelper.ThrowArgumentNullExceptionIf(fileStorage, nameof(fileStorage)); + ThrowHelper.ThrowIfNull(fileStorage, nameof(fileStorage)); + this.FileStorage = fileStorage; } /// diff --git a/src/TouchSocket.Core/IO/FileUtility.cs b/src/TouchSocket.Core/IO/FileUtility.cs index 21989fb8b..5f89ba16e 100644 --- a/src/TouchSocket.Core/IO/FileUtility.cs +++ b/src/TouchSocket.Core/IO/FileUtility.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.IO; -using System.Linq; using System.Security.Cryptography; namespace TouchSocket.Core; @@ -307,10 +304,6 @@ public static partial class FileUtility { return $"{(length / gb):F2}Gb"; } - if (length < pb) - { - return $"{(length / tb):F2}Tb"; - } - return $"{(length / pb):F2}Pb"; + return length < pb ? $"{(length / tb):F2}Tb" : $"{(length / pb):F2}Pb"; } } \ No newline at end of file diff --git a/src/TouchSocket.Core/IO/IBlockResult.cs b/src/TouchSocket.Core/IO/IBlockResult.cs index 5d0f1a955..d4217c831 100644 --- a/src/TouchSocket.Core/IO/IBlockResult.cs +++ b/src/TouchSocket.Core/IO/IBlockResult.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/IO/IReadOnlyMemoryBlockResult.cs b/src/TouchSocket.Core/IO/IReadOnlyMemoryBlockResult.cs index 08edf1b1f..6a85a2c4c 100644 --- a/src/TouchSocket.Core/IO/IReadOnlyMemoryBlockResult.cs +++ b/src/TouchSocket.Core/IO/IReadOnlyMemoryBlockResult.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 // ------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// /// 表示一个只读内存块的结果。 diff --git a/src/TouchSocket.Core/IO/ReadLease.cs b/src/TouchSocket.Core/IO/ReadLease.cs new file mode 100644 index 000000000..efc562782 --- /dev/null +++ b/src/TouchSocket.Core/IO/ReadLease.cs @@ -0,0 +1,69 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +namespace TouchSocket.Core; + +/// +/// 表示读取操作的租约结构体。 +/// +/// 租约中包含的值的类型。 +/// +/// ReadLease是一个只读结构体,用于管理读取操作的生命周期和资源释放。 +/// 当调用方法时表示消费完成,会触发相应的清理操作。 +/// 此结构体实现了接口以支持资源管理模式。 +/// +public readonly struct ReadLease : IDisposable +{ + private readonly Action m_owner; + + internal ReadLease(Action disposeAction, T value, bool isCompleted) + { + this.m_owner = disposeAction; + this.Value = value; + this.IsCompleted = isCompleted; + } + + /// + /// 获取一个值,指示读取操作是否已完成。 + /// + /// 如果读取操作已完成,则为;否则为 + /// + /// 当此属性为时,表示读取操作已成功完成,无需额外的清理操作。 + /// 当此属性为时,调用方法会触发清理操作。 + /// + public bool IsCompleted { get; } + + /// + /// 获取租约中包含的值。 + /// + /// 租约中包含的类型为的值。 + /// + /// 此属性包含读取操作的结果值。该值在租约的生命周期内保持有效。 + /// + public T Value { get; } + + /// + /// 释放租约相关的资源。 + /// + /// + /// 此方法表示消费完成。如果读取操作未完成且存在释放操作, + /// 则会调用构造时指定的释放回调来执行清理工作。 + /// 如果,则不会执行任何操作。 + /// + public void Dispose() + { + if (!this.IsCompleted && this.m_owner != null) + { + this.m_owner.Invoke(); + } + } +} diff --git a/src/TouchSocket.Core/IO/WrapStream.cs b/src/TouchSocket.Core/IO/WrapStream.cs index 3bfe0387d..8f27372d3 100644 --- a/src/TouchSocket.Core/IO/WrapStream.cs +++ b/src/TouchSocket.Core/IO/WrapStream.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.IO; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/Logger/ConsoleLogger.cs b/src/TouchSocket.Core/Logger/ConsoleLogger.cs index 4f2c7b239..99051eece 100644 --- a/src/TouchSocket.Core/Logger/ConsoleLogger.cs +++ b/src/TouchSocket.Core/Logger/ConsoleLogger.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/Logger/EasyLogger.cs b/src/TouchSocket.Core/Logger/EasyLogger.cs index 24b43c2c9..e75a417de 100644 --- a/src/TouchSocket.Core/Logger/EasyLogger.cs +++ b/src/TouchSocket.Core/Logger/EasyLogger.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/Logger/FileLogger.cs b/src/TouchSocket.Core/Logger/FileLogger.cs index 09c883c22..1c0227499 100644 --- a/src/TouchSocket.Core/Logger/FileLogger.cs +++ b/src/TouchSocket.Core/Logger/FileLogger.cs @@ -10,10 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Collections.Concurrent; -using System.IO; -using System.Text; namespace TouchSocket.Core; @@ -69,7 +66,11 @@ public sealed class FileLogger : LoggerBase, IDisposable public Func CreateLogFolder { get => this.m_createLogFolder; - set => this.m_createLogFolder = ThrowHelper.ThrowArgumentNullExceptionIf(value, nameof(this.CreateLogFolder)); + set + { + ThrowHelper.ThrowIfNull(value, nameof(this.CreateLogFolder)); + this.m_createLogFolder = value; + } } /// @@ -139,14 +140,7 @@ public sealed class FileLogger : LoggerBase, IDisposable writer.SeekToEnd(); writer.FileStorage.AccessTimeout = TimeSpan.MaxValue; - if (this.m_writers.TryAdd(dirPath, writer)) - { - return writer; - } - else - { - return this.GetFileStorageWriter(dirPath); - } + return this.m_writers.TryAdd(dirPath, writer) ? writer : this.GetFileStorageWriter(dirPath); } count++; } @@ -157,15 +151,15 @@ public sealed class FileLogger : LoggerBase, IDisposable { var dirPath = this.CreateLogFolder(logLevel); - var writer = this.GetFileStorageWriter(dirPath); + var writer1 = this.GetFileStorageWriter(dirPath); - lock (writer) + lock (writer1) { try { - writer.Write(Encoding.UTF8.GetBytes(logString)); - writer.FileStorage.Flush(); - if (writer.FileStorage.Length > this.MaxSize) + writer1.Write((Encoding.UTF8.GetBytes(logString))); + writer1.FileStorage.Flush(); + if (writer1.FileStorage.Length > this.MaxSize) { if (this.m_writers.TryRemove(dirPath, out var fileStorageWriter)) { diff --git a/src/TouchSocket.Core/Logger/ILog.cs b/src/TouchSocket.Core/Logger/ILog.cs index 584db6587..02dc2131e 100644 --- a/src/TouchSocket.Core/Logger/ILog.cs +++ b/src/TouchSocket.Core/Logger/ILog.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/Logger/LoggerBase.cs b/src/TouchSocket.Core/Logger/LoggerBase.cs index b45e80d41..07b965c27 100644 --- a/src/TouchSocket.Core/Logger/LoggerBase.cs +++ b/src/TouchSocket.Core/Logger/LoggerBase.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Text; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/Logger/LoggerContainerExtension.cs b/src/TouchSocket.Core/Logger/LoggerContainerExtension.cs index 1c38e4c06..ec0aacea7 100644 --- a/src/TouchSocket.Core/Logger/LoggerContainerExtension.cs +++ b/src/TouchSocket.Core/Logger/LoggerContainerExtension.cs @@ -10,8 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.IO; +using System.Diagnostics.CodeAnalysis; namespace TouchSocket.Core; @@ -208,10 +207,10 @@ public static class LoggerContainerExtension /// 要添加日志单例的注册器。 /// 要添加到容器的日志对象。 /// 返回更新后的注册器对象,以便进行链式调用。 - public static IRegistrator AddLogger(this IRegistrator registrator, ILog logger) + public static IRegistrator AddLogger<[DynamicallyAccessedMembers(AOT.Container)] TLog>(this IRegistrator registrator, TLog logger) where TLog : class, ILog { // 将日志对象作为单例注册到容器中 - registrator.RegisterSingleton(logger); + registrator.RegisterSingleton(logger); // 返回注册器对象,支持链式调用 return registrator; } @@ -229,7 +228,7 @@ public static class LoggerContainerExtension // 调用传入的委托来配置LoggerGroup loggerAction.Invoke(loggerGroup); // 将配置好的LoggerGroup注册为单例 - registrator.RegisterSingleton(loggerGroup); + registrator.RegisterSingleton(loggerGroup); // 返回注册器接口 return registrator; } diff --git a/src/TouchSocket.Core/Logger/LoggerExtensions.cs b/src/TouchSocket.Core/Logger/LoggerExtensions.cs index 942f34cd4..06e341107 100644 --- a/src/TouchSocket.Core/Logger/LoggerExtensions.cs +++ b/src/TouchSocket.Core/Logger/LoggerExtensions.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/Logger/LoggerGroup.cs b/src/TouchSocket.Core/Logger/LoggerGroup.cs index df1109ea9..c1b81bd05 100644 --- a/src/TouchSocket.Core/Logger/LoggerGroup.cs +++ b/src/TouchSocket.Core/Logger/LoggerGroup.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; - namespace TouchSocket.Core; /// @@ -28,7 +25,7 @@ public class LoggerGroup : LoggerBase /// public LoggerGroup(params ILog[] logs) : this() { - ThrowHelper.ThrowArgumentNullExceptionIf(logs, nameof(logs)); + ThrowHelper.ThrowIfNull(logs, nameof(logs)); foreach (var log in logs) { this.AddLogger(log); diff --git a/src/TouchSocket.Core/Mapper/Mapper.cs b/src/TouchSocket.Core/Mapper/Mapper.cs index 35e5e516f..a35179a2d 100644 --- a/src/TouchSocket.Core/Mapper/Mapper.cs +++ b/src/TouchSocket.Core/Mapper/Mapper.cs @@ -1,18 +1,17 @@ //------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +//交流QQ群:234762506 +// 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq.Expressions; using System.Reflection; namespace TouchSocket.Core; @@ -20,191 +19,236 @@ namespace TouchSocket.Core; /// /// 映射数据 /// - -public static partial class Mapper +public static class Mapper { - private static readonly ConcurrentDictionary> m_typeToProperty = new ConcurrentDictionary>(); - + private const DynamicallyAccessedMemberTypes RequiredMemberTypes = + DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.PublicFields; /// - /// 将源对象映射到指定目标类型的新实例。 + /// 泛型静态缓存类 - 每个类型组合都有独立的静态字段 + /// 这样完全避免了字典和键的概念 /// - /// 源对象,其属性将被映射到目标类型。 - /// 映射选项,用于自定义映射行为。 - /// 要映射到的目标类型。 - /// 一个新创建的目标类型实例,其属性根据源对象的属性值进行映射。 - public static TTarget Map(this object source, MapperOption option = default) where TTarget : class, new() + private static class MapperCache<[DynamicallyAccessedMembers(RequiredMemberTypes)] TIn, + [DynamicallyAccessedMembers(RequiredMemberTypes)] TOut> + where TIn : class + where TOut : class, new() { - // 调用泛型方法 Map,将源对象、目标类型和映射选项传递给它 - // 由于目标类型的实例化和类型转换由 Map 方法内部处理,这里直接返回转换后的结果 - return (TTarget)Map(source, typeof(TTarget), option); - } + // 每个类型组合都有一个独立的静态字段,JIT会为每个组合生成独立的代码 + public static readonly Func Mapper = CreateMapper(); + // 配置映射缓存 - 使用配置哈希作为键 + private static readonly Dictionary> ConfiguredMappers = new(); - /// - /// 扩展方法,用于将对象映射到相同类型的另一个对象。 - /// - /// 要映射的源对象。 - /// 映射选项,用于定制映射行为。 - /// 源对象和目标对象的类型。 - /// 返回映射后的目标对象。 - public static TTarget Map(this TTarget source, MapperOption option = default) where TTarget : class, new() - { - // 调用泛型方法 Map,将源对象映射为目标对象 - return (TTarget)Map(source, typeof(TTarget), option); - } - - - /// - /// 扩展方法,用于将一个对象映射到另一个类型。 - /// - /// 源对象的类型。 - /// 目标对象的类型,必须是引用类型且有默认构造函数。 - /// 要映射的源对象。 - /// 映射选项,用于控制映射行为。 - /// 返回映射后的目标对象实例。 - public static TTarget Map(this TSource source, MapperOption option = default) where TTarget : class, new() - { - // 调用泛型映射方法,将源对象、目标类型和映射选项传递给它 - // 由于目标类型在运行时才能确定,这里使用反射来动态调用合适的映射方法 - return (TTarget)Map(source, typeof(TTarget), option); - } - - - /// - /// 将源对象映射到目标类型的实例。 - /// - /// 要映射的源对象。 - /// 目标类型的 。 - /// 映射选项,用于控制映射行为。 - /// 映射后的目标类型实例。 - public static object Map(this object source, Type targetType, MapperOption option = default) - { - // 使用 Activator.CreateInstance 创建目标类型的实例,并将源对象映射到该实例 - return Map(source, Activator.CreateInstance(targetType), option); - } - - - /// - /// 将源对象的属性映射到目标对象的属性中。 - /// - /// 源对象,其属性将被映射。 - /// 目标对象,将接收映射的属性值。 - /// 映射选项,用于定制映射行为。 - /// 返回映射后的目标对象。 - public static object Map(this object source, object target, MapperOption option = default) - { - if (source is null) + public static Func GetMapper(MappingConfig config = null) { - return default; - } - var sourceType = source.GetType(); - if (sourceType.IsPrimitive || sourceType.IsEnum || sourceType == TouchSocketCoreUtility.StringType) - { - return source; - } - var sourcePairs = m_typeToProperty.GetOrAdd(sourceType, (k) => - { - var pairs = new Dictionary(); - var ps = k.GetProperties(BindingFlags.Default | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); - foreach (var item in ps) - { - pairs.Add(item.Name, new Property(item)); - } - return pairs; - }); - - var targetPairs = m_typeToProperty.GetOrAdd(target.GetType(), (k) => - { - var pairs = new Dictionary(); - var ps = k.GetProperties(BindingFlags.Default | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); - foreach (var item in ps) + if (config == null) { - pairs.Add(item.Name, new Property(item)); + return Mapper; } - return pairs; - }); - foreach (var item in sourcePairs) - { - if (item.Value.CanRead) + var configHash = GetConfigHash(config); + if (ConfiguredMappers.TryGetValue(configHash, out var cachedMapper)) { - var pkey = item.Key; - if (option != null && option.MapperProperties != null && option.MapperProperties.ContainsKey(pkey)) + return cachedMapper; + } + + lock (ConfiguredMappers) + { + if (ConfiguredMappers.TryGetValue(configHash, out cachedMapper)) { - pkey = option.MapperProperties[pkey]; + return cachedMapper; } - if (option?.IgnoreProperties?.Contains(pkey) == true) + var newMapper = CreateMapper(config); + ConfiguredMappers[configHash] = newMapper; + return newMapper; + } + } + + private static int GetConfigHash(MappingConfig config) + { + unchecked + { + var hash = 17; + + // 计算忽略属性的哈希 + foreach (var ignored in config.IgnoredProperties.OrderBy(x => x)) + { + hash = hash * 31 + (ignored?.GetHashCode() ?? 0); + } + + // 计算属性映射的哈希 + foreach (var mapping in config.PropertyMappings.OrderBy(x => x.Key)) + { + hash = hash * 31 + (mapping.Key?.GetHashCode() ?? 0); + hash = hash * 31 + (mapping.Value?.GetHashCode() ?? 0); + } + + return hash; + } + } + + private static Func CreateMapper(MappingConfig config = null) + { + var sourceParam = Expression.Parameter(typeof(TIn), "source"); + var memberBindings = new List(); + + // 映射属性 + var sourceProperties = typeof(TIn).GetProperties(BindingFlags.Public | BindingFlags.Instance); + var targetProperties = typeof(TOut).GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(p => p.CanWrite) + .ToArray(); + + foreach (var targetProp in targetProperties) + { + // 检查是否被忽略 + if (config?.IgnoredProperties.Contains(targetProp.Name) == true) { continue; } - if (targetPairs.TryGetValue(pkey, out var property)) + + // 查找源属性名(考虑映射配置) + var sourcePropertyName = GetSourcePropertyName(targetProp.Name, config); + + var sourceProp = Array.Find(sourceProperties, p => + string.Equals(p.Name, sourcePropertyName, StringComparison.Ordinal) && + p.CanRead); + + if (sourceProp is not null && IsAssignableFrom(targetProp.PropertyType, sourceProp.PropertyType)) { - if (property.CanWrite) - { - property.SetValue(target, item.Value.GetValue(source)); - } + var sourcePropertyAccess = Expression.Property(sourceParam, sourceProp); + var binding = Expression.Bind(targetProp, ConvertIfNeeded(sourcePropertyAccess, targetProp.PropertyType)); + memberBindings.Add(binding); } } + + // 映射字段 + var sourceFields = typeof(TIn).GetFields(BindingFlags.Public | BindingFlags.Instance); + var targetFields = typeof(TOut).GetFields(BindingFlags.Public | BindingFlags.Instance) + .Where(f => !f.IsInitOnly) + .ToArray(); + + foreach (var targetField in targetFields) + { + // 检查是否被忽略 + if (config?.IgnoredProperties.Contains(targetField.Name) == true) + { + continue; + } + + // 查找源字段名(考虑映射配置) + var sourceFieldName = GetSourcePropertyName(targetField.Name, config); + + var sourceField = Array.Find(sourceFields, f => + string.Equals(f.Name, sourceFieldName, StringComparison.Ordinal)); + + if (sourceField is not null && IsAssignableFrom(targetField.FieldType, sourceField.FieldType)) + { + var sourceFieldAccess = Expression.Field(sourceParam, sourceField); + var binding = Expression.Bind(targetField, ConvertIfNeeded(sourceFieldAccess, targetField.FieldType)); + memberBindings.Add(binding); + } + } + + var newExpression = Expression.New(typeof(TOut)); + var memberInitExpression = Expression.MemberInit(newExpression, memberBindings); + var lambda = Expression.Lambda>(memberInitExpression, sourceParam); + return lambda.Compile(); + } + + private static string GetSourcePropertyName(string targetPropertyName, MappingConfig config) + { + if (config?.PropertyMappings != null) + { + // 查找反向映射:如果目标属性名在映射值中,返回对应的源属性名 + var reverseMapping = config.PropertyMappings.FirstOrDefault(x => x.Value == targetPropertyName); + if (!string.IsNullOrEmpty(reverseMapping.Key)) + { + return reverseMapping.Key; + } + } + + return targetPropertyName; + } + + private static bool IsAssignableFrom(Type targetType, Type sourceType) + { + return targetType.IsAssignableFrom(sourceType) || + (targetType == sourceType) || + (Nullable.GetUnderlyingType(targetType) == sourceType) || + (Nullable.GetUnderlyingType(sourceType) == targetType); + } + + private static Expression ConvertIfNeeded(Expression sourceExpression, Type targetType) + { + if (sourceExpression.Type == targetType) + { + return sourceExpression; + } + + var sourceUnderlyingType = Nullable.GetUnderlyingType(sourceExpression.Type); + var targetUnderlyingType = Nullable.GetUnderlyingType(targetType); + + if (sourceUnderlyingType is not null && targetUnderlyingType is null && sourceUnderlyingType == targetType) + { + return Expression.Convert(sourceExpression, targetType); + } + + if (sourceUnderlyingType is null && targetUnderlyingType is not null && sourceExpression.Type == targetUnderlyingType) + { + return Expression.Convert(sourceExpression, targetType); + } + + if (targetType.IsAssignableFrom(sourceExpression.Type)) + { + return Expression.Convert(sourceExpression, targetType); + } + + return sourceExpression; } - return target; } - /// - /// 扩展方法,将一个泛型集合中的每个元素映射到另一个泛型类型的新集合。 + /// 映射方法 - 直接使用泛型静态缓存(无配置) /// - /// 要映射的原始集合。 - /// 映射选项,用于自定义映射行为。 - /// 原始集合中的元素类型。 - /// 目标集合中的元素类型。 - /// 一个新集合,包含原始集合中每个元素的映射结果。 - public static IEnumerable MapList(this IEnumerable list, MapperOption option = default) where T : class where T1 : class, new() + public static TOut Trans<[DynamicallyAccessedMembers(RequiredMemberTypes)] TIn, + [DynamicallyAccessedMembers(RequiredMemberTypes)] TOut>(TIn source) + where TIn : class + where TOut : class, new() { - // 检查输入的集合是否为,如果是,则抛出异常 - if (list is null) - { - throw new ArgumentNullException(nameof(list)); - } - - // 初始化结果集合,用于存储映射后的元素 - var result = new List(); - // 遍历原始集合中的每个元素 - foreach (var item in list) - { - // 将当前元素映射到目标类型,并将结果添加到结果集合中 - result.Add(Map(item, option)); - } - // 返回结果集合 - return result; + ThrowHelper.ThrowIfNull(source, nameof(source)); + return MapperCache.Mapper(source); } + /// + /// 映射方法 - 支持配置忽略属性和重新映射 + /// + public static TOut Trans<[DynamicallyAccessedMembers(RequiredMemberTypes)] TIn, + [DynamicallyAccessedMembers(RequiredMemberTypes)] TOut>(TIn source, MappingConfig config) + where TIn : class + where TOut : class, new() + { + ThrowHelper.ThrowIfNull(source, nameof(source)); + ThrowHelper.ThrowIfNull(config, nameof(config)); + return MapperCache.GetMapper(config)(source); + } /// - /// 将对象集合映射为指定类型的集合。 + /// 映射方法 - 支持通过委托配置 /// - /// 待映射的对象集合。 - /// 映射选项。 - /// 目标类型。 - /// 映射后的指定类型的集合。 - public static IEnumerable MapList(this IEnumerable list, MapperOption option = default) where T1 : class, new() + public static TOut Trans<[DynamicallyAccessedMembers(RequiredMemberTypes)] TIn, + [DynamicallyAccessedMembers(RequiredMemberTypes)] TOut>(TIn source, Action configAction) + where TIn : class + where TOut : class, new() { - // 检查输入集合是否为,如果是,则抛出异常 - if (list is null) - { - throw new ArgumentNullException(nameof(list)); - } + ThrowHelper.ThrowIfNull(source, nameof(source)); - // 初始化结果集合 - var result = new List(); - // 遍历输入集合中的每个对象 - foreach (var item in list) - { - // 将当前对象映射为目标类型,并添加到结果集合中 - result.Add(Map(item, option)); - } - // 返回结果集合 - return result; + ThrowHelper.ThrowIfNull(configAction, nameof(configAction)); + var config = new MappingConfig(); + configAction(config); + + return MapperCache.GetMapper(config)(source); } } \ No newline at end of file diff --git a/src/TouchSocket.Core/WaitPool/ValueWaitResult.cs b/src/TouchSocket.Core/Mapper/MappingConfig.cs similarity index 56% rename from src/TouchSocket.Core/WaitPool/ValueWaitResult.cs rename to src/TouchSocket.Core/Mapper/MappingConfig.cs index ff97c0485..d8e80cb97 100644 --- a/src/TouchSocket.Core/WaitPool/ValueWaitResult.cs +++ b/src/TouchSocket.Core/Mapper/MappingConfig.cs @@ -10,28 +10,39 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// -/// ValueWaitResult +/// 映射配置 /// -[Serializable] -public struct ValueWaitResult : IWaitResult +public class MappingConfig { - /// - /// 消息 - /// - public string Message { get; set; } /// - /// 标记号 + /// 需要忽略的属性名集合 /// - public int Sign { get; set; } + public HashSet IgnoredProperties { get; set; } = new(); /// - /// 状态 + /// 属性名映射字典 (源属性名 -> 目标属性名) /// - public byte Status { get; set; } + public Dictionary PropertyMappings { get; set; } = new(); + + /// + /// 添加需要忽略的属性 + /// + public MappingConfig Ignore(string propertyName) + { + this.IgnoredProperties.Add(propertyName); + return this; + } + + /// + /// 添加属性映射 + /// + public MappingConfig Map(string sourceProperty, string targetProperty) + { + this.PropertyMappings[sourceProperty] = targetProperty; + return this; + } } \ No newline at end of file diff --git a/src/TouchSocket.Core/Packages_/IPackage.cs b/src/TouchSocket.Core/Packages_/IPackage.cs index 1d7db9c3a..b67ce7541 100644 --- a/src/TouchSocket.Core/Packages_/IPackage.cs +++ b/src/TouchSocket.Core/Packages_/IPackage.cs @@ -21,13 +21,15 @@ public interface IPackage /// 打包。 /// 重写的话,约定基类方法必须先执行 /// - /// - void Package(ref TByteBlock byteBlock) where TByteBlock : IByteBlock; + /// + void Package(ref TWriter writer) + where TWriter : IBytesWriter; /// /// 解包。 /// 重写的话,约定基类方法必须先执行 /// - /// - void Unpackage(ref TByteBlock byteBlock) where TByteBlock : IByteBlock; -} \ No newline at end of file + /// + void Unpackage(ref TReader reader) + where TReader : IBytesReader; +} diff --git a/src/TouchSocket.Core/Packages_/IPackageConverter.cs b/src/TouchSocket.Core/Packages_/IPackageConverter.cs new file mode 100644 index 000000000..8e497ba31 --- /dev/null +++ b/src/TouchSocket.Core/Packages_/IPackageConverter.cs @@ -0,0 +1,37 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +namespace TouchSocket.Core; +/// +/// 定义用于将数据包类型 与字节流进行转换的接口。 +/// +/// 要转换的数据包类型。 +public interface IPackageConverter +{ + /// + /// 将 类型的数据包写入到字节写入器中。 + /// + /// 字节写入器类型,必须实现 接口。 + /// 字节写入器的引用。 + /// 要写入的数据包。 + void Package(ref TWriter writer, T value) + where TWriter : IBytesWriter; + + /// + /// 从字节读取器中读取并还原为 类型的数据包。 + /// + /// 字节读取器类型,必须实现 接口。 + /// 字节读取器的引用。 + /// 还原后的数据包。 + T Unpackage(ref TReader reader) + where TReader : IBytesReader; +} diff --git a/src/TouchSocket.Core/Packages_/MsgPackage.cs b/src/TouchSocket.Core/Packages_/MsgPackage.cs index 9c6f3cc5d..2c9a76181 100644 --- a/src/TouchSocket.Core/Packages_/MsgPackage.cs +++ b/src/TouchSocket.Core/Packages_/MsgPackage.cs @@ -23,14 +23,15 @@ public class MsgPackage : PackageBase public string Message { get; set; } /// - public override void Package(ref TByteBlock byteBlock) + public override void Package(ref TWriter writer) { - byteBlock.WriteString(this.Message, FixedHeaderType.Ushort); + + WriterExtension.WriteString(ref writer, this.Message, FixedHeaderType.Ushort); } /// - public override void Unpackage(ref TByteBlock byteBlock) + public override void Unpackage(ref TReader reader) { - this.Message = byteBlock.ReadString(FixedHeaderType.Ushort); + this.Message = ReaderExtension.ReadString(ref reader, FixedHeaderType.Ushort); } } \ No newline at end of file diff --git a/src/TouchSocket.Core/Packages_/MsgRouterPackage.cs b/src/TouchSocket.Core/Packages_/MsgRouterPackage.cs index ed5913621..0fa9ad0ae 100644 --- a/src/TouchSocket.Core/Packages_/MsgRouterPackage.cs +++ b/src/TouchSocket.Core/Packages_/MsgRouterPackage.cs @@ -23,14 +23,14 @@ public class MsgRouterPackage : RouterPackage public string Message { get; set; } /// - public override void PackageBody(ref TByteBlock byteBlock) + public override void PackageBody(ref TWriter writer) { - byteBlock.WriteString(this.Message, FixedHeaderType.Ushort); + WriterExtension.WriteString(ref writer, this.Message, FixedHeaderType.Ushort); } /// - public override void UnpackageBody(ref TByteBlock byteBlock) + public override void UnpackageBody(ref TReader reader) { - this.Message = byteBlock.ReadString(FixedHeaderType.Ushort); + this.Message = ReaderExtension.ReadString(ref reader, FixedHeaderType.Ushort); } } \ No newline at end of file diff --git a/src/TouchSocket.Core/Packages_/PackageBase.cs b/src/TouchSocket.Core/Packages_/PackageBase.cs index 238c2a418..c200be86f 100644 --- a/src/TouchSocket.Core/Packages_/PackageBase.cs +++ b/src/TouchSocket.Core/Packages_/PackageBase.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// @@ -20,9 +18,21 @@ namespace TouchSocket.Core; [Serializable] public abstract class PackageBase : IPackage { - /// - public abstract void Package(ref TByteBlock byteBlock) where TByteBlock : IByteBlock; /// - public abstract void Unpackage(ref TByteBlock byteBlock) where TByteBlock : IByteBlock; + public abstract void Package(ref TWriter writer) + where TWriter : IBytesWriter +#if AllowsRefStruct +, allows ref struct +#endif + ; + + /// + public abstract void Unpackage(ref TReader reader) + where TReader : IBytesReader +#if AllowsRefStruct +, allows ref struct +#endif + ; + } \ No newline at end of file diff --git a/src/TouchSocket.Core/Packages_/RouterPackage.cs b/src/TouchSocket.Core/Packages_/RouterPackage.cs index 56136f6c1..7cc7e3479 100644 --- a/src/TouchSocket.Core/Packages_/RouterPackage.cs +++ b/src/TouchSocket.Core/Packages_/RouterPackage.cs @@ -35,19 +35,23 @@ public class RouterPackage : PackageBase, IReadonlyRouterPackage /// /// 打包所有的路由包信息。顺序为:先调用,然后 /// - /// - public sealed override void Package(ref TByteBlock byteBlock) + /// + public sealed override void Package(ref TWriter writer) { - this.PackageRouter(ref byteBlock); - this.PackageBody(ref byteBlock); + this.PackageRouter(ref writer); + this.PackageBody(ref writer); } /// /// 打包数据体。一般不需要单独调用该方法。 /// 重写的话,约定基类方法必须先执行 /// - /// - public virtual void PackageBody(ref TByteBlock byteBlock) where TByteBlock : IByteBlock + /// + public virtual void PackageBody(ref TWriter writer) + where TWriter : IBytesWriter +#if AllowsRefStruct +, allows ref struct +#endif { } @@ -55,10 +59,12 @@ public class RouterPackage : PackageBase, IReadonlyRouterPackage /// 打包路由。 /// 重写的话,约定基类方法必须先执行 /// - public virtual void PackageRouter(ref TByteBlock byteBlock) where TByteBlock : IByteBlock + public virtual void PackageRouter(ref TWriter writer) + where TWriter : IBytesWriter + { - byteBlock.WriteString(this.SourceId, FixedHeaderType.Byte); - byteBlock.WriteString(this.TargetId, FixedHeaderType.Byte); + WriterExtension.WriteString(ref writer, this.SourceId, FixedHeaderType.Byte); + WriterExtension.WriteString(ref writer, this.TargetId, FixedHeaderType.Byte); } /// @@ -72,18 +78,20 @@ public class RouterPackage : PackageBase, IReadonlyRouterPackage } /// - public sealed override void Unpackage(ref TByteBlock byteBlock) + public sealed override void Unpackage(ref TReader reader) { - this.UnpackageRouter(ref byteBlock); - this.UnpackageBody(ref byteBlock); + this.UnpackageRouter(ref reader); + this.UnpackageBody(ref reader); } /// /// 解包数据体。一般不需要单独调用该方法。 /// 重写的话,约定基类方法必须先执行 /// - /// - public virtual void UnpackageBody(ref TByteBlock byteBlock) where TByteBlock : IByteBlock + /// + public virtual void UnpackageBody(ref TReader reader) + where TReader : IBytesReader + { } @@ -91,10 +99,12 @@ public class RouterPackage : PackageBase, IReadonlyRouterPackage /// 只解包路由部分。一般不需要单独调用该方法。 /// 重写的话,约定基类方法必须先执行 /// - /// - public virtual void UnpackageRouter(ref TByteBlock byteBlock) where TByteBlock : IByteBlock + /// + public virtual void UnpackageRouter(ref TReader reader) + where TReader : IBytesReader + { - this.SourceId = byteBlock.ReadString(FixedHeaderType.Byte); - this.TargetId = byteBlock.ReadString(FixedHeaderType.Byte); + this.SourceId = ReaderExtension.ReadString(ref reader, FixedHeaderType.Byte); + this.TargetId = ReaderExtension.ReadString(ref reader, FixedHeaderType.Byte); } } \ No newline at end of file diff --git a/src/TouchSocket.Core/Packages_/WaitPackage.cs b/src/TouchSocket.Core/Packages_/WaitPackage.cs index 1dc98656c..ecf5c545c 100644 --- a/src/TouchSocket.Core/Packages_/WaitPackage.cs +++ b/src/TouchSocket.Core/Packages_/WaitPackage.cs @@ -27,18 +27,18 @@ public class WaitPackage : PackageBase, IWaitResult public byte Status { get; set; } /// - public override void Package(ref TByteBlock byteBlock) + public override void Package(ref TWriter writer) { - byteBlock.WriteInt32(this.Sign); - byteBlock.WriteByte(this.Status); - byteBlock.WriteString(this.Message, FixedHeaderType.Ushort); + WriterExtension.WriteValue(ref writer, this.Sign); + WriterExtension.WriteValue(ref writer, this.Status); + WriterExtension.WriteString(ref writer, this.Message, FixedHeaderType.Ushort); } /// - public override void Unpackage(ref TByteBlock byteBlock) + public override void Unpackage(ref TReader reader) { - this.Sign = byteBlock.ReadInt32(); - this.Status = byteBlock.ReadByte(); - this.Message = byteBlock.ReadString(FixedHeaderType.Ushort); + this.Sign = ReaderExtension.ReadValue(ref reader); + this.Status = ReaderExtension.ReadValue(ref reader); + this.Message = ReaderExtension.ReadString(ref reader, FixedHeaderType.Ushort); } } \ No newline at end of file diff --git a/src/TouchSocket.Core/Packages_/WaitRouterPackage.cs b/src/TouchSocket.Core/Packages_/WaitRouterPackage.cs index 32861f84d..a6ceb46fc 100644 --- a/src/TouchSocket.Core/Packages_/WaitRouterPackage.cs +++ b/src/TouchSocket.Core/Packages_/WaitRouterPackage.cs @@ -29,50 +29,50 @@ public class WaitRouterPackage : MsgRouterPackage, IWaitResult protected virtual bool IncludedRouter { get; } /// - public override void PackageBody(ref TByteBlock byteBlock) + public override void PackageBody(ref TWriter writer) { - base.PackageBody(ref byteBlock); + base.PackageBody(ref writer); if (this.IncludedRouter) { return; } - byteBlock.WriteInt32(this.Sign); - byteBlock.WriteByte(this.Status); + WriterExtension.WriteValue(ref writer, this.Sign); + WriterExtension.WriteValue(ref writer, this.Status); } /// - public override void PackageRouter(ref TByteBlock byteBlock) + public override void PackageRouter(ref TWriter writer) { - base.PackageRouter(ref byteBlock); + base.PackageRouter(ref writer); if (!this.IncludedRouter) { return; } - byteBlock.WriteInt32(this.Sign); - byteBlock.WriteByte(this.Status); + WriterExtension.WriteValue(ref writer, this.Sign); + WriterExtension.WriteValue(ref writer, this.Status); } /// - public override void UnpackageBody(ref TByteBlock byteBlock) + public override void UnpackageBody(ref TReader reader) { - base.UnpackageBody(ref byteBlock); + base.UnpackageBody(ref reader); if (!this.IncludedRouter) { - this.Sign = byteBlock.ReadInt32(); - this.Status = byteBlock.ReadByte(); + this.Sign = ReaderExtension.ReadValue(ref reader); + this.Status = ReaderExtension.ReadValue(ref reader); } } /// - public override void UnpackageRouter(ref TByteBlock byteBlock) + public override void UnpackageRouter(ref TReader reader) { - base.UnpackageRouter(ref byteBlock); + base.UnpackageRouter(ref reader); if (this.IncludedRouter) { - this.Sign = byteBlock.ReadInt32(); - this.Status = byteBlock.ReadByte(); + this.Sign = ReaderExtension.ReadValue(ref reader); + this.Status = ReaderExtension.ReadValue(ref reader); } } } \ No newline at end of file diff --git a/src/TouchSocket.Core/Plugins/IPluginManager.cs b/src/TouchSocket.Core/Plugins/IPluginManager.cs index ed392dce4..54288e03c 100644 --- a/src/TouchSocket.Core/Plugins/IPluginManager.cs +++ b/src/TouchSocket.Core/Plugins/IPluginManager.cs @@ -10,10 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Threading.Tasks; namespace TouchSocket.Core; @@ -37,14 +34,14 @@ public interface IPluginManager : IDisposableObject, IResolverObject /// /// 插件 /// - void Add<[DynamicallyAccessedMembers(PluginManagerExtension.PluginAccessedMemberTypes)] TPlugin>(TPlugin plugin) where TPlugin : class, IPlugin; + void Add<[DynamicallyAccessedMembers(AOT.PluginMemberType)] TPlugin>(TPlugin plugin) where TPlugin : class, IPlugin; /// /// 添加插件 /// /// 插件类型 /// 添加的插件实例 - IPlugin Add([DynamicallyAccessedMembers(PluginManagerExtension.PluginAccessedMemberTypes)] Type pluginType); + IPlugin Add([DynamicallyAccessedMembers(AOT.PluginMemberType)] Type pluginType); /// /// 添加一个插件类型及其对应的调用处理程序。 @@ -52,21 +49,21 @@ public interface IPluginManager : IDisposableObject, IResolverObject /// 插件的类型。 /// 插件调用处理程序,当插件被调用时执行。 /// 可选的源委托,用于标识插件的来源。 - void Add(Type pluginType, Func pluginInvokeHandler, Delegate sourceDelegate = default); + void Add([DynamicallyAccessedMembers(AOT.PluginMemberType)] Type pluginType, Func pluginInvokeHandler, Delegate sourceDelegate = default); /// /// 获取来自IOC容器的指定名称的插件数量。 /// /// 插件类型 /// 插件数量 - int GetFromIocCount(Type pluginType); + int GetFromIocCount([DynamicallyAccessedMembers(AOT.PluginMemberType)] Type pluginType); /// /// 获取已添加的指定名称的插件数量。 /// /// /// - int GetPluginCount(Type pluginType); + int GetPluginCount([DynamicallyAccessedMembers(AOT.PluginMemberType)] Type pluginType); /// /// 触发对应插件 @@ -76,7 +73,7 @@ public interface IPluginManager : IDisposableObject, IResolverObject /// 事件发送者 /// 事件参数 /// 表示在执行的插件中,是否处理 - ValueTask RaiseAsync(Type pluginType, IResolver resolver, object sender, PluginEventArgs e); + ValueTask RaiseAsync([DynamicallyAccessedMembers(AOT.PluginMemberType)] Type pluginType, IResolver resolver, object sender, PluginEventArgs e); /// /// 移除指定的插件实例 @@ -89,5 +86,5 @@ public interface IPluginManager : IDisposableObject, IResolverObject /// /// 要移除的插件类型 /// 代表要移除的功能的委托 - void Remove(Type pluginType, Delegate func); + void Remove([DynamicallyAccessedMembers(AOT.PluginMemberType)] Type pluginType, Delegate func); } \ No newline at end of file diff --git a/src/TouchSocket.Core/Plugins/PluginEntity.cs b/src/TouchSocket.Core/Plugins/PluginEntity.cs index ee83e75cf..878bb01a9 100644 --- a/src/TouchSocket.Core/Plugins/PluginEntity.cs +++ b/src/TouchSocket.Core/Plugins/PluginEntity.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; - namespace TouchSocket.Core; internal sealed class PluginEntity diff --git a/src/TouchSocket.Core/Plugins/PluginEventArgs.cs b/src/TouchSocket.Core/Plugins/PluginEventArgs.cs index 0d0d6c5f2..9a1a9510f 100644 --- a/src/TouchSocket.Core/Plugins/PluginEventArgs.cs +++ b/src/TouchSocket.Core/Plugins/PluginEventArgs.cs @@ -10,9 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Collections.Generic; using System.Diagnostics; -using System.Threading.Tasks; namespace TouchSocket.Core; diff --git a/src/TouchSocket.Core/Plugins/PluginInvokeLine.cs b/src/TouchSocket.Core/Plugins/PluginInvokeLine.cs index 7b53e1b2f..aa87d88ae 100644 --- a/src/TouchSocket.Core/Plugins/PluginInvokeLine.cs +++ b/src/TouchSocket.Core/Plugins/PluginInvokeLine.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; - namespace TouchSocket.Core; internal sealed class PluginInvokeLine diff --git a/src/TouchSocket.Core/Plugins/PluginManager.cs b/src/TouchSocket.Core/Plugins/PluginManager.cs index c4691d88d..64509150c 100644 --- a/src/TouchSocket.Core/Plugins/PluginManager.cs +++ b/src/TouchSocket.Core/Plugins/PluginManager.cs @@ -10,12 +10,8 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; -using System.Threading; -using System.Threading.Tasks; namespace TouchSocket.Core; @@ -50,7 +46,7 @@ public sealed class PluginManager : DisposableObject, IPluginManager public IResolver Resolver => this.m_scopedResolver.Resolver; /// - public void Add(Type pluginType, Func pluginInvokeHandler, Delegate sourceDelegate = default) + public void Add([DynamicallyAccessedMembers(AOT.PluginMemberType)] Type pluginType, Func pluginInvokeHandler, Delegate sourceDelegate = default) { lock (this.m_locker) { @@ -60,9 +56,11 @@ public sealed class PluginManager : DisposableObject, IPluginManager } /// - public IPlugin Add([DynamicallyAccessedMembers(PluginManagerExtension.PluginAccessedMemberTypes)] Type pluginType) + [UnconditionalSuppressMessage("AOT", "IL2075", Justification = "pluginInterface类型可确定,AOT下不会丢失Method")] + [UnconditionalSuppressMessage("AOT", "IL2026", Justification = "Method方法在AOT确定")] + public IPlugin Add([DynamicallyAccessedMembers(AOT.PluginMemberType)] Type pluginType) { - ThrowHelper.ThrowArgumentNullExceptionIf(pluginType, nameof(pluginType)); + ThrowHelper.ThrowIfNull(pluginType, nameof(pluginType)); this.ThrowIfDisposed(); @@ -134,15 +132,17 @@ public sealed class PluginManager : DisposableObject, IPluginManager } /// - public void Add<[DynamicallyAccessedMembers(PluginManagerExtension.PluginAccessedMemberTypes)] TPlugin>(TPlugin plugin) where TPlugin : class, IPlugin + [UnconditionalSuppressMessage("AOT", "IL2075", Justification = "pluginInterface类型可确定,AOT下不会丢失Method")] + [UnconditionalSuppressMessage("AOT", "IL2026", Justification = "Method方法在AOT确定")] + public void Add<[DynamicallyAccessedMembers(AOT.PluginMemberType)] TPlugin>(TPlugin plugin) where TPlugin : class, IPlugin { - ThrowHelper.ThrowArgumentNullExceptionIf(plugin, nameof(plugin)); + ThrowHelper.ThrowIfNull(plugin, nameof(plugin)); this.ThrowIfDisposed(); lock (this.m_locker) { - var pluginType = plugin.GetType(); + var pluginType = typeof(TPlugin); var fromIoc = false; if (pluginType.GetCustomAttribute() is PluginOptionAttribute optionAttribute) @@ -184,36 +184,24 @@ public sealed class PluginManager : DisposableObject, IPluginManager } /// - public int GetFromIocCount(Type pluginType) + public int GetFromIocCount([DynamicallyAccessedMembers(AOT.PluginMemberType)] Type pluginType) { if (!this.Enable) { return 0; } - if (this.m_pluginMethods.TryGetValue(pluginType, out var pluginInvokeLine)) - { - return pluginInvokeLine.FromIocCount; - } - - return 0; + return this.m_pluginMethods.TryGetValue(pluginType, out var pluginInvokeLine) ? pluginInvokeLine.FromIocCount : 0; } /// - public int GetPluginCount(Type pluginType) + public int GetPluginCount([DynamicallyAccessedMembers(AOT.PluginMemberType)] Type pluginType) { - if (this.m_pluginMethods.TryGetValue(pluginType, out var pluginModel)) - { - return pluginModel.GetPluginEntities().Count; - } - else - { - return 0; - } + return this.m_pluginMethods.TryGetValue(pluginType, out var pluginModel) ? pluginModel.GetPluginEntities().Count : 0; } /// - public ValueTask RaiseAsync(Type pluginType, IResolver resolver, object sender, PluginEventArgs e) + public ValueTask RaiseAsync([DynamicallyAccessedMembers(AOT.PluginMemberType)] Type pluginType, IResolver resolver, object sender, PluginEventArgs e) { if (!this.Enable) { @@ -225,11 +213,9 @@ public sealed class PluginManager : DisposableObject, IPluginManager return new ValueTask(true); } - if (!this.m_pluginMethods.TryGetValue(pluginType, out var pluginInvokeLine)) - { - return new ValueTask(false); - } - return new ValueTask(this.RaisePluginAsync(pluginInvokeLine, resolver, sender, e)); + return !this.m_pluginMethods.TryGetValue(pluginType, out var pluginInvokeLine) + ? new ValueTask(false) + : new ValueTask(this.RaisePluginAsync(pluginInvokeLine, resolver, sender, e)); } /// @@ -259,7 +245,7 @@ public sealed class PluginManager : DisposableObject, IPluginManager } /// - public void Remove(Type pluginType, Delegate func) + public void Remove([DynamicallyAccessedMembers(AOT.PluginMemberType)] Type pluginType, Delegate func) { lock (this.m_locker) { @@ -290,11 +276,7 @@ public sealed class PluginManager : DisposableObject, IPluginManager private static bool IsPluginInterface(Type type) { - if (type == typeof(IPlugin)) - { - return false; - } - return typeof(IPlugin).IsAssignableFrom(type); + return type != typeof(IPlugin) && typeof(IPlugin).IsAssignableFrom(type); } private static bool IsPluginMethod(MethodInfo methodInfo) @@ -302,12 +284,12 @@ public sealed class PluginManager : DisposableObject, IPluginManager return methodInfo.GetParameters().Length == 2 && typeof(PluginEventArgs).IsAssignableFrom(methodInfo.GetParameters()[1].ParameterType) && methodInfo.ReturnType == typeof(Task); } - private PluginInvokeLine GetPluginInvokeLine([DynamicallyAccessedMembers(PluginManagerExtension.PluginAccessedMemberTypes)] Type interfeceType) + private PluginInvokeLine GetPluginInvokeLine(Type interfaceType) { - if (!this.m_pluginMethods.TryGetValue(interfeceType, out var pluginModel)) + if (!this.m_pluginMethods.TryGetValue(interfaceType, out var pluginModel)) { pluginModel = new PluginInvokeLine(); - this.m_pluginMethods.Add(interfeceType, pluginModel); + this.m_pluginMethods.Add(interfaceType, pluginModel); } return pluginModel; } diff --git a/src/TouchSocket.Core/Plugins/PluginManagerExtension.cs b/src/TouchSocket.Core/Plugins/PluginManagerExtension.cs index beeb7a3ab..3e72ba8d5 100644 --- a/src/TouchSocket.Core/Plugins/PluginManagerExtension.cs +++ b/src/TouchSocket.Core/Plugins/PluginManagerExtension.cs @@ -10,9 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Diagnostics.CodeAnalysis; -using System.Threading.Tasks; namespace TouchSocket.Core; @@ -21,11 +19,6 @@ namespace TouchSocket.Core; /// public static class PluginManagerExtension { - /// - /// 插件访问成员类型,用于指定动态访问的成员类型。 - /// - public const DynamicallyAccessedMemberTypes PluginAccessedMemberTypes = DynamicallyAccessedMemberTypes.All; - /// /// 向插件管理器中添加一个指定类型的插件。 /// @@ -33,10 +26,10 @@ public static class PluginManagerExtension /// 一个函数,用于通过解析器创建插件实例。 /// 要添加的插件类型。 /// 返回添加到插件管理器中的插件实例。 - public static TPlugin Add<[DynamicallyAccessedMembers(PluginAccessedMemberTypes)] TPlugin>(this IPluginManager pluginManager, Func func) where TPlugin : class, IPlugin + public static TPlugin Add<[DynamicallyAccessedMembers(AOT.PluginMemberType)] TPlugin>(this IPluginManager pluginManager, Func func) where TPlugin : class, IPlugin { // 检查传入的函数是否为,并抛出异常 - ThrowHelper.ThrowArgumentNullExceptionIf(func, nameof(func)); + ThrowHelper.ThrowIfNull(func, nameof(func)); // 使用提供的函数和插件管理器的解析器来创建插件实例 var plugin = func.Invoke(pluginManager.Resolver); @@ -54,7 +47,7 @@ public static class PluginManagerExtension /// /// 插件类型 /// 插件类型实例 - public static TPlugin Add<[DynamicallyAccessedMembers(PluginAccessedMemberTypes)] TPlugin>(this IPluginManager pluginManager) where TPlugin : class, IPlugin + public static TPlugin Add<[DynamicallyAccessedMembers(AOT.PluginMemberType)] TPlugin>(this IPluginManager pluginManager) where TPlugin : class, IPlugin { return (TPlugin)pluginManager.Add(typeof(TPlugin)); } @@ -67,7 +60,7 @@ public static class PluginManagerExtension /// 异步事件处理函数,接受事件发送者和事件参数作为输入,并返回一个任务。 /// 事件发送者的类型。 /// 事件参数的类型,必须继承自PluginEventArgs。 - public static void Add(this IPluginManager pluginManager, Type interfaceType, Func func) where TEventArgs : PluginEventArgs + public static void Add(this IPluginManager pluginManager, [DynamicallyAccessedMembers(AOT.PluginMemberType)] Type interfaceType, Func func) where TEventArgs : PluginEventArgs { // 创建一个新的任务,封装了传入的事件处理函数,以适应插件管理器所需的参数类型。 Task newFunc(object sender, PluginEventArgs e) @@ -87,7 +80,7 @@ public static class PluginManagerExtension /// 插件接口的类型,用于指定事件处理函数关联的插件类型。 /// 要添加的事件处理函数,当事件触发时将异步执行此函数。 /// 事件参数的类型,必须继承自PluginEventArgs。 - public static void Add(this IPluginManager pluginManager, Type interfaceType, Func func) where TEventArgs : PluginEventArgs + public static void Add(this IPluginManager pluginManager, [DynamicallyAccessedMembers(AOT.PluginMemberType)] Type interfaceType, Func func) where TEventArgs : PluginEventArgs { // 创建一个新的任务,封装传入的事件处理函数,使其与插件管理器期望的签名匹配。 Task newFunc(object sender, PluginEventArgs e) @@ -105,7 +98,7 @@ public static class PluginManagerExtension /// 插件管理器实例,用于添加插件。 /// 插件需要实现的接口类型。 /// 插件的具体逻辑,作为一个异步任务执行。 - public static void Add(this IPluginManager pluginManager, Type interfaceType, Func func) + public static void Add(this IPluginManager pluginManager, [DynamicallyAccessedMembers(AOT.PluginMemberType)] Type interfaceType, Func func) { // 定义一个新的异步任务,封装传入的插件逻辑和插件链的继续执行 async Task newFunc(object sender, PluginEventArgs e) @@ -127,7 +120,7 @@ public static class PluginManagerExtension /// 插件需要实现的接口类型。 /// 插件被调用时执行的操作。 /// 插件的类型,必须是类类型。 - public static void Add(this IPluginManager pluginManager, Type interfaceType, Action action) where T : class + public static void Add(this IPluginManager pluginManager, [DynamicallyAccessedMembers(AOT.PluginMemberType)] Type interfaceType, Action action) where T : class { // 判断泛型类型T是否继承自PluginEventArgs if (typeof(PluginEventArgs).IsAssignableFrom(typeof(T))) @@ -164,7 +157,7 @@ public static class PluginManagerExtension /// 插件管理器实例,允许通过扩展方法语法调用此方法。 /// 插件所实现的接口类型,用于标识和分类插件。 /// 插件处理程序将要执行的动作。 - public static void Add(this IPluginManager pluginManager, Type interfaceType, Action action) + public static void Add(this IPluginManager pluginManager, [DynamicallyAccessedMembers(AOT.PluginMemberType)] Type interfaceType, Action action) { // 创建一个新的异步处理程序,它将在插件事件被触发时执行指定的动作, // 并在动作完成后调用事件的InvokeNext方法以继续执行下一个插件处理程序。 @@ -179,7 +172,7 @@ public static class PluginManagerExtension } /// - public static ValueTask RaiseAsync(this IPluginManager pluginManager, Type pluginType, object sender, PluginEventArgs e) + public static ValueTask RaiseAsync(this IPluginManager pluginManager, [DynamicallyAccessedMembers(AOT.PluginMemberType)] Type pluginType, object sender, PluginEventArgs e) { return pluginManager.RaiseAsync(pluginType, default, sender, e); } diff --git a/src/TouchSocket.Core/Plugins/PluginOptionAttribute.cs b/src/TouchSocket.Core/Plugins/PluginOptionAttribute.cs index a5053e9b4..6f4473c34 100644 --- a/src/TouchSocket.Core/Plugins/PluginOptionAttribute.cs +++ b/src/TouchSocket.Core/Plugins/PluginOptionAttribute.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/Pool/ArrayPool.cs b/src/TouchSocket.Core/Pool/ArrayPool.cs deleted file mode 100644 index 35a03d13d..000000000 --- a/src/TouchSocket.Core/Pool/ArrayPool.cs +++ /dev/null @@ -1,362 +0,0 @@ -// ------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -// ------------------------------------------------------------------------------ - - - -//#if NET45 - -//using System; -//using System.Diagnostics; -//using System.Runtime.CompilerServices; -//using System.Threading; - -//namespace TouchSocket.Core; - -///// -///// 提供一个数组对象的池化容器。 -///// -///// -//public class ArrayPool -//{ -// private const int DefaultMaxArrayLength = 1024 * 1024; - -// private const int DefaultMaxNumberOfArraysPerBucket = 50; - -// private readonly Bucket[] m_buckets; - -// /// -// /// 提供一个数组对象的池化容器。 -// /// -// public ArrayPool() : this(DefaultMaxArrayLength, DefaultMaxNumberOfArraysPerBucket) -// { -// } - -// public static ArrayPool Shared { get; } = new ArrayPool(DefaultMaxArrayLength, DefaultMaxNumberOfArraysPerBucket); - -// /// -// /// 提供一个数组对象的池化容器。 -// /// -// /// -// /// -// public ArrayPool(int maxArrayLength, int maxArraysPerBucket) -// { -// const int MinimumArrayLength = 16, MaximumArrayLength = int.MaxValue; -// if (maxArrayLength > MaximumArrayLength) -// { -// maxArrayLength = MaximumArrayLength; -// } -// else if (maxArrayLength < MinimumArrayLength) -// { -// maxArrayLength = MinimumArrayLength; -// } - -// var capacity = 0L; -// var maxBuckets = SelectBucketIndex(maxArrayLength); -// var buckets = new Bucket[maxBuckets + 1]; -// for (var i = 0; i < buckets.Length; i++) -// { -// buckets[i] = new Bucket(GetMaxSizeForBucket(i), maxArraysPerBucket); -// long num = GetMaxSizeForBucket(i) * maxArraysPerBucket; -// capacity += num; -// } -// this.m_buckets = buckets; -// this.Capacity = capacity; -// } - -// /// -// /// 对象池的最大容量。 -// /// -// public long Capacity { get; private set; } - -// /// -// /// 清理池中所有对象。 -// /// -// public void Clear() -// { -// foreach (var item in this.m_buckets) -// { -// item.Clear(); -// } -// } - -// /// -// /// 获取当前池中的所有对象。 -// /// -// /// -// public long GetPoolSize() -// { -// long size = 0; -// foreach (var item in this.m_buckets) -// { -// size += item.Size; -// } -// return size; -// } - -// /// -// /// 最大请求尺寸梯度。 -// /// -// public int MaxBucketsToTry { get; set; } = 5; - -// /// -// /// 获取一个不小于指定尺寸的池化数组对象。 -// /// -// /// -// /// -// public virtual T[] Rent(int minimumLength) -// { -// if (minimumLength == 0) -// { -// return new T[0]; -// } - -// T[] buffer; - -// var index = SelectBucketIndex(minimumLength); -// if (index < this.m_buckets.Length) -// { -// var i = index; -// do -// { -// buffer = this.m_buckets[i].Rent(); -// if (buffer != null) -// { -// return buffer; -// } -// } -// while (++i < this.m_buckets.Length && i != index + this.MaxBucketsToTry); - -// buffer = new T[this.m_buckets[index].m_bufferLength]; -// } -// else -// { -// buffer = new T[minimumLength]; -// } - -// return buffer; -// } - -// /// -// /// 归还池化对象。 -// /// -// /// -// /// -// public virtual void Return(T[] array, bool clearArray = false) -// { -// if (array is null) -// { -// throw new ArgumentNullException(nameof(array)); -// } - -// if (array.Length == 0) -// { -// return; -// } - -// var bucket = SelectBucketIndex(array.Length); - -// var haveBucket = bucket < this.m_buckets.Length; -// if (haveBucket) -// { -// if (clearArray) -// { -// Array.Clear(array, 0, array.Length); -// } - -// this.m_buckets[bucket].Return(array); -// } -// } - -// /// -// /// 命中匹配尺寸 -// /// -// /// -// /// -// [MethodImpl(MethodImplOptions.AggressiveInlining)] -// public static int HitSize(int size) -// { -// return GetMaxSizeForBucket(SelectBucketIndex(size)); -// } - -// [MethodImpl(MethodImplOptions.AggressiveInlining)] -// internal static int GetMaxSizeForBucket(int binIndex) -// { -// return 16 << binIndex; -// } - -// [MethodImpl(MethodImplOptions.AggressiveInlining)] -// internal static int SelectBucketIndex(int bufferSize) -// { -// return (int)(Math.Log((uint)(bufferSize - 1) | 15u, 2) - 3); -// } - -// [DebuggerDisplay("Count={Count},Size={Size}")] -// private sealed class Bucket -// { -// internal readonly int m_bufferLength; -// private readonly int m_numberOfBuffers; -// private T[][] m_buffers; - -// private int m_index; -// private SpinLock m_lock; - -// internal Bucket(int bufferLength, int numberOfBuffers) -// { -// this.m_lock = new SpinLock(Debugger.IsAttached); -// this.m_buffers = new T[numberOfBuffers][]; -// this.m_bufferLength = bufferLength; -// this.m_numberOfBuffers = numberOfBuffers; -// } - -// public void Clear() -// { -// var lockTaken = false; -// try -// { -// this.m_lock.Enter(ref lockTaken); -// this.m_buffers = new T[this.m_numberOfBuffers][]; -// this.m_index = 0; -// } -// finally -// { -// if (lockTaken) -// { -// this.m_lock.Exit(false); -// } -// } -// } - -// public int Count -// { -// get -// { -// var lockTaken = false; -// try -// { -// this.m_lock.Enter(ref lockTaken); - -// var count = 0; -// foreach (var item in this.m_buffers) -// { -// if (item != null) -// { -// count++; -// } -// } - -// return count; -// } -// finally -// { -// if (lockTaken) -// { -// this.m_lock.Exit(false); -// } -// } -// } -// } - -// internal int Id => this.GetHashCode(); - -// public long Size -// { -// get -// { -// var lockTaken = false; -// try -// { -// this.m_lock.Enter(ref lockTaken); - -// long size = 0; -// foreach (var item in this.m_buffers) -// { -// if (item != null) -// { -// size += item.LongLength; -// } -// } - -// return size; -// } -// finally -// { -// if (lockTaken) -// { -// this.m_lock.Exit(false); -// } -// } -// } -// } - -// internal T[] Rent() -// { -// T[] buffer = null; - -// bool lockTaken = false, allocateBuffer = false; -// try -// { -// this.m_lock.Enter(ref lockTaken); - -// if (this.m_index < this.m_buffers.Length) -// { -// buffer = this.m_buffers[this.m_index]; -// this.m_buffers[this.m_index++] = null; -// allocateBuffer = buffer == null; -// } -// } -// finally -// { -// if (lockTaken) -// { -// this.m_lock.Exit(false); -// } -// } - -// if (allocateBuffer) -// { -// buffer = new T[this.m_bufferLength]; -// } - -// return buffer; -// } - -// internal void Return(T[] array) -// { -// if (array.Length != this.m_bufferLength) -// { -// throw new ArgumentException(); -// } - -// bool returned; - -// var lockTaken = false; -// try -// { -// this.m_lock.Enter(ref lockTaken); - -// returned = this.m_index != 0; -// if (returned) -// { -// this.m_buffers[--this.m_index] = array; -// } -// } -// finally -// { -// if (lockTaken) -// { -// this.m_lock.Exit(false); -// } -// } -// } -// } -//} -//#endif diff --git a/src/TouchSocket.Core/Pool/ByteBlockExtension.cs b/src/TouchSocket.Core/Pool/ByteBlockExtension.cs deleted file mode 100644 index 4e3541a49..000000000 --- a/src/TouchSocket.Core/Pool/ByteBlockExtension.cs +++ /dev/null @@ -1,183 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; - -namespace TouchSocket.Core; - -/// -/// 提供字节块扩展方法的静态类。 -/// -public static class ByteBlockExtension -{ - /// - /// 将值类型的字节块转换为普通的字节块。 - /// - /// 要转换的值类型字节块。 - /// 一个新的字节块对象。 - public static ByteBlock AsByteBlock(this in ValueByteBlock valueByteBlock) - { - return new ByteBlock(valueByteBlock); - } - - /// - /// 将字节块转换为字节块流。 - /// - /// 要转换的字节块。 - /// 是否在释放字节块时一起释放关联的资源,默认为true。 - /// 一个新的字节块流对象。 - public static ByteBlockStream AsStream(this ByteBlock byteBlock, bool releaseTogether = true) - { - return new ByteBlockStream(byteBlock, releaseTogether); - } - - #region ToArray - - /// - /// 将指定的字节块转换为【新】字节数组。 - /// - /// 实现接口的字节块类型。 - /// 字节块对象。 - /// 起始偏移量。 - /// 要转换为数组的长度。 - /// 包含指定长度的【新】字节数组。 - public static byte[] ToArray(this TByteBlock byteBlock, int offset, int length) where TByteBlock : IByteBlock - { - return byteBlock.Span.Slice(offset, length).ToArray(); - } - - /// - /// 将指定的字节块转换为【新】字节数组,从指定偏移量开始,直到字节块的末尾。 - /// - /// 实现接口的字节块类型。 - /// 字节块对象。 - /// 起始偏移量。 - /// 从指定偏移量到字节块末尾的【新】字节数组。 - public static byte[] ToArray(this TByteBlock byteBlock, int offset) where TByteBlock : IByteBlock - { - return ToArray(byteBlock, offset, byteBlock.Length - offset); - } - - /// - /// 将指定的字节块转换为【新】字节数组,从索引0开始,直到字节块的末尾。 - /// - /// 实现接口的字节块类型。 - /// 字节块对象。 - /// 整个字节块的【新】字节数组。 - public static byte[] ToArray(this TByteBlock byteBlock) where TByteBlock : IByteBlock - { - return ToArray(byteBlock, 0, byteBlock.Length); - } - - /// - /// 将指定的字节块从当前位置转换为【新】字节数组,直到字节块的末尾。 - /// - /// 实现接口的字节块类型。 - /// 字节块对象。 - /// 从当前位置到字节块末尾的【新】字节数组。 - public static byte[] ToArrayTake(this TByteBlock byteBlock) where TByteBlock : IByteBlock - { - return ToArray(byteBlock, byteBlock.Position, byteBlock.CanReadLength); - } - - /// - /// 将指定的字节块从当前位置转换为【新】字节数组,指定长度。 - /// - /// 实现接口的字节块类型。 - /// 字节块对象。 - /// 要转换为数组的长度。 - /// 从当前位置开始,指定长度的【新】字节数组。 - public static byte[] ToArrayTake(this TByteBlock byteBlock, int length) where TByteBlock : IByteBlock - { - return ToArray(byteBlock, byteBlock.Position, length); - } - - #endregion ToArray - - #region AsSegment - - /// - /// 将字节块【作为】数组段。 - /// - /// 【作为】的意思是,导出的数据内存实际上依旧是生命周期内的,不能脱离生命周期使用。 - /// - /// - /// 实现接口的字节块类型。 - /// 要转换的字节块实例。 - /// 数组段的起始偏移量。 - /// 数组段的长度。 - /// 一个包含指定偏移量和长度的数组段。 - public static ArraySegment AsSegment(this TByteBlock byteBlock, int offset, int length) where TByteBlock : IByteBlock - { - return byteBlock.TotalMemory.Slice(offset, length).GetArray(); - } - - /// - /// 将字节块【作为】数组段,从指定偏移量开始,长度为可读长度。 - /// - /// 【作为】的意思是,导出的数据内存实际上依旧是生命周期内的,不能脱离生命周期使用。 - /// - /// - /// 实现接口的字节块类型。 - /// 要转换的字节块实例。 - /// 数组段的起始偏移量。 - /// 一个从指定偏移量开始,长度为可读长度的数组段。 - public static ArraySegment AsSegment(this TByteBlock byteBlock, int offset) where TByteBlock : IByteBlock - { - return AsSegment(byteBlock, offset, byteBlock.Length - offset); - } - - /// - /// 将字节块【作为】数组段,从头开始,长度为指定长度。 - /// - /// 【作为】的意思是,导出的数据内存实际上依旧是生命周期内的,不能脱离生命周期使用。 - /// - /// - /// 实现接口的字节块类型。 - /// 要转换的字节块实例。 - /// 一个从头开始,长度为字节块长度的数组段。 - public static ArraySegment AsSegment(this TByteBlock byteBlock) where TByteBlock : IByteBlock - { - return AsSegment(byteBlock, 0, byteBlock.Length); - } - - /// - /// 将字节块【作为】数组段,从当前位置开始,指定长度。 - /// - /// 【作为】的意思是,导出的数据内存实际上依旧是生命周期内的,不能脱离生命周期使用。 - /// - /// - /// 实现接口的字节块类型。 - /// 要转换的字节块实例。 - /// 数组段的长度。 - /// 一个从当前位置开始,指定长度的数组段。 - public static ArraySegment AsSegmentTake(this TByteBlock byteBlock, int length) where TByteBlock : IByteBlock - { - return AsSegment(byteBlock, byteBlock.Position, length); - } - - /// - /// 将字节块【作为】数组段,从当前位置开始,长度为可读长度。 - /// - /// 【作为】的意思是,导出的数据内存实际上依旧是生命周期内的,不能脱离生命周期使用。 - /// - /// - /// 实现接口的字节块类型。 - /// 要转换的字节块实例。 - /// 一个从当前位置开始,长度为可读长度的数组段。 - public static ArraySegment AsSegmentTake(this TByteBlock byteBlock) where TByteBlock : IByteBlock - { - return AsSegment(byteBlock, byteBlock.Position, byteBlock.CanReadLength); - } - - #endregion AsSegment -} \ No newline at end of file diff --git a/src/TouchSocket.Core/Pool/ByteManager/ByteBlock.cs b/src/TouchSocket.Core/Pool/ByteManager/ByteBlock.cs deleted file mode 100644 index 32abf743b..000000000 --- a/src/TouchSocket.Core/Pool/ByteManager/ByteBlock.cs +++ /dev/null @@ -1,392 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; -using System.Buffers; -using System.Diagnostics; -using System.IO; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading; - -namespace TouchSocket.Core; - -/// -/// 字节块流 -/// -[DebuggerDisplay("Length={Length},Position={Position},Capacity={Capacity}")] -public sealed partial class ByteBlock : DisposableObject, IByteBlock -{ - private byte[] m_buffer; - private ArrayPool m_bytePool; - private int m_dis; - private bool m_holding; - private int m_length; - private int m_position; - - #region 构造函数 - - /// - /// 使用 ValueByteBlock 初始化 ByteBlock 对象。 - /// - /// 包含字节数据的 ValueByteBlock 对象。 - public ByteBlock(in ValueByteBlock valueByteBlock) - { - // 直接使用 ValueByteBlock 中的内存数组和字节池。 - this.m_buffer = valueByteBlock.TotalMemory.GetArray().Array; - this.m_bytePool = valueByteBlock.BytePool; - // 初始化位置和长度信息。 - this.m_position = valueByteBlock.Position; - this.m_length = valueByteBlock.Length; - // 设置一个固定的游标距离。 - this.m_dis = 1; - } - - /// - /// 无参数构造函数,初始化一个具有默认大小的 ByteBlock 对象。 - /// - /// ByteBlock 的初始大小。 - public ByteBlock(int byteSize) - { - if (byteSize < 1) - { - byteSize = 16; - } - // 使用默认字节池初始化。 - this.m_bytePool = ArrayPool.Shared; - // 从字节池租用指定大小的字节数组。 - this.m_buffer = this.m_bytePool.Rent(byteSize); - } - - /// - /// 使用指定的字节池和大小初始化 ByteBlock 对象。 - /// - /// ByteBlock 的初始大小。 - /// 用于 ByteBlock 的 BytePool 实例。 - public ByteBlock(int byteSize, ArrayPool bytePool) - { - if (byteSize < 1) - { - byteSize = 16; - } - // 确保字节池不为空。 - this.m_bytePool = ThrowHelper.ThrowArgumentNullExceptionIf(bytePool, nameof(bytePool)); - // 从指定的字节池租用字节数组。 - this.m_buffer = bytePool.Rent(byteSize); - } - - /// - /// 实例化一个已知内存的对象。且该内存不会被回收。 - /// - /// - /// - public ByteBlock(byte[] bytes, int length) - { - this.m_buffer = bytes ?? throw new ArgumentNullException(nameof(bytes)); - this.m_length = length; - } - - /// - /// 实例化一个已知内存的对象。且该内存不会被回收。 - /// - /// - public ByteBlock(byte[] bytes) : this(bytes, bytes.Length) - { - } - - #endregion 构造函数 - - #region 属性 - - /// - public ReadOnlyMemory Memory - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - this.ThrowIfDisposed(); - return new ReadOnlyMemory(this.m_buffer, 0, this.m_length); - } - } - - /// - public Memory TotalMemory - { - get - { - this.ThrowIfDisposed(); - return new Memory(this.m_buffer); - } - } - - /// - public ReadOnlySpan Span - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - this.ThrowIfDisposed(); - return new ReadOnlySpan(this.m_buffer, 0, this.m_length); - } - } - - /// - public bool CanRead => this.Using && this.CanReadLength > 0; - - /// - public int CanReadLength => this.m_length - this.m_position; - - /// - public int Capacity => this.m_buffer.Length; - - /// - public int FreeLength => this.Capacity - this.m_position; - - /// - public bool Holding => this.m_holding; - - /// - public int Length => this.m_length; - - /// - public int Position - { - get => this.m_position; - set => this.m_position = value; - } - - /// - public bool Using => this.m_dis == 0; - - /// - public bool IsStruct => false; - - /// - public ArrayPool BytePool => this.m_bytePool; - - /// - public byte this[int index] - { - get - { - this.ThrowIfDisposed(); - return this.m_buffer[index]; - } - set - { - this.ThrowIfDisposed(); - this.m_buffer[index] = value; - } - } - - #endregion 属性 - - /// - public void Clear() - { - this.ThrowIfDisposed(); - Array.Clear(this.m_buffer, 0, this.m_buffer.Length); - } - - /// - protected override void Dispose(bool disposing) - { - if (this.DisposedValue) - { - return; - } - - if (disposing) - { - if (this.m_holding) - { - return; - } - - if (Interlocked.Increment(ref this.m_dis) == 1) - { - this.m_bytePool?.Return(this.m_buffer); - - this.m_holding = false; - this.m_position = 0; - this.m_length = 0; - this.m_buffer = null; - this.m_bytePool = null; - } - } - base.Dispose(disposing); - } - - /// - public void Reset() - { - this.ThrowIfDisposed(); - this.m_position = 0; - this.m_length = 0; - this.m_holding = false; - } - - /// - public void SetCapacity(int capacity, bool retainedData = false) - { - this.ThrowIfDisposed(); - - if (this.Capacity == capacity) - { - return; - } - - byte[] bytes; - bool canReturn; - if (this.m_bytePool == null) - { - this.m_bytePool = ArrayPool.Shared; - canReturn = false; - } - else - { - canReturn = true; - } - - bytes = this.m_bytePool.Rent(capacity); - - if (retainedData) - { - Array.Copy(this.m_buffer, 0, bytes, 0, this.m_buffer.Length); - } - - if (canReturn) - { - this.m_bytePool.Return(this.m_buffer); - } - this.m_buffer = bytes; - } - - /// - public void SetHolding(bool holding) - { - this.ThrowIfDisposed(); - if (holding) - { - this.m_holding = holding; - } - else - { - this.Dispose(); - } - } - - /// - public void SetLength(int value) - { - this.ThrowIfDisposed(); - if (value > this.m_buffer.Length) - { - throw new Exception("设置值超出容量"); - } - this.m_length = value; - } - - #region Seek - - /// - public int Seek(int offset, SeekOrigin origin) - { - this.ThrowIfDisposed(); - switch (origin) - { - case SeekOrigin.Begin: - this.m_position = offset; - break; - - case SeekOrigin.Current: - this.m_position += offset; - break; - - case SeekOrigin.End: - this.m_position = this.m_length + offset; - break; - } - return this.m_position; - } - - /// - public void Seek(int position) - { - this.m_position = position; - } - - /// - public void SeekToEnd() - { - this.m_position = this.m_length; - } - - /// - public void SeekToStart() - { - this.m_position = 0; - } - - #endregion Seek - - #region ToString - - /// - public override string ToString() - { - return this.ToString(0, this.Length); - } - - /// - public string ToString(int offset, int length) - { - this.ThrowIfDisposed(); - - return Encoding.UTF8.GetString(this.m_buffer, offset, length); - } - - /// - public string ToString(int offset) - { - this.ThrowIfDisposed(); - - return Encoding.UTF8.GetString(this.m_buffer, offset, this.Length - offset); - } - - #endregion ToString - - #region BufferWriter - - /// - public void Advance(int count) - { - this.m_position += count; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - /// - public Memory GetMemory(int sizeHint = 0) - { - this.ExtendSize(sizeHint); - return new Memory(this.m_buffer, this.m_position, this.m_buffer.Length - this.m_position); - } - - /// - public Span GetSpan(int sizeHint = 0) - { - this.ExtendSize(sizeHint); - return new Span(this.m_buffer, this.m_position, this.m_buffer.Length - this.m_position); - } - - #endregion BufferWriter -} \ No newline at end of file diff --git a/src/TouchSocket.Core/Pool/ByteManager/ByteBlockStream.cs b/src/TouchSocket.Core/Pool/ByteManager/ByteBlockStream.cs deleted file mode 100644 index 4a378f8f7..000000000 --- a/src/TouchSocket.Core/Pool/ByteManager/ByteBlockStream.cs +++ /dev/null @@ -1,260 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; -using System.Diagnostics; -using System.IO; -using System.Runtime.CompilerServices; - -namespace TouchSocket.Core; - -/// -/// 字节块流 -/// -[DebuggerDisplay("Len={Length},Pos={Position},Capacity={Capacity}")] -public sealed partial class ByteBlockStream : Stream -{ - private readonly ByteBlock m_byteBlock; - private readonly bool m_releaseTogether; - - - /// - /// 初始化 ByteBlockStream 类的新实例。 - /// - /// 一个 ByteBlock 对象,表示字节块。 - /// 一个布尔值,指示是否在释放流时同时释放字节块。 - public ByteBlockStream(ByteBlock byteBlock, bool releaseTogether) - { - this.m_byteBlock = byteBlock; - this.m_releaseTogether = releaseTogether; - } - - /// - /// 获取此实例关联的 ByteBlock 对象。 - /// - public ByteBlock ByteBlock => this.m_byteBlock; - - /// - /// 仅当内存块可用,且>0时为。 - /// - public override bool CanRead => this.Using && this.CanReadLength > 0; - - /// - /// 还能读取的长度,计算为的差值。 - /// - public long CanReadLength => this.m_byteBlock.Length - this.m_byteBlock.Position; - - /// - /// 支持查找 - /// - public override bool CanSeek => this.Using; - - /// - /// 可写入 - /// - public override bool CanWrite => this.Using; - - /// - /// 容量 - /// - public int Capacity => this.m_byteBlock.Capacity; - - /// - /// 空闲长度,准确掌握该值,可以避免内存扩展,计算为的差值。 - /// - public long FreeLength => this.Capacity - this.Position; - - /// - /// 真实长度 - /// - public override long Length => this.m_byteBlock.Length; - - /// - /// 流位置 - /// - public override long Position - { - get => this.m_byteBlock.Position; - set => this.m_byteBlock.Position = (int)value; - } - - /// - /// 使用状态 - /// - public bool Using => this.m_byteBlock.Using; - - /// - /// 清空所有内存数据 - /// - /// 内存块已释放 - public void Clear() - { - this.ThrowIfDisposed(); - this.m_byteBlock.Clear(); - } - - /// - /// 无实际效果 - /// - public override void Flush() - { - } - - /// - /// 读取数据,然后递增Pos - /// - /// - /// - /// - /// - /// - public override int Read(byte[] buffer, int offset, int length) - { - this.ThrowIfDisposed(); - return this.m_byteBlock.Read(new Span(buffer, offset, length)); - } - - /// - /// 从当前流位置读取一个值 - /// - public override int ReadByte() - { - return this.m_byteBlock.ReadByte(); - } - - /// - /// 设置流位置 - /// - /// - /// - /// - /// - public override long Seek(long offset, SeekOrigin origin) - { - this.ThrowIfDisposed(); - return this.m_byteBlock.Seek((int)offset, origin); - } - - /// - /// 移动游标 - /// - /// - /// - public void Seek(int position) - { - this.Position = position; - } - - /// - /// 设置游标到末位 - /// - /// - public void SeekToEnd() - { - this.Position = this.m_byteBlock.Length; - } - - /// - /// 设置游标到首位 - /// - /// - public void SeekToStart() - { - this.Position = 0; - } - - /// - /// 设置实际长度 - /// - /// - /// - public override void SetLength(long value) - { - this.ThrowIfDisposed(); - this.m_byteBlock.SetLength((int)value); - } - - /// - /// 从指定位置转化到指定长度的有效内存。本操作不递增 - /// - /// - /// - /// - public byte[] ToArray(int offset, int length) - { - this.ThrowIfDisposed(); - return this.m_byteBlock.ToArray(offset, length); - } - - /// - /// 转换为有效内存。本操作不递增 - /// - /// - public byte[] ToArray() - { - return this.ToArray(0, (int)this.Length); - } - - /// - /// 从指定位置转为有效内存。本操作不递增 - /// - /// - /// - public byte[] ToArray(int offset) - { - return this.ToArray(offset, (int)(this.Length - offset)); - } - - /// - /// 将当前至指定长度转化为有效内存。本操作不递增 - /// - /// - /// - public byte[] ToArrayTake(int length) - { - return this.ToArray((int)this.Position, length); - } - - /// - /// 写入 - /// - /// - /// - /// - /// - public override void Write(byte[] buffer, int offset, int count) - { - this.ThrowIfDisposed(); - this.m_byteBlock.Write(new ReadOnlySpan(buffer, offset, count)); - } - - /// - /// - protected override void Dispose(bool disposing) - { - if (disposing && this.m_releaseTogether) - { - this.m_byteBlock.Dispose(); - } - - base.Dispose(disposing); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ThrowIfDisposed() - { - if (!this.Using) - { - throw new ObjectDisposedException(this.GetType().FullName); - } - } -} \ No newline at end of file diff --git a/src/TouchSocket.Core/Pool/ByteManager/ByteBlock_Read.cs b/src/TouchSocket.Core/Pool/ByteManager/ByteBlock_Read.cs deleted file mode 100644 index 17384bc06..000000000 --- a/src/TouchSocket.Core/Pool/ByteManager/ByteBlock_Read.cs +++ /dev/null @@ -1,1047 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using System.Text; - -namespace TouchSocket.Core; - -public sealed partial class ByteBlock -{ - #region Read - - /// - public int Read(Span span) - { - // 获取目标数组的长度 - var length = span.Length; - // 如果目标数组长度为0,则无需读取,直接返回0 - if (length == 0) - { - return 0; - } - // 计算本次可以读取的长度,不超过当前流中剩余的未读取长度,也不超过目标数组的长度 - var len = this.m_length - this.m_position > length ? length : this.CanReadLength; - // 使用不安全方式复制数据块 - // 从流的当前位置开始复制len长度的字节数据到目标数组中 - Unsafe.CopyBlock(ref span[0], ref this.m_buffer[this.m_position], (uint)len); - // 更新流的当前位置,移动到已读取的位置之后 - this.m_position += len; - // 返回实际读取到的字节数 - return len; - } - /// - public ReadOnlySpan ReadToSpan(int length) - { - var span = new ReadOnlySpan(this.m_buffer, this.m_position, length); - - this.m_position += length; - return span; - } - #endregion - - #region ByteBlock - - /// - public ByteBlock ReadByteBlock() - { - var len = (int)this.ReadVarUInt32() - 1; - - if (len < 0) - { - return default; - } - - var byteBlock = new ByteBlock(len); - byteBlock.Write(new ReadOnlySpan(this.m_buffer, this.m_position, len)); - byteBlock.SeekToStart(); - this.m_position += len; - return byteBlock; - } - - #endregion ByteBlock - - #region Package - - /// - public TPackage ReadPackage() where TPackage : class, IPackage, new() - { - if (this.ReadIsNull()) - { - return default; - } - else - { - var package = new TPackage(); - var block = this; - package.Unpackage(ref block); - return package; - } - } - - #endregion Package - - #region BytesPackage - - /// - public byte[] ReadBytesPackage() - { - var memory = this.ReadBytesPackageMemory(); - return memory.HasValue ? memory.Value.ToArray() : null; - } - - /// - public ReadOnlyMemory? ReadBytesPackageMemory() - { - var length = this.ReadInt32(); - if (length < 0) - { - return null; - } - - var memory = new ReadOnlyMemory(this.m_buffer, this.m_position, length); - this.m_position += length; - return memory; - } - - #endregion BytesPackage - - #region Byte - - /// - public byte ReadByte() - { - var size = 1; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - - var value = this.m_buffer[this.m_position]; - this.m_position += size; - return value; - } - - #endregion Byte - - #region String - - /// - public string ReadString(FixedHeaderType headerType = FixedHeaderType.Int) - { - int len; - switch (headerType) - { - case FixedHeaderType.Byte: - len = this.ReadByte(); - if (len == byte.MaxValue) - { - return null; - } - break; - case FixedHeaderType.Ushort: - len = this.ReadUInt16(); - if (len == ushort.MaxValue) - { - return null; - } - break; - case FixedHeaderType.Int: - default: - len = this.ReadInt32(); - if (len == int.MaxValue) - { - return null; - } - break; - } - - var str = Encoding.UTF8.GetString(this.m_buffer, this.m_position, len); - this.m_position += len; - return str; - } - - #endregion String - - #region VarUInt32 - - /// - public uint ReadVarUInt32() - { - uint value = 0; - var bytelength = 0; - while (true) - { - var b = this.m_buffer[this.m_position++]; - var temp = (b & 0x7F); //取每个字节的后7位 - temp <<= (7 * bytelength); //向左移位,越是后面的字节,移位越多 - value += (uint)temp; //把每个字节的值加起来就是最终的值了 - bytelength++; - if (b <= 0x7F) - { //127=0x7F=0b01111111,小于等于说明msb=0,即最后一个字节 - break; - } - } - return value; - } - #endregion - - #region Int32 - - /// - public int ReadInt32() - { - var size = 4; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public int ReadInt32(EndianType endianType) - { - var size = 4; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.GetBitConverter(endianType).UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public IEnumerable ToInt32s() - { - this.m_position = 0; - var size = 4; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - yield return this.ReadInt32(); - } - } - - /// - public IEnumerable ToInt32s(EndianType endianType) - { - this.m_position = 0; - var size = 4; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - yield return this.ReadInt32(endianType); - } - } - - #endregion Int32 - - #region T - - /// - public T ReadT() where T : unmanaged - { - var size = Unsafe.SizeOf(); - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public T ReadT(EndianType endianType) where T : unmanaged - { - var size = Unsafe.SizeOf(); - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.GetBitConverter(endianType).UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public IEnumerable ToTs() where T : unmanaged - { - this.m_position = 0; - var size = Unsafe.SizeOf(); - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - yield return this.ReadT(); - } - } - - /// - public IEnumerable ToTs(EndianType endianType) where T : unmanaged - { - this.m_position = 0; - var size = Unsafe.SizeOf(); - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - yield return this.ReadT(endianType); - } - } - - #endregion T - - #region Int16 - - /// - public short ReadInt16() - { - var size = 2; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public short ReadInt16(EndianType endianType) - { - var size = 2; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.GetBitConverter(endianType).UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public IEnumerable ToInt16s() - { - this.m_position = 0; - var size = 2; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - yield return this.ReadInt16(); - } - } - - /// - public IEnumerable ToInt16s(EndianType endianType) - { - this.m_position = 0; - var size = 2; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - yield return this.ReadInt16(endianType); - } - } - - #endregion Int16 - - #region Int64 - - /// - public long ReadInt64() - { - var size = 8; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public long ReadInt64(EndianType endianType) - { - var size = 8; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.GetBitConverter(endianType).UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public IEnumerable ToInt64s() - { - this.m_position = 0; - var size = 8; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - yield return this.ReadInt64(); - } - } - - /// - public IEnumerable ToInt64s(EndianType endianType) - { - this.m_position = 0; - var size = 8; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - yield return this.ReadInt64(endianType); - } - } - - #endregion Int64 - - #region Boolean - - /// - public bool ReadBoolean() - { - var size = 1; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public bool[] ReadBooleans() - { - var size = 1; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.ToBooleansByBit(ref this.m_buffer[this.m_position], 1); - this.m_position += size; - return value; - } - - /// - public IEnumerable ToBoolensFromBit() - { - this.m_position = 0; - var size = 1; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - foreach (var item in this.ReadBooleans()) - { - yield return item; - } - } - } - - /// - public IEnumerable ToBoolensFromByte() - { - this.m_position = 0; - var size = 1; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - yield return this.ReadBoolean(); - } - } - - #endregion Boolean - - #region Char - - /// - public char ReadChar() - { - var size = 2; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public char ReadChar(EndianType endianType) - { - var size = 1; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.GetBitConverter(endianType).UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public IEnumerable ToChars() - { - this.m_position = 0; - var list = new List(); - var size = 2; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadChar()); - } - return list; - } - - /// - public IEnumerable ToChars(EndianType endianType) - { - this.m_position = 0; - var list = new List(); - var size = 2; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadChar(endianType)); - } - return list; - } - - #endregion Char - - #region Double - - /// - public double ReadDouble() - { - var size = 8; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public double ReadDouble(EndianType endianType) - { - var size = 8; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.GetBitConverter(endianType).UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public IEnumerable ToDoubles() - { - this.m_position = 0; - var list = new List(); - var size = 8; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadDouble()); - } - return list; - } - - /// - public IEnumerable ToDoubles(EndianType endianType) - { - this.m_position = 0; - var list = new List(); - var size = 8; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadDouble(endianType)); - } - return list; - } - - #endregion Double - - #region Float - - /// - public float ReadFloat() - { - var size = 4; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public float ReadFloat(EndianType endianType) - { - var size = 4; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.GetBitConverter(endianType).UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public IEnumerable ToFloats() - { - this.m_position = 0; - var list = new List(); - var size = 4; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadFloat()); - } - return list; - } - - /// - public IEnumerable ToFloats(EndianType endianType) - { - this.m_position = 0; - var list = new List(); - var size = 4; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadFloat(endianType)); - } - return list; - } - - #endregion Float - - #region UInt16 - - /// - public ushort ReadUInt16() - { - var size = 2; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public ushort ReadUInt16(EndianType endianType) - { - var size = 2; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.GetBitConverter(endianType).UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public IEnumerable ToUInt16s() - { - this.m_position = 0; - var list = new List(); - var size = 2; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadUInt16()); - } - return list; - } - - /// - public IEnumerable ToUInt16s(EndianType endianType) - { - this.m_position = 0; - var list = new List(); - var size = 2; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadUInt16(endianType)); - } - return list; - } - - #endregion UInt16 - - #region UInt32 - - /// - public uint ReadUInt32() - { - var size = 4; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public uint ReadUInt32(EndianType endianType) - { - var size = 4; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.GetBitConverter(endianType).UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public IEnumerable ToUInt32s() - { - this.m_position = 0; - var list = new List(); - var size = 4; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadUInt32()); - } - return list; - } - - /// - public IEnumerable ToUInt32s(EndianType endianType) - { - this.m_position = 0; - var list = new List(); - var size = 4; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadUInt32(endianType)); - } - return list; - } - - #endregion UInt32 - - #region UInt64 - - /// - public ulong ReadUInt64() - { - var size = 8; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public ulong ReadUInt64(EndianType endianType) - { - var size = 8; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.GetBitConverter(endianType).UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public IEnumerable ToUInt64s() - { - this.m_position = 0; - var list = new List(); - var size = 8; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadUInt64()); - } - return list; - } - - /// - public IEnumerable ToUInt64s(EndianType endianType) - { - this.m_position = 0; - var list = new List(); - var size = 8; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadUInt64(endianType)); - } - return list; - } - - #endregion UInt64 - - #region Decimal - - /// - public decimal ReadDecimal() - { - var size = 16; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public decimal ReadDecimal(EndianType endianType) - { - var size = 16; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.GetBitConverter(endianType).UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public IEnumerable ToDecimals() - { - this.m_position = 0; - var list = new List(); - var size = 16; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadDecimal()); - } - return list; - } - - /// - public IEnumerable ToDecimals(EndianType endianType) - { - this.m_position = 0; - var list = new List(); - var size = 16; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadDecimal(endianType)); - } - return list; - } - - #endregion Decimal - - #region Null - - /// - public bool ReadIsNull() - { - var status = this.ReadByte(); - return status == 0 || (status == 1 ? false : throw new Exception("标识既非Null,也非NotNull,可能是流位置发生了错误。")); - } - - #endregion Null - - #region DateTime - - /// - public DateTime ReadDateTime() - { - var size = 8; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.BigEndian.UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return DateTime.FromBinary(value); - } - - /// - public IEnumerable ToDateTimes() - { - this.m_position = 0; - var list = new List(); - var size = 8; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadDateTime()); - } - return list; - } - - #endregion DateTime - - #region TimeSpan - - /// - public TimeSpan ReadTimeSpan() - { - var size = 8; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.BigEndian.UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return TimeSpan.FromTicks(value); - } - - /// - public IEnumerable ToTimeSpans() - { - this.m_position = 0; - var list = new List(); - var size = 8; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadTimeSpan()); - } - return list; - } - - #endregion TimeSpan - - #region GUID - /// - public Guid ReadGuid() - { - Guid guid; -#if NET6_0_OR_GREATER - guid = new Guid(this.Span.Slice(this.m_position, 16)); -#else - - var bytes = this.Span.Slice(this.m_position, 16).ToArray(); - guid = new Guid(bytes); -#endif - this.m_position += 16; - return guid; - } - - #endregion GUID -} \ No newline at end of file diff --git a/src/TouchSocket.Core/Pool/ByteManager/ByteBlock_Write.cs b/src/TouchSocket.Core/Pool/ByteManager/ByteBlock_Write.cs deleted file mode 100644 index 37f497ef8..000000000 --- a/src/TouchSocket.Core/Pool/ByteManager/ByteBlock_Write.cs +++ /dev/null @@ -1,680 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; -using System.Runtime.CompilerServices; -using System.Text; - -namespace TouchSocket.Core; - -public sealed partial class ByteBlock -{ - #region Write - /// - public unsafe void Write(ReadOnlySpan span) - { - this.ThrowIfDisposed(); - - if (span.IsEmpty) - { - return; - } - - this.ExtendSize(span.Length); - - fixed (byte* p1 = &span[0]) - { - fixed (byte* p2 = &this.m_buffer[this.m_position]) - { - Unsafe.CopyBlock(p2, p1, (uint)span.Length); - } - } - this.m_position += span.Length; - this.m_length = Math.Max(this.m_position, this.m_length); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ExtendSize(int size) - { - if (this.m_buffer.Length - this.m_position < size) - { - var need = this.m_buffer.Length + size - (this.m_buffer.Length - this.m_position); - long lend = this.m_buffer.Length; - while (need > lend) - { - lend *= 2; - } - - if (lend > int.MaxValue) - { - lend = Math.Min(need + 1024 * 1024 * 100, int.MaxValue); - } - this.SetCapacity((int)lend, true); - } - } - - #endregion Write - - #region ByteBlock - - /// - public void WriteByteBlock(ByteBlock byteBlock) - { - if (byteBlock is null) - { - this.WriteVarUInt32(0); - } - else - { - this.WriteVarUInt32((uint)(byteBlock.Length + 1)); - this.Write(byteBlock.Span); - } - } - - #endregion ByteBlock - - #region Package - - /// - public void WritePackage(TPackage package) where TPackage : class, IPackage - { - this.WriteIsNull(package); - var byteBlock = this; - package?.Package(ref byteBlock); - } - - #endregion Package - - #region Null - - /// - public void WriteIsNull(T t) where T : class - { - if (t == null) - { - this.WriteNull(); - } - else - { - this.WriteNotNull(); - } - } - - /// - public void WriteIsNull(T? t) where T : struct - { - if (t.HasValue) - { - this.WriteNotNull(); - } - else - { - this.WriteNull(); - } - } - - /// - public void WriteNotNull() - { - this.WriteByte(1); - } - - /// - public void WriteNull() - { - this.WriteByte(0); - } - - #endregion Null - - #region BytesPackage - - /// - public void WriteBytesPackage(byte[] value, int offset, int length) - { - if (value == null) - { - this.WriteInt32(-1); - } - else if (length == 0) - { - this.WriteInt32(0); - } - else - { - this.WriteInt32(length); - this.Write(new Span(value, offset, length)); - } - } - - /// - public void WriteBytesPackage(byte[] value) - { - if (value == null) - { - this.WriteInt32(-1); - } - else if (value.Length == 0) - { - this.WriteInt32(0); - } - else - { - this.WriteInt32(value.Length); - this.Write(new Span(value, 0, value.Length)); - } - } - - #endregion BytesPackage - - #region String - - /// - public void WriteString(string value, FixedHeaderType headerType = FixedHeaderType.Int) - { - if (value == null) - { - switch (headerType) - { - case FixedHeaderType.Byte: - this.WriteByte(byte.MaxValue); - return; - case FixedHeaderType.Ushort: - this.WriteUInt16(ushort.MaxValue); - return; - case FixedHeaderType.Int: - default: - this.WriteInt32(int.MaxValue); - return; - } - - } - else if (value == string.Empty) - { - switch (headerType) - { - case FixedHeaderType.Byte: - this.WriteByte(0); - return; - case FixedHeaderType.Ushort: - this.WriteUInt16(0); - return; - case FixedHeaderType.Int: - default: - this.WriteInt32(0); - return; - } - } - else - { - var maxSize = (value.Length + 1) * 3 + 4; - this.ExtendSize(maxSize); - var chars = value.AsSpan(); - - var offset = headerType switch - { - FixedHeaderType.Byte => (byte)1, - FixedHeaderType.Ushort => (byte)2, - _ => (byte)4, - }; - - var pos = this.m_position; - - //this.m_position += offset; - - unsafe - { - fixed (char* p = &chars[0]) - { - fixed (byte* p1 = &this.m_buffer[this.m_position + offset]) - { - var len = Encoding.UTF8.GetBytes(p, chars.Length, p1, maxSize); - - switch (headerType) - { - case FixedHeaderType.Byte: - if (len >= byte.MaxValue) - { - ThrowHelper.ThrowArgumentOutOfRangeException_MoreThan(nameof(value), len, byte.MaxValue); - } - - this.WriteByte((byte)len); - break; - case FixedHeaderType.Ushort: - if (len >= ushort.MaxValue) - { - ThrowHelper.ThrowArgumentOutOfRangeException_MoreThan(nameof(value), len, ushort.MaxValue); - } - this.WriteUInt16((ushort)len); - break; - case FixedHeaderType.Int: - default: - if (len >= int.MaxValue) - { - ThrowHelper.ThrowArgumentOutOfRangeException_MoreThan(nameof(value), len, int.MaxValue); - } - this.WriteInt32(len); - break; - } - - this.m_position += len; - - this.m_length = Math.Max(this.m_position, this.m_length); - } - - } - } - } - } - #endregion String - - #region NormalString - /// - public void WriteNormalString(string value, Encoding encoding) - { - ThrowHelper.ThrowArgumentNullExceptionIf(value, nameof(value)); - var maxSize = encoding.GetMaxByteCount(value.Length); - this.ExtendSize(maxSize); - var chars = value.AsSpan(); - - unsafe - { - fixed (char* p = &chars[0]) - { - fixed (byte* p1 = &this.m_buffer[this.m_position]) - { - var len = Encoding.UTF8.GetBytes(p, chars.Length, p1, maxSize); - - this.m_position += len; - - this.m_length = Math.Max(this.m_position, this.m_length); - } - - } - } - } - #endregion - - #region VarUInt32 - - /// - public int WriteVarUInt32(uint value) - { - this.ExtendSize(5); - - byte bytelength = 0; - while (value > 0x7F) - { - //127=0x7F=0b01111111,大于说明msb=1,即后续还有字节 - var temp = value & 0x7F; //得到数值的后7位,0x7F=0b01111111,0与任何数与都是0,1与任何数与还是任何数 - temp |= 0x80; //后7位不变最高位固定为1,0x80=0b10000000,1与任何数或都是1,0与任何数或都是任何数 - this.m_buffer[this.m_position++] = (byte)temp; //存储msb=1的数据 - value >>= 7; //右移已经计算过的7位得到下次需要计算的数值 - bytelength++; - } - this.m_buffer[this.m_position++] = (byte)value; //最后一个字节msb=0 - - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - return bytelength + 1; - } - - #endregion VarUInt32 - - #region Int32 - - /// - public void WriteInt32(int value) - { - var size = 4; - this.ExtendSize(size); - TouchSocketBitConverter.Default.GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - /// - public void WriteInt32(int value, EndianType endianType) - { - var size = 4; - this.ExtendSize(size); - TouchSocketBitConverter.GetBitConverter(endianType).GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - #endregion Int32 - - #region T - - /// - public void WriteT(T value) where T : unmanaged - { - var size = Unsafe.SizeOf(); - this.ExtendSize(size); - TouchSocketBitConverter.Default.UnsafeWriteBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - /// - public void WriteT(T value, EndianType endianType) where T : unmanaged - { - var size = Unsafe.SizeOf(); - this.ExtendSize(size); - TouchSocketBitConverter.GetBitConverter(endianType).UnsafeWriteBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - #endregion T - - #region Int16 - - /// - public void WriteInt16(short value) - { - var size = 2; - this.ExtendSize(size); - TouchSocketBitConverter.Default.GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - /// - public void WriteInt16(short value, EndianType endianType) - { - var size = 2; - this.ExtendSize(size); - TouchSocketBitConverter.GetBitConverter(endianType).GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - #endregion Int16 - - #region Int64 - - /// - public void WriteInt64(long value) - { - var size = 8; - this.ExtendSize(size); - TouchSocketBitConverter.Default.GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - /// - public void WriteInt64(long value, EndianType endianType) - { - var size = 8; - this.ExtendSize(size); - TouchSocketBitConverter.GetBitConverter(endianType).GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - #endregion Int64 - - #region Boolean - - /// - public void WriteBoolean(bool value) - { - var size = 1; - this.ExtendSize(size); - TouchSocketBitConverter.Default.GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - /// - public void WriteBooleans(ReadOnlySpan values) - { - var size = values.Length % 8 == 0 ? values.Length / 8 : values.Length / 8 + 1; - this.ExtendSize(size); - TouchSocketBitConverter.Default.GetBytes(ref this.m_buffer[this.m_position], values); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - #endregion Boolean - - #region Byte - - /// - public void WriteByte(byte value) - { - var size = 1; - this.ExtendSize(size); - this.m_buffer[this.m_position] = value; - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - #endregion Byte - - #region Char - - /// - public void WriteChar(char value) - { - var size = 2; - this.ExtendSize(size); - TouchSocketBitConverter.Default.GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - /// - public void WriteChar(char value, EndianType endianType) - { - var size = 2; - this.ExtendSize(size); - TouchSocketBitConverter.GetBitConverter(endianType).GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - #endregion Char - - #region Double - - /// - public void WriteDouble(double value) - { - var size = 8; - this.ExtendSize(size); - TouchSocketBitConverter.Default.GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - /// - public void WriteDouble(double value, EndianType endianType) - { - var size = 8; - this.ExtendSize(size); - TouchSocketBitConverter.GetBitConverter(endianType).GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - #endregion Double - - #region Float - - /// - public void WriteFloat(float value) - { - var size = 4; - this.ExtendSize(size); - TouchSocketBitConverter.Default.GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - /// - public void WriteFloat(float value, EndianType endianType) - { - var size = 4; - this.ExtendSize(size); - TouchSocketBitConverter.GetBitConverter(endianType).GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - #endregion Float - - #region UInt16 - - /// - public void WriteUInt16(ushort value) - { - var size = 2; - this.ExtendSize(size); - TouchSocketBitConverter.Default.UnsafeWriteBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - /// - public void WriteUInt16(ushort value, EndianType endianType) - { - var size = 2; - this.ExtendSize(size); - TouchSocketBitConverter.GetBitConverter(endianType).UnsafeWriteBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - #endregion UInt16 - - #region UInt32 - - /// - public void WriteUInt32(uint value) - { - var size = 4; - this.ExtendSize(size); - TouchSocketBitConverter.Default.GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - /// - public void WriteUInt32(uint value, EndianType endianType) - { - var size = 4; - this.ExtendSize(size); - TouchSocketBitConverter.GetBitConverter(endianType).GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - #endregion UInt32 - - #region UInt64 - - /// - public void WriteUInt64(ulong value) - { - var size = 8; - this.ExtendSize(size); - TouchSocketBitConverter.Default.GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - /// - public void WriteUInt64(ulong value, EndianType endianType) - { - var size = 8; - this.ExtendSize(size); - TouchSocketBitConverter.GetBitConverter(endianType).GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - #endregion UInt64 - - #region Decimal - - /// - public void WriteDecimal(decimal value) - { - var size = sizeof(decimal); - this.ExtendSize(size); - TouchSocketBitConverter.Default.GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - /// - public void WriteDecimal(decimal value, EndianType endianType) - { - var size = 16; - this.ExtendSize(size); - TouchSocketBitConverter.GetBitConverter(endianType).GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - #endregion Decimal - - #region DateTime - - /// - public void WriteDateTime(DateTime value) - { - var size = 8; - this.ExtendSize(size); - TouchSocketBitConverter.BigEndian.GetBytes(ref this.m_buffer[this.m_position], value.ToBinary()); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - #endregion DateTime - - #region TimeSpan - - /// - public void WriteTimeSpan(TimeSpan value) - { - var size = 8; - this.ExtendSize(size); - TouchSocketBitConverter.BigEndian.GetBytes(ref this.m_buffer[this.m_position], value.Ticks); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - #endregion TimeSpan - - #region GUID - /// - public void WriteGuid(in Guid value) - { - var size = 16; - this.ExtendSize(size); -#if NET6_0_OR_GREATER - value.TryWriteBytes(this.TotalMemory.Span.Slice(this.m_position, size)); -#else - var bytes = value.ToByteArray(); - this.Write(bytes); -#endif - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - #endregion GUID -} \ No newline at end of file diff --git a/src/TouchSocket.Core/Pool/ByteManager/BytePool.cs b/src/TouchSocket.Core/Pool/ByteManager/BytePool.cs deleted file mode 100644 index acc7345b6..000000000 --- a/src/TouchSocket.Core/Pool/ByteManager/BytePool.cs +++ /dev/null @@ -1,89 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; - -namespace TouchSocket.Core; - -/// -/// 内存池 -/// -[Obsolete("此类已被弃用,请使用ArrayPool代替", true)] -public sealed class BytePool -{ - static BytePool() - { - Default = new BytePool(); - } - - /// - /// 内存池 - /// - public BytePool() : this(1024 * 1024 * 10, 100) - { - } - - /// - /// 内存池 - /// - /// - /// - public BytePool(int maxArrayLength, int maxArraysPerBucket) - { - this.AutoZero = false; - this.MaxBlockSize = maxArrayLength; - } - - /// - /// 默认的内存池实例 - /// - public static BytePool Default { get; private set; } - - /// - /// 设置默认内存池实例。 - /// - /// - public static void SetDefault(BytePool bytePool) - { - Default = bytePool; - } - - /// - /// 回收内存时,自动归零 - /// - public bool AutoZero { get; set; } - - /// - /// 单个块最大值 - /// - public int MaxBlockSize { get; private set; } - - /// - /// 获取ByteBlock - /// - /// 长度 - /// - public ByteBlock GetByteBlock(int byteSize) - { - throw new NotImplementedException(); - } - - /// - /// 获取ValueByteBlock - /// - /// - /// - public ValueByteBlock GetValueByteBlock(int byteSize) - { - throw new NotImplementedException(); - } -} \ No newline at end of file diff --git a/src/TouchSocket.Core/Pool/ByteManager/ValueByteBlock.cs b/src/TouchSocket.Core/Pool/ByteManager/ValueByteBlock.cs deleted file mode 100644 index de51fb7de..000000000 --- a/src/TouchSocket.Core/Pool/ByteManager/ValueByteBlock.cs +++ /dev/null @@ -1,384 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; -using System.Buffers; -using System.Diagnostics; -using System.IO; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading; - -namespace TouchSocket.Core; - -/// -/// 字节块流 -/// -[DebuggerDisplay("Length={Length},Position={Position},Capacity={Capacity}")] -public partial struct ValueByteBlock : IByteBlock, IEquatable -{ - private static ValueByteBlock s_empty = new ValueByteBlock(); - private byte[] m_buffer; - private ArrayPool m_bytePool; - private int m_dis; - private bool m_holding; - private int m_length; - private int m_position; - - #region 构造函数 - - - /// - /// 初始化 ValueByteBlock 类的新实例,从默认字节池租用指定大小的字节。 - /// - /// 要从字节池租用的字节数。 - public ValueByteBlock(int byteSize) - { - if (byteSize < 1) - { - byteSize = 16; - } - this.m_bytePool = ArrayPool.Shared; - this.m_buffer = this.m_bytePool.Rent(byteSize); - } - - /// - /// 初始化 ValueByteBlock 类的新实例,从指定的字节池租用指定大小的字节。 - /// - /// 要从字节池租用的字节数。 - /// 用于租用字节的 BytePool 实例。 - public ValueByteBlock(int byteSize, ArrayPool bytePool) - { - if (byteSize < 1) - { - byteSize = 16; - } - this.m_bytePool = bytePool; - this.m_buffer = bytePool.Rent(byteSize); - } - - /// - /// 初始化 ValueByteBlock 类的新实例,使用现有的字节数组。 - /// - /// 包含字节的数组。 - /// 数组中要使用的字节数。 - /// 如果 bytes 参数为 null,则引发此异常。 - public ValueByteBlock(byte[] bytes, int length) - { - this.m_buffer = bytes ?? throw new ArgumentNullException(nameof(bytes)); - this.m_length = length; - } - - /// - /// 初始化 ValueByteBlock 类的新实例,使用现有的字节数组,并使用整个数组的长度。 - /// - /// 要使用的字节数组。 - /// 如果 bytes 参数为 null,则引发此异常。 - public ValueByteBlock(byte[] bytes) : this(bytes, bytes.Length) - { - } - - #endregion 构造函数 - - #region 属性 - - /// - public static ValueByteBlock Empty => s_empty; - - /// - public readonly ArrayPool BytePool => this.m_bytePool; - - /// - public bool CanRead => this.Using && this.CanReadLength > 0; - - /// - public readonly int CanReadLength => this.m_length - this.m_position; - - /// - public readonly int Capacity => this.m_buffer.Length; - - /// - public readonly int FreeLength => this.Capacity - this.m_position; - - /// - public readonly bool Holding => this.m_holding; - - /// - public readonly bool IsEmpty => this.m_buffer == null; - - /// - public readonly bool IsStruct => true; - - /// - public readonly int Length => this.m_length; - - /// - public ReadOnlyMemory Memory - { - get - { - this.ThrowIfDisposed(); - return new ReadOnlyMemory(this.m_buffer, 0, this.m_length); - } - } - - /// - public int Position - { - get => this.m_position; - set => this.m_position = value; - } - - /// - public ReadOnlySpan Span - { - get - { - this.ThrowIfDisposed(); - return new ReadOnlySpan(this.m_buffer, 0, this.m_length); - } - } - - /// - public Memory TotalMemory - { - get - { - this.ThrowIfDisposed(); - return new Memory(this.m_buffer); - } - } - - /// - public readonly bool Using => this.m_dis == 0; - - /// - public readonly byte this[int index] - { - get - { - this.ThrowIfDisposed(); - return this.m_buffer[index]; - } - set - { - this.ThrowIfDisposed(); - this.m_buffer[index] = value; - } - } - - #endregion 属性 - - /// - public readonly void Clear() - { - this.ThrowIfDisposed(); - Array.Clear(this.m_buffer, 0, this.m_buffer.Length); - } - - /// - public void Dispose() - { - var dis = this.m_dis; - if (dis != 0) - { - return; - } - - if (this.m_holding) - { - return; - } - - if (Interlocked.Increment(ref this.m_dis) == 1) - { - this.m_bytePool?.Return(this.m_buffer); - this.m_holding = false; - this.m_position = 0; - this.m_length = 0; - this.m_buffer = null; - this.m_bytePool = null; - } - } - - /// - public readonly bool Equals(ValueByteBlock other) - { - return this.m_buffer == other.m_buffer; - } - - /// - public void Reset() - { - this.ThrowIfDisposed(); - this.m_position = 0; - this.m_length = 0; - } - - /// - public void SetCapacity(int capacity, bool retainedData = false) - { - this.ThrowIfDisposed(); - - if (this.Capacity == capacity) - { - return; - } - - byte[] bytes; - bool canReturn; - if (this.m_bytePool == null) - { - this.m_bytePool = ArrayPool.Shared; - canReturn = false; - } - else - { - canReturn = true; - } - - bytes = this.m_bytePool.Rent(capacity); - - if (retainedData) - { - Array.Copy(this.m_buffer, 0, bytes, 0, this.m_buffer.Length); - } - - if (canReturn) - { - this.m_bytePool.Return(this.m_buffer); - } - this.m_buffer = bytes; - } - - /// - public void SetHolding(bool holding) - { - this.ThrowIfDisposed(); - this.m_holding = holding; - if (!holding) - { - this.Dispose(); - } - } - - /// - public void SetLength(int value) - { - this.ThrowIfDisposed(); - if (value > this.m_buffer.Length) - { - throw new Exception("设置值超出容量"); - } - this.m_length = value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private readonly bool ThrowIfDisposed() - { - return this.m_dis != 0; - } - - #region Seek - - /// - public int Seek(int offset, SeekOrigin origin) - { - this.ThrowIfDisposed(); - switch (origin) - { - case SeekOrigin.Begin: - this.m_position = offset; - break; - - case SeekOrigin.Current: - this.m_position += offset; - break; - - case SeekOrigin.End: - this.m_position = this.m_length + offset; - break; - } - return this.m_position; - } - - /// - public void Seek(int position) - { - this.m_position = position; - } - - /// - public void SeekToEnd() - { - this.m_position = this.m_length; - } - - /// - public void SeekToStart() - { - this.m_position = 0; - } - - #endregion Seek - - #region ToString - - /// - public override string ToString() - { - return this.ToString(0, this.Length); - } - - /// - public string ToString(int offset, int length) - { - this.ThrowIfDisposed(); - - return Encoding.UTF8.GetString(this.m_buffer, offset, length); - } - - /// - public string ToString(int offset) - { - this.ThrowIfDisposed(); - - return Encoding.UTF8.GetString(this.m_buffer, offset, this.Length - offset); - } - - #endregion ToString - - #region BufferWriter - - /// - public void Advance(int count) - { - this.m_position += count; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - /// - public Memory GetMemory(int sizeHint = 0) - { - this.ExtendSize(sizeHint); - return new Memory(this.m_buffer, this.m_position, this.m_buffer.Length - this.m_position); - } - - /// - public Span GetSpan(int sizeHint = 0) - { - this.ExtendSize(sizeHint); - return new Span(this.m_buffer, this.m_position, this.m_buffer.Length - this.m_position); - } - - #endregion BufferWriter -} \ No newline at end of file diff --git a/src/TouchSocket.Core/Pool/ByteManager/ValueByteBlock_Read.cs b/src/TouchSocket.Core/Pool/ByteManager/ValueByteBlock_Read.cs deleted file mode 100644 index 1c504bc3f..000000000 --- a/src/TouchSocket.Core/Pool/ByteManager/ValueByteBlock_Read.cs +++ /dev/null @@ -1,1063 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using System.Text; - -namespace TouchSocket.Core; - -public partial struct ValueByteBlock -{ - #region Read - - /// - public int Read(Span span) - { - var length = span.Length; - if (length == 0) - { - return 0; - } - var len = this.m_length - this.m_position > length ? length : this.CanReadLength; - Unsafe.CopyBlock(ref span[0], ref this.m_buffer[this.m_position], (uint)len); - this.m_position += len; - return len; - } - - /// - public ReadOnlySpan ReadToSpan(int length) - { - var span = new ReadOnlySpan(this.m_buffer, this.m_position, length); - - this.m_position += length; - return span; - } - - #endregion Read - - #region ByteBlock - - /// - public ByteBlock ReadByteBlock() - { - var len = (int)this.ReadVarUInt32() - 1; - - if (len < 0) - { - return default; - } - - var byteBlock = new ByteBlock(len); - byteBlock.Write(new ReadOnlySpan(this.m_buffer, this.m_position, len)); - byteBlock.SeekToStart(); - this.m_position += len; - return byteBlock; - } - - #endregion ByteBlock - - #region Package - - /// - public TPackage ReadPackage() where TPackage : class, IPackage, new() - { - if (this.ReadIsNull()) - { - return default; - } - else - { - var package = new TPackage(); - var block = this; - package.Unpackage(ref block); - return package; - } - } - - #endregion Package - - #region BytesPackage - - /// - public byte[] ReadBytesPackage() - { - var memory = this.ReadBytesPackageMemory(); - return memory.HasValue ? memory.Value.ToArray() : null; - } - - /// - public ReadOnlyMemory? ReadBytesPackageMemory() - { - var length = this.ReadInt32(); - if (length < 0) - { - return null; - } - - var memory = new ReadOnlyMemory(this.m_buffer, this.m_position, length); - this.m_position += length; - return memory; - } - - #endregion BytesPackage - - #region Byte - - /// - public byte ReadByte() - { - var size = 1; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - - var value = this.m_buffer[this.m_position]; - this.m_position += size; - return value; - } - - #endregion Byte - - #region String - - /// - public string ReadString(FixedHeaderType headerType = FixedHeaderType.Int) - { - int len; - switch (headerType) - { - case FixedHeaderType.Byte: - len = this.ReadByte(); - if (len == byte.MaxValue) - { - return null; - } - break; - - case FixedHeaderType.Ushort: - len = this.ReadUInt16(); - if (len == ushort.MaxValue) - { - return null; - } - break; - - case FixedHeaderType.Int: - default: - len = this.ReadInt32(); - if (len == int.MaxValue) - { - return null; - } - break; - } - - var str = Encoding.UTF8.GetString(this.m_buffer, this.m_position, len); - this.m_position += len; - return str; - } - - #endregion String - - #region VarUInt32 - - /// - public uint ReadVarUInt32() - { - uint value = 0; - var bytelength = 0; - while (true) - { - var b = this.m_buffer[this.m_position++]; - var temp = (b & 0x7F); //取每个字节的后7位 - temp <<= (7 * bytelength); //向左移位,越是后面的字节,移位越多 - value += (uint)temp; //把每个字节的值加起来就是最终的值了 - bytelength++; - if (b <= 0x7F) - { //127=0x7F=0b01111111,小于等于说明msb=0,即最后一个字节 - break; - } - } - return value; - } - - #endregion VarUInt32 - - #region T - - /// - public T ReadT() where T : unmanaged - { - var size = Unsafe.SizeOf(); - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public T ReadT(EndianType endianType) where T : unmanaged - { - var size = Unsafe.SizeOf(); - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.GetBitConverter(endianType).UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public IEnumerable ToTs() where T : unmanaged - { - this.m_position = 0; - var list = new List(); - var size = Unsafe.SizeOf(); - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadT()); - } - return list; - } - - /// - public IEnumerable ToTs(EndianType endianType) where T : unmanaged - { - this.m_position = 0; - var list = new List(); - var size = Unsafe.SizeOf(); - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadT(endianType)); - } - return list; - } - - #endregion T - - #region Int32 - - /// - public int ReadInt32() - { - var size = 4; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public int ReadInt32(EndianType endianType) - { - var size = 4; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.GetBitConverter(endianType).UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public IEnumerable ToInt32s() - { - this.m_position = 0; - var list = new List(); - var size = 4; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadInt32()); - } - return list; - } - - /// - public IEnumerable ToInt32s(EndianType endianType) - { - this.m_position = 0; - var list = new List(); - var size = 4; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadInt32(endianType)); - } - return list; - } - - #endregion Int32 - - #region Int16 - - /// - public short ReadInt16() - { - var size = 2; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public short ReadInt16(EndianType endianType) - { - var size = 2; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.GetBitConverter(endianType).UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public IEnumerable ToInt16s() - { - this.m_position = 0; - var list = new List(); - var size = 2; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadInt16()); - } - return list; - } - - /// - public IEnumerable ToInt16s(EndianType endianType) - { - this.m_position = 0; - var list = new List(); - var size = 2; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadInt16(endianType)); - } - return list; - } - - #endregion Int16 - - #region Int64 - - /// - public long ReadInt64() - { - var size = 8; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public long ReadInt64(EndianType endianType) - { - var size = 8; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.GetBitConverter(endianType).UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public IEnumerable ToInt64s() - { - this.m_position = 0; - var list = new List(); - var size = 8; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadInt64()); - } - return list; - } - - /// - public IEnumerable ToInt64s(EndianType endianType) - { - this.m_position = 0; - var list = new List(); - var size = 8; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadInt64(endianType)); - } - return list; - } - - #endregion Int64 - - #region Boolean - - /// - public bool ReadBoolean() - { - var size = 1; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public bool[] ReadBooleans() - { - var size = 1; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.ToBooleansByBit(ref this.m_buffer[this.m_position], 1); - this.m_position += size; - return value; - } - - /// - public IEnumerable ToBoolensFromBit() - { - this.m_position = 0; - var list = new List(); - var size = 1; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.AddRange(this.ReadBooleans()); - } - return list; - } - - /// - public IEnumerable ToBoolensFromByte() - { - this.m_position = 0; - var list = new List(); - var size = 1; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadBoolean()); - } - return list; - } - - #endregion Boolean - - #region Char - - /// - public char ReadChar() - { - var size = 2; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public char ReadChar(EndianType endianType) - { - var size = 1; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.GetBitConverter(endianType).UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public IEnumerable ToChars() - { - this.m_position = 0; - var list = new List(); - var size = 2; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadChar()); - } - return list; - } - - /// - public IEnumerable ToChars(EndianType endianType) - { - this.m_position = 0; - var list = new List(); - var size = 2; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadChar(endianType)); - } - return list; - } - - #endregion Char - - #region Double - - /// - public double ReadDouble() - { - var size = 8; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public double ReadDouble(EndianType endianType) - { - var size = 8; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.GetBitConverter(endianType).UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public IEnumerable ToDoubles() - { - this.m_position = 0; - var list = new List(); - var size = 8; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadDouble()); - } - return list; - } - - /// - public IEnumerable ToDoubles(EndianType endianType) - { - this.m_position = 0; - var list = new List(); - var size = 8; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadDouble(endianType)); - } - return list; - } - - #endregion Double - - #region Float - - /// - public float ReadFloat() - { - var size = 4; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public float ReadFloat(EndianType endianType) - { - var size = 4; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.GetBitConverter(endianType).UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public IEnumerable ToFloats() - { - this.m_position = 0; - var list = new List(); - var size = 4; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadFloat()); - } - return list; - } - - /// - public IEnumerable ToFloats(EndianType endianType) - { - this.m_position = 0; - var list = new List(); - var size = 4; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadFloat(endianType)); - } - return list; - } - - #endregion Float - - #region UInt16 - - /// - public ushort ReadUInt16() - { - var size = 2; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public ushort ReadUInt16(EndianType endianType) - { - var size = 2; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.GetBitConverter(endianType).UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public IEnumerable ToUInt16s() - { - this.m_position = 0; - var list = new List(); - var size = 2; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadUInt16()); - } - return list; - } - - /// - public IEnumerable ToUInt16s(EndianType endianType) - { - this.m_position = 0; - var list = new List(); - var size = 2; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadUInt16(endianType)); - } - return list; - } - - #endregion UInt16 - - #region UInt32 - - /// - public uint ReadUInt32() - { - var size = 4; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public uint ReadUInt32(EndianType endianType) - { - var size = 4; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.GetBitConverter(endianType).UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public IEnumerable ToUInt32s() - { - this.m_position = 0; - var list = new List(); - var size = 4; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadUInt32()); - } - return list; - } - - /// - public IEnumerable ToUInt32s(EndianType endianType) - { - this.m_position = 0; - var list = new List(); - var size = 4; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadUInt32(endianType)); - } - return list; - } - - #endregion UInt32 - - #region UInt64 - - /// - public ulong ReadUInt64() - { - var size = 8; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public ulong ReadUInt64(EndianType endianType) - { - var size = 8; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.GetBitConverter(endianType).UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public IEnumerable ToUInt64s() - { - this.m_position = 0; - var list = new List(); - var size = 8; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadUInt64()); - } - return list; - } - - /// - public IEnumerable ToUInt64s(EndianType endianType) - { - this.m_position = 0; - var list = new List(); - var size = 8; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadUInt64(endianType)); - } - return list; - } - - #endregion UInt64 - - #region Decimal - - /// - public decimal ReadDecimal() - { - var size = 16; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public decimal ReadDecimal(EndianType endianType) - { - var size = 16; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.GetBitConverter(endianType).UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return value; - } - - /// - public IEnumerable ToDecimals() - { - this.m_position = 0; - var list = new List(); - var size = 16; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadDecimal()); - } - return list; - } - - /// - public IEnumerable ToDecimals(EndianType endianType) - { - this.m_position = 0; - var list = new List(); - var size = 16; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadDecimal(endianType)); - } - return list; - } - - #endregion Decimal - - #region Null - - /// - public bool ReadIsNull() - { - var status = this.ReadByte(); - return status == 0 || (status == 1 ? false : throw new Exception("标识既非Null,也非NotNull,可能是流位置发生了错误。")); - } - - #endregion Null - - #region DateTime - - /// - public DateTime ReadDateTime() - { - var size = 8; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.BigEndian.UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return DateTime.FromBinary(value); - } - - /// - public IEnumerable ToDateTimes() - { - this.m_position = 0; - var list = new List(); - var size = 8; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadDateTime()); - } - return list; - } - - #endregion DateTime - - #region TimeSpan - - /// - public TimeSpan ReadTimeSpan() - { - var size = 8; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.BigEndian.UnsafeTo(ref this.m_buffer[this.m_position]); - this.m_position += size; - return TimeSpan.FromTicks(value); - } - - /// - public IEnumerable ToTimeSpans() - { - this.m_position = 0; - var list = new List(); - var size = 8; - while (true) - { - if (this.m_position + size > this.m_length) - { - break; - } - list.Add(this.ReadTimeSpan()); - } - return list; - } - - #endregion TimeSpan - - #region GUID - - /// - public Guid ReadGuid() - { - Guid guid; -#if NET6_0_OR_GREATER - guid = new Guid(this.Span.Slice(this.m_position, 16)); -#else - - var bytes = this.Span.Slice(this.m_position, 16).ToArray(); - guid = new Guid(bytes); -#endif - this.m_position += 16; - return guid; - } - - #endregion GUID -} \ No newline at end of file diff --git a/src/TouchSocket.Core/Pool/ByteManager/ValueByteBlock_Write.cs b/src/TouchSocket.Core/Pool/ByteManager/ValueByteBlock_Write.cs deleted file mode 100644 index f47de3f49..000000000 --- a/src/TouchSocket.Core/Pool/ByteManager/ValueByteBlock_Write.cs +++ /dev/null @@ -1,691 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; -using System.Runtime.CompilerServices; -using System.Text; - -namespace TouchSocket.Core; - -public partial struct ValueByteBlock -{ - #region Write - - /// - public unsafe void Write(ReadOnlySpan span) - { - this.ThrowIfDisposed(); - if (span.IsEmpty) - { - return; - } - this.ExtendSize(span.Length); - - fixed (byte* p1 = &span[0]) - { - fixed (byte* p2 = &this.m_buffer[this.m_position]) - { - Unsafe.CopyBlock(p2, p1, (uint)span.Length); - } - } - this.m_position += span.Length; - this.m_length = Math.Max(this.m_position, this.m_length); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ExtendSize(int size) - { - if (this.m_buffer.Length - this.m_position < size) - { - var need = this.m_buffer.Length + size - (this.m_buffer.Length - this.m_position); - long lend = this.m_buffer.Length; - while (need > lend) - { - lend *= 2; - } - - if (lend > int.MaxValue) - { - lend = Math.Min(need + 1024 * 1024 * 100, int.MaxValue); - } - this.SetCapacity((int)lend, true); - } - } - - #endregion Write - - #region ByteBlock - - /// - public void WriteByteBlock(ByteBlock byteBlock) - { - if (byteBlock is null) - { - this.WriteVarUInt32(0); - } - else - { - this.WriteVarUInt32((uint)(byteBlock.Length + 1)); - this.Write(byteBlock.Span); - } - } - - #endregion ByteBlock - - #region Package - - /// - public void WritePackage(TPackage package) where TPackage : class, IPackage - { - if (package is null) - { - this.WriteNull(); - } - else - { - this.WriteNotNull(); - package.Package(ref this); - } - } - - #endregion Package - - #region Null - - /// - public void WriteIsNull(T t) where T : class - { - if (t == null) - { - this.WriteNull(); - } - else - { - this.WriteNotNull(); - } - } - - /// - public void WriteIsNull(T? t) where T : struct - { - if (t.HasValue) - { - this.WriteNotNull(); - } - else - { - this.WriteNull(); - } - } - - /// - public void WriteNotNull() - { - this.WriteByte(1); - } - - /// - public void WriteNull() - { - this.WriteByte(0); - } - - #endregion Null - - #region BytesPackage - - /// - public void WriteBytesPackage(byte[] value, int offset, int length) - { - if (value == null) - { - this.WriteInt32(-1); - } - else if (length == 0) - { - this.WriteInt32(0); - } - else - { - this.WriteInt32(length); - this.Write(new Span(value, offset, length)); - } - } - - /// - public void WriteBytesPackage(byte[] value) - { - if (value == null) - { - this.WriteInt32(-1); - } - else if (value.Length == 0) - { - this.WriteInt32(0); - } - else - { - this.WriteInt32(value.Length); - this.Write(new Span(value, 0, value.Length)); - } - } - - #endregion BytesPackage - - #region String - - /// - public void WriteString(string value, FixedHeaderType headerType = FixedHeaderType.Int) - { - if (value == null) - { - switch (headerType) - { - case FixedHeaderType.Byte: - this.WriteByte(byte.MaxValue); - return; - - case FixedHeaderType.Ushort: - this.WriteUInt16(ushort.MaxValue); - return; - - case FixedHeaderType.Int: - default: - this.WriteInt32(int.MaxValue); - return; - } - } - else if (value == string.Empty) - { - switch (headerType) - { - case FixedHeaderType.Byte: - this.WriteByte(0); - return; - - case FixedHeaderType.Ushort: - this.WriteUInt16(0); - return; - - case FixedHeaderType.Int: - default: - this.WriteInt32(0); - return; - } - } - else - { - var maxSize = (value.Length + 1) * 3 + 4; - this.ExtendSize(maxSize); - var chars = value.AsSpan(); - - var offset = headerType switch - { - FixedHeaderType.Byte => (byte)1, - FixedHeaderType.Ushort => (byte)2, - _ => (byte)4, - }; - - var pos = this.m_position; - - //this.m_position += offset; - - unsafe - { - fixed (char* p = &chars[0]) - { - fixed (byte* p1 = &this.m_buffer[this.m_position + offset]) - { - var len = Encoding.UTF8.GetBytes(p, chars.Length, p1, maxSize); - - switch (headerType) - { - case FixedHeaderType.Byte: - if (len >= byte.MaxValue) - { - ThrowHelper.ThrowArgumentOutOfRangeException_MoreThan(nameof(value), len, byte.MaxValue); - } - - this.WriteByte((byte)len); - break; - - case FixedHeaderType.Ushort: - if (len >= ushort.MaxValue) - { - ThrowHelper.ThrowArgumentOutOfRangeException_MoreThan(nameof(value), len, ushort.MaxValue); - } - this.WriteUInt16((ushort)len); - break; - - case FixedHeaderType.Int: - default: - if (len >= int.MaxValue) - { - ThrowHelper.ThrowArgumentOutOfRangeException_MoreThan(nameof(value), len, int.MaxValue); - } - this.WriteInt32(len); - break; - } - - this.m_position += len; - - this.m_length = Math.Max(this.m_position, this.m_length); - } - } - } - } - } - - #endregion String - - #region NormalString - /// - public void WriteNormalString(string value, Encoding encoding) - { - ThrowHelper.ThrowArgumentNullExceptionIf(value, nameof(value)); - var maxSize = encoding.GetMaxByteCount(value.Length); - this.ExtendSize(maxSize); - var chars = value.AsSpan(); - - unsafe - { - fixed (char* p = &chars[0]) - { - fixed (byte* p1 = &this.m_buffer[this.m_position]) - { - var len = Encoding.UTF8.GetBytes(p, chars.Length, p1, maxSize); - - this.m_position += len; - - this.m_length = Math.Max(this.m_position, this.m_length); - } - - } - } - } - #endregion - - #region VarUInt32 - - /// - public int WriteVarUInt32(uint value) - { - this.ExtendSize(5); - - byte bytelength = 0; - while (value > 0x7F) - { - //127=0x7F=0b01111111,大于说明msb=1,即后续还有字节 - var temp = value & 0x7F; //得到数值的后7位,0x7F=0b01111111,0与任何数与都是0,1与任何数与还是任何数 - temp |= 0x80; //后7位不变最高位固定为1,0x80=0b10000000,1与任何数或都是1,0与任何数或都是任何数 - this.m_buffer[this.m_position++] = (byte)temp; //存储msb=1的数据 - value >>= 7; //右移已经计算过的7位得到下次需要计算的数值 - bytelength++; - } - this.m_buffer[this.m_position++] = (byte)value; //最后一个字节msb=0 - - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - return bytelength + 1; - } - - #endregion VarUInt32 - - #region T - - /// - public void WriteT(T value) where T : unmanaged - { - var size = Unsafe.SizeOf(); - this.ExtendSize(size); - TouchSocketBitConverter.Default.UnsafeWriteBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - /// - public void WriteT(T value, EndianType endianType) where T : unmanaged - { - var size = Unsafe.SizeOf(); - this.ExtendSize(size); - TouchSocketBitConverter.GetBitConverter(endianType).UnsafeWriteBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - #endregion T - - #region Int32 - - /// - public void WriteInt32(int value) - { - var size = 4; - this.ExtendSize(size); - TouchSocketBitConverter.Default.GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - /// - public void WriteInt32(int value, EndianType endianType) - { - var size = 4; - this.ExtendSize(size); - TouchSocketBitConverter.GetBitConverter(endianType).GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - #endregion Int32 - - #region Int16 - - /// - public void WriteInt16(short value) - { - var size = 2; - this.ExtendSize(size); - TouchSocketBitConverter.Default.GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - /// - public void WriteInt16(short value, EndianType endianType) - { - var size = 2; - this.ExtendSize(size); - TouchSocketBitConverter.GetBitConverter(endianType).GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - #endregion Int16 - - #region Int64 - - /// - public void WriteInt64(long value) - { - var size = 8; - this.ExtendSize(size); - TouchSocketBitConverter.Default.GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - /// - public void WriteInt64(long value, EndianType endianType) - { - var size = 8; - this.ExtendSize(size); - TouchSocketBitConverter.GetBitConverter(endianType).GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - #endregion Int64 - - #region Boolean - - /// - public void WriteBoolean(bool value) - { - var size = 1; - this.ExtendSize(size); - TouchSocketBitConverter.Default.GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - /// - public void WriteBooleans(ReadOnlySpan values) - { - var size = values.Length % 8 == 0 ? values.Length / 8 : values.Length / 8 + 1; - this.ExtendSize(size); - TouchSocketBitConverter.Default.GetBytes(ref this.m_buffer[this.m_position], values); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - #endregion Boolean - - #region Byte - - /// - public void WriteByte(byte value) - { - var size = 1; - this.ExtendSize(size); - this.m_buffer[this.m_position] = value; - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - #endregion Byte - - #region Char - - /// - public void WriteChar(char value) - { - var size = 2; - this.ExtendSize(size); - TouchSocketBitConverter.Default.GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - /// - public void WriteChar(char value, EndianType endianType) - { - var size = 2; - this.ExtendSize(size); - TouchSocketBitConverter.GetBitConverter(endianType).GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - #endregion Char - - #region Double - - /// - public void WriteDouble(double value) - { - var size = 8; - this.ExtendSize(size); - TouchSocketBitConverter.Default.GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - /// - public void WriteDouble(double value, EndianType endianType) - { - var size = 8; - this.ExtendSize(size); - TouchSocketBitConverter.GetBitConverter(endianType).GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - #endregion Double - - #region Float - - /// - public void WriteFloat(float value) - { - var size = 4; - this.ExtendSize(size); - TouchSocketBitConverter.Default.GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - /// - public void WriteFloat(float value, EndianType endianType) - { - var size = 4; - this.ExtendSize(size); - TouchSocketBitConverter.GetBitConverter(endianType).GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - #endregion Float - - #region UInt16 - - /// - public void WriteUInt16(ushort value) - { - var size = 2; - this.ExtendSize(size); - TouchSocketBitConverter.Default.UnsafeWriteBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - /// - public void WriteUInt16(ushort value, EndianType endianType) - { - var size = 2; - this.ExtendSize(size); - TouchSocketBitConverter.GetBitConverter(endianType).UnsafeWriteBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - #endregion UInt16 - - #region UInt32 - - /// - public void WriteUInt32(uint value) - { - var size = 4; - this.ExtendSize(size); - TouchSocketBitConverter.Default.GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - /// - public void WriteUInt32(uint value, EndianType endianType) - { - var size = 4; - this.ExtendSize(size); - TouchSocketBitConverter.GetBitConverter(endianType).GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - #endregion UInt32 - - #region UInt64 - - /// - public void WriteUInt64(ulong value) - { - var size = 8; - this.ExtendSize(size); - TouchSocketBitConverter.Default.GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - /// - public void WriteUInt64(ulong value, EndianType endianType) - { - var size = 8; - this.ExtendSize(size); - TouchSocketBitConverter.GetBitConverter(endianType).GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - #endregion UInt64 - - #region Decimal - - /// - public void WriteDecimal(decimal value) - { - var size = 16; - this.ExtendSize(size); - TouchSocketBitConverter.Default.GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - /// - public void WriteDecimal(decimal value, EndianType endianType) - { - var size = 16; - this.ExtendSize(size); - TouchSocketBitConverter.GetBitConverter(endianType).GetBytes(ref this.m_buffer[this.m_position], value); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - #endregion Decimal - - #region DateTime - - /// - public void WriteDateTime(DateTime value) - { - var size = 8; - this.ExtendSize(size); - TouchSocketBitConverter.BigEndian.GetBytes(ref this.m_buffer[this.m_position], value.ToBinary()); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - #endregion DateTime - - #region TimeSpan - - /// - public void WriteTimeSpan(TimeSpan value) - { - var size = 8; - this.ExtendSize(size); - TouchSocketBitConverter.BigEndian.GetBytes(ref this.m_buffer[this.m_position], value.Ticks); - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - #endregion TimeSpan - - #region GUID - - /// - public void WriteGuid(in Guid value) - { - var size = 16; - this.ExtendSize(size); -#if NET6_0_OR_GREATER - value.TryWriteBytes(this.TotalMemory.Span.Slice(this.m_position, size)); -#else - var bytes = value.ToByteArray(); - this.Write(bytes); -#endif - this.m_position += size; - this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; - } - - #endregion GUID -} \ No newline at end of file diff --git a/src/TouchSocket.Core/Pool/BytesReader.cs b/src/TouchSocket.Core/Pool/BytesReader.cs deleted file mode 100644 index 08eb4c907..000000000 --- a/src/TouchSocket.Core/Pool/BytesReader.cs +++ /dev/null @@ -1,1383 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Runtime.CompilerServices; -using System.Text; - -namespace TouchSocket.Core; - -/// -/// 字节块流 -/// -[DebuggerDisplay("Len={Length},Pos={Position},Capacity={Capacity}")] -public ref struct BytesReader -{ - private readonly ReadOnlySpan m_span; - private int m_position; - - /// - /// 初始化 BytesReader 类的新实例。 - /// - /// 一个只读字节跨度,用于初始化 BytesReader。 - public BytesReader(ReadOnlySpan span) - { - this.m_span = span; - } - - /// - /// 还能读取的长度,计算为的差值。 - /// - public readonly int CanReadLength => this.m_span.Length - this.Position; - - /// - /// 获取当前实例的长度。 - /// - /// 实例的长度。 - public readonly int Length => this.m_span.Length; - - /// - /// 流位置 - /// - public int Position - { - readonly get => this.m_position; - set => this.m_position = value; - } - - /// - /// 获取当前实例的只读字节序列视图。 - /// - /// - /// 此属性提供了一个只读的字节序列视图,它允许对内部存储的字节数据进行只读访问, - /// 而不需要修改或拥有这些数据。这对于读取数据而不更改其原始值时非常有用。 - /// - /// - /// 当前实例的只读字节序列视图。 - /// - public readonly ReadOnlySpan Span => this.m_span; - - #region Read - - /// - /// 从当前流中读取数据到指定的字节 span 中。 - /// - /// 要写入数据的字节 span。 - /// 实际读取到的字节数。 - public int Read(Span span) - { - // 获取 span 的长度 - var length = span.Length; - // 如果 span 的长度为 0,则无需读取数据,直接返回 0 - if (length == 0) - { - return 0; - } - // 确定本次可以读取的长度:取剩余长度和请求读取长度的较小值 - var len = this.Length - this.m_position > length ? length : this.CanReadLength; - // 从流中读取数据到内部 buffer - // 将从流中读取到的数据复制到指定的 span 中 - this.ReadToSpan(len).CopyTo(span); - // 返回实际读取到的字节数 - return len; - } - - /// - /// 从当前位置读取指定长度的数组。并递增 - /// - /// - /// - /// - public ReadOnlySpan ReadToSpan(int length) - { - var span = this.m_span.Slice(this.m_position, length); - - this.m_position += length; - return span; - } - - #endregion Read - - #region ToArray - - /// - /// 从指定位置转化到指定长度的有效内存。本操作不递增 - /// - /// - /// - /// - public readonly byte[] ToArray(int offset, int length) - { - return this.m_span.Slice(offset, length).ToArray(); - } - - /// - /// 转换为有效内存。本操作不递增 - /// - /// - public readonly byte[] ToArray() - { - return this.ToArray(0, this.Length); - } - - /// - /// 从指定位置转为有效内存。本操作不递增 - /// - /// - /// - public readonly byte[] ToArray(int offset) - { - return this.ToArray(offset, this.Length - offset); - } - - /// - /// 将当前至指定长度转化为有效内存。本操作不递增 - /// - /// - /// - public readonly byte[] ToArrayTake(int length) - { - return this.ToArray(this.m_position, length); - } - - /// - /// 将当前至有效长度转化为有效内存。本操作不递增 - /// - /// - public readonly byte[] ToArrayTake() - { - return this.ToArray(this.m_position, this.Length - this.m_position); - } - - #endregion ToArray - - #region VarUInt32 - - /// - /// 从当前的字节序列位置开始读取一个使用可变长度编码的无符号32位整数。 - /// - /// 解码后的无符号32位整数。 - public uint ReadVarUInt32() - { - // 初始化值变量,用于存储解码后的结果 - uint value = 0; - // 初始化变量以记录已读取的字节数 - var bytelength = 0; - // 循环读取字节,直到遇到终止字节 - while (true) - { - // 获取当前位置的字节,并移动到下一个字节 - var b = this.m_span[this.m_position++]; - // 提取当前字节的低7位,并将其存储在temp变量中 - var temp = (b & 0x7F); // 取每个字节的后7位 - // 根据字节在序列中的位置,将temp向左移位,以正确地将其插入到累加的值中 - temp <<= (7 * bytelength); // 向左移位,越是后面的字节,移位越多 - // 将当前字节的值加到最终结果中 - value += (uint)temp; // 把每个字节的值加起来就是最终的值了 - // 增加已读取字节数的计数 - bytelength++; - // 检查是否达到了终止字节,即字节的最高位为0 - if (b <= 0x7F) - { // 127=0x7F=0b01111111,小于等于说明msb=0,即最后一个字节 - // 如果是终止字节,则退出循环 - break; - } - } - // 返回解码后的无符号32位整数 - return value; - } - - #endregion VarUInt32 - - #region ByteBlock - - /// - /// 从当前流位置读取一个值。 - /// - /// 注意,使用该方式读取到的内存块,会脱离释放周期,所以最好在使用完成后自行释放。 - /// - /// - public ByteBlock ReadByteBlock() - { - var len = (int)this.ReadVarUInt32() - 1; - - if (len < 0) - { - return default; - } - - var byteBlock = new ByteBlock(len); - byteBlock.Write(this.m_span.Slice(this.m_position, len)); - byteBlock.SeekToStart(); - this.m_position += len; - return byteBlock; - } - - #endregion ByteBlock - - #region Seek - - /// - /// 设置流位置 - /// - /// - /// - /// - /// - public int Seek(int offset, SeekOrigin origin) - { - switch (origin) - { - case SeekOrigin.Begin: - this.m_position = offset; - break; - - case SeekOrigin.Current: - this.m_position += offset; - break; - - case SeekOrigin.End: - this.m_position = this.Length + offset; - break; - } - return this.m_position; - } - - /// - /// 移动游标 - /// - /// - /// - public void Seek(int position) - { - this.Position = position; - } - - /// - /// 设置游标到末位 - /// - /// - public void SeekToEnd() - { - this.Position = this.Length; - } - - /// - /// 设置游标到首位 - /// - /// - public void SeekToStart() - { - this.Position = 0; - } - - #endregion Seek - - #region BytesPackage - - /// - /// 从当前流位置读取一个独立的数组包 - /// - public byte[] ReadBytesPackage() - { - var memory = this.ReadBytesPackageMemory(); - return memory.ToArray(); - } - - /// - /// 从当前的字节流中读取一个长度确定的字节包。 - /// - /// 一个只读的字节跨度,表示读取的字节包,如果读取失败则返回null。 - public ReadOnlySpan ReadBytesPackageMemory() - { - // 读取下一个32位整数,该整数表示后续字节包的长度。 - var length = this.ReadInt32(); - // 如果长度小于0,则认为读取失败,返回null。 - if (length < 0) - { - return default; - } - - // 根据读取的长度,从当前位置开始,截取一个长度为length的内存块。 - var memory = this.m_span.Slice(this.m_position, length); - // 更新当前位置,跳过已读取的字节包。 - this.m_position += length; - // 返回截取的内存块作为只读跨度。 - return memory; - } - - #endregion BytesPackage - - #region Byte - - /// - /// 从当前流位置读取一个值 - /// - public byte ReadByte() - { - var size = 1; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - - var value = this.m_span[this.m_position]; - this.m_position += size; - return value; - } - - #endregion Byte - - #region String - - /// - /// 从当前流位置读取一个值 - /// - public string ReadString(FixedHeaderType headerType = FixedHeaderType.Int) - { - int len; - switch (headerType) - { - case FixedHeaderType.Byte: - len = this.ReadByte(); - if (len == byte.MaxValue) - { - return null; - } - break; - - case FixedHeaderType.Ushort: - len = this.ReadUInt16(); - if (len == ushort.MaxValue) - { - return null; - } - break; - - case FixedHeaderType.Int: - default: - len = this.ReadInt32(); - if (len == int.MaxValue) - { - return null; - } - break; - } - - var str = this.m_span.Slice(this.m_position, len).ToString(Encoding.UTF8); - this.m_position += len; - return str; - } - - #endregion String - - #region T - - /// - public T ReadT() where T : unmanaged - { - var size = Unsafe.SizeOf(); - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.To(this.m_span.Slice(this.m_position)); - this.m_position += size; - return value; - } - - /// - public T ReadT(EndianType endianType) where T : unmanaged - { - var size = Unsafe.SizeOf(); - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.GetBitConverter(endianType).To(this.m_span.Slice(this.m_position)); - this.m_position += size; - return value; - } - - /// - public IEnumerable ToTs() where T : unmanaged - { - this.m_position = 0; - var list = new List(); - var size = Unsafe.SizeOf(); - while (true) - { - if (this.m_position + size > this.Length) - { - break; - } - list.Add(this.ReadT()); - } - return list; - } - - /// - public IEnumerable ToTs(EndianType endianType) where T : unmanaged - { - this.m_position = 0; - var list = new List(); - var size = Unsafe.SizeOf(); - while (true) - { - if (this.m_position + size > this.Length) - { - break; - } - list.Add(this.ReadT(endianType)); - } - return list; - } - - #endregion T - - #region Int32 - - /// - /// 从当前的输入流中读取一个32位整数。 - /// - /// 读取到的32位整数。 - /// 当可读取长度小于4字节时抛出。 - public int ReadInt32() - { - // 定义Int32的大小,为4字节 - var size = 4; - // 检查当前可读取长度是否小于Int32的大小 - if (this.CanReadLength < size) - { - // 如果是,抛出ArgumentOutOfRangeException异常,提示可读取长度不足 - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - // 使用TouchSocketBitConverter将字节序列转换为Int32 - var value = TouchSocketBitConverter.Default.To(this.m_span.Slice(this.m_position)); - // 更新读取位置,跳过刚读取的4字节 - this.m_position += size; - // 返回转换后的Int32值 - return value; - } - - /// - /// 从当前流位置读取一个指定端序的值 - /// - /// - public int ReadInt32(EndianType endianType) - { - var size = 4; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.GetBitConverter(endianType).To(this.m_span.Slice(this.m_position)); - this.m_position += size; - return value; - } - - /// - /// 将当前有效内存转为默认端序的集合。 - /// - /// - public IEnumerable ToInt32s() - { - this.m_position = 0; - var list = new List(); - var size = 4; - while (true) - { - if (this.m_position + size > this.Length) - { - break; - } - list.Add(this.ReadInt32()); - } - return list; - } - - /// - /// 将当前有效内存转为指定端序的集合。 - /// - /// - /// - public IEnumerable ToInt32s(EndianType endianType) - { - this.m_position = 0; - var list = new List(); - var size = 4; - while (true) - { - if (this.m_position + size > this.Length) - { - break; - } - list.Add(this.ReadInt32(endianType)); - } - return list; - } - - #endregion Int32 - - #region Int16 - - /// - /// 从当前流位置读取一个默认端序的值 - /// - public short ReadInt16() - { - var size = 2; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.To(this.m_span.Slice(this.m_position)); - this.m_position += size; - return value; - } - - /// - /// 从当前流位置读取一个值 - /// - /// 指定端序 - public short ReadInt16(EndianType endianType) - { - var size = 2; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.GetBitConverter(endianType).To(this.m_span.Slice(this.m_position)); - this.m_position += size; - return value; - } - - /// - /// 将当前有效内存转为默认端序的集合。 - /// - /// - public IEnumerable ToInt16s() - { - this.m_position = 0; - var list = new List(); - var size = 2; - while (true) - { - if (this.m_position + size > this.Length) - { - break; - } - list.Add(this.ReadInt16()); - } - return list; - } - - /// - /// 将当前有效内存转为指定端序的集合。 - /// - /// - /// - public IEnumerable ToInt16s(EndianType endianType) - { - this.m_position = 0; - var list = new List(); - var size = 2; - while (true) - { - if (this.m_position + size > this.Length) - { - break; - } - list.Add(this.ReadInt16(endianType)); - } - return list; - } - - #endregion Int16 - - #region Int64 - - /// - /// 从当前流位置读取一个默认端序的值 - /// - public long ReadInt64() - { - var size = 8; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.To(this.m_span.Slice(this.m_position)); - this.m_position += size; - return value; - } - - /// - /// 从当前流位置读取一个值 - /// - /// 指定端序 - public long ReadInt64(EndianType endianType) - { - var size = 8; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.GetBitConverter(endianType).To(this.m_span.Slice(this.m_position)); - this.m_position += size; - return value; - } - - /// - /// 将当前有效内存转为默认端序的集合。 - /// - /// - public IEnumerable ToInt64s() - { - this.m_position = 0; - var list = new List(); - var size = 8; - while (true) - { - if (this.m_position + size > this.Length) - { - break; - } - list.Add(this.ReadInt64()); - } - return list; - } - - /// - /// 将当前有效内存转为指定端序的集合。 - /// - /// - /// - public IEnumerable ToInt64s(EndianType endianType) - { - this.m_position = 0; - var list = new List(); - var size = 8; - while (true) - { - if (this.m_position + size > this.Length) - { - break; - } - list.Add(this.ReadInt64(endianType)); - } - return list; - } - - #endregion Int64 - - #region Boolean - - /// - /// 从当前流位置读取1个值 - /// - public bool ReadBoolean() - { - var size = 1; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.To(this.m_span.Slice(this.m_position)); - this.m_position += size; - return value; - } - - /// - /// 从当前流位置读取1个字节,按位解析为bool值数组。 - /// - /// - public bool[] ReadBooleans() - { - var size = 1; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.ToBooleansByBit(this.m_span.Slice(this.m_position,1)); - this.m_position += size; - return value; - } - - /// - /// 将当前有效内存按位转为集合。 - /// - /// - public IEnumerable ToBoolensFromBit() - { - this.m_position = 0; - var list = new List(); - var size = 1; - while (true) - { - if (this.m_position + size > this.Length) - { - break; - } - list.AddRange(this.ReadBooleans()); - } - return list; - } - - /// - /// 将当前有效内存按字节转为集合。 - /// - /// - public IEnumerable ToBoolensFromByte() - { - this.m_position = 0; - var list = new List(); - var size = 1; - while (true) - { - if (this.m_position + size > this.Length) - { - break; - } - list.Add(this.ReadBoolean()); - } - return list; - } - - #endregion Boolean - - #region Char - - /// - /// 从当前流位置读取一个默认端序的值 - /// - public char ReadChar() - { - var size = 2; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.To(this.m_span.Slice(this.m_position)); - this.m_position += size; - return value; - } - - /// - /// 从当前流位置读取一个值 - /// - /// 指定端序 - public char ReadChar(EndianType endianType) - { - var size = 1; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.GetBitConverter(endianType).To(this.m_span.Slice(this.m_position)); - this.m_position += size; - return value; - } - - /// - /// 将当前有效内存转为默认端序的集合。 - /// - /// - public IEnumerable ToChars() - { - this.m_position = 0; - var list = new List(); - var size = 2; - while (true) - { - if (this.m_position + size > this.Length) - { - break; - } - list.Add(this.ReadChar()); - } - return list; - } - - /// - /// 将当前有效内存转为指定端序的集合。 - /// - /// - /// - public IEnumerable ToChars(EndianType endianType) - { - this.m_position = 0; - var list = new List(); - var size = 2; - while (true) - { - if (this.m_position + size > this.Length) - { - break; - } - list.Add(this.ReadChar(endianType)); - } - return list; - } - - #endregion Char - - #region Double - - /// - /// 从当前流位置读取一个默认端序的值 - /// - public double ReadDouble() - { - var size = 8; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.To(this.m_span.Slice(this.m_position)); - this.m_position += size; - return value; - } - - /// - /// 从当前流位置读取一个值 - /// - /// 指定端序 - public double ReadDouble(EndianType endianType) - { - var size = 8; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.GetBitConverter(endianType).To(this.m_span.Slice(this.m_position)); - this.m_position += size; - return value; - } - - /// - /// 将当前有效内存转为默认端序的集合。 - /// - /// - public IEnumerable ToDoubles() - { - this.m_position = 0; - var list = new List(); - var size = 8; - while (true) - { - if (this.m_position + size > this.Length) - { - break; - } - list.Add(this.ReadDouble()); - } - return list; - } - - /// - /// 将当前有效内存转为指定端序的集合。 - /// - /// - /// - public IEnumerable ToDoubles(EndianType endianType) - { - this.m_position = 0; - var list = new List(); - var size = 8; - while (true) - { - if (this.m_position + size > this.Length) - { - break; - } - list.Add(this.ReadDouble(endianType)); - } - return list; - } - - #endregion Double - - #region Float - - /// - /// 从当前流位置读取一个默认端序的值 - /// - public float ReadFloat() - { - var size = 4; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.To(this.m_span.Slice(this.m_position)); - this.m_position += size; - return value; - } - - /// - /// 从当前流位置读取一个值 - /// - /// 指定端序 - public float ReadFloat(EndianType endianType) - { - var size = 4; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.GetBitConverter(endianType).To(this.m_span.Slice(this.m_position)); - this.m_position += size; - return value; - } - - /// - /// 将当前有效内存转为默认端序的集合。 - /// - /// - public IEnumerable ToFloats() - { - this.m_position = 0; - var list = new List(); - var size = 4; - while (true) - { - if (this.m_position + size > this.Length) - { - break; - } - list.Add(this.ReadFloat()); - } - return list; - } - - /// - /// 将当前有效内存转为指定端序的集合。 - /// - /// - /// - public IEnumerable ToFloats(EndianType endianType) - { - this.m_position = 0; - var list = new List(); - var size = 4; - while (true) - { - if (this.m_position + size > this.Length) - { - break; - } - list.Add(this.ReadFloat(endianType)); - } - return list; - } - - #endregion Float - - #region UInt16 - - /// - /// 从当前流位置读取一个默认端序的值 - /// - public ushort ReadUInt16() - { - var size = 2; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.To(this.m_span.Slice(this.m_position)); - this.m_position += size; - return value; - } - - /// - /// 从当前流位置读取一个值 - /// - /// 指定端序 - public ushort ReadUInt16(EndianType endianType) - { - var size = 2; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.GetBitConverter(endianType).To(this.m_span.Slice(this.m_position)); - this.m_position += size; - return value; - } - - /// - /// 将当前有效内存转为默认端序的集合。 - /// - /// - public IEnumerable ToUInt16s() - { - this.m_position = 0; - var list = new List(); - var size = 2; - while (true) - { - if (this.m_position + size > this.Length) - { - break; - } - list.Add(this.ReadUInt16()); - } - return list; - } - - /// - /// 将当前有效内存转为指定端序的集合。 - /// - /// - /// - public IEnumerable ToUInt16s(EndianType endianType) - { - this.m_position = 0; - var list = new List(); - var size = 2; - while (true) - { - if (this.m_position + size > this.Length) - { - break; - } - list.Add(this.ReadUInt16(endianType)); - } - return list; - } - - #endregion UInt16 - - #region UInt32 - - /// - /// 从当前流位置读取一个默认端序的值 - /// - public uint ReadUInt32() - { - var size = 4; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.To(this.m_span.Slice(this.m_position)); - this.m_position += size; - return value; - } - - /// - /// 从当前流位置读取一个值 - /// - /// 指定端序 - public uint ReadUInt32(EndianType endianType) - { - var size = 4; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.GetBitConverter(endianType).To(this.m_span.Slice(this.m_position)); - this.m_position += size; - return value; - } - - /// - /// 将当前有效内存转为默认端序的集合。 - /// - /// - public IEnumerable ToUInt32s() - { - this.m_position = 0; - var list = new List(); - var size = 4; - while (true) - { - if (this.m_position + size > this.Length) - { - break; - } - list.Add(this.ReadUInt32()); - } - return list; - } - - /// - /// 将当前有效内存转为指定端序的集合。 - /// - /// - /// - public IEnumerable ToUInt32s(EndianType endianType) - { - this.m_position = 0; - var list = new List(); - var size = 4; - while (true) - { - if (this.m_position + size > this.Length) - { - break; - } - list.Add(this.ReadUInt32(endianType)); - } - return list; - } - - #endregion UInt32 - - #region UInt64 - - /// - /// 从当前流位置读取一个默认端序的值 - /// - public ulong ReadUInt64() - { - var size = 8; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.To(this.m_span.Slice(this.m_position)); - this.m_position += size; - return value; - } - - /// - /// 从当前流位置读取一个值 - /// - /// 指定端序 - public ulong ReadUInt64(EndianType endianType) - { - var size = 8; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.GetBitConverter(endianType).To(this.m_span.Slice(this.m_position)); - this.m_position += size; - return value; - } - - /// - /// 将当前有效内存转为默认端序的集合。 - /// - /// - public IEnumerable ToUInt64s() - { - this.m_position = 0; - var list = new List(); - var size = 8; - while (true) - { - if (this.m_position + size > this.Length) - { - break; - } - list.Add(this.ReadUInt64()); - } - return list; - } - - /// - /// 将当前有效内存转为指定端序的集合。 - /// - /// - /// - public IEnumerable ToUInt64s(EndianType endianType) - { - this.m_position = 0; - var list = new List(); - var size = 8; - while (true) - { - if (this.m_position + size > this.Length) - { - break; - } - list.Add(this.ReadUInt64(endianType)); - } - return list; - } - - #endregion UInt64 - - #region Decimal - - /// - /// 从当前流位置读取一个默认端序的值 - /// - public decimal ReadDecimal() - { - var size = 16; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.Default.To(this.m_span.Slice(this.m_position)); - this.m_position += size; - return value; - } - - /// - /// 从当前流位置读取一个值 - /// - /// 指定端序 - public decimal ReadDecimal(EndianType endianType) - { - var size = 16; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.GetBitConverter(endianType).To(this.m_span.Slice(this.m_position)); - this.m_position += size; - return value; - } - - /// - /// 将当前有效内存转为默认端序的集合。 - /// - /// - public IEnumerable ToDecimals() - { - this.m_position = 0; - var list = new List(); - var size = 16; - while (true) - { - if (this.m_position + size > this.Length) - { - break; - } - list.Add(this.ReadDecimal()); - } - return list; - } - - /// - /// 将当前有效内存转为指定端序的集合。 - /// - /// - /// - public IEnumerable ToDecimals(EndianType endianType) - { - this.m_position = 0; - var list = new List(); - var size = 16; - while (true) - { - if (this.m_position + size > this.Length) - { - break; - } - list.Add(this.ReadDecimal(endianType)); - } - return list; - } - - #endregion Decimal - - #region Null - - /// - /// 从当前流位置读取一个标识值,判断是否为。 - /// - public bool ReadIsNull() - { - var status = this.ReadByte(); - return status == 0 || (status == 1 ? false : throw new Exception("标识既非Null,也非NotNull,可能是流位置发生了错误。")); - } - - #endregion Null - - #region DateTime - - /// - /// 从当前流位置读取一个值 - /// - public DateTime ReadDateTime() - { - var size = 8; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.BigEndian.To(this.m_span.Slice(this.m_position)); - this.m_position += size; - return DateTime.FromBinary(value); - } - - /// - /// 将当前有效内存转为集合。 - /// - /// - public IEnumerable ToDateTimes() - { - this.m_position = 0; - var list = new List(); - var size = 8; - while (true) - { - if (this.m_position + size > this.Length) - { - break; - } - list.Add(this.ReadDateTime()); - } - return list; - } - - #endregion DateTime - - #region TimeSpan - - /// - /// 从当前流位置读取一个值 - /// - public TimeSpan ReadTimeSpan() - { - var size = 8; - if (this.CanReadLength < size) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), this.CanReadLength, size); - } - var value = TouchSocketBitConverter.BigEndian.To(this.m_span.Slice(this.m_position)); - this.m_position += size; - return TimeSpan.FromTicks(value); - } - - /// - /// 将当前有效内存转为集合。 - /// - /// - public IEnumerable ToTimeSpans() - { - this.m_position = 0; - var list = new List(); - var size = 8; - while (true) - { - if (this.m_position + size > this.Length) - { - break; - } - list.Add(this.ReadTimeSpan()); - } - return list; - } - - #endregion TimeSpan -} \ No newline at end of file diff --git a/src/TouchSocket.Core/Pool/BytesWriter.cs b/src/TouchSocket.Core/Pool/BytesWriter.cs deleted file mode 100644 index f7dfa0231..000000000 --- a/src/TouchSocket.Core/Pool/BytesWriter.cs +++ /dev/null @@ -1,976 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -//using System; -//using System.Diagnostics; -//using System.IO; -//using System.Runtime.CompilerServices; -//using System.Text; - -//namespace TouchSocket.Core -//{ -// /// -// /// 字节块流 -// /// -// [DebuggerDisplay("Len={Length},Pos={Position},Capacity={Capacity}")] -// public struct IByteBlock -// { -// public IByteBlock(Memory memory, Func, int, Memory> expansionDelegate) -// { -// this.m_memory = memory; -// this.m_expansionDelegate = expansionDelegate; -// } - -// public IByteBlock(Memory memory) : this(memory, default) -// { -// } - -// #region 字段 - -// private readonly Func, int, Memory> m_expansionDelegate; -// private int m_length; -// private Memory m_memory; -// private int m_position; - -// #endregion 字段 - -// #region 属性 - -// /// -// /// 容量 -// /// -// public readonly int Capacity => this.m_memory.Length; - -// public readonly int Length => this.m_length; - -// /// -// /// 流位置 -// /// -// public int Position -// { -// readonly get => this.m_position; -// set => this.m_position = value; -// } - -// public readonly Memory Memory => this.m_memory; - -// /// -// /// 空闲长度,准确掌握该值,可以避免内存扩展,计算为的差值。 -// /// -// public readonly int FreeLength => this.m_memory.Length - this.Position; - -// #endregion 属性 - -// /// -// /// 清空所有内存数据 -// /// -// /// 内存块已释放 -// public readonly void Clear() -// { -// this.m_memory.Span.Clear(); -// } - -// #region Seek - -// /// -// /// 设置流位置 -// /// -// /// -// /// -// /// -// /// -// public int Seek(int offset, SeekOrigin origin) -// { -// switch (origin) -// { -// case SeekOrigin.Begin: -// this.m_position = offset; -// break; - -// case SeekOrigin.Current: -// this.m_position += offset; -// break; - -// case SeekOrigin.End: -// this.m_position = this.m_memory.Length + offset; -// break; -// } -// return this.m_position; -// } - -// /// -// /// 移动游标 -// /// -// /// -// /// -// public void Seek(int position) -// { -// this.m_position = position; -// } - -// /// -// /// 设置游标到末位 -// /// -// /// -// public void SeekToEnd() -// { -// this.m_position = this.m_memory.Length; -// } - -// /// -// /// 设置游标到首位 -// /// -// /// -// public void SeekToStart() -// { -// this.m_position = 0; -// } - -// #endregion Seek - -// /// -// /// 设置实际长度 -// /// -// /// -// public void SetLength(int value) -// { -// if (value > this.m_memory.Length) -// { -// throw new Exception("设置值超出容量"); -// } -// this.m_length = value; -// } - -// #region ToArray - -// /// -// /// 从指定位置转化到指定长度的有效内存。本操作不递增 -// /// -// /// -// /// -// /// -// public readonly byte[] ToArray(int offset, int length) -// { -// return this.m_memory.Slice(offset, length).ToArray(); -// } - -// /// -// /// 转换为有效内存。本操作不递增 -// /// -// /// -// public readonly byte[] ToArray() -// { -// return this.ToArray(0, this.Length); -// } - -// /// -// /// 从指定位置转为有效内存。本操作不递增 -// /// -// /// -// /// -// public readonly byte[] ToArray(int offset) -// { -// return this.ToArray(offset, this.Length - offset); -// } - -// /// -// /// 将当前至指定长度转化为有效内存。本操作不递增 -// /// -// /// -// /// -// public readonly byte[] ToArrayTake(int length) -// { -// return this.ToArray(this.m_position, length); -// } - -// /// -// /// 将当前至有效长度转化为有效内存。本操作不递增 -// /// -// /// -// public readonly byte[] ToArrayTake() -// { -// return this.ToArray(this.m_position, this.m_length - this.m_position); -// } - -// #endregion ToArray - -// #region AsMemory - -// /// -// /// 从指定位置转化到指定长度的有效内存。本操作不递增 -// /// -// /// -// /// -// /// -// public readonly Memory AsMemory(int offset, int length) -// { -// return this.m_memory.Slice(offset, length); -// } - -// /// -// /// 转换为有效内存。本操作不递增 -// /// -// /// -// public readonly Memory AsMemory() -// { -// return this.AsMemory(0, this.Length); -// } - -// /// -// /// 从指定位置转为有效内存。本操作不递增 -// /// -// /// -// /// -// public readonly Memory AsMemory(int offset) -// { -// return this.AsMemory(offset, this.Length - offset); -// } - -// /// -// /// 将当前至指定长度转化为有效内存。本操作不递增 -// /// -// /// -// /// -// public readonly Memory AsMemoryTake(int length) -// { -// return this.AsMemory(this.m_position, length); -// } - -// /// -// /// 将当前至有效长度转化为有效内存。本操作不递增 -// /// -// /// -// public readonly Memory AsMemoryTake() -// { -// return this.AsMemory(this.m_position, this.m_length - this.m_position); -// } - -// #endregion AsMemory - -// #region ToMemory - -// /// -// /// 从指定位置转化到指定长度的有效内存。本操作不递增 -// /// -// /// -// /// -// /// -// public readonly Memory ToMemory(int offset, int length) -// { -// return this.m_memory.Slice(offset, length).ToArray(); -// } - -// /// -// /// 转换为有效内存。本操作不递增 -// /// -// /// -// public readonly Memory ToMemory() -// { -// return this.ToMemory(0, this.Length); -// } - -// /// -// /// 从指定位置转为有效内存。本操作不递增 -// /// -// /// -// /// -// public readonly Memory ToMemory(int offset) -// { -// return this.ToMemory(offset, this.Length - offset); -// } - -// /// -// /// 将当前至指定长度转化为有效内存。本操作不递增 -// /// -// /// -// /// -// public readonly Memory ToMemoryTake(int length) -// { -// return this.ToMemory(this.m_position, length); -// } - -// /// -// /// 将当前至有效长度转化为有效内存。本操作不递增 -// /// -// /// -// public readonly Memory ToMemoryTake() -// { -// return this.ToMemory(this.m_position, this.m_length - this.m_position); -// } - -// #endregion ToMemory - -// #region Write -// /// -// /// 写入 -// /// -// /// -// public void Write(Span span) -// { -// if (span.IsEmpty) -// { -// return; -// } - -// this.WriteSize(span.Length); - -// Unsafe.CopyBlock(ref this.m_memory.Span[this.m_position], ref span[0], (uint)span.Length); -// this.m_position += span.Length; -// this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; -// } - -// [MethodImpl(MethodImplOptions.AggressiveInlining)] -// private void WriteSize(int size) -// { -// if (this.m_memory.Length - this.m_position < size) -// { -// var need = this.m_memory.Length + size - (this.m_memory.Length - this.m_position); -// long lend = this.m_memory.Length; -// while (need > lend) -// { -// lend *= 2; -// } - -// if (lend > int.MaxValue) -// { -// lend = Math.Min(need + 1024 * 1024 * 100, int.MaxValue); -// } -// if (this.m_expansionDelegate == null) -// { -// ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(size), size, this.Capacity - this.m_position); -// } -// this.m_memory = this.m_expansionDelegate.Invoke(this.m_memory, (int)lend); -// } -// } -// #endregion - -// #region Object - -// /// -// /// 写入值 -// /// -// /// -// /// -// public void WriteObject(object value, SerializationType serializationType = SerializationType.FastBinary) -// { -// if (value == null) -// { -// this.Write(0); -// } -// else -// { -// byte[] data; -// switch (serializationType) -// { -// case SerializationType.FastBinary: -// { -// data = FastBinaryFormatter.Serialize(value); -// } -// break; - -// case SerializationType.Json: -// { -// data = SerializeConvert.JsonSerializeToBytes(value); -// } -// break; - -// case SerializationType.Xml: -// { -// data = Encoding.UTF8.GetBytes(SerializeConvert.XmlSerializeToString(value)); -// } -// break; - -// case SerializationType.SystemBinary: -// { -// data = SerializeConvert.BinarySerialize(value); -// } -// break; - -// default: -// throw new Exception("未定义的序列化类型"); -// } -// this.Write(data.Length); -// this.Write(data); -// } -// } - -// #endregion Object - -// #region ByteBlock -// /// -// /// 写入值 -// /// -// public void WriteByteBlock(ByteBlock byteBlock) -// { -// var len = byteBlock is null ? -1 : byteBlock.Length; -// this.Write(len); -// this.Write(byteBlock.Memory.Span); -// } - -// #endregion ByteBlock - -// #region Package -// /// -// /// 以包进行写入。允许null值。 -// /// 读取时调用,解包。或者先判断,然后自行解包。 -// /// -// /// -// /// -// /// -// public void WritePackage(TPackage package) where TPackage : class, IPackage -// { -// this.WriteIsNull(package); -// package?.Package(ref this); -// } - -// #endregion Package - -// #region Null -// /// -// /// 判断该值是否为Null,然后写入标识值 -// /// -// public void WriteIsNull(T t) where T : class -// { -// if (t == null) -// { -// this.WriteNull(); -// } -// else -// { -// this.WriteNotNull(); -// } -// } - -// /// -// /// 判断该值是否为Null,然后写入标识值 -// /// -// /// -// /// -// /// -// public void WriteIsNull(T? t) where T : struct -// { -// if (t.HasValue) -// { -// this.WriteNotNull(); -// } -// else -// { -// this.WriteNull(); -// } -// } - -// /// -// /// 写入一个标识非Null值 -// /// -// public void WriteNotNull() -// { -// this.Write((byte)1); -// } - -// /// -// /// 写入一个标识Null值 -// /// -// public void WriteNull() -// { -// this.Write((byte)0); -// } - -// #endregion Null - -// #region BytesPackage - -// /// -// /// 写入一个独立的数组包,值可以为。 -// /// -// /// -// /// -// /// -// public void WriteBytesPackage(byte[] value, int offset, int length) -// { -// if (value == null) -// { -// this.Write((int)-1); -// } -// else if (length == 0) -// { -// this.Write((int)0); -// } -// else -// { -// this.Write(length); -// this.Write(new Span(value, offset, length)); -// } -// } - -// /// -// /// 写入一个独立的数组包。值可以为。 -// /// -// /// -// public void WriteBytesPackage(byte[] value) -// { -// if (value == null) -// { -// this.Write((int)-1); -// } -// else if (value.Length == 0) -// { -// this.Write((int)0); -// } -// else -// { -// this.Write(value.Length); -// this.Write(new Span(value, 0, value.Length)); -// } -// } - -// #endregion BytesPackage - -// #region String - -// /// -// /// 写入值。值可以为,或者空。 -// /// 注意:该操作不具备通用性,读取时必须使用ReadString。或者得先做出判断,由默认端序的int32值标识,具体如下: -// /// -// /// 小于0,表示字符串为 -// /// 等于0,表示字符串为"" -// /// 大于0,表示字符串在utf-8编码下的字节长度。 -// /// -// /// -// /// -// public void Write(string value) -// { -// if (value == null) -// { -// this.Write(-1); -// } -// else -// { -// var buffer = Encoding.UTF8.GetBytes(value); -// this.Write(buffer.Length); -// this.Write(buffer); -// } -// } - -// /// -// /// 写入值。值必须为有效值。可通用解析。 -// /// -// /// -// /// -// public void WriteString(string value, Encoding encoding = null) -// { -// this.Write((encoding ?? Encoding.UTF8).GetBytes(value)); -// } - -// #endregion String - -// #region Int32 - -// /// -// /// 写入默认端序的值 -// /// -// /// -// public void Write(int value) -// { -// var size = 4; -// this.WriteSize(size); -// TouchSocketBitConverter.Default.GetBytes(ref this.m_memory.Span[this.m_position], value); -// this.m_position += size; -// this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; -// } - -// /// -// /// 写入指定端序的值 -// /// -// /// -// /// 指定端序 -// public void Write(int value, EndianType endianType) -// { -// var size = 4; -// this.WriteSize(size); -// TouchSocketBitConverter.GetBitConverter(endianType).GetBytes(ref this.m_memory.Span[this.m_position], value); -// this.m_position += size; -// this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; -// } - -// #endregion Int32 - -// #region Int16 - -// /// -// /// 写入默认端序的值 -// /// -// /// -// public void Write(short value) -// { -// var size = 2; -// this.WriteSize(size); -// TouchSocketBitConverter.Default.GetBytes(ref this.m_memory.Span[this.m_position], value); -// this.m_position += size; -// this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; -// } - -// /// -// /// 写入值 -// /// -// /// -// /// 指定端序 -// public void Write(short value, EndianType endianType) -// { -// var size = 2; -// this.WriteSize(size); -// TouchSocketBitConverter.GetBitConverter(endianType).GetBytes(ref this.m_memory.Span[this.m_position], value); -// this.m_position += size; -// this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; -// } - -// #endregion Int16 - -// #region Int64 - -// /// -// /// 写入默认端序的值 -// /// -// /// -// public void Write(long value) -// { -// var size = 8; -// this.WriteSize(size); -// TouchSocketBitConverter.Default.GetBytes(ref this.m_memory.Span[this.m_position], value); -// this.m_position += size; -// this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; -// } - -// /// -// /// 写入值 -// /// -// /// -// /// 指定端序 -// public void Write(long value, EndianType endianType) -// { -// var size = 8; -// this.WriteSize(size); -// TouchSocketBitConverter.GetBitConverter(endianType).GetBytes(ref this.m_memory.Span[this.m_position], value); -// this.m_position += size; -// this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; -// } - -// #endregion Int64 - -// #region Boolean - -// /// -// /// 写入值 -// /// -// /// -// public void Write(bool value) -// { -// var size = 1; -// this.WriteSize(size); -// TouchSocketBitConverter.Default.GetBytes(ref this.m_memory.Span[this.m_position], value); -// this.m_position += size; -// this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; -// } - -// /// -// /// 写入bool数组。 -// /// -// /// -// public void Write(bool[] values) -// { -// int size; -// if (values.Length % 8 == 0) -// { -// size = values.Length / 8; -// } -// else -// { -// size = values.Length / 8 + 1; -// } - -// this.WriteSize(size); -// TouchSocketBitConverter.Default.GetBytes(ref this.m_memory.Span[this.m_position], values); -// this.m_position += size; -// this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; -// } - -// #endregion Boolean - -// #region Byte - -// /// -// /// 写入值 -// /// -// /// -// /// -// public void Write(byte value) -// { -// var size = 1; -// this.WriteSize(size); -// this.m_memory.Span[this.m_position] = value; -// this.m_position += size; -// this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; -// } - -// #endregion Byte - -// #region Char - -// /// -// /// 写入默认端序的值 -// /// -// /// -// public void Write(char value) -// { -// var size = 2; -// this.WriteSize(size); -// TouchSocketBitConverter.Default.GetBytes(ref this.m_memory.Span[this.m_position], value); -// this.m_position += size; -// this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; -// } - -// /// -// /// 写入值 -// /// -// /// -// /// 指定端序 -// public void Write(char value, EndianType endianType) -// { -// var size = 2; -// this.WriteSize(size); -// TouchSocketBitConverter.GetBitConverter(endianType).GetBytes(ref this.m_memory.Span[this.m_position], value); -// this.m_position += size; -// this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; -// } - -// #endregion Char - -// #region Double - -// /// -// /// 写入默认端序的值 -// /// -// /// -// public void Write(double value) -// { -// var size = 8; -// this.WriteSize(size); -// TouchSocketBitConverter.Default.GetBytes(ref this.m_memory.Span[this.m_position], value); -// this.m_position += size; -// this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; -// } - -// /// -// /// 写入值 -// /// -// /// -// /// 指定端序 -// public void Write(double value, EndianType endianType) -// { -// var size = 8; -// this.WriteSize(size); -// TouchSocketBitConverter.GetBitConverter(endianType).GetBytes(ref this.m_memory.Span[this.m_position], value); -// this.m_position += size; -// this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; -// } - -// #endregion Double - -// #region Float - -// /// -// /// 写入默认端序的值 -// /// -// /// -// public void Write(float value) -// { -// var size = 4; -// this.WriteSize(size); -// TouchSocketBitConverter.Default.GetBytes(ref this.m_memory.Span[this.m_position], value); -// this.m_position += size; -// this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; -// } - -// /// -// /// 写入值 -// /// -// /// -// /// 指定端序 -// public void Write(float value, EndianType endianType) -// { -// var size = 4; -// this.WriteSize(size); -// TouchSocketBitConverter.GetBitConverter(endianType).GetBytes(ref this.m_memory.Span[this.m_position], value); -// this.m_position += size; -// this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; -// } - -// #endregion Float - -// #region UInt16 - -// /// -// /// 写入默认端序的值 -// /// -// /// -// public void Write(ushort value) -// { -// var size = 2; -// this.WriteSize(size); -// TouchSocketBitConverter.Default.GetBytes(ref this.m_memory.Span[this.m_position], value); -// this.m_position += size; -// this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; -// } - -// /// -// /// 写入值 -// /// -// /// -// /// 指定端序 -// public void Write(ushort value, EndianType endianType) -// { -// var size = 2; -// this.WriteSize(size); -// TouchSocketBitConverter.GetBitConverter(endianType).GetBytes(ref this.m_memory.Span[this.m_position], value); -// this.m_position += size; -// this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; -// } - -// #endregion UInt16 - -// #region UInt32 - -// /// -// /// 写入默认端序的值 -// /// -// /// -// public void Write(uint value) -// { -// var size = 4; -// this.WriteSize(size); -// TouchSocketBitConverter.Default.GetBytes(ref this.m_memory.Span[this.m_position], value); -// this.m_position += size; -// this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; -// } - -// /// -// /// 写入值 -// /// -// /// -// /// 指定端序 -// public void Write(uint value, EndianType endianType) -// { -// var size = 4; -// this.WriteSize(size); -// TouchSocketBitConverter.GetBitConverter(endianType).GetBytes(ref this.m_memory.Span[this.m_position], value); -// this.m_position += size; -// this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; -// } - -// #endregion UInt32 - -// #region UInt64 - -// /// -// /// 写入默认端序的值 -// /// -// /// -// public void Write(ulong value) -// { -// var size = 8; -// this.WriteSize(size); -// TouchSocketBitConverter.Default.GetBytes(ref this.m_memory.Span[this.m_position], value); -// this.m_position += size; -// this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; -// } - -// /// -// /// 写入值 -// /// -// /// -// /// 指定端序 -// public void Write(ulong value, EndianType endianType) -// { -// var size = 8; -// this.WriteSize(size); -// TouchSocketBitConverter.GetBitConverter(endianType).GetBytes(ref this.m_memory.Span[this.m_position], value); -// this.m_position += size; -// this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; -// } - -// #endregion UInt64 - -// #region Decimal - -// /// -// /// 写入默认端序的值 -// /// -// /// -// public void Write(decimal value) -// { -// var size = 16; -// this.WriteSize(size); -// TouchSocketBitConverter.Default.GetBytes(ref this.m_memory.Span[this.m_position], value); -// this.m_position += size; -// this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; -// } - -// /// -// /// 写入值 -// /// -// /// -// /// 指定端序 -// public void Write(decimal value, EndianType endianType) -// { -// var size = 16; -// this.WriteSize(size); -// TouchSocketBitConverter.GetBitConverter(endianType).GetBytes(ref this.m_memory.Span[this.m_position], value); -// this.m_position += size; -// this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; -// } - -// #endregion Decimal - -// #region DateTime - -// /// -// /// 写入值 -// /// -// /// -// public void Write(DateTime value) -// { -// var size = 8; -// this.WriteSize(size); -// TouchSocketBitConverter.BigEndian.GetBytes(ref this.m_memory.Span[this.m_position], value.ToBinary()); -// this.m_position += size; -// this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; -// } - -// #endregion DateTime - -// #region TimeSpan - -// /// -// /// 写入值 -// /// -// /// -// public void Write(TimeSpan value) -// { -// var size = 8; -// this.WriteSize(size); -// TouchSocketBitConverter.BigEndian.GetBytes(ref this.m_memory.Span[this.m_position], value.Ticks); -// this.m_position += size; -// this.m_length = this.m_position > this.m_length ? this.m_position : this.m_length; -// } - -// #endregion TimeSpan -// } -//} \ No newline at end of file diff --git a/src/TouchSocket.Core/Pool/IByteBlock.cs b/src/TouchSocket.Core/Pool/IByteBlock.cs deleted file mode 100644 index 3ce07d4f1..000000000 --- a/src/TouchSocket.Core/Pool/IByteBlock.cs +++ /dev/null @@ -1,956 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; -using System.Buffers; -using System.Collections.Generic; -using System.IO; -using System.Runtime.CompilerServices; -using System.Text; - -namespace TouchSocket.Core; - -/// -/// 定义了一个可释放的字节块接口,用于支持将字节数据写入缓冲区。 -/// 该接口继承自,表明它既是一个可释放资源的对象, -/// 也是一个能够将字节数据写入缓冲区的对象。 -/// -public interface IByteBlock : IDisposable, IBufferWriter -{ - #region 属性 - - /// - /// 获取字节池。 - /// - ArrayPool BytePool { get; } - - /// - /// 获取是否可以读取。 - /// - bool CanRead { get; } - - /// - /// 获取可以读取的长度。 - /// - int CanReadLength { get; } - - /// - /// 获取容量。 - /// - int Capacity { get; } - - /// - /// 获取空闲长度。 - /// - int FreeLength { get; } - - /// - /// 获取是否持有数据。 - /// - bool Holding { get; } - - /// - /// 获取是否为结构类型。 - /// - bool IsStruct { get; } - - /// - /// 获取长度。 - /// - int Length { get; } - - /// - /// 获取只读内存。 - /// - ReadOnlyMemory Memory { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; } - - /// - /// 获取或设置位置。 - /// - int Position { get; set; } - - /// - /// 获取只读字节跨度。 - /// - ReadOnlySpan Span { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; } - - /// - /// 获取总内存。 - /// - Memory TotalMemory { get; } - - /// - /// 获取是否正在使用。 - /// - bool Using { get; } - - /// - /// 获取或设置指定索引处的字节。 - /// - /// 索引位置。 - /// 指定索引处的字节。 - byte this[int index] { get; set; } - - #endregion 属性 - - /// - /// 清空当前数据结构中的所有数据。 - /// - void Clear(); - - #region Boolean - - /// - /// 读取一个布尔值。 - /// - /// 返回读取的布尔值。 - bool ReadBoolean(); - - /// - /// 读取一个布尔值数组。 - /// - /// 返回读取的布尔值数组。 - bool[] ReadBooleans(); - - /// - /// 从位数据中转换为布尔值集合。 - /// - /// 返回转换得到的布尔值集合。 - IEnumerable ToBoolensFromBit(); - - /// - /// 从字节数据中转换为布尔值集合。 - /// - /// 返回转换得到的布尔值集合。 - IEnumerable ToBoolensFromByte(); - - /// - /// 写入一个布尔值。 - /// - /// 要写入的布尔值。 - void WriteBoolean(bool value); - - /// - /// 写入一个布尔值数组。 - /// - /// 要写入的布尔值数组。 - void WriteBooleans(ReadOnlySpan values); - - #endregion Boolean - - #region Byte - - /// - /// 读取一个字节。 - /// - /// 返回读取到的字节。 - byte ReadByte(); - - /// - /// 写入一个字节。 - /// - /// 要写入的字节值。 - void WriteByte(byte value); - - #endregion Byte - - #region Write - - /// - /// 将指定的字节序列写入到当前内存的处。 - /// - /// 一个只读的字节序列,表示要写入的数据。 - void Write(ReadOnlySpan span); - - #endregion Write - - #region Read - - /// - /// 从数据源读取字节并将其写入指定的字节范围。 - /// - /// 要写入读取字节的字节范围。 - /// 实际写入字节范围的字节数。 - int Read(Span span); - - #endregion Read - - #region ByteBlock - - /// - /// 读取一个字节块。 - /// - /// 返回读取到的字节块。 - ByteBlock ReadByteBlock(); - - /// - /// 写入一个字节块。 - /// - /// 要写入的字节块。 - void WriteByteBlock(ByteBlock byteBlock); - - #endregion ByteBlock - - #region Char - - /// - /// 从输入流中读取一个字符。 - /// - /// 返回读取的字符。 - char ReadChar(); - - /// - /// 根据指定的字节序从输入流中读取一个字符。 - /// - /// 指定的字节序类型。 - /// 返回读取的字符。 - char ReadChar(EndianType endianType); - - /// - /// 从输入流中读取一系列字符。 - /// - /// 返回一个字符序列。 - IEnumerable ToChars(); - - /// - /// 根据指定的字节序从输入流中读取一系列字符。 - /// - /// 指定的字节序类型。 - /// 返回一个字符序列。 - IEnumerable ToChars(EndianType endianType); - - /// - /// 向输出流写入一个字符。 - /// - /// 要写入的字符。 - void WriteChar(char value); - - /// - /// 根据指定的字节序向输出流写入一个字符。 - /// - /// 要写入的字符。 - /// 指定的字节序类型。 - void WriteChar(char value, EndianType endianType); - - #endregion Char - - #region Decimal - - /// - /// 读取一个字节序列并将其转换为十进制数。 - /// - /// 转换后的十进制数。 - decimal ReadDecimal(); - - /// - /// 根据指定的字节序读取一个字节序列并将其转换为十进制数。 - /// - /// 指定的字节序。 - /// 转换后的十进制数。 - decimal ReadDecimal(EndianType endianType); - - /// - /// 将一系列字节序列转换为十进制数的集合。 - /// - /// 转换后的十进制数集合。 - IEnumerable ToDecimals(); - - /// - /// 根据指定的字节序将一系列字节序列转换为十进制数的集合。 - /// - /// 指定的字节序。 - /// 转换后的十进制数集合。 - IEnumerable ToDecimals(EndianType endianType); - - /// - /// 将一个十进制数转换为字节序列并写入。 - /// - /// 要转换并写入的十进制数。 - void WriteDecimal(decimal value); - - /// - /// 根据指定的字节序将一个十进制数转换为字节序列并写入。 - /// - /// 要转换并写入的十进制数。 - /// 指定的字节序。 - void WriteDecimal(decimal value, EndianType endianType); - - #endregion Decimal - - #region Float - - /// - /// 读取一个浮点数。 - /// - /// 读取到的浮点数。 - float ReadFloat(); - - /// - /// 按指定的字节序读取一个浮点数。 - /// - /// 指定的字节序类型。 - /// 读取到的浮点数。 - float ReadFloat(EndianType endianType); - - /// - /// 读取一系列浮点数。 - /// - /// 一个浮点数的集合。 - IEnumerable ToFloats(); - - /// - /// 按指定的字节序读取一系列浮点数。 - /// - /// 指定的字节序类型。 - /// 一个浮点数的集合。 - IEnumerable ToFloats(EndianType endianType); - - /// - /// 写入一个浮点数。 - /// - /// 要写入的浮点数值。 - void WriteFloat(float value); - - /// - /// 按指定的字节序写入一个浮点数。 - /// - /// 要写入的浮点数值。 - /// 指定的字节序类型。 - void WriteFloat(float value, EndianType endianType); - - #endregion Float - - #region Int64 - - /// - /// 读取一个 Int64 类型的值。 - /// - /// 读取到的 Int64 类型的值。 - long ReadInt64(); - - /// - /// 按指定的字节序读取一个 Int64 类型的值。 - /// - /// 指定的字节序类型。 - /// 读取到的 Int64 类型的值。 - long ReadInt64(EndianType endianType); - - /// - /// 将当前对象转换为一系列 Int64 类型的值。 - /// - /// 一系列 Int64 类型的值。 - IEnumerable ToInt64s(); - - /// - /// 按指定的字节序将当前对象转换为一系列 Int64 类型的值。 - /// - /// 指定的字节序类型。 - /// 一系列 Int64 类型的值。 - IEnumerable ToInt64s(EndianType endianType); - - /// - /// 写入一个 Int64 类型的值。 - /// - /// 要写入的 Int64 类型的值。 - void WriteInt64(long value); - - /// - /// 按指定的字节序写入一个 Int64 类型的值。 - /// - /// 要写入的 Int64 类型的值。 - /// 指定的字节序类型。 - void WriteInt64(long value, EndianType endianType); - - #endregion Int64 - - #region Null - - /// - /// 读取是否为。 - /// - /// 如果读取内容为,则返回;否则返回 - bool ReadIsNull(); - - /// - /// 写入一个值,并标记该值是否为。 - /// - /// 要写入的值。 - /// 值的类型,必须是引用类型。 - void WriteIsNull(T t) where T : class; - - /// - /// 写入一个可能为的值,并标记该值是否为。 - /// - /// 要写入的值,可以为。 - /// 值的类型,必须是值类型。 - void WriteIsNull(T? t) where T : struct; - - /// - /// 写入一个非null的标记。 - /// - void WriteNotNull(); - - /// - /// 写入一个null的标记。 - /// - void WriteNull(); - - #endregion Null - - #region Guid - - /// - /// 读取一个全局唯一标识符(GUID)。 - /// - /// 返回读取到的GUID。 - Guid ReadGuid(); - - /// - /// 写入一个全局唯一标识符(GUID)。 - /// - /// 要写入的GUID,使用in修饰符确保参数只读且不复制。 - void WriteGuid(in Guid value); - - #endregion Guid - - #region TimeSpan - - /// - /// 读取一个时间间隔。 - /// - /// 返回读取的时间间隔。 - TimeSpan ReadTimeSpan(); - - /// - /// 将当前对象转换为一系列时间间隔。 - /// - /// 返回一个时间间隔的集合。 - IEnumerable ToTimeSpans(); - - /// - /// 写入一个时间间隔。 - /// - /// 要写入的时间间隔。 - void WriteTimeSpan(TimeSpan value); - - #endregion TimeSpan - - #region UInt32 - - /// - /// 读取一个无符号32位整数,使用默认的大端字节序。 - /// - /// 读取到的无符号32位整数。 - uint ReadUInt32(); - - /// - /// 按指定的字节序读取一个无符号32位整数。 - /// - /// 指定的字节序类型。 - /// 读取到的无符号32位整数。 - uint ReadUInt32(EndianType endianType); - - /// - /// 将输入的数据流转换为一系列无符号32位整数,使用默认的大端字节序。 - /// - /// 转换得到的一系列无符号32位整数。 - IEnumerable ToUInt32s(); - - /// - /// 按指定的字节序将输入的数据流转换为一系列无符号32位整数。 - /// - /// 指定的字节序类型。 - /// 转换得到的一系列无符号32位整数。 - IEnumerable ToUInt32s(EndianType endianType); - - /// - /// 将无符号32位整数写入输出流,使用默认的大端字节序。 - /// - /// 要写入的无符号32位整数。 - void WriteUInt32(uint value); - - /// - /// 按指定的字节序将无符号32位整数写入输出流。 - /// - /// 要写入的无符号32位整数。 - /// 指定的字节序类型。 - void WriteUInt32(uint value, EndianType endianType); - - #endregion UInt32 - - #region UInt64 - - /// - /// 读取一个指定类型的值。 - /// - /// 要读取的值的类型。 - /// 读取到的值。 - T ReadT() where T : unmanaged; - - /// - /// 按指定的字节序读取一个值。 - /// - /// 要读取的值的类型。 - /// 指定的字节序类型。 - /// 读取到的值。 - T ReadT(EndianType endianType) where T : unmanaged; - - /// - /// 读取一个无符号64位整数。 - /// - /// 读取到的无符号64位整数。 - ulong ReadUInt64(); - - /// - /// 按指定字节序读取一个无符号64位整数。 - /// - /// 指定的字节序。 - /// 读取到的无符号64位整数。 - ulong ReadUInt64(EndianType endianType); - - /// - /// 将当前对象转换为一系列值。 - /// - /// 值的类型。 - /// 一系列值的集合。 - IEnumerable ToTs() where T : unmanaged; - - /// - /// 按指定的字节序将当前对象转换为一系列值。 - /// - /// 值的类型。 - /// 指定的字节序类型。 - /// 一系列值的集合。 - IEnumerable ToTs(EndianType endianType) where T : unmanaged; - - /// - /// 将输入流中的数据全部转换为无符号64位整数的集合。 - /// - /// 转换得到的无符号64位整数集合。 - IEnumerable ToUInt64s(); - - /// - /// 按指定字节序将输入流中的数据全部转换为无符号64位整数的集合。 - /// - /// 指定的字节序。 - /// 转换得到的无符号64位整数集合。 - IEnumerable ToUInt64s(EndianType endianType); - - /// - /// 写入一个值。 - /// - /// 要写入的值的类型。 - /// 要写入的值。 - void WriteT(T value) where T : unmanaged; - - /// - /// 按指定的字节序写入一个值。 - /// - /// 要写入的值的类型。 - /// 要写入的值。 - /// 指定的字节序类型。 - void WriteT(T value, EndianType endianType) where T : unmanaged; - - /// - /// 写入一个无符号64位整数。 - /// - /// 要写入的无符号64位整数值。 - void WriteUInt64(ulong value); - - /// - /// 按指定字节序写入一个无符号64位整数。 - /// - /// 要写入的无符号64位整数值。 - /// 指定的字节序。 - void WriteUInt64(ulong value, EndianType endianType); - - #endregion UInt64 - - #region Seek - - /// - /// 将流定位到指定位置。 - /// - /// 要定位到的位置。 - void Seek(int position); - - /// - /// 将流定位到相对于指定起始点的偏移位置。 - /// - /// 从起始点开始的偏移量。 - /// 偏移量的起始点。 - /// 新的位置。 - int Seek(int offset, SeekOrigin origin); - - /// - /// 将流定位到末尾位置。 - /// - void SeekToEnd(); - - /// - /// 将流定位到起始位置。 - /// - void SeekToStart(); - - #endregion Seek - - /// - /// 扩展当前对象的大小。 - /// - /// 要扩展的大小。 - void ExtendSize(int size); - - /// - /// 将指定长度的数据读取到只读字节跨度中。 - /// - /// 要读取的数据长度。 - /// 包含读取数据的只读字节跨度。 - ReadOnlySpan ReadToSpan(int length); - - /// - /// 重置当前对象的状态到初始状态。 - /// - void Reset(); - - /// - /// 设置当前对象的容量。 - /// - /// 新的容量大小。 - /// 是否保留原有数据,默认为false。 - void SetCapacity(int size, bool retainedData = false); - - /// - /// 设置当前对象是否处于持有状态。 - /// - /// true表示处于持有状态,false表示非持有状态。 - void SetHolding(bool holding); - - /// - /// 设置当前对象的长度。 - /// - /// 新的长度值。 - void SetLength(int value); - - #region ToString - - /// - /// 将当前实例转换为字符串表示形式。 - /// - /// 当前实例的字符串表示。 - string ToString(); - - /// - /// 将当前实例从指定位置转换为字符串表示形式。 - /// - /// 开始转换的索引位置。 - /// 从指定位置开始的当前实例的字符串表示。 - string ToString(int offset); - - /// - /// 将当前实例从指定位置开始,并在指定长度内转换为字符串表示形式。 - /// - /// 开始转换的索引位置。 - /// 转换的字符数量。 - /// 从指定位置开始,并在指定长度内的当前实例的字符串表示。 - string ToString(int offset, int length); - - #endregion ToString - - #region DateTime - - /// - /// 读取一个DateTime值。 - /// - /// 返回读取到的DateTime值。 - DateTime ReadDateTime(); - - /// - /// 将当前实例转换为一系列的DateTime值。 - /// - /// 返回一个IEnumerable<DateTime>,包含转换后的DateTime值。 - IEnumerable ToDateTimes(); - - /// - /// 写入一个DateTime值。 - /// - /// 要写入的DateTime值。 - void WriteDateTime(DateTime value); - - #endregion DateTime - - #region Double - - /// - /// 读取一个双精度浮点数。 - /// - /// 读取到的双精度浮点数。 - double ReadDouble(); - - /// - /// 按指定字节序读取一个双精度浮点数。 - /// - /// 指定的字节序。 - /// 读取到的双精度浮点数。 - double ReadDouble(EndianType endianType); - - /// - /// 读取一系列双精度浮点数。 - /// - /// 一个包含读取到的双精度浮点数的序列。 - IEnumerable ToDoubles(); - - /// - /// 按指定字节序读取一系列双精度浮点数。 - /// - /// 指定的字节序。 - /// 一个包含读取到的双精度浮点数的序列。 - IEnumerable ToDoubles(EndianType endianType); - - /// - /// 写入一个双精度浮点数。 - /// - /// 要写入的双精度浮点数。 - void WriteDouble(double value); - - /// - /// 按指定字节序写入一个双精度浮点数。 - /// - /// 要写入的双精度浮点数。 - /// 指定的字节序。 - void WriteDouble(double value, EndianType endianType); - - #endregion Double - - #region Int32 - - /// - /// 读取一个32位整数。 - /// - /// 读取到的32位整数值。 - int ReadInt32(); - - /// - /// 按指定的字节序读取一个32位整数。 - /// - /// 指定的字节序类型。 - /// 读取到的32位整数值。 - int ReadInt32(EndianType endianType); - - /// - /// 读取一系列32位整数。 - /// - /// 一系列32位整数值的集合。 - IEnumerable ToInt32s(); - - /// - /// 按指定的字节序读取一系列32位整数。 - /// - /// 指定的字节序类型。 - /// 一系列32位整数值的集合。 - IEnumerable ToInt32s(EndianType endianType); - - /// - /// 写入一个32位整数。 - /// - /// 要写入的32位整数值。 - void WriteInt32(int value); - - /// - /// 按指定的字节序写入一个32位整数。 - /// - /// 要写入的32位整数值。 - /// 指定的字节序类型。 - void WriteInt32(int value, EndianType endianType); - - #endregion Int32 - - #region Int16 - - /// - /// 读取一个Int16类型的数据,使用默认的字节序。 - /// - /// 读取到的Int16数据。 - short ReadInt16(); - - /// - /// 按指定字节序读取一个Int16类型的数据。 - /// - /// 指定的字节序类型。 - /// 读取到的Int16数据。 - short ReadInt16(EndianType endianType); - - /// - /// 读取一系列Int16类型的数据,使用默认的字节序。 - /// - /// 一系列读取到的Int16数据。 - IEnumerable ToInt16s(); - - /// - /// 按指定字节序读取一系列Int16类型的数据。 - /// - /// 指定的字节序类型。 - /// 一系列读取到的Int16数据。 - IEnumerable ToInt16s(EndianType endianType); - - /// - /// 写入一个Int16类型的数据,使用默认的字节序。 - /// - /// 要写入的Int16数据。 - void WriteInt16(short value); - - /// - /// 按指定字节序写入一个Int16类型的数据。 - /// - /// 要写入的Int16数据。 - /// 指定的字节序类型。 - void WriteInt16(short value, EndianType endianType); - - #endregion Int16 - - #region String - - /// - /// 读取一个字符串。 - /// - /// 可选的固定头部类型,默认为Int类型。 - /// 读取到的字符串。 - string ReadString(FixedHeaderType headerType = FixedHeaderType.Int); - - /// - /// 写入一个字符串。 - /// - /// 要写入的字符串值。 - /// 可选的固定头部类型,默认为Int类型。 - void WriteString(string value, FixedHeaderType headerType = FixedHeaderType.Int); - - #endregion String - - #region NormalString - - /// - /// 写入一个普通字符串。和不同的是,该方法不会写入字符串的长度。 - /// 其功能类似于。 - /// - /// 字符串 - /// 编码 - void WriteNormalString(string value, Encoding encoding); - - #endregion NormalString - - #region UInt16 - - /// - /// 读取一个无符号16位整数。 - /// - /// 读取到的无符号16位整数。 - ushort ReadUInt16(); - - /// - /// 根据指定的字节顺序读取一个无符号16位整数。 - /// - /// 指定的字节顺序。 - /// 读取到的无符号16位整数。 - ushort ReadUInt16(EndianType endianType); - - /// - /// 将输入流中的数据全部转换为无符号16位整数的集合。 - /// - /// 转换得到的无符号16位整数集合。 - IEnumerable ToUInt16s(); - - /// - /// 根据指定的字节顺序,将输入流中的数据全部转换为无符号16位整数的集合。 - /// - /// 指定的字节顺序。 - /// 转换得到的无符号16位整数集合。 - IEnumerable ToUInt16s(EndianType endianType); - - /// - /// 写入一个无符号16位整数。 - /// - /// 要写入的无符号16位整数值。 - void WriteUInt16(ushort value); - - /// - /// 根据指定的字节顺序写入一个无符号16位整数。 - /// - /// 要写入的无符号16位整数值。 - /// 指定的字节顺序。 - void WriteUInt16(ushort value, EndianType endianType); - - #endregion UInt16 - - #region BytesPackage - - /// - /// 读取字节流数据包。 - /// - /// 字节数组 - byte[] ReadBytesPackage(); - - /// - /// 读取字节流数据包为只读内存。 - /// - /// 只读内存字节块,可能为 - ReadOnlyMemory? ReadBytesPackageMemory(); - - /// - /// 写入字节流数据包。 - /// - /// 要写入的字节数组 - void WriteBytesPackage(byte[] value); - - /// - /// 从指定偏移量开始写入指定长度的字节流数据包。 - /// - /// 要写入的字节数组 - /// 从数组中开始写入的索引位置 - /// 要写入的字节数 - void WriteBytesPackage(byte[] value, int offset, int length); - - #endregion BytesPackage - - #region Package - - /// - /// 读取指定类型的包装对象。 - /// - /// 包装对象的类型,必须是IPackage的实现。 - /// 返回读取到的包装对象。 - TPackage ReadPackage() where TPackage : class, IPackage, new(); - - /// - /// 写入指定类型的包装对象。 - /// - /// 包装对象的类型,必须是IPackage的实现。 - /// 要写入的包装对象。 - void WritePackage(TPackage package) where TPackage : class, IPackage; - - #endregion Package - - #region VarUInt32 - - /// - /// 读取一个变量长度的无符号32位整数。 - /// - /// 返回读取到的无符号32位整数值。 - uint ReadVarUInt32(); - - /// - /// 写入一个变量长度的无符号32位整数。 - /// - /// 要写入的无符号32位整数值。 - /// 返回写入操作的结果,成功返回0,否则返回-1。 - int WriteVarUInt32(uint value); - - #endregion VarUInt32 -} \ No newline at end of file diff --git a/src/TouchSocket.Core/Readme.md b/src/TouchSocket.Core/Readme.md index c0544480f..df3b2531f 100644 --- a/src/TouchSocket.Core/Readme.md +++ b/src/TouchSocket.Core/Readme.md @@ -37,7 +37,7 @@ - netstandard2.0 - netstandard2.1 - net6.0 -- net9.0 +- net10.0 - net8.0 ## 使用方法 diff --git a/src/TouchSocket.Core/Reflection/DynamicMethodAttribute.cs b/src/TouchSocket.Core/Reflection/DynamicMethodAttribute.cs index 15dad8e34..6efb52ce1 100644 --- a/src/TouchSocket.Core/Reflection/DynamicMethodAttribute.cs +++ b/src/TouchSocket.Core/Reflection/DynamicMethodAttribute.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/Reflection/DynamicMethodInfoBase.cs b/src/TouchSocket.Core/Reflection/DynamicMethodInfoBase.cs index 8c23a7878..aae201c93 100644 --- a/src/TouchSocket.Core/Reflection/DynamicMethodInfoBase.cs +++ b/src/TouchSocket.Core/Reflection/DynamicMethodInfoBase.cs @@ -10,16 +10,15 @@ // 感谢您的下载和使用 // ------------------------------------------------------------------------------ -using System; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Reflection; -using System.Threading.Tasks; namespace TouchSocket.Core; -abstract class DynamicMethodInfoBase : IDynamicMethodInfo +[RequiresUnreferencedCode("此方法可能会使用反射构建访问器,与剪裁不兼容。")] +internal abstract class DynamicMethodInfoBase : IDynamicMethodInfo { + public DynamicMethodInfoBase(MethodInfo method) { if (method.ReturnType == typeof(void)) @@ -57,16 +56,14 @@ abstract class DynamicMethodInfoBase : IDynamicMethodInfo public MethodReturnKind ReturnKind { get; set; } - public async Task GetResultAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] object result) + public async Task GetResultAsync(object result) { if (result is Task task) { await task.ConfigureAwait(EasyTask.ContinueOnCapturedContext); - if (this.ReturnKind == MethodReturnKind.AwaitableObject) - { - return DynamicMethodMemberAccessor.Default.GetValue(task, nameof(Task.Result)); - } - return null; + return this.ReturnKind == MethodReturnKind.AwaitableObject + ? MemberAccessor.StaticGetValue(task, nameof(Task.Result)) + : null; } ThrowHelper.ThrowException("当源生成无法使用时,无法处理非Task的Awaitable对象。"); return null; @@ -115,14 +112,7 @@ abstract class DynamicMethodInfoBase : IDynamicMethodInfo } // 6. 检查GetResult方法的返回类型 - if (getResultMethod.ReturnType == typeof(void)) - { - returnType = null; - } - else - { - returnType = getResultMethod.ReturnType; - } + returnType = getResultMethod.ReturnType == typeof(void) ? null : getResultMethod.ReturnType; return true; } diff --git a/src/TouchSocket.Core/Reflection/DynamicMethodMemberAccessor.cs b/src/TouchSocket.Core/Reflection/DynamicMethodMemberAccessor.cs index 98ff4a4c2..41e83766c 100644 --- a/src/TouchSocket.Core/Reflection/DynamicMethodMemberAccessor.cs +++ b/src/TouchSocket.Core/Reflection/DynamicMethodMemberAccessor.cs @@ -1,80 +1,77 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ -using System; -using System.Collections.Concurrent; -using System.Diagnostics.CodeAnalysis; -using System.Reflection; +//using System.Collections.Concurrent; +//using System.Reflection; -namespace TouchSocket.Core; +//namespace TouchSocket.Core; -/// -/// DynamicMethodMemberAccessor -/// -public class DynamicMethodMemberAccessor : IMemberAccessor -{ - private readonly ConcurrentDictionary m_classAccessors = new ConcurrentDictionary(); +///// +///// DynamicMethodMemberAccessor +///// +//public class DynamicMethodMemberAccessor +//{ - static DynamicMethodMemberAccessor() - { - Default = new DynamicMethodMemberAccessor(); - } +// static DynamicMethodMemberAccessor() +// { +// Default = new DynamicMethodMemberAccessor(); +// } - /// - /// DynamicMethodMemberAccessor的默认实例。 - /// - public static DynamicMethodMemberAccessor Default { get; private set; } +// /// +// /// DynamicMethodMemberAccessor的默认实例。 +// /// +// public static DynamicMethodMemberAccessor Default { get; private set; } - /// - /// 获取字段 - /// - public Func OnGetFieldInfes { get; set; } +// /// +// /// 获取字段 +// /// +// public Func OnGetFieldInfes { get; set; } - /// - /// 获取属性 - /// - public Func OnGetProperties { get; set; } +// /// +// /// 获取属性 +// /// +// public Func OnGetProperties { get; set; } - /// - public object GetValue([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] object instance, string memberName) - { - return this.FindClassAccessor(instance).GetValue(instance, memberName); - } +// /// +// public object GetValue(object instance, string memberName) +// { +// return this.FindClassAccessor(instance).GetValue(instance, memberName); +// } - /// - public void SetValue([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] object instance, string memberName, object newValue) - { - this.FindClassAccessor(instance).SetValue(instance, memberName, newValue); - } +// /// +// public void SetValue(object instance, string memberName, object newValue) +// { +// this.FindClassAccessor(instance).SetValue(instance, memberName, newValue); +// } - private IMemberAccessor FindClassAccessor([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] object instance) - { - var typeKey = instance.GetType(); - if (!this.m_classAccessors.TryGetValue(typeKey, out var classAccessor)) - { - var memberAccessor = new MemberAccessor(typeKey); - if (this.OnGetFieldInfes != null) - { - memberAccessor.OnGetFieldInfos = this.OnGetFieldInfes; - } +// private DynamicMethodMemberAccessor FindClassAccessor(object instance) +// { +// var typeKey = instance.GetType(); +// if (!this.m_classAccessors.TryGetValue(typeKey, out var classAccessor)) +// { +// var memberAccessor = new MemberAccessor(typeKey); +// if (this.OnGetFieldInfes != null) +// { +// memberAccessor.OnGetFieldInfos = this.OnGetFieldInfes; +// } - if (this.OnGetProperties != null) - { - memberAccessor.OnGetProperties = this.OnGetProperties; - } - memberAccessor.Build(); - classAccessor = memberAccessor; - this.m_classAccessors.TryAdd(typeKey, classAccessor); - } - return classAccessor; - } -} \ No newline at end of file +// if (this.OnGetProperties != null) +// { +// memberAccessor.OnGetProperties = this.OnGetProperties; +// } +// memberAccessor.Build(); +// classAccessor = memberAccessor; +// this.m_classAccessors.TryAdd(typeKey, classAccessor); +// } +// return classAccessor; +// } +//} \ No newline at end of file diff --git a/src/TouchSocket.Core/Reflection/ExpressionDynamicMethodInfo.cs b/src/TouchSocket.Core/Reflection/ExpressionDynamicMethodInfo.cs index 41587c5b0..b61594c1e 100644 --- a/src/TouchSocket.Core/Reflection/ExpressionDynamicMethodInfo.cs +++ b/src/TouchSocket.Core/Reflection/ExpressionDynamicMethodInfo.cs @@ -10,12 +10,13 @@ // 感谢您的下载和使用 // ------------------------------------------------------------------------------ -using System; -using System.Linq; +using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using System.Reflection; namespace TouchSocket.Core; + +[RequiresUnreferencedCode("此方法可能会使用反射构建访问器,与剪裁不兼容。")] internal class ExpressionDynamicMethodInfo : DynamicMethodInfoBase { private readonly Func m_func; diff --git a/src/TouchSocket.Core/Reflection/ExpressionMapper.cs b/src/TouchSocket.Core/Reflection/ExpressionMapper.cs deleted file mode 100644 index 2e6e41cc6..000000000 --- a/src/TouchSocket.Core/Reflection/ExpressionMapper.cs +++ /dev/null @@ -1,55 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; -using System.Collections.Generic; -using System.Linq.Expressions; - -namespace TouchSocket.Core; - -/// -/// 表达式复制 -/// -public class ExpressionMapper -{ - private static readonly Dictionary m_dic = new Dictionary(); - - /// - /// 字典缓存表达式树 - /// - public static TOut Trans(TIn tIn) - { - var key = string.Format("funckey_{0}_{1}", typeof(TIn).FullName, typeof(TOut).FullName); - if (!m_dic.ContainsKey(key)) - { - var parameterExpression = Expression.Parameter(typeof(TIn), "p"); - var memberBindingList = new List(); - foreach (var item in typeof(TOut).GetProperties()) - { - var property = Expression.Property(parameterExpression, typeof(TIn).GetProperty(item.Name)); - MemberBinding memberBinding = Expression.Bind(item, property); - memberBindingList.Add(memberBinding); - } - foreach (var item in typeof(TOut).GetFields()) - { - var property = Expression.Field(parameterExpression, typeof(TIn).GetField(item.Name)); - MemberBinding memberBinding = Expression.Bind(item, property); - memberBindingList.Add(memberBinding); - } - var memberInitExpression = Expression.MemberInit(Expression.New(typeof(TOut)), memberBindingList.ToArray()); - var lambda = Expression.Lambda>(memberInitExpression, parameterExpression); - var func = lambda.Compile();//拼装是一次性的 - m_dic[key] = func; - } - return ((Func)m_dic[key]).Invoke(tIn); - } -} \ No newline at end of file diff --git a/src/TouchSocket.Core/Reflection/Field.cs b/src/TouchSocket.Core/Reflection/Field.cs new file mode 100644 index 000000000..dd9cd6490 --- /dev/null +++ b/src/TouchSocket.Core/Reflection/Field.cs @@ -0,0 +1,87 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.Diagnostics.CodeAnalysis; +using System.Reflection; + +namespace TouchSocket.Core; + +public sealed class Field +{ + /// + /// 获取器 + /// + private readonly MemberGetter m_geter; + + /// + /// 设置器 + /// + private readonly MemberSetter m_seter; + + /// + /// 字段 + /// + /// 字段信息 + public Field(FieldInfo fieldInfo) + { + this.Info = fieldInfo; + + this.m_geter = new MemberGetter(fieldInfo); + + if (!fieldInfo.IsInitOnly) + { + this.m_seter = new MemberSetter(fieldInfo); + } + } + + /// + /// 获取字段信息 + /// + public FieldInfo Info { get; } + + /// + /// 从类型的字段获取字段 + /// + /// 类型 + /// + public static Field[] GetFields([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] Type type) + { + return type.GetFields().Select(p => new Field(p)).ToArray(); + } + + /// + /// 获取字段的值 + /// + /// 实例 + /// + /// + public object GetValue(object instance) + { + return this.m_geter == null ? throw new NotSupportedException() : this.m_geter.Invoke(instance); + } + + /// + /// 设置字段的值 + /// + /// 实例 + /// 值 + /// + public void SetValue(object instance, object value) + { + if (this.m_seter == null) + { + throw new NotSupportedException($"{this.Info.Name}不允许赋值"); + } + this.m_seter.Invoke(instance, value); + } +} + diff --git a/src/TouchSocket.Core/Reflection/IDynamicMethodInfo.cs b/src/TouchSocket.Core/Reflection/IDynamicMethodInfo.cs index 7e106d314..6d923a392 100644 --- a/src/TouchSocket.Core/Reflection/IDynamicMethodInfo.cs +++ b/src/TouchSocket.Core/Reflection/IDynamicMethodInfo.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 // ------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/Reflection/ILDynamicMethodInfo.cs b/src/TouchSocket.Core/Reflection/ILDynamicMethodInfo.cs deleted file mode 100644 index bf864b296..000000000 --- a/src/TouchSocket.Core/Reflection/ILDynamicMethodInfo.cs +++ /dev/null @@ -1,185 +0,0 @@ -// ------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -// ------------------------------------------------------------------------------ - -using System; -using System.Reflection; -using System.Reflection.Emit; - -namespace TouchSocket.Core; -internal class ILDynamicMethodInfo : DynamicMethodInfoBase -{ - private readonly Func m_func; - public ILDynamicMethodInfo(MethodInfo method) : base(method) - { - this.m_func = CreateILInvoker(method); - } - - public override object Invoke(object instance, object[] parameters) - { - return this.m_func(instance, parameters); - } - - protected static Func CreateILInvoker(MethodInfo methodInfo) - { - var dynamicMethod = new DynamicMethod(string.Empty, typeof(object), new Type[] { typeof(object), typeof(object[]) -}, methodInfo.DeclaringType.Module); - var il = dynamicMethod.GetILGenerator(); - var ps = methodInfo.GetParameters(); - var paramTypes = new Type[ps.Length]; - for (var i = 0; i < paramTypes.Length; i++) - { - paramTypes[i] = ps[i].ParameterType.IsByRef ? ps[i].ParameterType.GetElementType() : ps[i].ParameterType; - } - var locals = new LocalBuilder[paramTypes.Length]; - - for (var i = 0; i < paramTypes.Length; i++) - { - locals[i] = il.DeclareLocal(paramTypes[i], true); - } - for (var i = 0; i < paramTypes.Length; i++) - { - il.Emit(OpCodes.Ldarg_1); - EmitFastInt(il, i); - il.Emit(OpCodes.Ldelem_Ref); - EmitCastToReference(il, paramTypes[i]); - il.Emit(OpCodes.Stloc, locals[i]); - } - if (!methodInfo.IsStatic) - { - il.Emit(OpCodes.Ldarg_0); - } - for (var i = 0; i < paramTypes.Length; i++) - { - if (ps[i].ParameterType.IsByRef) - { - il.Emit(OpCodes.Ldloca_S, locals[i]); - } - else - { - il.Emit(OpCodes.Ldloc, locals[i]); - } - } - if (methodInfo.IsStatic) - { - il.EmitCall(OpCodes.Call, methodInfo, null); - } - else - { - il.EmitCall(OpCodes.Callvirt, methodInfo, null); - } - - if (methodInfo.ReturnType == typeof(void)) - { - il.Emit(OpCodes.Ldnull); - } - else - { - EmitBoxIfNeeded(il, methodInfo.ReturnType); - } - - for (var i = 0; i < paramTypes.Length; i++) - { - if (ps[i].ParameterType.IsByRef) - { - il.Emit(OpCodes.Ldarg_1); - EmitFastInt(il, i); - il.Emit(OpCodes.Ldloc, locals[i]); - if (locals[i].LocalType.IsValueType) - { - il.Emit(OpCodes.Box, locals[i].LocalType); - } - - il.Emit(OpCodes.Stelem_Ref); - } - } - - il.Emit(OpCodes.Ret); - var invoker = (Func)dynamicMethod.CreateDelegate(typeof(Func)); - return invoker; - } - - private static void EmitBoxIfNeeded(ILGenerator il, System.Type type) - { - if (type.IsValueType) - { - il.Emit(OpCodes.Box, type); - } - } - - private static void EmitCastToReference(ILGenerator il, System.Type type) - { - if (type.IsValueType) - { - il.Emit(OpCodes.Unbox_Any, type); - } - else - { - il.Emit(OpCodes.Castclass, type); - } - } - - private static void EmitFastInt(ILGenerator il, int value) - { - switch (value) - { - case -1: - il.Emit(OpCodes.Ldc_I4_M1); - return; - - case 0: - il.Emit(OpCodes.Ldc_I4_0); - return; - - case 1: - il.Emit(OpCodes.Ldc_I4_1); - return; - - case 2: - il.Emit(OpCodes.Ldc_I4_2); - return; - - case 3: - il.Emit(OpCodes.Ldc_I4_3); - return; - - case 4: - il.Emit(OpCodes.Ldc_I4_4); - return; - - case 5: - il.Emit(OpCodes.Ldc_I4_5); - return; - - case 6: - il.Emit(OpCodes.Ldc_I4_6); - return; - - case 7: - il.Emit(OpCodes.Ldc_I4_7); - return; - - case 8: - il.Emit(OpCodes.Ldc_I4_8); - return; - } - - if (value > -129 && value < 128) - { - il.Emit(OpCodes.Ldc_I4_S, (SByte)value); - } - else - { - il.Emit(OpCodes.Ldc_I4, value); - } - } - -} diff --git a/src/TouchSocket.Core/Reflection/IMemberAccessor.cs b/src/TouchSocket.Core/Reflection/IMemberAccessor.cs deleted file mode 100644 index 28d863892..000000000 --- a/src/TouchSocket.Core/Reflection/IMemberAccessor.cs +++ /dev/null @@ -1,35 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -namespace TouchSocket.Core; - -/// -/// 一个成员访问接口 -/// -public interface IMemberAccessor -{ - /// - /// 获取指定成员的值 - /// - /// - /// - /// - object GetValue(object instance, string memberName); - - /// - ///设置指定成员的值 - /// - /// - /// - /// - void SetValue(object instance, string memberName, object newValue); -} \ No newline at end of file diff --git a/src/TouchSocket.Core/Reflection/MemberAccessor.cs b/src/TouchSocket.Core/Reflection/MemberAccessor.cs index 2fb8d5ce5..4cb123a0e 100644 --- a/src/TouchSocket.Core/Reflection/MemberAccessor.cs +++ b/src/TouchSocket.Core/Reflection/MemberAccessor.cs @@ -10,10 +10,8 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; +using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -22,84 +20,153 @@ namespace TouchSocket.Core; /// /// 动态成员访问器 /// -/// -public class MemberAccessor : MemberAccessor +public class MemberAccessor { - /// - /// 动态成员访问器 - /// - public MemberAccessor() : base(typeof(T)) - { - } -} + #region static -/// -/// 动态成员访问器 -/// -public class MemberAccessor : IMemberAccessor -{ - private Func m_getValueDelegate; - private Dictionary m_dicFieldInfos; - private Dictionary m_dicProperties; - private Action m_setValueDelegate; + private static readonly ConcurrentDictionary s_classAccessors = new(); /// - /// 动态成员访问器 + /// 静态获取成员值 /// - /// - public MemberAccessor([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] Type type) + /// 对象实例 + /// 成员名称 + /// 成员的值 + [RequiresUnreferencedCode("此方法使用反射构建访问器,与剪裁不兼容。请改用安全的替代方法。")] + public static object StaticGetValue(object instance, string memberName) { - this.Type = type; - this.OnGetFieldInfos = (t) => { return t.GetFields(); }; - this.OnGetProperties = (t) => { return t.GetProperties(); }; + return FindClassAccessor(instance).GetValue(instance, memberName); } /// - /// 获取字段 + /// 静态设置成员值 /// - public Func OnGetFieldInfos { get; set; } - - /// - /// 获取属性 - /// - public Func OnGetProperties { get; set; } - - /// - /// 所属类型 - /// - public Type Type { get; } - - /// - /// 构建 - /// - public void Build() + /// 对象实例 + /// 成员名称 + /// 新值 + [RequiresUnreferencedCode("此方法使用反射构建访问器,与剪裁不兼容。请改用安全的替代方法。")] + public static void StaticSetValue(object instance, string memberName, object newValue) { - if (GlobalEnvironment.DynamicBuilderType == DynamicBuilderType.Reflect) + FindClassAccessor(instance).SetValue(instance, memberName, newValue); + } + + [RequiresUnreferencedCode("此方法使用反射构建访问器,与剪裁不兼容。请改用安全的替代方法。")] + private static MemberAccessor FindClassAccessor(object instance) + { + var typeKey = instance.GetType(); + if (!s_classAccessors.TryGetValue(typeKey, out var classAccessor)) { - this.m_dicFieldInfos = this.OnGetFieldInfos.Invoke(this.Type).ToDictionary(a => a.Name); - this.m_dicProperties = this.OnGetProperties.Invoke(this.Type).ToDictionary(a => a.Name); + var memberAccessor = new MemberAccessor(typeKey); + classAccessor = memberAccessor; + s_classAccessors.TryAdd(typeKey, classAccessor); } - - this.m_getValueDelegate = this.GenerateGetValue(); - this.m_setValueDelegate = this.GenerateSetValue(); + return classAccessor; } - /// + #endregion static + + private readonly Dictionary m_dicFieldInfos; + private readonly Dictionary m_dicProperties; + private readonly Func m_getValueDelegate; + private readonly Action m_setValueDelegate; + + /// + /// 初始化实例 + /// + /// 类型 + public MemberAccessor([DynamicallyAccessedMembers(AOT.MemberAccessor)] Type type) + { + this.m_dicFieldInfos = type.GetFields(BindingFlags.Instance | BindingFlags.Public) + .ToDictionary(a => a.Name); + + this.m_dicProperties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public).ToDictionary(a => a.Name); + + this.m_getValueDelegate = this.GenerateGetValue(type); + this.m_setValueDelegate = this.GenerateSetValue(type); + } + + /// + /// 初始化实例 + /// + /// 类型 + /// 字段信息字典 + /// 属性信息字典 + public MemberAccessor([DynamicallyAccessedMembers(AOT.MemberAccessor)] Type type, Dictionary fieldInfos, Dictionary properties) + { + this.m_dicFieldInfos = fieldInfos; + this.m_dicProperties = properties; + this.m_getValueDelegate = this.GenerateGetValue(type); + this.m_setValueDelegate = this.GenerateSetValue(type); + } + + /// + /// 获取成员值 + /// + /// 对象实例 + /// 成员名称 + /// 成员的值 public object GetValue(object instance, string memberName) { return this.m_getValueDelegate(instance, memberName); } - /// + /// + /// 设置成员值 + /// + /// 对象实例 + /// 成员名称 + /// 新值 public void SetValue(object instance, string memberName, object newValue) { this.m_setValueDelegate(instance, memberName, newValue); } - - private Func GenerateGetValue() + [UnconditionalSuppressMessage("AOT", "IL2026", Justification = "属性一定存在")] + private Func GenerateGetValue([DynamicallyAccessedMembers(AOT.MemberAccessor)] Type type) { - if (GlobalEnvironment.DynamicBuilderType == DynamicBuilderType.Reflect) + try + { + var instance = Expression.Parameter(typeof(object), "instance"); + var memberName = Expression.Parameter(typeof(string), "memberName"); + var nameHash = Expression.Variable(typeof(int), "nameHash"); + var calHash = Expression.Assign(nameHash, Expression.Call(memberName, typeof(object).GetMethod("GetHashCode"))); + var cases = new List(); + foreach (var propertyInfo in this.m_dicFieldInfos.Values) + { + try + { + var property = Expression.Field(Expression.Convert(instance, type), propertyInfo.Name); + var propertyHash = Expression.Constant(propertyInfo.Name.GetHashCode(), typeof(int)); + + cases.Add(Expression.SwitchCase(Expression.Convert(property, typeof(object)), propertyHash)); + } + catch + { + } + } + foreach (var propertyInfo in this.m_dicProperties.Values) + { + try + { + var property = Expression.Property(Expression.Convert(instance, type), propertyInfo.Name); + var propertyHash = Expression.Constant(propertyInfo.Name.GetHashCode(), typeof(int)); + + cases.Add(Expression.SwitchCase(Expression.Convert(property, typeof(object)), propertyHash)); + } + catch + { + } + } + if (cases.Count == 0) + { + return (a, b) => default; + } + var switchEx = Expression.Switch(nameHash, Expression.Constant(null), cases.ToArray()); + var methodBody = Expression.Block(typeof(object), new[] { nameHash }, calHash, switchEx); + + return Expression.Lambda>(methodBody, instance, memberName).Compile(); + } + catch { return (obj, key) => { @@ -109,50 +176,49 @@ public class MemberAccessor : IMemberAccessor }; } - var instance = Expression.Parameter(typeof(object), "instance"); - var memberName = Expression.Parameter(typeof(string), "memberName"); - var nameHash = Expression.Variable(typeof(int), "nameHash"); - var calHash = Expression.Assign(nameHash, Expression.Call(memberName, typeof(object).GetMethod("GetHashCode"))); - var cases = new List(); - foreach (var propertyInfo in this.OnGetFieldInfos.Invoke(this.Type)) - { - try - { - var property = Expression.Field(Expression.Convert(instance, this.Type), propertyInfo.Name); - var propertyHash = Expression.Constant(propertyInfo.Name.GetHashCode(), typeof(int)); - - cases.Add(Expression.SwitchCase(Expression.Convert(property, typeof(object)), propertyHash)); - } - catch - { - } - } - foreach (var propertyInfo in this.OnGetProperties.Invoke(this.Type)) - { - try - { - var property = Expression.Property(Expression.Convert(instance, this.Type), propertyInfo.Name); - var propertyHash = Expression.Constant(propertyInfo.Name.GetHashCode(), typeof(int)); - - cases.Add(Expression.SwitchCase(Expression.Convert(property, typeof(object)), propertyHash)); - } - catch - { - } - } - if (cases.Count == 0) - { - return (a, b) => default; - } - var switchEx = Expression.Switch(nameHash, Expression.Constant(null), cases.ToArray()); - var methodBody = Expression.Block(typeof(object), new[] { nameHash }, calHash, switchEx); - - return Expression.Lambda>(methodBody, instance, memberName).Compile(); } - private Action GenerateSetValue() + [UnconditionalSuppressMessage("AOT", "IL2026", Justification = "属性一定存在")] + private Action GenerateSetValue([DynamicallyAccessedMembers(AOT.MemberAccessor)] Type type) { - if (GlobalEnvironment.DynamicBuilderType == DynamicBuilderType.Reflect) + try + { + var instance = Expression.Parameter(typeof(object), "instance"); + var memberName = Expression.Parameter(typeof(string), "memberName"); + var newValue = Expression.Parameter(typeof(object), "newValue"); + var nameHash = Expression.Variable(typeof(int), "nameHash"); + var calHash = Expression.Assign(nameHash, Expression.Call(memberName, typeof(object).GetMethod("GetHashCode"))); + var cases = new List(); + foreach (var propertyInfo in this.m_dicFieldInfos.Values) + { + var property = Expression.Field(Expression.Convert(instance, type), propertyInfo.Name); + var setValue = Expression.Assign(property, Expression.Convert(newValue, propertyInfo.FieldType)); + var propertyHash = Expression.Constant(propertyInfo.Name.GetHashCode(), typeof(int)); + + cases.Add(Expression.SwitchCase(Expression.Convert(setValue, typeof(object)), propertyHash)); + } + foreach (var propertyInfo in this.m_dicProperties.Values) + { + if (!propertyInfo.CanWrite) + { + continue; + } + var property = Expression.Property(Expression.Convert(instance, type), propertyInfo.Name); + var setValue = Expression.Assign(property, Expression.Convert(newValue, propertyInfo.PropertyType)); + var propertyHash = Expression.Constant(propertyInfo.Name.GetHashCode(), typeof(int)); + + cases.Add(Expression.SwitchCase(Expression.Convert(setValue, typeof(object)), propertyHash)); + } + if (cases.Count == 0) + { + return (a, b, c) => { }; + } + var switchEx = Expression.Switch(nameHash, Expression.Constant(null), cases.ToArray()); + var methodBody = Expression.Block(typeof(object), new[] { nameHash }, calHash, switchEx); + + return Expression.Lambda>(methodBody, instance, memberName, newValue).Compile(); + } + catch { return (obj, key, value) => { @@ -167,39 +233,6 @@ public class MemberAccessor : IMemberAccessor }; } - var instance = Expression.Parameter(typeof(object), "instance"); - var memberName = Expression.Parameter(typeof(string), "memberName"); - var newValue = Expression.Parameter(typeof(object), "newValue"); - var nameHash = Expression.Variable(typeof(int), "nameHash"); - var calHash = Expression.Assign(nameHash, Expression.Call(memberName, typeof(object).GetMethod("GetHashCode"))); - var cases = new List(); - foreach (var propertyInfo in this.OnGetFieldInfos.Invoke(this.Type)) - { - var property = Expression.Field(Expression.Convert(instance, this.Type), propertyInfo.Name); - var setValue = Expression.Assign(property, Expression.Convert(newValue, propertyInfo.FieldType)); - var propertyHash = Expression.Constant(propertyInfo.Name.GetHashCode(), typeof(int)); - cases.Add(Expression.SwitchCase(Expression.Convert(setValue, typeof(object)), propertyHash)); - } - foreach (var propertyInfo in this.OnGetProperties(this.Type)) - { - if (!propertyInfo.CanWrite) - { - continue; - } - var property = Expression.Property(Expression.Convert(instance, this.Type), propertyInfo.Name); - var setValue = Expression.Assign(property, Expression.Convert(newValue, propertyInfo.PropertyType)); - var propertyHash = Expression.Constant(propertyInfo.Name.GetHashCode(), typeof(int)); - - cases.Add(Expression.SwitchCase(Expression.Convert(setValue, typeof(object)), propertyHash)); - } - if (cases.Count == 0) - { - return (a, b, c) => { }; - } - var switchEx = Expression.Switch(nameHash, Expression.Constant(null), cases.ToArray()); - var methodBody = Expression.Block(typeof(object), new[] { nameHash }, calHash, switchEx); - - return Expression.Lambda>(methodBody, instance, memberName, newValue).Compile(); } } \ No newline at end of file diff --git a/src/TouchSocket.Core/Reflection/MemberGetter.cs b/src/TouchSocket.Core/Reflection/MemberGetter.cs index b765551ee..35b84caf1 100644 --- a/src/TouchSocket.Core/Reflection/MemberGetter.cs +++ b/src/TouchSocket.Core/Reflection/MemberGetter.cs @@ -10,21 +10,22 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Linq.Expressions; using System.Reflection; namespace TouchSocket.Core; -/// -/// 表示属性的Getter -/// -public class MemberGetter +internal class MemberGetter { /// /// get方法委托 /// private readonly Func m_getFunc; + + /// + /// 是否为静态成员 + /// + private readonly bool m_isStatic; /// /// 表示属性的Getter @@ -33,7 +34,9 @@ public class MemberGetter /// public MemberGetter(PropertyInfo property) { - this.m_getFunc = CreateGetterDelegate(property); + var getMethod = property.GetGetMethod(true); + this.m_isStatic = getMethod != null && getMethod.IsStatic; + this.m_getFunc = CreateGetterDelegate(property, this.m_isStatic); } /// @@ -42,7 +45,8 @@ public class MemberGetter /// public MemberGetter(FieldInfo fieldInfo) { - this.m_getFunc = CreateGetterDelegate(fieldInfo); + this.m_isStatic = fieldInfo.IsStatic; + this.m_getFunc = CreateGetterDelegate(fieldInfo, this.m_isStatic); } /// @@ -55,23 +59,41 @@ public class MemberGetter return this.m_getFunc.Invoke(instance); } - private static Func CreateGetterDelegate(PropertyInfo property) + private static Func CreateGetterDelegate(PropertyInfo property, bool isStatic) { var param_instance = Expression.Parameter(typeof(object)); - var body_instance = Expression.Convert(param_instance, property.DeclaringType); - var body_property = Expression.Property(body_instance, property); - var body_return = Expression.Convert(body_property, typeof(object)); - - return Expression.Lambda>(body_return, param_instance).Compile(); + + if (isStatic) + { + var body_property = Expression.Property(null, property); + var body_return = Expression.Convert(body_property, typeof(object)); + return Expression.Lambda>(body_return, param_instance).Compile(); + } + else + { + var body_instance = Expression.Convert(param_instance, property.DeclaringType); + var body_property = Expression.Property(body_instance, property); + var body_return = Expression.Convert(body_property, typeof(object)); + return Expression.Lambda>(body_return, param_instance).Compile(); + } } - private static Func CreateGetterDelegate(FieldInfo fieldInfo) + private static Func CreateGetterDelegate(FieldInfo fieldInfo, bool isStatic) { var param_instance = Expression.Parameter(typeof(object)); - var body_instance = Expression.Convert(param_instance, fieldInfo.DeclaringType); - var body_field = Expression.Field(body_instance, fieldInfo); - var body_return = Expression.Convert(body_field, typeof(object)); - - return Expression.Lambda>(body_return, param_instance).Compile(); + + if (isStatic) + { + var body_field = Expression.Field(null, fieldInfo); + var body_return = Expression.Convert(body_field, typeof(object)); + return Expression.Lambda>(body_return, param_instance).Compile(); + } + else + { + var body_instance = Expression.Convert(param_instance, fieldInfo.DeclaringType); + var body_field = Expression.Field(body_instance, fieldInfo); + var body_return = Expression.Convert(body_field, typeof(object)); + return Expression.Lambda>(body_return, param_instance).Compile(); + } } } \ No newline at end of file diff --git a/src/TouchSocket.Core/Reflection/MemberSetter.cs b/src/TouchSocket.Core/Reflection/MemberSetter.cs index b02f0f5ed..fd7eb948c 100644 --- a/src/TouchSocket.Core/Reflection/MemberSetter.cs +++ b/src/TouchSocket.Core/Reflection/MemberSetter.cs @@ -10,56 +10,88 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Linq.Expressions; using System.Reflection; namespace TouchSocket.Core; -/// -/// 表示属性的设置器 -/// -public class MemberSetter +internal class MemberSetter { - /// - /// set方法委托 - /// - private readonly Action setFunc; + private readonly Action setFunc; + /// - /// 表示属性的Getter + /// 是否为静态成员 /// - /// 属性 - /// + private readonly bool m_isStatic; + + public MemberSetter(PropertyInfo property) { if (property == null) { throw new ArgumentNullException(nameof(property)); } - this.setFunc = CreateSetterDelegate(property); + var setMethod = property.GetSetMethod(true); + this.m_isStatic = setMethod != null && setMethod.IsStatic; + this.setFunc = CreateSetterDelegate(property, this.m_isStatic); } - /// - /// 设置属性的值 - /// - /// 实例 - /// 值 - /// + public MemberSetter(FieldInfo fieldInfo) + { + if (fieldInfo == null) + { + throw new ArgumentNullException(nameof(fieldInfo)); + } + this.m_isStatic = fieldInfo.IsStatic; + this.setFunc = CreateSetterDelegate(fieldInfo, this.m_isStatic); + } + + public void Invoke(object instance, object value) { this.setFunc.Invoke(instance, value); } - private static Action CreateSetterDelegate(PropertyInfo property) + private static Action CreateSetterDelegate(PropertyInfo property, bool isStatic) { var param_instance = Expression.Parameter(typeof(object)); var param_value = Expression.Parameter(typeof(object)); - var body_instance = Expression.Convert(param_instance, property.DeclaringType); var body_value = Expression.Convert(param_value, property.PropertyType); - var body_call = Expression.Call(body_instance, property.GetSetMethod(true), body_value); + + if (isStatic) + { + var body_call = Expression.Call(null, property.GetSetMethod(true), body_value); + return Expression.Lambda>(body_call, param_instance, param_value).Compile(); + } + else + { + var body_instance = Expression.Convert(param_instance, property.DeclaringType); + var body_call = Expression.Call(body_instance, property.GetSetMethod(true), body_value); + return Expression.Lambda>(body_call, param_instance, param_value).Compile(); + } + } - return Expression.Lambda>(body_call, param_instance, param_value).Compile(); + private static Action CreateSetterDelegate(FieldInfo fieldInfo, bool isStatic) + { + var param_instance = Expression.Parameter(typeof(object)); + var param_value = Expression.Parameter(typeof(object)); + + var body_value = Expression.Convert(param_value, fieldInfo.FieldType); + + if (isStatic) + { + var body_field = Expression.Field(null, fieldInfo); + var body_assign = Expression.Assign(body_field, body_value); + return Expression.Lambda>(body_assign, param_instance, param_value).Compile(); + } + else + { + var body_instance = Expression.Convert(param_instance, fieldInfo.DeclaringType); + var body_field = Expression.Field(body_instance, fieldInfo); + var body_assign = Expression.Assign(body_field, body_value); + return Expression.Lambda>(body_assign, param_instance, param_value).Compile(); + } } } \ No newline at end of file diff --git a/src/TouchSocket.Core/Reflection/Method.cs b/src/TouchSocket.Core/Reflection/Method.cs index 5025e7e57..e586cf5ac 100644 --- a/src/TouchSocket.Core/Reflection/Method.cs +++ b/src/TouchSocket.Core/Reflection/Method.cs @@ -10,10 +10,8 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Diagnostics.CodeAnalysis; using System.Reflection; -using System.Threading.Tasks; namespace TouchSocket.Core; @@ -33,8 +31,9 @@ public class Method /// 与该方法相关联的动态方法信息。不可能 . public Method(MethodInfo method, IDynamicMethodInfo dynamicMethodInfo) { - ThrowHelper.ThrowArgumentNullExceptionIf(dynamicMethodInfo, nameof(dynamicMethodInfo)); - this.m_info = ThrowHelper.ThrowArgumentNullExceptionIf(method, nameof(method)); + ThrowHelper.ThrowIfNull(dynamicMethodInfo, nameof(dynamicMethodInfo)); + ThrowHelper.ThrowIfNull(method, nameof(method)); + this.m_info = method; this.m_dynamicMethodInfo = dynamicMethodInfo; } @@ -42,62 +41,26 @@ public class Method /// 构造方法,初始化 Method 实例。 /// /// 目标方法信息。 - /// 指定动态构建类型。 - public Method(MethodInfo method, DynamicBuilderType? dynamicBuilderType = default) + [UnconditionalSuppressMessage("AOT", "IL2026", Justification = "Method方法在AOT确定")] + public Method(MethodInfo method) { - this.m_info = ThrowHelper.ThrowArgumentNullExceptionIf(method, nameof(method)); - - if (dynamicBuilderType.HasValue) - { - switch (dynamicBuilderType.Value) - { - case DynamicBuilderType.IL: - { - if (!GlobalEnvironment.IsDynamicCodeSupported) - { - ThrowHelper.ThrowNotSupportedException($"当前环境不支持{dynamicBuilderType.Value}"); - } - this.m_dynamicMethodInfo = new ILDynamicMethodInfo(method); - break; - } - case DynamicBuilderType.Expression: - this.m_dynamicMethodInfo = new ExpressionDynamicMethodInfo(method); - break; - - case DynamicBuilderType.Reflect: - this.m_dynamicMethodInfo = new ReflectDynamicMethodInfo(method); - break; - - case DynamicBuilderType.SourceGenerator: - this.m_dynamicMethodInfo = this.CreateDynamicMethodInfoFromSG(); - break; - - default: - break; - } - - this.DynamicBuilderType = dynamicBuilderType.Value; - return; - } - + ThrowHelper.ThrowIfNull(method, nameof(method)); + this.m_info = method; this.m_dynamicMethodInfo = this.CreateDynamicMethodInfoFromSG(); if (this.m_dynamicMethodInfo != null) { - this.DynamicBuilderType = DynamicBuilderType.SourceGenerator; return; } try { this.m_dynamicMethodInfo = new ExpressionDynamicMethodInfo(method); - this.DynamicBuilderType = DynamicBuilderType.Expression; return; } catch { } this.m_dynamicMethodInfo = new ReflectDynamicMethodInfo(method); - this.DynamicBuilderType = DynamicBuilderType.Reflect; } /// @@ -105,17 +68,11 @@ public class Method /// /// 目标类型。 /// 目标方法名。 - /// 指定构建的类型。 - public Method([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type targetType, string methodName, DynamicBuilderType? dynamicBuilderType = default) - : this(targetType.GetMethod(methodName), dynamicBuilderType) + public Method([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type targetType, string methodName) + : this(targetType.GetMethod(methodName)) { } - /// - /// 获取调用器的构建类型。 - /// - public DynamicBuilderType DynamicBuilderType { get; private set; } - /// /// 是否具有返回值。当返回值为Task时,也会认为没有返回值。 /// @@ -213,6 +170,8 @@ public class Method /// 通过源生成器创建动态方法信息。 /// /// 动态方法信息接口。 + [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "源生成器生成的代码在AOT环境中是安全的")] + [UnconditionalSuppressMessage("Trimming", "IL2075:'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The parameter of method does not have matching annotations.", Justification = "源生成器确保相关成员在编译时存在")] private IDynamicMethodInfo CreateDynamicMethodInfoFromSG() { var typeName = $"{GeneratorTypeNamespace}.__{StringExtension.MakeIdentifier(this.Info.DeclaringType.FullName)}MethodExtension"; @@ -225,11 +184,6 @@ public class Method var methodName = $"{this.Info.GetDeterminantName()}ClassProperty"; var property = type.GetProperty(methodName, BindingFlags.Public | BindingFlags.Static); - if (property == null) - { - return default; - } - - return (IDynamicMethodInfo)property.GetValue(null); + return property == null ? default : (IDynamicMethodInfo)property.GetValue(null); } } \ No newline at end of file diff --git a/src/TouchSocket.Core/Reflection/Property.cs b/src/TouchSocket.Core/Reflection/Property.cs index 000feeca8..2db95d1cc 100644 --- a/src/TouchSocket.Core/Reflection/Property.cs +++ b/src/TouchSocket.Core/Reflection/Property.cs @@ -10,9 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Concurrent; -using System.Linq; +using System.Diagnostics.CodeAnalysis; using System.Reflection; namespace TouchSocket.Core; @@ -20,13 +18,8 @@ namespace TouchSocket.Core; /// /// 表示属性 /// -public class Property : Member +public sealed class Property { - /// - /// 类型属性的Setter缓存 - /// - private static readonly ConcurrentDictionary m_cached = new ConcurrentDictionary(); - /// /// 获取器 /// @@ -43,49 +36,31 @@ public class Property : Member /// 属性信息 public Property(PropertyInfo property) { - this.Name = property.Name; this.Info = property; if (property.CanRead == true) { - this.CanRead = true; this.m_geter = new MemberGetter(property); } if (property.CanWrite == true) { - this.CanWrite = true; this.m_seter = new MemberSetter(property); } } - /// - /// 是否可以读取 - /// - public bool CanRead { get; private set; } - - /// - /// 是否可以写入 - /// - public bool CanWrite { get; private set; } - /// /// 获取属性信息 /// - public PropertyInfo Info { get; private set; } - - /// - /// 获取属性名称 - /// - public string Name { get; protected set; } + public PropertyInfo Info { get; } /// /// 从类型的属性获取属性 /// /// 类型 /// - public static Property[] GetProperties(Type type) + public static Property[] GetProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type) { - return m_cached.GetOrAdd(type, t => t.GetProperties().Select(p => new Property(p)).ToArray()); + return type.GetProperties().Select(p => new Property(p)).ToArray(); } /// @@ -109,7 +84,7 @@ public class Property : Member { if (this.m_seter == null) { - throw new NotSupportedException($"{this.Name}不允许赋值"); + throw new NotSupportedException($"{this.Info.Name}不允许赋值"); } this.m_seter.Invoke(instance, value); } diff --git a/src/TouchSocket.Core/Reflection/ReflectDynamicMethodInfo.cs b/src/TouchSocket.Core/Reflection/ReflectDynamicMethodInfo.cs index cb7e11a6a..59c28a046 100644 --- a/src/TouchSocket.Core/Reflection/ReflectDynamicMethodInfo.cs +++ b/src/TouchSocket.Core/Reflection/ReflectDynamicMethodInfo.cs @@ -10,9 +10,12 @@ // 感谢您的下载和使用 // ------------------------------------------------------------------------------ +using System.Diagnostics.CodeAnalysis; using System.Reflection; namespace TouchSocket.Core; + +[RequiresUnreferencedCode("此方法可能会使用反射构建访问器,与剪裁不兼容。")] internal class ReflectDynamicMethodInfo : DynamicMethodInfoBase { private readonly MethodInfo m_method; diff --git a/src/TouchSocket.Core/Results/IResultT.cs b/src/TouchSocket.Core/Results/IResultT.cs deleted file mode 100644 index d842f33ab..000000000 --- a/src/TouchSocket.Core/Results/IResultT.cs +++ /dev/null @@ -1,27 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -namespace TouchSocket.Core; - -/// -/// 泛型接口,继承自。 -/// 用于定义具有特定类型结果值的对象所需的行为。 -/// -/// 结果值的类型。 -public interface IResult : IResult -{ - /// - /// 获取结果值。 - /// - /// 结果值,类型为 T。 - T Value { get; } -} \ No newline at end of file diff --git a/src/TouchSocket.Core/Results/Result.cs b/src/TouchSocket.Core/Results/Result.cs index 4fffcacdb..51c1df287 100644 --- a/src/TouchSocket.Core/Results/Result.cs +++ b/src/TouchSocket.Core/Results/Result.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Diagnostics; using TouchSocket.Resources; @@ -20,7 +19,7 @@ namespace TouchSocket.Core; /// 结果返回 /// [DebuggerDisplay("ResultCode = {ResultCode}, Message = {Message}")] -public record struct Result : IResult +public record struct Result { /// /// 成功 @@ -63,16 +62,6 @@ public record struct Result : IResult this.Message = message; } - /// - /// 构造函数 - /// - /// 传入的结果对象,用于初始化当前结果对象的属性 - public Result(IResult result) - { - this.ResultCode = result.ResultCode; // 初始化结果代码 - this.Message = result.Message; // 初始化结果消息 - } - /// /// 构造函数 /// diff --git a/src/TouchSocket.Core/Results/ResultBase.cs b/src/TouchSocket.Core/Results/ResultBase.cs index 8475f8561..5671af690 100644 --- a/src/TouchSocket.Core/Results/ResultBase.cs +++ b/src/TouchSocket.Core/Results/ResultBase.cs @@ -10,16 +10,13 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Resources; - namespace TouchSocket.Core; /// /// 结果返回 /// -public class ResultBase : IResult +public class ResultBase { - /// /// 初始化 ResultBase 类的新实例。 /// @@ -31,7 +28,6 @@ public class ResultBase : IResult this.Message = message; } - /// /// 初始化 ResultBase 类的新实例。 /// @@ -58,7 +54,6 @@ public class ResultBase : IResult this.Message = result.Message; } - /// /// ResultBase 类的构造函数。 /// @@ -67,17 +62,11 @@ public class ResultBase : IResult } /// - public ResultCode ResultCode { get; protected set; } + public bool IsSuccess => this.ResultCode == ResultCode.Success; /// public string Message { get; protected set; } /// - public bool IsSuccess => this.ResultCode == ResultCode.Success; - - /// - public override string ToString() - { - return TouchSocketCoreResource.ResultToString.Format(this.ResultCode, this.Message); - } + public ResultCode ResultCode { get; protected set; } } \ No newline at end of file diff --git a/src/TouchSocket.Core/Results/ResultCode.cs b/src/TouchSocket.Core/Results/ResultCode.cs index 1d6ea87ba..5598c4fc0 100644 --- a/src/TouchSocket.Core/Results/ResultCode.cs +++ b/src/TouchSocket.Core/Results/ResultCode.cs @@ -27,6 +27,11 @@ public enum ResultCode : byte /// Success, + /// + /// 失败,程度较轻的错误,可能是由于参数错误或其他可恢复的原因导致的 + /// + Failure, + /// /// 错误,程度较重的错误,但不影响系统的运行 /// @@ -37,11 +42,6 @@ public enum ResultCode : byte /// Exception, - /// - /// 失败,程度较轻的错误,可能是由于参数错误或其他可恢复的原因导致的 - /// - Failure, - /// /// 操作超时 /// diff --git a/src/TouchSocket.Core/Results/ResultExtensions.cs b/src/TouchSocket.Core/Results/ResultExtensions.cs index bdbd0c702..16127d44c 100644 --- a/src/TouchSocket.Core/Results/ResultExtensions.cs +++ b/src/TouchSocket.Core/Results/ResultExtensions.cs @@ -10,44 +10,21 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// -/// ResultExtensions +/// 结果扩展方法。 /// public static class ResultExtensions { /// - /// 是否成功。 + /// 将 转换为 。 /// - /// - /// - [Obsolete("建议使用IsSuccess属性代替该扩展方法")] - public static bool IsSuccess(this IResult result) + /// 结果值的类型。 + /// 要转换的 对象。 + /// 转换后的 对象。 + public static Result ToResult(this Result value) { - return result.ResultCode == ResultCode.Success; - } - - /// - /// 是否没有成功。 - /// - /// - /// - [Obsolete("建议使用IsSuccess属性取反代替该扩展方法")] - public static bool NotSuccess(this IResult result) - { - return result.ResultCode != ResultCode.Success; - } - - /// - /// 转换为 - /// - /// - /// - public static Result ToResult(this IResult result) - { - return new Result(result); + return value; } } \ No newline at end of file diff --git a/src/TouchSocket.Core/Results/ResultT.cs b/src/TouchSocket.Core/Results/ResultT.cs index ed479a87d..33c0a4709 100644 --- a/src/TouchSocket.Core/Results/ResultT.cs +++ b/src/TouchSocket.Core/Results/ResultT.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -21,7 +20,7 @@ namespace TouchSocket.Core; /// /// 结果值的类型。 [DebuggerDisplay("ResultCode = {ResultCode}, Message = {Message}, Value={Value}")] -public readonly struct Result : IResult +public readonly struct Result { /// /// 结果消息,提供操作的描述信息。 @@ -98,11 +97,7 @@ public readonly struct Result : IResult /// 一个新的实例,值为提供的值,结果代码和消息为成功。 public static implicit operator Result(T value) { - if (value is null) - { - return new Result(ResultCode.Failure, "value is null."); - } - return new Result(value); + return value is null ? new Result(ResultCode.Failure, "value is null.") : new Result(value); } /// diff --git a/src/TouchSocket.Core/Run/Action/LoopAction.cs b/src/TouchSocket.Core/Run/Action/LoopAction.cs index d4739b081..815c4232e 100644 --- a/src/TouchSocket.Core/Run/Action/LoopAction.cs +++ b/src/TouchSocket.Core/Run/Action/LoopAction.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; - namespace TouchSocket.Core; @@ -20,7 +17,7 @@ namespace TouchSocket.Core; /// LoopAction 类用于在指定循环次数和间隔下执行异步操作。 /// 它支持暂停、恢复和重新运行操作。 /// -public sealed class LoopAction : DisposableObject +public sealed class LoopAction : SafetyDisposableObject { /// /// 异步自动重置事件,用于控制循环执行的同步点。 @@ -164,7 +161,7 @@ public sealed class LoopAction : DisposableObject { return this.RunStatus != RunStatus.None ? EasyTask.CompletedTask - : Task.Run(async () => + : EasyTask.SafeRun(async () => { this.RunStatus = RunStatus.Running; if (this.LoopCount >= 0) @@ -212,7 +209,6 @@ public sealed class LoopAction : DisposableObject { if (this.RunStatus == RunStatus.Running) { - this.m_waitHandle.Reset(); this.RunStatus = RunStatus.Paused; } } @@ -233,14 +229,16 @@ public sealed class LoopAction : DisposableObject /// 处置 LoopAction 实例,释放所有资源。 /// /// 是否为托管资源。 - protected override void Dispose(bool disposing) + protected override void SafetyDispose(bool disposing) { if (this.DisposedValue) { return; } - this.m_waitHandle.Dispose(); - this.RunStatus = RunStatus.Disposed; - base.Dispose(disposing); + if (disposing) + { + this.RunStatus = RunStatus.Disposed; + this.m_waitHandle.SetAll(); + } } } \ No newline at end of file diff --git a/src/TouchSocket.Core/Run/Timers/SingleTimer.cs b/src/TouchSocket.Core/Run/Timers/SingleTimer.cs index feaf8e86f..6e08667cf 100644 --- a/src/TouchSocket.Core/Run/Timers/SingleTimer.cs +++ b/src/TouchSocket.Core/Run/Timers/SingleTimer.cs @@ -10,15 +10,12 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading; - namespace TouchSocket.Core; /// /// 不可重入的Timer /// -public class SingleTimer : DisposableObject +public class SingleTimer : SafetyDisposableObject { private readonly Action m_action1; private readonly Action m_action2; @@ -164,9 +161,8 @@ public class SingleTimer : DisposableObject /// /// - protected override void Dispose(bool disposing) + protected override void SafetyDispose(bool disposing) { this.m_timer.SafeDispose(); - base.Dispose(disposing); } } \ No newline at end of file diff --git a/src/TouchSocket.Core/Runtime/EasyMemoryMarshal.cs b/src/TouchSocket.Core/Runtime/EasyMemoryMarshal.cs index 68b5ab8e0..1c52d8f86 100644 --- a/src/TouchSocket.Core/Runtime/EasyMemoryMarshal.cs +++ b/src/TouchSocket.Core/Runtime/EasyMemoryMarshal.cs @@ -5,31 +5,77 @@ // 哔哩哔哩视频:https://space.bilibili.com/94253567 // Gitee源代码仓库:https://gitee.com/RRQM_Home // Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ +// API首页:https://touchsocket.net/() // 交流QQ群:234762506 // 感谢您的下载和使用 // ------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Runtime.InteropServices; namespace TouchSocket.Core; -/// -/// 提供用于创建 的辅助方法。 -/// +/// +/// 提供用于创建 的辅助方法。 +/// public static class EasyMemoryMarshal { - /// - /// 创建一个 ,其引用指定的内存地址和长度。 - /// - /// 元素的类型,必须是非托管类型。 - /// 引用的内存地址。 - /// 引用的内存长度。 - /// 一个新的 - public static unsafe ReadOnlySpan CreateReadOnlySpanRef(ref T reference, int length) + + + +#if NET6_0_OR_GREATER + /// + /// 创建一个可写的 ,其引用指定的内存地址和长度。 + /// + /// 元素的类型,必须是非托管类型。 + /// 引用的内存地址(首元素)。 + /// 引用的元素个数。 + /// 一个新的 + public static Span CreateSpan(ref T reference, int length) where T : unmanaged + { + return MemoryMarshal.CreateSpan(ref reference, length); + } + + /// + /// 创建一个可写的 ,其引用指定的内存地址,长度为 1。 + /// + /// 元素的类型,必须是非托管类型。 + /// 引用的内存地址(单个元素)。 + /// 一个新的长度为 1 的 + public static Span CreateSpan(ref T reference) where T : unmanaged + { + return MemoryMarshal.CreateSpan(ref reference, 1); + } + + /// + /// 创建一个只读的 ,其引用指定的内存地址和长度。 + /// + /// 元素的类型,必须是非托管类型。 + /// 引用的内存地址(首元素)。 + /// 引用的元素个数。 + /// 一个新的 + public static ReadOnlySpan CreateReadOnlySpan(ref T reference, int length) where T : unmanaged + { + return MemoryMarshal.CreateReadOnlySpan(ref reference, length); + } + /// + /// 创建一个只读的 ,其引用指定的内存地址,长度为 1。 + /// + /// 元素的类型,必须是非托管类型。 + /// 引用的内存地址(单个元素)。 + /// 一个新的长度为 1 的 + public static ReadOnlySpan CreateReadOnlySpan(ref T reference) where T : unmanaged + { + return MemoryMarshal.CreateReadOnlySpan(ref reference, 1); + } +#else + + /// + /// 创建一个 ,其引用指定的内存地址和长度。 + /// + /// 元素的类型,必须是非托管类型。 + /// 引用的内存地址(首元素)。 + /// 引用的元素个数。 + /// 一个新的 + public static unsafe ReadOnlySpan CreateReadOnlySpan(ref T reference, int length) where T : unmanaged { // 使用不安全代码创建 @@ -39,13 +85,13 @@ public static class EasyMemoryMarshal } } - /// - /// 创建一个 ,其引用指定的内存地址,长度为 1。 - /// - /// 元素的类型,必须是非托管类型。 - /// 引用的内存地址。 - /// 一个新的 - public static unsafe ReadOnlySpan CreateReadOnlySpanRef(ref T reference) + /// + /// 创建一个 ,其引用指定的内存地址,长度为 1。 + /// + /// 元素的类型,必须是非托管类型。 + /// 引用的内存地址(单个元素)。 + /// 一个新的长度为 1 的 + public static unsafe ReadOnlySpan CreateReadOnlySpan(ref T reference) where T : unmanaged { // 使用不安全代码创建 @@ -54,4 +100,38 @@ public static class EasyMemoryMarshal return new ReadOnlySpan(ptr, 1); } } + /// + /// 创建一个可写的 ,其引用指定的内存地址和长度。 + /// + /// 元素的类型,必须是非托管类型。 + /// 引用的内存地址(首元素)。 + /// 引用的元素个数。 + /// 一个新的 + public static unsafe Span CreateSpan(ref T reference, int length) where T : unmanaged + { + // 使用不安全代码创建 + fixed (T* ptr = &reference) + { + return new Span(ptr, length); + } + } + + /// + /// 创建一个可写的 ,其引用指定的内存地址,长度为 1。 + /// + /// 元素的类型,必须是非托管类型。 + /// 引用的内存地址(单个元素)。 + /// 一个新的长度为 1 的 + public static unsafe Span CreateSpan(ref T reference) where T : unmanaged + { + // 使用不安全代码创建 + fixed (T* ptr = &reference) + { + return new Span(ptr, 1); + } + } +#endif + + + } diff --git a/src/TouchSocket.Core/Serialization/Attributes/FastConverterAttribute.cs b/src/TouchSocket.Core/Serialization/Attributes/FastConverterAttribute.cs index 3ef0f437a..40738804d 100644 --- a/src/TouchSocket.Core/Serialization/Attributes/FastConverterAttribute.cs +++ b/src/TouchSocket.Core/Serialization/Attributes/FastConverterAttribute.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/Serialization/Attributes/FastMemberAttribute.cs b/src/TouchSocket.Core/Serialization/Attributes/FastMemberAttribute.cs index 605df53f9..d7c5b1a84 100644 --- a/src/TouchSocket.Core/Serialization/Attributes/FastMemberAttribute.cs +++ b/src/TouchSocket.Core/Serialization/Attributes/FastMemberAttribute.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/Serialization/Attributes/FastNonSerializedAttribute.cs b/src/TouchSocket.Core/Serialization/Attributes/FastNonSerializedAttribute.cs index 41f5dd461..beb77929e 100644 --- a/src/TouchSocket.Core/Serialization/Attributes/FastNonSerializedAttribute.cs +++ b/src/TouchSocket.Core/Serialization/Attributes/FastNonSerializedAttribute.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/Serialization/Attributes/FastSerializedAttribute.cs b/src/TouchSocket.Core/Serialization/Attributes/FastSerializedAttribute.cs index 9b91d73e7..d258bcb1f 100644 --- a/src/TouchSocket.Core/Serialization/Attributes/FastSerializedAttribute.cs +++ b/src/TouchSocket.Core/Serialization/Attributes/FastSerializedAttribute.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/Serialization/FastBinary/Converts/ByteBlockFastBinaryConverter.cs b/src/TouchSocket.Core/Serialization/FastBinary/Converts/ByteBlockFastBinaryConverter.cs index e63eea609..ee785b8b2 100644 --- a/src/TouchSocket.Core/Serialization/FastBinary/Converts/ByteBlockFastBinaryConverter.cs +++ b/src/TouchSocket.Core/Serialization/FastBinary/Converts/ByteBlockFastBinaryConverter.cs @@ -10,19 +10,17 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; internal class ByteBlockFastBinaryConverter : FastBinaryConverter { - protected override ByteBlock Read(ref TByteBlock byteBlock, Type type) + protected override ByteBlock Read(ref TReader reader, Type type) { - return byteBlock.ReadByteBlock(); + return ReaderExtension.ReadByteBlock(ref reader); } - protected override void Write(ref TByteBlock byteBlock, in ByteBlock obj) + protected override void Write(ref TWriter writer, in ByteBlock obj) { - byteBlock.WriteByteBlock(obj); + WriterExtension.WriteByteBlock(ref writer, obj); } } \ No newline at end of file diff --git a/src/TouchSocket.Core/Serialization/FastBinary/Converts/DataSetFastBinaryConverter.cs b/src/TouchSocket.Core/Serialization/FastBinary/Converts/DataSetFastBinaryConverter.cs deleted file mode 100644 index 6209377c2..000000000 --- a/src/TouchSocket.Core/Serialization/FastBinary/Converts/DataSetFastBinaryConverter.cs +++ /dev/null @@ -1,30 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; -using System.Data; - -namespace TouchSocket.Core; - -internal class DataSetFastBinaryConverter : FastBinaryConverter -{ - protected override DataSet Read(ref TByteBlock byteBlock, Type type) - { - return SerializeConvert.BinaryDeserialize(byteBlock.ReadBytesPackage()); - } - - protected override void Write(ref TByteBlock byteBlock, in DataSet obj) - { - var bytes = SerializeConvert.BinarySerialize(obj); - byteBlock.WriteBytesPackage(bytes); - } -} \ No newline at end of file diff --git a/src/TouchSocket.Core/Serialization/FastBinary/Converts/GuidFastBinaryConverter.cs b/src/TouchSocket.Core/Serialization/FastBinary/Converts/GuidFastBinaryConverter.cs index 3ab79af45..e85e7a9ca 100644 --- a/src/TouchSocket.Core/Serialization/FastBinary/Converts/GuidFastBinaryConverter.cs +++ b/src/TouchSocket.Core/Serialization/FastBinary/Converts/GuidFastBinaryConverter.cs @@ -10,19 +10,18 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; internal class GuidFastBinaryConverter : FastBinaryConverter { - protected override Guid Read(ref TByteBlock byteBlock, Type type) + protected override Guid Read(ref TReader reader, Type type) { - return byteBlock.ReadGuid(); + return ReaderExtension.ReadValue(ref reader); } - protected override void Write(ref TByteBlock byteBlock, in Guid obj) + protected override void Write(ref TWriter writer, in Guid obj) { - byteBlock.WriteGuid(obj); + WriterExtension.WriteValue(ref writer, obj); } + } \ No newline at end of file diff --git a/src/TouchSocket.Core/Serialization/FastBinary/Converts/MemoryStreamFastBinaryConverter.cs b/src/TouchSocket.Core/Serialization/FastBinary/Converts/MemoryStreamFastBinaryConverter.cs index 3ec9f271e..26fcbd98e 100644 --- a/src/TouchSocket.Core/Serialization/FastBinary/Converts/MemoryStreamFastBinaryConverter.cs +++ b/src/TouchSocket.Core/Serialization/FastBinary/Converts/MemoryStreamFastBinaryConverter.cs @@ -10,27 +10,20 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.IO; - namespace TouchSocket.Core; internal class MemoryStreamFastBinaryConverter : FastBinaryConverter { - protected override MemoryStream Read(ref TByteBlock byteBlock, Type type) + protected override MemoryStream Read(ref TReader reader, Type type) { - return new MemoryStream(byteBlock.ReadBytesPackage()); + return new MemoryStream(ReaderExtension.ReadByteSpan(ref reader).ToArray()); } - protected override void Write(ref TByteBlock byteBlock, in MemoryStream obj) + protected override void Write(ref TWriter writer, in MemoryStream obj) { -#if !NET45 if (obj.TryGetBuffer(out var array)) { - byteBlock.WriteBytesPackage(array.Array, array.Offset, array.Count); + WriterExtension.WriteByteSpan(ref writer, new ReadOnlySpan(array.Array, array.Offset, array.Count)); } -#endif - var bytes = obj.ToArray(); - byteBlock.Write(bytes); } } \ No newline at end of file diff --git a/src/TouchSocket.Core/Serialization/FastBinary/Converts/MetadataFastBinaryConverter.cs b/src/TouchSocket.Core/Serialization/FastBinary/Converts/MetadataFastBinaryConverter.cs index 06d85bdf5..83b47958a 100644 --- a/src/TouchSocket.Core/Serialization/FastBinary/Converts/MetadataFastBinaryConverter.cs +++ b/src/TouchSocket.Core/Serialization/FastBinary/Converts/MetadataFastBinaryConverter.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// @@ -19,25 +17,27 @@ namespace TouchSocket.Core; /// internal sealed class MetadataFastBinaryConverter : FastBinaryConverter { - protected override Metadata Read(ref TByteBlock byteBlock, Type type) + protected override Metadata Read(ref TReader reader, Type type) { - var count = byteBlock.ReadInt32(); + var count = ReaderExtension.ReadValue(ref reader); var metadata = new Metadata(); for (var i = 0; i < count; i++) { - metadata.Add(byteBlock.ReadString(), byteBlock.ReadString()); + var key = ReaderExtension.ReadString(ref reader, FixedHeaderType.Ushort); + var value = ReaderExtension.ReadString(ref reader, FixedHeaderType.Ushort); + metadata.Add(key, value); } return metadata; } - protected override void Write(ref TByteBlock byteBlock, in Metadata obj) + protected override void Write(ref TWriter writer, in Metadata obj) { - byteBlock.WriteInt32(obj.Count); + WriterExtension.WriteValue(ref writer, obj.Count); foreach (var item in obj) { - byteBlock.WriteString(item.Key); - byteBlock.WriteString(item.Value); + WriterExtension.WriteString(ref writer, item.Key, FixedHeaderType.Ushort); + WriterExtension.WriteString(ref writer, item.Value, FixedHeaderType.Ushort); } } } \ No newline at end of file diff --git a/src/TouchSocket.Core/Serialization/FastBinary/Converts/PackageFastBinaryConverter.cs b/src/TouchSocket.Core/Serialization/FastBinary/Converts/PackageFastBinaryConverter.cs index 3473471c4..200e737ab 100644 --- a/src/TouchSocket.Core/Serialization/FastBinary/Converts/PackageFastBinaryConverter.cs +++ b/src/TouchSocket.Core/Serialization/FastBinary/Converts/PackageFastBinaryConverter.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/Serialization/FastBinary/Converts/VersionFastBinaryConverter.cs b/src/TouchSocket.Core/Serialization/FastBinary/Converts/VersionFastBinaryConverter.cs index a4ccc4825..26c60064e 100644 --- a/src/TouchSocket.Core/Serialization/FastBinary/Converts/VersionFastBinaryConverter.cs +++ b/src/TouchSocket.Core/Serialization/FastBinary/Converts/VersionFastBinaryConverter.cs @@ -10,22 +10,24 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; internal class VersionFastBinaryConverter : FastBinaryConverter { - protected override Version Read(ref TByteBlock byteBlock, Type type) + protected override Version Read(ref TReader reader, Type type) { - return new Version(byteBlock.ReadInt32(), byteBlock.ReadInt32(), byteBlock.ReadInt32(), byteBlock.ReadInt32()); + var major = ReaderExtension.ReadValue(ref reader); + var minor = ReaderExtension.ReadValue(ref reader); + var build = ReaderExtension.ReadValue(ref reader); + var revision = ReaderExtension.ReadValue(ref reader); + return new Version(major, minor, build, revision); } - protected override void Write(ref TByteBlock byteBlock, in Version obj) + protected override void Write(ref TWriter writer, in Version obj) { - byteBlock.WriteInt32(obj.Major); - byteBlock.WriteInt32(obj.Minor); - byteBlock.WriteInt32(obj.Build); - byteBlock.WriteInt32(obj.Revision); + WriterExtension.WriteValue(ref writer, obj.Major); + WriterExtension.WriteValue(ref writer, obj.Minor); + WriterExtension.WriteValue(ref writer, obj.Build); + WriterExtension.WriteValue(ref writer, obj.Revision); } } \ No newline at end of file diff --git a/src/TouchSocket.Core/Serialization/FastBinary/FastBinaryFormatter.cs b/src/TouchSocket.Core/Serialization/FastBinary/FastBinaryFormatter.cs index 0fd443e1a..62144a7ad 100644 --- a/src/TouchSocket.Core/Serialization/FastBinary/FastBinaryFormatter.cs +++ b/src/TouchSocket.Core/Serialization/FastBinary/FastBinaryFormatter.cs @@ -10,125 +10,128 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Collections; using System.Diagnostics.CodeAnalysis; -using System.IO; +using System.Runtime.CompilerServices; namespace TouchSocket.Core; /// -/// 快速二进制序列化。 +/// 提供快速二进制序列化和反序列化功能的静态工具类。 /// +/// +/// FastBinaryFormatter使用高性能的二进制序列化算法,支持基本数据类型、集合、数组、字典和自定义对象的序列化。 +/// 内置魔数校验机制,支持自定义转换器,适用于高频序列化场景。 +/// 所有序列化数据都包含协议头,用于反序列化时的快速校验。 +/// public static class FastBinaryFormatter { - /// - /// 定义一个常量,指定动态访问的成员类型,包括公共构造函数、方法、字段和属性。 - /// - public const DynamicallyAccessedMemberTypes DynamicallyAccessed = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties; - - /// - /// 初始化一个默认的快速序列化上下文对象,用于支持快速序列化操作。 - /// private static readonly DefaultFastSerializerContext s_defaultFastSerializerContext = new DefaultFastSerializerContext(); /// /// 获取默认的快速序列化上下文。 /// + /// 全局共享的实例。 + /// + /// 此上下文包含了所有注册的转换器和序列化对象缓存,在整个应用程序生命周期内重复使用。 + /// public static FastSerializerContext DefaultFastSerializerContext => s_defaultFastSerializerContext; + #region Converter Register + /// - /// 添加一个新的快速二进制转换器,该转换器由指定的泛型参数提供。 + /// 为指定类型添加快速二进制转换器。 /// /// 要序列化的类型。 - /// 转换器类型,必须实现 接口。 + /// 实现接口的转换器类型。 /// - /// [DynamicallyAccessedMembers(DynamicallyAccessed)] 特性确保类型和转换器在反射时可被动态访问。 + /// 转换器必须有公共无参构造函数。注册后,该类型的所有实例都将使用指定的转换器进行序列化。 /// - public static void AddFastBinaryConverter<[DynamicallyAccessedMembers(DynamicallyAccessed)] TType, [DynamicallyAccessedMembers(DynamicallyAccessed)] TConverter>() where TConverter : IFastBinaryConverter, new() + public static void AddFastBinaryConverter<[DynamicallyAccessedMembers(AOT.FastBinaryFormatter)] TType, [DynamicallyAccessedMembers(AOT.FastBinaryFormatter)] TConverter>() where TConverter : IFastBinaryConverter, new() { - // 使用 Activator.CreateInstance 创建转换器实例,并将其添加到默认的序列化上下文中 AddFastBinaryConverter(typeof(TType), (IFastBinaryConverter)Activator.CreateInstance(typeof(TConverter))); } /// - /// 为指定类型添加一个快速二进制转换器。 + /// 为指定类型添加快速二进制转换器实例。 /// /// 要序列化的类型。 - /// 要添加的转换器,必须实现 接口。 + /// 转换器实例。 /// - /// [DynamicallyAccessedMembers(DynamicallyAccessed)] 特性确保类型在反射时可被动态访问。 + /// 注册后,该类型的所有实例都将使用指定的转换器进行序列化。 /// - public static void AddFastBinaryConverter<[DynamicallyAccessedMembers(DynamicallyAccessed)] TType>(IFastBinaryConverter converter) + public static void AddFastBinaryConverter<[DynamicallyAccessedMembers(AOT.FastBinaryFormatter)] TType>(IFastBinaryConverter converter) { - // 将转换器添加到默认的序列化上下文中 AddFastBinaryConverter(typeof(TType), converter); } /// - /// 为指定类型添加一个快速二进制转换器。 + /// 为指定类型添加快速二进制转换器实例。 /// - /// 要序列化的类型。 - /// 要添加的转换器,必须实现 接口。 + /// 要序列化的。 + /// 转换器实例。 /// - /// [DynamicallyAccessedMembers(DynamicallyAccessed)] 特性确保类型在反射时可被动态访问。 + /// 注册后,该类型的所有实例都将使用指定的转换器进行序列化。 /// - public static void AddFastBinaryConverter([DynamicallyAccessedMembers(DynamicallyAccessed)] Type type, IFastBinaryConverter converter) + public static void AddFastBinaryConverter([DynamicallyAccessedMembers(AOT.FastBinaryFormatter)] Type type, IFastBinaryConverter converter) { - // 将转换器添加到默认的序列化上下文中 s_defaultFastSerializerContext.AddFastBinaryConverter(type, converter); } + #endregion Converter Register + #region Serialize /// - /// 序列化给定对象并将其写入字节块。 + /// 将对象序列化到字节块中。 /// - /// 用于存储序列化数据的字节块。 - /// 要序列化的对象。 - /// (可选)序列化上下文,提供额外的序列化设置或上下文。 /// 要序列化的对象类型。 + /// 目标。 + /// 要序列化的对象实例。 + /// 序列化上下文,为 时使用默认上下文。 /// - /// 此方法提供了一种便捷的方式来序列化对象至字节块,利用提供的序列化上下文进行序列化过程。 + /// 此方法会在序列化数据前写入魔数(协议头),用于反序列化时的校验。 /// - public static void Serialize<[DynamicallyAccessedMembers(DynamicallyAccessed)] T>(ByteBlock byteBlock, in T graph, FastSerializerContext serializerContext = null) + [RequiresUnreferencedCode("此方法可能会使用反射构建访问器,与剪裁不兼容。如果已使用源生成上下文,可以忽略此警告。")] + public static void Serialize<[DynamicallyAccessedMembers(AOT.FastBinaryFormatter)] T>(ByteBlock byteBlock, in T graph, FastSerializerContext serializerContext = null) { Serialize(ref byteBlock, graph, serializerContext); } /// - /// 使用指定的序列化上下文将对象序列化到提供的字节块中。 + /// 将对象序列化到字节写入器中。 /// - /// 字节块的类型,必须实现IByteBlock接口。 + /// 实现接口的写入器类型。 /// 要序列化的对象类型。 - /// 用于存储序列化数据的字节块。 - /// 要序列化的对象。 - /// 用于序列化的上下文。 - public static void Serialize(ref TByteBlock byteBlock, in T graph, FastSerializerContext serializerContext = null) - where TByteBlock : IByteBlock + /// 字节写入器实例。 + /// 要序列化的对象实例。 + /// 序列化上下文,为 时使用默认上下文。 + /// + /// 此方法会在序列化数据前写入魔数(值为1的字节),用于反序列化时的快速校验。 + /// + [RequiresUnreferencedCode("此方法可能会使用反射构建访问器,与剪裁不兼容。如果已使用源生成上下文,可以忽略此警告。")] + public static void Serialize(ref TWriter writer, in T graph, FastSerializerContext serializerContext = null) + where TWriter : IBytesWriter { serializerContext ??= s_defaultFastSerializerContext; - - var startPosition = byteBlock.Position; - - byteBlock.Position = startPosition + 1; - SerializeObject(ref byteBlock, graph, serializerContext); - - var pos = byteBlock.Position; - byteBlock.Position = startPosition; - byteBlock.WriteByte(1); - - byteBlock.Position = pos; + var span = writer.GetSpan(1); + span[0] = 1; // 魔数(协议头);反序列化时用于快速校验 + writer.Advance(1); + SerializeObject(ref writer, graph, serializerContext); } /// - /// 使用指定的序列化上下文将对象序列化为字节数组。 + /// 将对象序列化为字节数组。 /// /// 要序列化的对象类型。 - /// 要序列化的对象。 - /// 用于序列化的上下文。 - /// 序列化后的字节数组。 - public static byte[] SerializeToBytes<[DynamicallyAccessedMembers(DynamicallyAccessed)] T>([DynamicallyAccessedMembers(DynamicallyAccessed)] in T graph, FastSerializerContext serializerContext = null) + /// 要序列化的对象实例。 + /// 序列化上下文,为 时使用默认上下文。 + /// 包含序列化数据的字节数组。 + /// + /// 此方法内部使用64KB的进行序列化,完成后返回数据副本。 + /// + [RequiresUnreferencedCode("此方法可能会使用反射构建访问器,与剪裁不兼容。如果已使用源生成上下文,可以忽略此警告。")] + public static byte[] SerializeToBytes<[DynamicallyAccessedMembers(AOT.FastBinaryFormatter)] T>(in T graph, FastSerializerContext serializerContext = null) { var byteBlock = new ValueByteBlock(1024 * 64); try @@ -142,261 +145,146 @@ public static class FastBinaryFormatter } } - private static void SerializeIListOrArray(ref TByteBlock byteBlock, in IEnumerable param, FastSerializerContext serializerContext) where TByteBlock : IByteBlock + [RequiresUnreferencedCode("此方法可能会使用反射构建访问器,与剪裁不兼容。如果已使用源生成上下文,可以忽略此警告。")] + private static void SerializeIListOrArray(ref TWriter writer, in IEnumerable param, FastSerializerContext serializerContext) + where TWriter : IBytesWriter { - var oldPosition = byteBlock.Position; - byteBlock.Position += 4; + var writerAnchor = new WriterAnchor(ref writer, 4); // 先占位集合元素个数 uint paramLen = 0; - foreach (var item in param) { paramLen++; - SerializeObject(ref byteBlock, item, serializerContext); + SerializeObject(ref writer, item, serializerContext); } - var newPosition = byteBlock.Position; - byteBlock.Position = oldPosition; - byteBlock.WriteUInt32(paramLen); - byteBlock.Position = newPosition; + var span = writerAnchor.Rewind(ref writer, out _); + span.WriteValue(paramLen); } - private static void SerializeMutilDimensionalArray(ref TByteBlock byteBlock, Array array, FastSerializerContext serializerContext) where TByteBlock : IByteBlock + [RequiresUnreferencedCode("此方法可能会使用反射构建访问器,与剪裁不兼容。如果已使用源生成上下文,可以忽略此警告。")] + private static void SerializeMutilDimensionalArray(ref TWriter writer, Array array, FastSerializerContext serializerContext) + where TWriter : IBytesWriter { var rank = array.Rank; for (var i = 0; i < rank; i++) { - byteBlock.WriteInt32(array.GetLength(i)); + WriterExtension.WriteValue(ref writer, array.GetLength(i)); } - - //var oldPosition = byteBlock.Position; - //byteBlock.Position += 4; - //uint paramLen = 0; - foreach (var item in array) { - //paramLen++; - SerializeObject(ref byteBlock, item, serializerContext); + SerializeObject(ref writer, item, serializerContext); } - //var newPosition = byteBlock.Position; - //byteBlock.Position = oldPosition; - //byteBlock.WriteUInt32(paramLen); - //byteBlock.Position = newPosition; } - private static void SerializeObject(ref TByteBlock byteBlock, in T graph, FastSerializerContext serializerContext) - where TByteBlock : IByteBlock + /// + /// 序列化对象(包含复杂类型递归) + /// + [RequiresUnreferencedCode("此方法可能会使用反射构建访问器,与剪裁不兼容。如果已使用源生成上下文,可以忽略此警告。")] + private static void SerializeObject(ref TWriter writer, T graph, FastSerializerContext serializerContext) + where TWriter : IBytesWriter { - if (graph is null) + // 基础+枚举+null 处理 + if (TryWriteBasic(ref writer, graph)) { - byteBlock.WriteNull(); return; } - byteBlock.WriteNotNull(); - switch (graph) + // 复杂类型:写入长度占位(对象体长度,不含自身4字节) + var writerAnchor = new WriterAnchor(ref writer, 4); + var type = graph.GetType(); + var serializeObject = serializerContext.GetSerializeObject(type); + + if (serializeObject.Converter != null) { - case byte value: - { - byteBlock.WriteByte(value); - return; - } - case sbyte value: - { - byteBlock.WriteInt16(value); - return; - } - case bool value: - { - byteBlock.WriteBoolean(value); - return; - } - case short value: - { - byteBlock.WriteInt16(value); - return; - } - case ushort value: - { - byteBlock.WriteUInt16(value); - return; - } - case int value: - { - byteBlock.WriteInt32(value); - return; - } - case uint value: - { - byteBlock.WriteUInt32(value); - return; - } - case long value: - { - byteBlock.WriteInt64(value); - return; - } - case ulong value: - { - byteBlock.WriteUInt64(value); - return; - } - case float value: - { - byteBlock.WriteFloat(value); - return; - } - case double value: - { - byteBlock.WriteDouble(value); - return; - } - case char value: - { - byteBlock.WriteChar(value); - return; - } - case decimal value: - { - byteBlock.WriteDecimal(value); - return; - } - case DateTime value: - { - byteBlock.WriteDateTime(value); - return; - } - case TimeSpan value: - { - byteBlock.WriteTimeSpan(value); - return; - } - case string value: - { - byteBlock.WriteString(value); - return; - } - case Enum _: - { - var type = graph.GetType(); - var enumValType = Enum.GetUnderlyingType(type); - - if (enumValType == typeof(byte)) - { - byteBlock.WriteByte(Convert.ToByte(graph)); - } - else if (enumValType == typeof(sbyte)) - { - byteBlock.WriteInt16(Convert.ToSByte(graph)); - } - else if (enumValType == typeof(short)) - { - byteBlock.WriteInt16(Convert.ToInt16(graph)); - } - else if (enumValType == typeof(ushort)) - { - byteBlock.WriteUInt16(Convert.ToUInt16(graph)); - } - else if (enumValType == typeof(int)) - { - byteBlock.WriteInt32(Convert.ToInt32(graph)); - } - else if (enumValType == typeof(uint)) - { - byteBlock.WriteUInt32(Convert.ToUInt32(graph)); - } - else if (enumValType == typeof(ulong)) - { - byteBlock.WriteUInt64(Convert.ToUInt64(graph)); - } - else - { - byteBlock.WriteInt64(Convert.ToInt64(graph)); - } - return; - } - case byte[] value: - { - byteBlock.WriteBytesPackage(value); - return; - } - default: - { - var startPosition = byteBlock.Position; - byteBlock.Position += 4; - var type = graph.GetType(); - var serializeObject = serializerContext.GetSerializeObject(type); - if (serializeObject.Converter != null) - { - serializeObject.Converter.Write(ref byteBlock, graph); - } - else - { - switch (serializeObject.InstanceType) - { - case InstanceType.List: - SerializeIListOrArray(ref byteBlock, (IEnumerable)graph, serializerContext); - break; - - case InstanceType.Array: - var array = (Array)(object)graph; - if (array.Rank == 1) - { - SerializeIListOrArray(ref byteBlock, array, serializerContext); - } - else - { - SerializeMutilDimensionalArray(ref byteBlock, array, serializerContext); - } - break; - - case InstanceType.Dictionary: - { - var oldPosition = byteBlock.Position; - byteBlock.Position += 4; - uint paramLen = 0; - - foreach (DictionaryEntry item in (IDictionary)graph) - { - SerializeObject(ref byteBlock, item.Key, serializerContext); - SerializeObject(ref byteBlock, item.Value, serializerContext); - paramLen++; - } - var newPosition = byteBlock.Position; - byteBlock.Position = oldPosition; - byteBlock.Write(TouchSocketBitConverter.Default.GetBytes(paramLen)); - byteBlock.Position = newPosition; - } - break; - - default: - { - for (var i = 0; i < serializeObject.MemberInfos.Length; i++) - { - var memberInfo = serializeObject.MemberInfos[i]; - if (serializeObject.EnableIndex) - { - byteBlock.WriteByte(memberInfo.Index); - } - else - { - byteBlock.WriteString(memberInfo.Name, FixedHeaderType.Byte); - } - - SerializeObject(ref byteBlock, serializeObject.MemberAccessor.GetValue(graph, memberInfo.Name), serializerContext); - } - } - - break; - } - } - - var endPosition = byteBlock.Position; - byteBlock.Position = startPosition; - - byteBlock.WriteInt32(endPosition - startPosition - 4); - byteBlock.Position = endPosition; - break; - } + serializeObject.Converter.Write(ref writer, graph); } + else + { + switch (serializeObject.InstanceType) + { + case InstanceType.List: + SerializeIListOrArray(ref writer, (IEnumerable)graph, serializerContext); + break; + + case InstanceType.Array: + { + var array = Unsafe.As(ref graph); + if (array.Rank == 1) + { + SerializeIListOrArray(ref writer, array, serializerContext); + } + else + { + SerializeMutilDimensionalArray(ref writer, array, serializerContext); + } + break; + } + case InstanceType.Dictionary: + { + var writerAnchorDic = new WriterAnchor(ref writer, 4); + uint paramLen = 0; + foreach (DictionaryEntry item in (IDictionary)graph) + { + SerializeObject(ref writer, item.Key, serializerContext); + SerializeObject(ref writer, item.Value, serializerContext); + paramLen++; + } + var oldPositionSpan = writerAnchorDic.Rewind(ref writer, out _); + oldPositionSpan.WriteValue(paramLen); + break; + } + default: + { + // 普通对象成员 + for (var i = 0; i < serializeObject.MemberInfos.Length; i++) + { + var memberInfo = serializeObject.MemberInfos[i]; + if (serializeObject.EnableIndex) + { + WriterExtension.WriteValue(ref writer, memberInfo.Index); + } + else + { + WriterExtension.WriteString(ref writer, memberInfo.Name, FixedHeaderType.Byte); + } + SerializeObject(ref writer, serializeObject.MemberAccessor.GetValue(graph, memberInfo.Name), serializerContext); + } + break; + } + } + } + + // 回写长度 + var startPositionSpan = writerAnchor.Rewind(ref writer, out var length); + startPositionSpan.WriteValue(length); + } + + /// + /// 写入基础(可直接写)类型。返回 true 表示已完成写入,外层不再处理。 + /// + private static bool TryWriteBasic(ref TWriter writer, T graph) where TWriter : IBytesWriter + { + if (graph is null) + { + WriterExtension.WriteNull(ref writer); + return true; + } + + WriterExtension.WriteNotNull(ref writer); + + // 原生/基础类型 + if (FastBinaryPrimitiveHelper.TryWritePrimitive(ref writer, graph)) + { + return true; + } + + // 枚举 + if (graph is Enum enumValue) + { + WriterExtension.WriteEnum(ref writer, enumValue); + return true; + } + + return false; // 不是基础类型,后续继续序列化复杂结构 } #endregion Serialize @@ -404,291 +292,210 @@ public static class FastBinaryFormatter #region Deserialize /// - /// 反序列化字节块为指定类型的对象。 + /// 从字节块中反序列化对象。 /// - /// 包含待反序列化数据的字节块。 - /// (可选)快速序列化上下文,用于优化性能。 - /// 要反序列化为的类型。 - /// 反序列化后的对象。 - public static T Deserialize<[DynamicallyAccessedMembers(DynamicallyAccessed)] T>(ByteBlock byteBlock, FastSerializerContext serializerContext = null) + /// 要反序列化的对象类型。 + /// 包含序列化数据的。 + /// 序列化上下文,为 时使用默认上下文。 + /// 反序列化的对象实例。 + /// 当数据流解析错误时抛出。 + /// + /// 此方法会先校验魔数(协议头),确保数据格式正确。 + /// + [RequiresUnreferencedCode("此方法可能会使用反射构建访问器,与剪裁不兼容。如果已使用源生成上下文,可以忽略此警告。")] + public static T Deserialize<[DynamicallyAccessedMembers(AOT.FastBinaryFormatter)] T>(ByteBlock byteBlock, FastSerializerContext serializerContext = null) { - // 调用泛型方法 Deserialize,使用 ByteBlock 作为泛型参数,进行反序列化操作 return Deserialize(ref byteBlock, serializerContext); } + /// - /// 反序列化字节块为指定类型。 + /// 从值类型字节块中反序列化对象。 /// - /// 包含待反序列化数据的字节块。 - /// (可选)序列化上下文,用于控制序列化行为。 - /// 要反序列化的类型,该类型标记有[DynamicallyAccessedMembers]特性,表示在运行时会动态访问其成员。 - /// 反序列化后的对象。 - public static T Deserialize<[DynamicallyAccessedMembers(DynamicallyAccessed)] T>(ref ValueByteBlock byteBlock, FastSerializerContext serializerContext = null) + /// 要反序列化的对象类型。 + /// 包含序列化数据的。 + /// 序列化上下文,为 时使用默认上下文。 + /// 反序列化的对象实例。 + /// 当数据流解析错误时抛出。 + /// + /// 此方法会先校验魔数(协议头),确保数据格式正确。 + /// + [RequiresUnreferencedCode("此方法可能会使用反射构建访问器,与剪裁不兼容。如果已使用源生成上下文,可以忽略此警告。")] + public static T Deserialize<[DynamicallyAccessedMembers(AOT.FastBinaryFormatter)] T>(ref ValueByteBlock byteBlock, FastSerializerContext serializerContext = null) { - // 调用泛型方法 Deserialize,使用 ValueByteBlock 作为数据容器,T 为目标类型,进行反序列化操作 return Deserialize(ref byteBlock, serializerContext); } /// - /// 将字节数组反序列化为指定类型的实例。 + /// 从字节数组中反序列化对象。 /// - /// 包含已序列化数据的字节数组。 - /// (可选)用于序列化和反序列化过程的上下文对象。 - /// 要反序列化的目标类型。 - /// 反序列化后的目标类型实例。 - public static T Deserialize<[DynamicallyAccessedMembers(DynamicallyAccessed)] T>(byte[] bytes, FastSerializerContext serializerContext = null) + /// 要反序列化的对象类型。 + /// 包含序列化数据的字节数组。 + /// 序列化上下文,为 时使用默认上下文。 + /// 反序列化的对象实例。 + /// 当数据流解析错误时抛出。 + /// + /// 此方法内部创建包装字节数组,然后进行反序列化。 + /// + [RequiresUnreferencedCode("此方法可能会使用反射构建访问器,与剪裁不兼容。如果已使用源生成上下文,可以忽略此警告。")] + public static T Deserialize<[DynamicallyAccessedMembers(AOT.FastBinaryFormatter)] T>(byte[] bytes, FastSerializerContext serializerContext = null) { - // 创建一个包装字节数组的ValueByteBlock对象,以便处理反序列化过程。 var byteBlock = new ValueByteBlock(bytes); - // 调用重载的Deserialize方法,使用ValueByteBlock帮助类进行反序列化操作。 return Deserialize(ref byteBlock, serializerContext); } /// - /// 使用指定的序列化上下文从字节块中反序列化出指定类型的对象。 + /// 从字节读取器中反序列化指定类型的对象。 /// - /// 实现IByteBlock接口的类型,用于读取字节数据。 - /// 要反序列化为的类型。 - /// 包含序列化数据的字节块。 - /// 用于反序列化的FastSerializerContext实例。 - /// 反序列化后的对象。 - public static T Deserialize(ref TByteBlock byteBlock, FastSerializerContext serializerContext = null) - where TByteBlock : IByteBlock + /// 实现接口的读取器类型。 + /// 要反序列化的对象类型。 + /// 字节读取器实例。 + /// 序列化上下文,为 时使用默认上下文。 + /// 反序列化的对象实例。 + /// 当数据流解析错误时抛出。 + /// + /// 此方法会先校验魔数(协议头),确保数据格式正确。 + /// + [RequiresUnreferencedCode("此方法可能会使用反射构建访问器,与剪裁不兼容。如果已使用源生成上下文,可以忽略此警告。")] + public static T Deserialize(ref TReader reader, FastSerializerContext serializerContext = null) + where TReader : IBytesReader { - // 调用重载的Deserialize方法,传入类型信息和序列化上下文进行反序列化。 - return (T)Deserialize(ref byteBlock, typeof(T), serializerContext); + return (T)Deserialize(ref reader, typeof(T), serializerContext); } /// - /// 使用指定的序列化上下文从字节块中反序列化对象。 + /// 从字节读取器中反序列化指定类型的对象。 /// - /// 实现IByteBlock接口的类型,用于读取字节数据。 - /// 包含序列化数据的字节块。 - /// 要反序列化为的类型。 - /// 用于反序列化的FastSerializerContext实例。 - /// 反序列化后的对象。 - public static object Deserialize(ref TByteBlock byteBlock, [DynamicallyAccessedMembers(DynamicallyAccessed)] Type type, FastSerializerContext serializerContext = null) - where TByteBlock : IByteBlock + /// 实现接口的读取器类型。 + /// 字节读取器实例。 + /// 要反序列化的对象。 + /// 序列化上下文,为 时使用默认上下文。 + /// 反序列化的对象实例。 + /// 当数据流解析错误时抛出。 + /// + /// 此方法会先校验魔数(协议头),确保数据格式正确。魔数必须为1,否则抛出异常。 + /// + [RequiresUnreferencedCode("此方法可能会使用反射构建访问器,与剪裁不兼容。如果已使用源生成上下文,可以忽略此警告。")] + public static object Deserialize(ref TReader reader, [DynamicallyAccessedMembers(AOT.FastBinaryFormatter)] Type type, FastSerializerContext serializerContext = null) + where TReader : IBytesReader { - // 检查数据流是否正确,如果不是,则抛出异常。 - if (byteBlock.ReadByte() != 1) + if (ReaderExtension.ReadValue(ref reader) != 1) { throw new Exception("Fast反序列化数据流解析错误。"); } serializerContext ??= s_defaultFastSerializerContext; - // 使用提供的序列化上下文进行反序列化。 - return Deserialize(type, ref byteBlock, serializerContext); + return Deserialize(type, ref reader, serializerContext); } - private static object Deserialize(Type type, ref TByteBlock byteBlock, FastSerializerContext serializerContext) - where TByteBlock : IByteBlock + [RequiresUnreferencedCode("此方法可能会使用反射构建访问器,与剪裁不兼容。如果已使用源生成上下文,可以忽略此警告。")] + private static object Deserialize([DynamicallyAccessedMembers(AOT.FastBinaryFormatter)] Type type, ref TReader reader, FastSerializerContext serializerContext) + where TReader : IBytesReader { - var nullable = type.IsNullableType(); + var nullable = type.IsNullableType(out var actualType); if (nullable) { - type = type.GenericTypeArguments[0]; + type = actualType; } - #region Null - - if (byteBlock.ReadIsNull()) + // 基础类型统一处理 + if (TryReadBasic(type, nullable, ref reader, out var basicValue)) { - return nullable ? null : type.GetDefault(); + return basicValue; } - #endregion Null - - #region Enum - - if (type.IsEnum) + // 复杂类型:先读体长度 + var len = ReaderExtension.ReadValue(ref reader); + var serializeObj = serializerContext.GetSerializeObject(type); + if (serializeObj.Converter != null) { - var enumType = Enum.GetUnderlyingType(type); - - if (enumType == typeof(byte)) - { - return Enum.ToObject(type, byteBlock.ReadByte()); - } - else if (enumType == typeof(sbyte)) - { - return Enum.ToObject(type, byteBlock.ReadInt16()); - } - else if (enumType == typeof(short)) - { - return Enum.ToObject(type, byteBlock.ReadInt16()); - } - else if (enumType == typeof(ushort)) - { - return Enum.ToObject(type, byteBlock.ReadUInt16()); - } - else if (enumType == typeof(int)) - { - return Enum.ToObject(type, byteBlock.ReadInt32()); - } - else - { - return enumType == typeof(uint) - ? Enum.ToObject(type, byteBlock.ReadUInt32()) - : enumType == typeof(ulong) ? Enum.ToObject(type, byteBlock.ReadUInt64()) : Enum.ToObject(type, byteBlock.ReadInt64()); - } - } - - #endregion Enum - - switch (Type.GetTypeCode(type)) - { - case TypeCode.Boolean: - return byteBlock.ReadBoolean(); - - case TypeCode.Char: - return byteBlock.ReadChar(); - - case TypeCode.SByte: - return (sbyte)byteBlock.ReadInt16(); - - case TypeCode.Byte: - return byteBlock.ReadByte(); - - case TypeCode.Int16: - return byteBlock.ReadInt16(); - - case TypeCode.UInt16: - return byteBlock.ReadUInt16(); - - case TypeCode.Int32: - return byteBlock.ReadInt32(); - - case TypeCode.UInt32: - return byteBlock.ReadUInt32(); - - case TypeCode.Int64: - return byteBlock.ReadInt64(); - - case TypeCode.UInt64: - return byteBlock.ReadUInt64(); - - case TypeCode.Single: - return byteBlock.ReadFloat(); - - case TypeCode.Double: - return byteBlock.ReadDouble(); - - case TypeCode.Decimal: - return byteBlock.ReadDecimal(); - - case TypeCode.DateTime: - return byteBlock.ReadDateTime(); - - case TypeCode.String: - return byteBlock.ReadString(); - - default: - { - if (type == typeof(byte[])) - { - return byteBlock.ReadBytesPackage(); - } - else if (type.IsClass || type.IsStruct()) - { - var len = byteBlock.ReadInt32(); - var serializeObj = serializerContext.GetSerializeObject(type); - return serializeObj.Converter != null - ? serializeObj.Converter.Read(ref byteBlock, type) - : DeserializeClass(type, ref byteBlock, len, serializerContext); - } - else - { - throw new Exception("未定义的类型:" + type.ToString()); - } - } + return serializeObj.Converter.Read(ref reader, type); } + return DeserializeClass(type, serializeObj, ref reader, len, serializerContext); } - private static object DeserializeClass(Type type, ref TByteBlock byteBlock, int length, FastSerializerContext serializerContext) where TByteBlock : IByteBlock + [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "数组元素类型已通过DynamicallyAccessedMembers标记保证存在")] + [RequiresUnreferencedCode("此方法可能会使用反射构建访问器,与剪裁不兼容。如果已使用源生成上下文,可以忽略此警告。")] + private static object DeserializeClass([DynamicallyAccessedMembers(AOT.FastBinaryFormatter)] Type type, SerializObject serializeObject, ref TReader reader, int length, FastSerializerContext serializerContext) + where TReader : IBytesReader { - var serializeObject = serializerContext.GetSerializeObject(type); - object instance; switch (serializeObject.InstanceType) { case InstanceType.Class: { instance = serializerContext.GetNewInstance(type); - - var index = byteBlock.Position + length; - + var endIndex = reader.BytesRead + length; if (serializeObject.EnableIndex) { - while (byteBlock.Position < index) + while (reader.BytesRead < endIndex) { - var propertyNameIndex = byteBlock.ReadByte(); + var propertyNameIndex = ReaderExtension.ReadValue(ref reader); if (serializeObject.IsStruct) { if (serializeObject.FastMemberInfoDicForIndex.TryGetValue(propertyNameIndex, out var property)) { - var obj = Deserialize(property.Type, ref byteBlock, serializerContext); + var obj = Deserialize(property.Type, ref reader, serializerContext); property.SetValue(ref instance, obj); } else { - IgnoreLength(ref byteBlock, type); + IgnoreLength(ref reader, type); } } else { if (serializeObject.FastMemberInfoDicForIndex.TryGetValue(propertyNameIndex, out var property)) { - var obj = Deserialize(property.Type, ref byteBlock, serializerContext); + var obj = Deserialize(property.Type, ref reader, serializerContext); serializeObject.MemberAccessor.SetValue(instance, property.Name, obj); } else { - IgnoreLength(ref byteBlock, type); + IgnoreLength(ref reader, type); } } } } else { - while (byteBlock.Position < index) + while (reader.BytesRead < endIndex) { - var propertyName = byteBlock.ReadString(FixedHeaderType.Byte); - + var propertyName = ReaderExtension.ReadString(ref reader, FixedHeaderType.Byte); if (serializeObject.IsStruct) { if (serializeObject.FastMemberInfoDicForName.TryGetValue(propertyName, out var property)) { - var obj = Deserialize(property.Type, ref byteBlock, serializerContext); + var obj = Deserialize(property.Type, ref reader, serializerContext); property.SetValue(ref instance, obj); } else { - IgnoreLength(ref byteBlock, type); + IgnoreLength(ref reader, type); } } else { if (serializeObject.FastMemberInfoDicForName.TryGetValue(propertyName, out var property)) { - var obj = Deserialize(property.Type, ref byteBlock, serializerContext); + var obj = Deserialize(property.Type, ref reader, serializerContext); serializeObject.MemberAccessor.SetValue(instance, property.Name, obj); } else { - IgnoreLength(ref byteBlock, type); + IgnoreLength(ref reader, type); } } } } - break; } case InstanceType.List: { instance = serializerContext.GetNewInstance(type); - var paramLen = byteBlock.ReadUInt32(); + var paramLen = ReaderExtension.ReadValue(ref reader); for (uint i = 0; i < paramLen; i++) { - var obj = Deserialize(serializeObject.ArgTypes[0], ref byteBlock, serializerContext); + var obj = Deserialize(serializeObject.ArgTypes[0], ref reader, serializerContext); serializeObject.AddMethod.Invoke(instance, new object[] { obj }); } break; @@ -698,12 +505,11 @@ public static class FastBinaryFormatter var rank = serializeObject.Type.GetArrayRank(); if (rank == 1) { - var paramLen = byteBlock.ReadUInt32(); + var paramLen = ReaderExtension.ReadValue(ref reader); var array = Array.CreateInstance(serializeObject.ArgTypes[0], paramLen); - for (uint i = 0; i < paramLen; i++) { - var obj = Deserialize(serializeObject.ArgTypes[0], ref byteBlock, serializerContext); + var obj = Deserialize(serializeObject.ArgTypes[0], ref reader, serializerContext); array.SetValue(obj, i); } instance = array; @@ -713,34 +519,23 @@ public static class FastBinaryFormatter var rankArray = new int[rank]; for (var i = 0; i < rank; i++) { - rankArray[i] = byteBlock.ReadInt32(); + rankArray[i] = ReaderExtension.ReadValue(ref reader); } - - //var paramLen = (int)byteBlock.ReadUInt32(); var array = Array.CreateInstance(serializeObject.ArgTypes[0], rankArray); - - //for (int i = 0; i < rank; i++) - //{ - // var obj = Deserialize(serializeObject.ArgTypes[0], ref byteBlock, serializerContext); - // array.SetValue(obj, i); - //} - var indices = new int[rank]; - FillArrayRecursive(serializeObject, ref byteBlock, serializerContext, array, rankArray, indices, 0); - + FillArrayRecursive(serializeObject, ref reader, serializerContext, array, rankArray, indices, 0); instance = array; } - break; } case InstanceType.Dictionary: { instance = serializerContext.GetNewInstance(type); - var paramLen = byteBlock.ReadUInt32(); + var paramLen = ReaderExtension.ReadValue(ref reader); for (uint i = 0; i < paramLen; i++) { - var key = Deserialize(serializeObject.ArgTypes[0], ref byteBlock, serializerContext); - var value = Deserialize(serializeObject.ArgTypes[1], ref byteBlock, serializerContext); + var key = Deserialize(serializeObject.ArgTypes[0], ref reader, serializerContext); + var value = Deserialize(serializeObject.ArgTypes[1], ref reader, serializerContext); serializeObject.AddMethod.Invoke(instance, new object[] { key, value }); } break; @@ -749,98 +544,82 @@ public static class FastBinaryFormatter instance = null; break; } - return instance; } - private static void FillArrayRecursive(SerializObject serializObject, ref TByteBlock byteBlock, FastSerializerContext serializerContext, Array array, int[] rankArray, int[] indices, int dimension) - where TByteBlock : IByteBlock + [RequiresUnreferencedCode("此方法可能会使用反射构建访问器,与剪裁不兼容。如果已使用源生成上下文,可以忽略此警告。")] + private static void FillArrayRecursive(SerializObject serializObject, ref TReader reader, FastSerializerContext serializerContext, Array array, int[] rankArray, int[] indices, int dimension) + where TReader : IBytesReader { if (dimension == rankArray.Length) { - // 已经到达最后一维,进行赋值操作 - var obj = Deserialize(serializObject.ArgTypes[0], ref byteBlock, serializerContext); + var obj = Deserialize(serializObject.ArgTypes[0], ref reader, serializerContext); array.SetValue(obj, indices); return; } - for (var i = 0; i < rankArray[dimension]; i++) { indices[dimension] = i; - FillArrayRecursive(serializObject, ref byteBlock, serializerContext, array, rankArray, indices, dimension + 1); + FillArrayRecursive(serializObject, ref reader, serializerContext, array, rankArray, indices, dimension + 1); } } - private static void IgnoreLength(ref TByteBlock byteBlock, Type type) where TByteBlock : IByteBlock + private static void IgnoreLength(ref TReader reader, Type type) + where TReader : IBytesReader { switch (Type.GetTypeCode(type)) { - case TypeCode.Boolean: - byteBlock.Seek(1, SeekOrigin.Current); - break; - - case TypeCode.Char: - byteBlock.Seek(2, SeekOrigin.Current); - break; - - case TypeCode.SByte: - byteBlock.Seek(2, SeekOrigin.Current); - break; - - case TypeCode.Byte: - byteBlock.Seek(1, SeekOrigin.Current); - break; - - case TypeCode.Int16: - byteBlock.Seek(2, SeekOrigin.Current); - break; - - case TypeCode.UInt16: - byteBlock.Seek(2, SeekOrigin.Current); - break; - - case TypeCode.Int32: - byteBlock.Seek(4, SeekOrigin.Current); - break; - - case TypeCode.UInt32: - byteBlock.Seek(4, SeekOrigin.Current); - break; - - case TypeCode.Int64: - byteBlock.Seek(8, SeekOrigin.Current); - break; - - case TypeCode.UInt64: - byteBlock.Seek(8, SeekOrigin.Current); - break; - - case TypeCode.Single: - byteBlock.Seek(4, SeekOrigin.Current); - break; - - case TypeCode.Double: - byteBlock.Seek(8, SeekOrigin.Current); - break; - - case TypeCode.Decimal: - byteBlock.Seek(16, SeekOrigin.Current); - break; - - case TypeCode.DateTime: - byteBlock.Seek(8, SeekOrigin.Current); - break; - - case TypeCode.String: - byteBlock.ReadString(); - break; - + case TypeCode.Boolean: reader.Advance(1); break; + case TypeCode.Char: reader.Advance(2); break; + case TypeCode.SByte: reader.Advance(1); break; + case TypeCode.Byte: reader.Advance(1); break; + case TypeCode.Int16: reader.Advance(2); break; + case TypeCode.UInt16: reader.Advance(2); break; + case TypeCode.Int32: reader.Advance(4); break; + case TypeCode.UInt32: reader.Advance(4); break; + case TypeCode.Int64: reader.Advance(8); break; + case TypeCode.UInt64: reader.Advance(8); break; + case TypeCode.Single: reader.Advance(4); break; + case TypeCode.Double: reader.Advance(8); break; + case TypeCode.Decimal: reader.Advance(16); break; + case TypeCode.DateTime: reader.Advance(8); break; + case TypeCode.String: ReaderExtension.ReadString(ref reader); break; default: - var len = byteBlock.ReadInt32(); - byteBlock.Seek(len, SeekOrigin.Current); + var len = ReaderExtension.ReadValue(ref reader); + reader.Advance(len); break; } } + /// + /// 读取基础类型(含 null、枚举、原生数值等)。返回 true 表示已完成,外层无需再处理。 + /// + private static bool TryReadBasic([DynamicallyAccessedMembers(AOT.FastBinaryFormatter)] Type type, bool nullable, ref TReader reader, out object value) where TReader : IBytesReader + { + // Null 标记 + if (ReaderExtension.ReadIsNull(ref reader)) + { + value = nullable ? null : type.GetDefault(); + return true; + } + + // 枚举 + if (type.IsEnum) + { + value = ReaderExtension.ReadEnum(ref reader, type); + return true; + } + + // 原生/基础 + if (FastBinaryPrimitiveHelper.TryReadPrimitive(ref reader, type, out var primitiveObj)) + { + value = primitiveObj; + return true; + } + + value = null; + return false; + } + #endregion Deserialize } \ No newline at end of file diff --git a/src/TouchSocket.Core/Serialization/FastBinary/FastBinaryPrimitiveHelper.cs b/src/TouchSocket.Core/Serialization/FastBinary/FastBinaryPrimitiveHelper.cs new file mode 100644 index 000000000..b6730aa17 --- /dev/null +++ b/src/TouchSocket.Core/Serialization/FastBinary/FastBinaryPrimitiveHelper.cs @@ -0,0 +1,75 @@ +//------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +//------------------------------------------------------------------------------ + +namespace TouchSocket.Core; +/// +/// 原生类型(含字符串、时间、decimal 等)读写辅助。保持与原始协议编码完全一致(尤其 sbyte 仍按短整型写入以保证兼容)。 +/// +internal static class FastBinaryPrimitiveHelper +{ + /// + /// 写入已知原生类型。成功返回 true。 + /// + public static bool TryWritePrimitive(ref TWriter writer, T value) + where TWriter : IBytesWriter + { + switch (value) + { + case byte v: WriterExtension.WriteValue(ref writer, v); return true; + case sbyte v: WriterExtension.WriteValue(ref writer, v); return true; // 协议历史:按短整型 + case bool v: WriterExtension.WriteValue(ref writer, v); return true; + case short v: WriterExtension.WriteValue(ref writer, v); return true; + case ushort v: WriterExtension.WriteValue(ref writer, v); return true; + case int v: WriterExtension.WriteValue(ref writer, v); return true; + case uint v: WriterExtension.WriteValue(ref writer, v); return true; + case long v: WriterExtension.WriteValue(ref writer, v); return true; + case ulong v: WriterExtension.WriteValue(ref writer, v); return true; + case float v: WriterExtension.WriteValue(ref writer, v); return true; + case double v: WriterExtension.WriteValue(ref writer, v); return true; + case char v: WriterExtension.WriteValue(ref writer, v); return true; + case decimal v: WriterExtension.WriteValue(ref writer, v); return true; + case DateTime v: WriterExtension.WriteValue(ref writer, v); return true; + case TimeSpan v: WriterExtension.WriteValue(ref writer, v); return true; + case string s: WriterExtension.WriteString(ref writer, s); return true; + } + return false; + } + + /// + /// 读取基础类型。若处理则返回 true。 + /// + public static bool TryReadPrimitive(ref TReader reader, Type type, out object value) + where TReader : IBytesReader + { + switch (Type.GetTypeCode(type)) + { + case TypeCode.Boolean: value = ReaderExtension.ReadValue(ref reader); return true; + case TypeCode.Char: value = ReaderExtension.ReadValue(ref reader); return true; + case TypeCode.SByte: value = (sbyte)ReaderExtension.ReadValue(ref reader); return true; // 按短整型读取 + case TypeCode.Byte: value = ReaderExtension.ReadValue(ref reader); return true; + case TypeCode.Int16: value = ReaderExtension.ReadValue(ref reader); return true; + case TypeCode.UInt16: value = ReaderExtension.ReadValue(ref reader); return true; + case TypeCode.Int32: value = ReaderExtension.ReadValue(ref reader); return true; + case TypeCode.UInt32: value = ReaderExtension.ReadValue(ref reader); return true; + case TypeCode.Int64: value = ReaderExtension.ReadValue(ref reader); return true; + case TypeCode.UInt64: value = ReaderExtension.ReadValue(ref reader); return true; + case TypeCode.Single: value = ReaderExtension.ReadValue(ref reader); return true; + case TypeCode.Double: value = ReaderExtension.ReadValue(ref reader); return true; + case TypeCode.Decimal: value = ReaderExtension.ReadValue(ref reader); return true; + case TypeCode.DateTime: value = ReaderExtension.ReadValue(ref reader); return true; + case TypeCode.String: value = ReaderExtension.ReadString(ref reader); return true; + default: + value = null; + return false; + } + } +} \ No newline at end of file diff --git a/src/TouchSocket.Core/Serialization/FastBinary/FastMemberInfo.cs b/src/TouchSocket.Core/Serialization/FastBinary/FastMemberInfo.cs index d05beaad3..5d258e21f 100644 --- a/src/TouchSocket.Core/Serialization/FastBinary/FastMemberInfo.cs +++ b/src/TouchSocket.Core/Serialization/FastBinary/FastMemberInfo.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Reflection; namespace TouchSocket.Core; diff --git a/src/TouchSocket.Core/Serialization/FastBinary/IFastBinaryConverter.cs b/src/TouchSocket.Core/Serialization/FastBinary/IFastBinaryConverter.cs index a0cf77e13..7c78ae513 100644 --- a/src/TouchSocket.Core/Serialization/FastBinary/IFastBinaryConverter.cs +++ b/src/TouchSocket.Core/Serialization/FastBinary/IFastBinaryConverter.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; @@ -23,19 +21,19 @@ public interface IFastBinaryConverter /// /// 从字节块中读取对象。 /// - /// 包含对象数据的字节块。 + /// 包含对象数据的字节块。 /// 要读取的对象的类型。 - /// 字节块的类型,实现了IByteBlock接口。 + /// 字节块的类型,实现了IByteBlock接口。 /// 从字节块中读取的对象实例。 - object Read(ref TByteBlock byteBlock, Type type) where TByteBlock : IByteBlock; + object Read(ref TReader reader, Type type) where TReader : IBytesReader; /// /// 将对象写入字节块。 /// - /// 将要包含对象数据的字节块。 + /// 将要包含对象数据的字节块。 /// 要写入的对象实例。 - /// 字节块的类型,实现了IByteBlock接口。 - void Write(ref TByteBlock byteBlock, in object obj) where TByteBlock : IByteBlock; + /// 字节块的类型,实现了IByteBlock接口。 + void Write(ref TWriter writer, in object obj) where TWriter : IBytesWriter; } /// @@ -47,40 +45,40 @@ public abstract class FastBinaryConverter : IFastBinaryConverter /// /// 通过此实现从字节块中读取对象。 /// - /// 包含对象数据的字节块。 + /// 包含对象数据的字节块。 /// 要读取的对象的类型。 - /// 字节块的类型,实现了IByteBlock接口。 + /// 字节块的类型,实现了IByteBlock接口。 /// 从字节块中读取的对象实例。 - object IFastBinaryConverter.Read(ref TByteBlock byteBlock, Type type) + object IFastBinaryConverter.Read(ref TReader reader, Type type) { - return this.Read(ref byteBlock, type); + return this.Read(ref reader, type); } /// /// 通过此实现将对象写入字节块。 /// - /// 将要包含对象数据的字节块。 + /// 将要包含对象数据的字节块。 /// 要写入的对象实例。 - /// 字节块的类型,实现了IByteBlock接口。 - void IFastBinaryConverter.Write(ref TByteBlock byteBlock, in object obj) + /// 字节块的类型,实现了IByteBlock接口。 + void IFastBinaryConverter.Write(ref TWriter writer, in object obj) { - this.Write(ref byteBlock, (T)obj); + this.Write(ref writer, (T)obj); } /// /// 从字节块中读取对象。必须由具体实现类实现。 /// - /// 包含对象数据的字节块。 + /// 包含对象数据的字节块。 /// 要读取的对象的类型。 - /// 字节块的类型,实现了IByteBlock接口。 + /// 字节块的类型,实现了IByteBlock接口。 /// 从字节块中读取的对象实例。 - protected abstract T Read(ref TByteBlock byteBlock, Type type) where TByteBlock : IByteBlock; + protected abstract T Read(ref TReader reader, Type type) where TReader : IBytesReader; /// /// 将对象写入字节块。必须由具体实现类实现。 /// - /// 将要包含对象数据的字节块。 + /// 将要包含对象数据的字节块。 /// 要写入的对象实例。 - /// 字节块的类型,实现了IByteBlock接口。 - protected abstract void Write(ref TByteBlock byteBlock, in T obj) where TByteBlock : IByteBlock; + /// 字节块的类型,实现了IByteBlock接口。 + protected abstract void Write(ref TWriter writer, in T obj) where TWriter : IBytesWriter; } \ No newline at end of file diff --git a/src/TouchSocket.Core/Serialization/FastBinary/SerializObject.cs b/src/TouchSocket.Core/Serialization/FastBinary/SerializObject.cs index 27a6b54f0..be16b7916 100644 --- a/src/TouchSocket.Core/Serialization/FastBinary/SerializObject.cs +++ b/src/TouchSocket.Core/Serialization/FastBinary/SerializObject.cs @@ -10,9 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Diagnostics.CodeAnalysis; using System.Reflection; namespace TouchSocket.Core; @@ -49,7 +47,8 @@ public sealed class SerializObject /// /// /// - public SerializObject(Type type) + [RequiresUnreferencedCode("此方法可能会使用反射构建访问器,与剪裁不兼容。")] + public SerializObject([DynamicallyAccessedMembers(AOT.FastBinaryFormatter)] Type type) { this.Type = type; if (type.IsArray)//数组 @@ -59,9 +58,9 @@ public sealed class SerializObject } else if (type.IsClass || type.IsStruct()) { - if (type.IsNullableType()) + if (type.IsNullableType(out var actualType)) { - type = type.GetGenericArguments()[0]; + type = actualType; } this.Type = type; if (type.IsList()) @@ -105,27 +104,22 @@ public sealed class SerializObject else { this.InstanceType = InstanceType.Class; - this.MemberAccessor = new MemberAccessor(type) - { - OnGetFieldInfos = GetFieldInfos, - OnGetProperties = GetProperties - }; - this.MemberAccessor.Build(); + this.MemberAccessor = new MemberAccessor(type, GetFieldInfos(type), GetProperties(type)); } - if (type.GetCustomAttribute(typeof(FastConverterAttribute), false) is FastConverterAttribute attribute) + if (type.GetCustomAttribute(false) is FastConverterAttribute attribute) { this.Converter = (IFastBinaryConverter)Activator.CreateInstance(attribute.Type); } - if (type.GetCustomAttribute(typeof(FastSerializedAttribute), false) is FastSerializedAttribute fastSerializedAttribute) + if (type.GetCustomAttribute(false) is FastSerializedAttribute fastSerializedAttribute) { this.EnableIndex = fastSerializedAttribute.EnableIndex; } var list = new List(); - list.AddRange(GetProperties(type)); - list.AddRange(GetFieldInfos(type)); + list.AddRange(GetProperties(type).Values); + list.AddRange(GetFieldInfos(type).Values); this.FastMemberInfoDicForIndex = new Dictionary(); this.FastMemberInfoDicForName = new Dictionary(); @@ -174,17 +168,17 @@ public sealed class SerializObject /// public Type Type { get; private set; } - private static FieldInfo[] GetFieldInfos(Type type) + private static Dictionary GetFieldInfos([DynamicallyAccessedMembers(AOT.FastBinaryFormatter)] Type type) { return type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.Default) .Where(p => { return !p.IsInitOnly && (!p.IsDefined(typeof(FastNonSerializedAttribute), false)); }) - .ToArray(); + .ToDictionary(a => a.Name); } - private static PropertyInfo[] GetProperties(Type type) + private static Dictionary GetProperties([DynamicallyAccessedMembers(AOT.FastBinaryFormatter)] Type type) { return type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.Default) .Where(p => @@ -199,12 +193,8 @@ public sealed class SerializObject return false; } - if (p.CanPublicRead() && p.CanPublicWrite()) - { - return true; - } - return false; + return p.CanPublicRead() && p.CanPublicWrite(); }) - .ToArray(); + .ToDictionary(a => a.Name); } } \ No newline at end of file diff --git a/src/TouchSocket.Core/Serialization/FastBinary/SerializerContext/DefaultFastSerializerContext.cs b/src/TouchSocket.Core/Serialization/FastBinary/SerializerContext/DefaultFastSerializerContext.cs index fae8d9e98..16961d773 100644 --- a/src/TouchSocket.Core/Serialization/FastBinary/SerializerContext/DefaultFastSerializerContext.cs +++ b/src/TouchSocket.Core/Serialization/FastBinary/SerializerContext/DefaultFastSerializerContext.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; @@ -25,12 +24,13 @@ internal sealed class DefaultFastSerializerContext : FastSerializerContext /// /// /// - public void AddFastBinaryConverter([DynamicallyAccessedMembers(FastBinaryFormatter.DynamicallyAccessed)] Type type, IFastBinaryConverter converter) + public void AddFastBinaryConverter([DynamicallyAccessedMembers(AOT.FastBinaryFormatter)] Type type, IFastBinaryConverter converter) { base.AddConverter(type, converter); } - public override SerializObject GetSerializeObject(Type type) + [RequiresUnreferencedCode("此方法可能会使用反射构建访问器,与剪裁不兼容。")] + public override SerializObject GetSerializeObject([DynamicallyAccessedMembers(AOT.FastBinaryFormatter)] Type type) { var serializObject = base.GetSerializeObject(type); if (serializObject != null) @@ -38,9 +38,9 @@ internal sealed class DefaultFastSerializerContext : FastSerializerContext return serializObject; } - if (type.IsNullableType()) + if (type.IsNullableType(out var actualType)) { - type = type.GetGenericArguments()[0]; + type = actualType; } if (this.m_instanceCache.TryGetValue(type, out var instance)) diff --git a/src/TouchSocket.Core/Serialization/FastBinary/SerializerContext/FastSerializerContext.cs b/src/TouchSocket.Core/Serialization/FastBinary/SerializerContext/FastSerializerContext.cs index 1bb95cebb..edbe35c1d 100644 --- a/src/TouchSocket.Core/Serialization/FastBinary/SerializerContext/FastSerializerContext.cs +++ b/src/TouchSocket.Core/Serialization/FastBinary/SerializerContext/FastSerializerContext.cs @@ -10,10 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.IO; namespace TouchSocket.Core; @@ -33,18 +30,16 @@ public abstract class FastSerializerContext this.AddConverter(typeof(ByteBlock), new ByteBlockFastBinaryConverter()); this.AddConverter(typeof(MemoryStream), new MemoryStreamFastBinaryConverter()); this.AddConverter(typeof(Guid), new GuidFastBinaryConverter()); - //this.AddConverter(typeof(DataTable), new DataTableFastBinaryConverter()); - //this.AddConverter(typeof(DataSet), new DataSetFastBinaryConverter()); + this.AddConverter(typeof(Metadata), new MetadataFastBinaryConverter()); } /// /// 获取新实例 /// /// - /// - public virtual object GetNewInstance(Type type) + public virtual object GetNewInstance([DynamicallyAccessedMembers(AOT.FastBinaryFormatter)] Type type) { - return InstanceCreater.Create(type, null); + return Activator.CreateInstance(type, null); } /// @@ -52,7 +47,8 @@ public abstract class FastSerializerContext /// /// /// - public virtual SerializObject GetSerializeObject(Type type) + [RequiresUnreferencedCode("此方法可能会使用反射构建访问器,与剪裁不兼容。")] + public virtual SerializObject GetSerializeObject([DynamicallyAccessedMembers(AOT.FastBinaryFormatter)] Type type) { return this.m_instanceCache.TryGetValue(type, out var serializObject) ? serializObject : null; } @@ -62,7 +58,7 @@ public abstract class FastSerializerContext /// /// /// - protected void AddConverter([DynamicallyAccessedMembers(FastBinaryFormatter.DynamicallyAccessed)] Type type, IFastBinaryConverter converter) + protected void AddConverter([DynamicallyAccessedMembers(AOT.FastBinaryFormatter)] Type type, IFastBinaryConverter converter) { var serializObject = new SerializObject(type, converter); this.m_instanceCache.AddOrUpdate(type, serializObject); diff --git a/src/TouchSocket.Core/Serialization/SerializeConvert.cs b/src/TouchSocket.Core/Serialization/SerializeConvert.cs index ad8153d44..c405881a0 100644 --- a/src/TouchSocket.Core/Serialization/SerializeConvert.cs +++ b/src/TouchSocket.Core/Serialization/SerializeConvert.cs @@ -10,11 +10,9 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.IO; +using System.Diagnostics.CodeAnalysis; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; -using System.Text; using System.Xml.Serialization; namespace TouchSocket.Core; @@ -35,6 +33,7 @@ public static partial class SerializeConvert /// /// 数据对象 /// + [RequiresUnreferencedCode("BinaryFormatter serialization is not trim compatible because the type of objects being processed cannot be statically discovered.")] public static byte[] BinarySerialize(in object obj) { using (var serializeStream = new MemoryStream()) @@ -50,6 +49,7 @@ public static partial class SerializeConvert /// /// 数据对象 /// 路径 + [RequiresUnreferencedCode("BinaryFormatter serialization is not trim compatible because the type of objects being processed cannot be statically discovered.")] public static void BinarySerializeToFile(in object obj, string path) { using (var serializeStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite)) @@ -65,6 +65,7 @@ public static partial class SerializeConvert /// /// /// + [RequiresUnreferencedCode("BinaryFormatter serialization is not trim compatible because the type of objects being processed cannot be statically discovered.")] public static void BinarySerialize(Stream stream, in object obj) { var bf = new BinaryFormatter(); @@ -84,7 +85,8 @@ public static partial class SerializeConvert /// /// /// - + [RequiresDynamicCode("BinaryFormatter serialization uses dynamic code generation, the type of objects being processed cannot be statically discovered.")] + [RequiresUnreferencedCode("BinaryFormatter serialization is not trim compatible because the type of objects being processed cannot be statically discovered.")] public static T BinaryDeserialize(byte[] data, int offset, int length, SerializationBinder binder = null) { using (var DeserializeStream = new MemoryStream(data, offset, length)) @@ -107,6 +109,8 @@ public static partial class SerializeConvert /// /// /// + [RequiresDynamicCode("BinaryFormatter serialization uses dynamic code generation, the type of objects being processed cannot be statically discovered.")] + [RequiresUnreferencedCode("BinaryFormatter serialization is not trim compatible because the type of objects being processed cannot be statically discovered.")] public static object BinaryDeserialize(byte[] data, int offset, int length, SerializationBinder binder = null) { using (var DeserializeStream = new MemoryStream(data, offset, length)) @@ -128,6 +132,8 @@ public static partial class SerializeConvert /// /// /// + [RequiresDynamicCode("BinaryFormatter serialization uses dynamic code generation, the type of objects being processed cannot be statically discovered.")] + [RequiresUnreferencedCode("BinaryFormatter serialization is not trim compatible because the type of objects being processed cannot be statically discovered.")] public static T BinaryDeserialize(Stream stream, SerializationBinder binder = null) { return (T)BinaryDeserialize(stream); @@ -139,6 +145,8 @@ public static partial class SerializeConvert /// 包含序列化对象数据的流。 /// 可选的绑定器,用于控制反序列化过程中的类型绑定。 /// 反序列化后的对象。 + [RequiresDynamicCode("BinaryFormatter serialization uses dynamic code generation, the type of objects being processed cannot be statically discovered.")] + [RequiresUnreferencedCode("BinaryFormatter serialization is not trim compatible because the type of objects being processed cannot be statically discovered.")] public static object BinaryDeserialize(Stream stream, SerializationBinder binder = null) { // 创建BinaryFormatter实例以进行反序列化操作 @@ -158,6 +166,8 @@ public static partial class SerializeConvert /// /// /// + [RequiresDynamicCode("BinaryFormatter serialization uses dynamic code generation, the type of objects being processed cannot be statically discovered.")] + [RequiresUnreferencedCode("BinaryFormatter serialization is not trim compatible because the type of objects being processed cannot be statically discovered.")] public static T BinaryDeserializeFromFile(string path) { using (var serializeStream = new FileStream(path, FileMode.Open, FileAccess.Read)) @@ -173,6 +183,8 @@ public static partial class SerializeConvert /// /// /// + [RequiresDynamicCode("BinaryFormatter serialization uses dynamic code generation, the type of objects being processed cannot be statically discovered.")] + [RequiresUnreferencedCode("BinaryFormatter serialization is not trim compatible because the type of objects being processed cannot be statically discovered.")] public static T BinaryDeserialize(byte[] data) { return BinaryDeserialize(data, 0, data.Length); @@ -185,6 +197,8 @@ public static partial class SerializeConvert /// /// /// + [RequiresDynamicCode("BinaryFormatter serialization uses dynamic code generation, the type of objects being processed cannot be statically discovered.")] + [RequiresUnreferencedCode("BinaryFormatter serialization is not trim compatible because the type of objects being processed cannot be statically discovered.")] public static T BinaryDeserialize(byte[] data, SerializationBinder binder = null) { return BinaryDeserialize(data, 0, data.Length, binder); @@ -194,68 +208,6 @@ public static partial class SerializeConvert #pragma warning restore SYSLIB0011 // 微软觉得不安全,不推荐使用 - //#region Fast二进制序列化 - - ///// - ///// Fast二进制序列化对象 - ///// - ///// - ///// - ///// - //public static void FastBinarySerialize(ref TByteBlock byteBlock, in T obj) - // where TByteBlock:IByteBlock - //{ - // FastBinaryFormatter.Serialize(ref byteBlock, obj); - // byteBlock.SetLength(byteBlock.Length); - //} - - ///// - ///// Fast二进制序列化对象 - ///// - ///// - ///// - //public static byte[] FastBinarySerialize<[DynamicallyAccessedMembers(FastBinaryFormatter.DynamicallyAccessed)] T>( in T obj) - //{ - // var byteBlock = new ByteBlock(1024*64); - // try - // { - // FastBinarySerialize(ref byteBlock, obj); - // return byteBlock.ToArray(); - // } - // finally - // { - // byteBlock.Dispose(); - // } - //} - - //#endregion Fast二进制序列化 - - //#region Fast二进制反序列化 - - ///// - ///// Fast反序列化 - ///// - ///// - ///// - ///// - ///// - //public static T FastBinaryDeserialize(ref TByteBlock byteBlock)where TByteBlock:IByteBlock - //{ - // return (T)FastBinaryFormatter.Deserialize(ref byteBlock, typeof(T)); - //} - - ///// - ///// Fast反序列化 - ///// - ///// - ///// - ///// - ///// - //public static object FastBinaryDeserialize(ref TByteBlock byteBlock, [DynamicallyAccessedMembers(FastBinaryFormatter.DynamicallyAccessed)] Type type)where TByteBlock:IByteBlock - //{ - // return FastBinaryFormatter.Deserialize(ref byteBlock, type); - //} - //#endregion Fast二进制反序列化 #region Xml序列化和反序列化 @@ -265,6 +217,8 @@ public static partial class SerializeConvert /// 数据对象 /// 编码格式 /// + [RequiresUnreferencedCode("Members from serialized types may be trimmed if not referenced directly")] + [RequiresDynamicCode("XML serializer relies on dynamic code generation which is not available with Ahead of Time compilation")] public static string XmlSerializeToString(object obj, Encoding encoding) { return encoding.GetString(XmlSerializeToBytes(obj)); @@ -275,6 +229,8 @@ public static partial class SerializeConvert /// /// 数据对象 /// + [RequiresUnreferencedCode("Members from serialized types may be trimmed if not referenced directly")] + [RequiresDynamicCode("XML serializer relies on dynamic code generation which is not available with Ahead of Time compilation")] public static string XmlSerializeToString(object obj) { return XmlSerializeToString(obj, Encoding.UTF8); @@ -285,6 +241,8 @@ public static partial class SerializeConvert /// /// 数据对象 /// + [RequiresUnreferencedCode("Members from serialized types may be trimmed if not referenced directly")] + [RequiresDynamicCode("XML serializer relies on dynamic code generation which is not available with Ahead of Time compilation")] public static byte[] XmlSerializeToBytes(object obj) { using (var fileStream = new MemoryStream()) @@ -300,6 +258,8 @@ public static partial class SerializeConvert /// /// /// + [RequiresUnreferencedCode("Members from serialized types may be trimmed if not referenced directly")] + [RequiresDynamicCode("XML serializer relies on dynamic code generation which is not available with Ahead of Time compilation")] public static void XmlSerializeToFile(object obj, string path) { using (var fileStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite)) @@ -316,6 +276,8 @@ public static partial class SerializeConvert /// 反序列化类型 /// 数据 /// + [RequiresUnreferencedCode("Members from serialized types may be trimmed if not referenced directly")] + [RequiresDynamicCode("XML serializer relies on dynamic code generation which is not available with Ahead of Time compilation")] public static T XmlDeserializeFromBytes(byte[] dataBytes) { var xmlSerializer = new XmlSerializer(typeof(T)); @@ -331,6 +293,8 @@ public static partial class SerializeConvert /// /// /// + [RequiresUnreferencedCode("Members from deserialized types may be trimmed if not referenced directly")] + [RequiresDynamicCode("XML serializer relies on dynamic code generation which is not available with Ahead of Time compilation")] public static object XmlDeserializeFromBytes(byte[] dataBytes, Type type) { var xmlSerializer = new XmlSerializer(type); @@ -346,6 +310,8 @@ public static partial class SerializeConvert /// /// /// + [RequiresUnreferencedCode("Members from deserialized types may be trimmed if not referenced directly")] + [RequiresDynamicCode("XML serializer relies on dynamic code generation which is not available with Ahead of Time compilation")] public static object XmlDeserializeFromStream(Stream xmlStream, Type targetType) { var xmlSerializer = new XmlSerializer(targetType); @@ -358,6 +324,8 @@ public static partial class SerializeConvert /// /// /// + [RequiresUnreferencedCode("Members from deserialized types may be trimmed if not referenced directly")] + [RequiresDynamicCode("XML serializer relies on dynamic code generation which is not available with Ahead of Time compilation")] public static T XmlDeserializeFromStream(Stream xmlStream) { var xmlSerializer = new XmlSerializer(typeof(T)); @@ -370,6 +338,8 @@ public static partial class SerializeConvert /// xml字符串 /// /// + [RequiresUnreferencedCode("Members from deserialized types may be trimmed if not referenced directly")] + [RequiresDynamicCode("XML serializer relies on dynamic code generation which is not available with Ahead of Time compilation")] public static object XmlDeserializeFromString(string xmlString, Type targetType) { return XmlDeserializeFromStream(new MemoryStream(Encoding.UTF8.GetBytes(xmlString)), targetType); @@ -381,6 +351,8 @@ public static partial class SerializeConvert /// /// /// + [RequiresUnreferencedCode("Members from deserialized types may be trimmed if not referenced directly")] + [RequiresDynamicCode("XML serializer relies on dynamic code generation which is not available with Ahead of Time compilation")] public static T XmlDeserializeFromString(string xmlString) { return (T)XmlDeserializeFromString(xmlString, typeof(T)); @@ -392,6 +364,8 @@ public static partial class SerializeConvert /// 反序列化类型 /// 文件路径 /// + [RequiresUnreferencedCode("Members from deserialized types may be trimmed if not referenced directly")] + [RequiresDynamicCode("XML serializer relies on dynamic code generation which is not available with Ahead of Time compilation")] public static T XmlDeserializeFromFile(string path) { using (Stream xmlStream = new FileStream(path, FileMode.Open, FileAccess.Read)) @@ -442,7 +416,7 @@ public static partial class SerializeConvert /// /// 数据对象 /// - public static byte[] JsonSerializeToBytes(object obj) + public static ReadOnlyMemory JsonSerializeToBytes(object obj) { return ToJsonString(obj).ToUtf8Bytes(); } @@ -457,7 +431,7 @@ public static partial class SerializeConvert using (var fileStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite)) { var date = JsonSerializeToBytes(obj); - fileStream.Write(date, 0, date.Length); + fileStream.Write(date.Span); fileStream.Close(); } } @@ -466,22 +440,22 @@ public static partial class SerializeConvert /// Json反序列化 /// /// 反序列化类型 - /// 数据 + /// 数据 /// - public static T JsonDeserializeFromBytes(byte[] dataBytes) + public static T JsonDeserializeFromBytes(ReadOnlyMemory memory) { - return (T)JsonDeserializeFromBytes(dataBytes, typeof(T)); + return (T)JsonDeserializeFromBytes(memory, typeof(T)); } /// /// Xml反序列化 /// - /// + /// /// /// - public static object JsonDeserializeFromBytes(byte[] dataBytes, Type type) + public static object JsonDeserializeFromBytes(ReadOnlyMemory memory, Type type) { - return FromJsonString(Encoding.UTF8.GetString(dataBytes), type); + return FromJsonString(memory.Span.ToUtf8String(), type); } /// diff --git a/src/TouchSocket.Core/Serialization/SerializerFormatter/BytesSerializerConverter.cs b/src/TouchSocket.Core/Serialization/SerializerFormatter/BytesSerializerConverter.cs index d5e0793f3..2a399b580 100644 --- a/src/TouchSocket.Core/Serialization/SerializerFormatter/BytesSerializerConverter.cs +++ b/src/TouchSocket.Core/Serialization/SerializerFormatter/BytesSerializerConverter.cs @@ -10,20 +10,15 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ +using System.Diagnostics.CodeAnalysis; + namespace TouchSocket.Core; /// /// 字节类转换器 /// -public class BytesSerializerConverter : TouchSocketSerializerConverter +public class BytesSerializerConverter<[DynamicallyAccessedMembers(AOT.SerializerFormatterMemberType)] TState> : TouchSocketSerializerConverter, TState> { - ///// - ///// 字节类转换器 - ///// - //public BytesConverter() - //{ - // this.Add(new JsonBytesToClassConverter()); - //} } /// diff --git a/src/TouchSocket.Core/Serialization/SerializerFormatter/ISerializerFormatter.cs b/src/TouchSocket.Core/Serialization/SerializerFormatter/ISerializerFormatter.cs index 821bf79bf..badf41ece 100644 --- a/src/TouchSocket.Core/Serialization/SerializerFormatter/ISerializerFormatter.cs +++ b/src/TouchSocket.Core/Serialization/SerializerFormatter/ISerializerFormatter.cs @@ -10,38 +10,39 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; +using System.Diagnostics.CodeAnalysis; namespace TouchSocket.Core; /// -/// 转换器接口 +/// 序列化转换器接口。 /// public interface ISerializerFormatter { /// - /// 转换器执行顺序 - /// 该属性值越小,越靠前执行。值相等时,按添加先后顺序 - /// 该属性效果,仅在之前设置有效。 + /// 转换器执行顺序。 + /// 该属性值越小,越靠前执行。值相等时,按添加先后顺序。 + /// 该属性效果,仅在 之前设置有效。 /// int Order { get; set; } /// - /// 尝试将源数据转换目标类型对象 + /// 尝试将源数据反序列化为目标类型对象。 /// - /// - /// - /// - /// - /// - bool TryDeserialize(TState state, in TSource source, Type targetType, out object target); + /// 转换器状态。 + /// 源数据。 + /// 目标类型。 + /// 转换后的目标对象。 + /// 操作是否成功。 + bool TryDeserialize(TState state, in TSource source, [DynamicallyAccessedMembers(AOT.SerializerFormatterMemberType)] Type targetType, out object target); /// - /// 尝试将目标类型对象转换源数据 + /// 尝试将目标类型对象序列化为源数据。 /// - /// - /// - /// - /// - bool TrySerialize(TState state, in object target, out TSource source); + /// 目标对象类型。 + /// 转换器状态。 + /// 目标对象。 + /// 序列化后的源数据。 + /// 操作是否成功。 + bool TrySerialize(TState state, in TTarget target, out TSource source); } \ No newline at end of file diff --git a/src/TouchSocket.Core/Serialization/SerializerFormatter/JsonBytesToClassSerializerFormatter.cs b/src/TouchSocket.Core/Serialization/SerializerFormatter/JsonBytesToClassSerializerFormatter.cs index 17dfb0947..877a0f78c 100644 --- a/src/TouchSocket.Core/Serialization/SerializerFormatter/JsonBytesToClassSerializerFormatter.cs +++ b/src/TouchSocket.Core/Serialization/SerializerFormatter/JsonBytesToClassSerializerFormatter.cs @@ -11,15 +11,14 @@ //------------------------------------------------------------------------------ using Newtonsoft.Json; -using System; -using System.Text; +using System.Diagnostics.CodeAnalysis; namespace TouchSocket.Core; /// /// Json字节转到对应类 /// -public class JsonBytesToClassSerializerFormatter : ISerializerFormatter +public class JsonBytesToClassSerializerFormatter : ISerializerFormatter, TState> { /// /// JsonSettings @@ -31,11 +30,11 @@ public class JsonBytesToClassSerializerFormatter : ISerializerFormatter< /// - public virtual bool TryDeserialize(TState state, in byte[] source, Type targetType, out object target) + public virtual bool TryDeserialize(TState state, in ReadOnlyMemory source, [DynamicallyAccessedMembers(AOT.SerializerFormatterMemberType)] Type targetType, out object target) { try { - target = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(source), targetType, this.JsonSettings); + target = JsonConvert.DeserializeObject(source.Span.ToUtf8String(), targetType, this.JsonSettings); return true; } catch @@ -46,11 +45,7 @@ public class JsonBytesToClassSerializerFormatter : ISerializerFormatter< } /// - /// - /// - /// - /// - public virtual bool TrySerialize(TState state, in object target, out byte[] source) + public virtual bool TrySerialize(TState state, in TTarget target, out ReadOnlyMemory source) { try { @@ -59,7 +54,7 @@ public class JsonBytesToClassSerializerFormatter : ISerializerFormatter< } catch (Exception) { - source = null; + source = ReadOnlyMemory.Empty; return false; } } diff --git a/src/TouchSocket.Core/Serialization/SerializerFormatter/JsonMemoryToClassSerializerFormatter.cs b/src/TouchSocket.Core/Serialization/SerializerFormatter/JsonMemoryToClassSerializerFormatter.cs index ee2c23569..8880d2eb0 100644 --- a/src/TouchSocket.Core/Serialization/SerializerFormatter/JsonMemoryToClassSerializerFormatter.cs +++ b/src/TouchSocket.Core/Serialization/SerializerFormatter/JsonMemoryToClassSerializerFormatter.cs @@ -11,8 +11,7 @@ //------------------------------------------------------------------------------ using Newtonsoft.Json; -using System; -using System.Text; +using System.Diagnostics.CodeAnalysis; namespace TouchSocket.Core; @@ -32,7 +31,7 @@ public class JsonMemoryToClassSerializerFormatter : ISerializerFormatter public int Order { get; set; } /// - public bool TryDeserialize(TState state, in ReadOnlyMemory source, Type targetType, out object target) + public bool TryDeserialize(TState state, in ReadOnlyMemory source, [DynamicallyAccessedMembers(AOT.SerializerFormatterMemberType)] Type targetType, out object target) { try { @@ -47,7 +46,7 @@ public class JsonMemoryToClassSerializerFormatter : ISerializerFormatter } /// - public bool TrySerialize(TState state, in object target, out ReadOnlyMemory source) + public bool TrySerialize(TState state, in TTarget target, out ReadOnlyMemory source) { try { diff --git a/src/TouchSocket.Core/Serialization/SerializerFormatter/JsonStringToClassSerializerFormatter.cs b/src/TouchSocket.Core/Serialization/SerializerFormatter/JsonStringToClassSerializerFormatter.cs index d0da962fc..515d00159 100644 --- a/src/TouchSocket.Core/Serialization/SerializerFormatter/JsonStringToClassSerializerFormatter.cs +++ b/src/TouchSocket.Core/Serialization/SerializerFormatter/JsonStringToClassSerializerFormatter.cs @@ -11,7 +11,7 @@ //------------------------------------------------------------------------------ using Newtonsoft.Json; -using System; +using System.Diagnostics.CodeAnalysis; namespace TouchSocket.Core; @@ -29,12 +29,7 @@ public class JsonStringToClassSerializerFormatter : ISerializerFormatter public JsonSerializerSettings JsonSettings { get; set; } = new JsonSerializerSettings(); /// - /// - /// - /// - /// - /// - public virtual bool TryDeserialize(TState state, in string source, Type targetType, out object target) + public virtual bool TryDeserialize(TState state, in string source, [DynamicallyAccessedMembers(AOT.SerializerFormatterMemberType)] Type targetType, out object target) { try { @@ -49,11 +44,7 @@ public class JsonStringToClassSerializerFormatter : ISerializerFormatter } /// - /// - /// - /// - /// - public virtual bool TrySerialize(TState state, in object target, out string source) + public virtual bool TrySerialize(TState state, in TTarget target, out string source) { try { diff --git a/src/TouchSocket.Core/Serialization/SerializerFormatter/StringSerializerConverter.cs b/src/TouchSocket.Core/Serialization/SerializerFormatter/StringSerializerConverter.cs index beb79fe7f..03e72ba2a 100644 --- a/src/TouchSocket.Core/Serialization/SerializerFormatter/StringSerializerConverter.cs +++ b/src/TouchSocket.Core/Serialization/SerializerFormatter/StringSerializerConverter.cs @@ -10,12 +10,14 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ +using System.Diagnostics.CodeAnalysis; + namespace TouchSocket.Core; /// /// String类型数据转换器 /// -public class StringSerializerConverter : TouchSocketSerializerConverter +public class StringSerializerConverter<[DynamicallyAccessedMembers(AOT.SerializerFormatterMemberType)] TState> : TouchSocketSerializerConverter { /// /// String类型数据转换器 diff --git a/src/TouchSocket.Core/Serialization/SerializerFormatter/StringToPrimitiveSerializerFormatter.cs b/src/TouchSocket.Core/Serialization/SerializerFormatter/StringToPrimitiveSerializerFormatter.cs index 539c90181..4c186d7a4 100644 --- a/src/TouchSocket.Core/Serialization/SerializerFormatter/StringToPrimitiveSerializerFormatter.cs +++ b/src/TouchSocket.Core/Serialization/SerializerFormatter/StringToPrimitiveSerializerFormatter.cs @@ -10,7 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; +using System.Diagnostics.CodeAnalysis; namespace TouchSocket.Core; @@ -23,12 +23,7 @@ public class StringToPrimitiveSerializerFormatter : ISerializerFormatter public int Order { get; set; } /// - /// - /// - /// - /// - /// - public virtual bool TryDeserialize(TState state, in string source, Type targetType, out object target) + public virtual bool TryDeserialize(TState state, in string source, [DynamicallyAccessedMembers(AOT.SerializerFormatterMemberType)] Type targetType, out object target) { if (targetType.IsPrimitive()) { @@ -39,11 +34,7 @@ public class StringToPrimitiveSerializerFormatter : ISerializerFormatter } /// - /// - /// - /// - /// - public virtual bool TrySerialize(TState state, in object target, out string source) + public virtual bool TrySerialize(TState state, in TTarget target, out string source) { if (target != null) { diff --git a/src/TouchSocket.Core/Serialization/SerializerFormatter/SystemTextJsonSerializerFormatter.cs b/src/TouchSocket.Core/Serialization/SerializerFormatter/SystemTextJsonSerializerFormatter.cs new file mode 100644 index 000000000..f2e40d8fb --- /dev/null +++ b/src/TouchSocket.Core/Serialization/SerializerFormatter/SystemTextJsonSerializerFormatter.cs @@ -0,0 +1,33 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; + +namespace TouchSocket.Core; + +public abstract class SystemTextJsonSerializerFormatter : ISerializerFormatter +{ + /// + public int Order { get; set; } + /// + /// 获取或设置Json序列化选项。 + /// + public JsonSerializerOptions JsonSettings { get; set; } = new JsonSerializerOptions(); + + /// + public abstract bool TryDeserialize(TState state, in TSource source, [DynamicallyAccessedMembers(AOT.SerializerFormatterMemberType)] Type targetType, out object target); + + /// + public abstract bool TrySerialize(TState state, in TTarget target, out TSource source); + +} diff --git a/src/TouchSocket.Core/Serialization/SerializerFormatter/SystemTextJsonStringToClassSerializerFormatter.cs b/src/TouchSocket.Core/Serialization/SerializerFormatter/SystemTextJsonStringToClassSerializerFormatter.cs index dae6c245d..170431020 100644 --- a/src/TouchSocket.Core/Serialization/SerializerFormatter/SystemTextJsonStringToClassSerializerFormatter.cs +++ b/src/TouchSocket.Core/Serialization/SerializerFormatter/SystemTextJsonStringToClassSerializerFormatter.cs @@ -10,14 +10,8 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -#if SystemTextJson - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Diagnostics.CodeAnalysis; using System.Text.Json; -using System.Threading.Tasks; namespace TouchSocket.Core; @@ -25,27 +19,12 @@ namespace TouchSocket.Core; /// 使用System.Text.Json进行字符串与类之间序列化和反序列化的格式化器。 /// /// 状态类型。 -public class SystemTextJsonStringToClassSerializerFormatter : ISerializerFormatter +[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "使用该序列化时,会和源生成配合使用")] +[UnconditionalSuppressMessage("AOT", "IL3050:", Justification = "使用该序列化时,会和源生成配合使用")] +public class SystemTextJsonStringToClassSerializerFormatter : SystemTextJsonSerializerFormatter { - /// - /// 获取或设置Json序列化选项。 - /// - public JsonSerializerOptions JsonSettings { get; set; } = new JsonSerializerOptions(); - - /// - /// 获取或设置格式化器的顺序。 - /// - public int Order { get; set; } - - /// - /// 尝试将字符串反序列化为指定类型的对象。 - /// - /// 状态对象。 - /// 源字符串。 - /// 目标类型。 - /// 反序列化后的对象。 - /// 如果反序列化成功,则为true;否则为false。 - public bool TryDeserialize(TState state, in string source, Type targetType, out object target) + /// + public override bool TryDeserialize(TState state, in string source, [DynamicallyAccessedMembers(AOT.SerializerFormatterMemberType)] Type targetType, out object target) { try { @@ -64,14 +43,8 @@ public class SystemTextJsonStringToClassSerializerFormatter : ISerialize } } - /// - /// 尝试将对象序列化为字符串。 - /// - /// 状态对象。 - /// 要序列化的对象。 - /// 序列化后的字符串。 - /// 如果序列化成功,则为true;否则为false。 - public bool TrySerialize(TState state, in object target, out string source) + /// + public override bool TrySerialize(TState state, in TTarget target, out string source) { try { @@ -90,5 +63,4 @@ public class SystemTextJsonStringToClassSerializerFormatter : ISerialize } } -} -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/src/TouchSocket.Core/Serialization/SerializerFormatter/TouchSocketSerializerConverter.cs b/src/TouchSocket.Core/Serialization/SerializerFormatter/TouchSocketSerializerConverter.cs index e412f1182..91b95f63a 100644 --- a/src/TouchSocket.Core/Serialization/SerializerFormatter/TouchSocketSerializerConverter.cs +++ b/src/TouchSocket.Core/Serialization/SerializerFormatter/TouchSocketSerializerConverter.cs @@ -10,8 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace TouchSocket.Core; @@ -20,8 +19,10 @@ namespace TouchSocket.Core; /// /// 源数据类型。 /// 状态类型。 -public class TouchSocketSerializerConverter +public class TouchSocketSerializerConverter<[DynamicallyAccessedMembers(AOT.SerializerFormatterMemberType)] TSource, [DynamicallyAccessedMembers(AOT.SerializerFormatterMemberType)] TState> { + private readonly List> m_converters = new List>(); + /// /// 初始化 TouchSocketSerializerConverter 类的新实例。 /// @@ -37,9 +38,8 @@ public class TouchSocketSerializerConverter /// /// 初始化 TouchSocketSerializerConverter 类的新实例。 /// - public TouchSocketSerializerConverter() { } - - private readonly List> m_converters = new List>(); + public TouchSocketSerializerConverter() + { } /// /// 添加插件 @@ -78,7 +78,7 @@ public class TouchSocketSerializerConverter /// 目标类型 /// 转换后的目标类型对象 /// 当无法转换时抛出异常 - public virtual object Deserialize(TState state, TSource source, Type targetType) + public virtual object Deserialize(TState state, TSource source, [DynamicallyAccessedMembers(AOT.SerializerFormatterMemberType)] Type targetType) { foreach (var item in this.m_converters) { @@ -91,25 +91,6 @@ public class TouchSocketSerializerConverter throw new Exception($"{source}无法转换为{targetType}类型。"); } - /// - /// 将目标类型对象转换源数据 - /// - /// - /// - /// - public virtual TSource Serialize(TState state, in object target) - { - foreach (var item in this.m_converters) - { - if (item.TrySerialize(state, target, out var source)) - { - return source; - } - } - - throw new Exception($"{target}无法转换为{typeof(TSource)}类型。"); - } - /// /// 移除插件 /// @@ -138,4 +119,23 @@ public class TouchSocketSerializerConverter } } } + + /// + /// 将目标类型对象转换源数据 + /// + /// + /// + /// + public virtual TSource Serialize(TState state, in object target) + { + foreach (var item in this.m_converters) + { + if (item.TrySerialize(state, target, out var source)) + { + return source; + } + } + + throw new Exception($"{target}无法转换为{typeof(TSource)}类型。"); + } } \ No newline at end of file diff --git a/src/TouchSocket.Core/Serialization/SerializerFormatter/XmlStringToClassSerializerFormatter.cs b/src/TouchSocket.Core/Serialization/SerializerFormatter/XmlStringToClassSerializerFormatter.cs index 82ae8b809..c8bc0d975 100644 --- a/src/TouchSocket.Core/Serialization/SerializerFormatter/XmlStringToClassSerializerFormatter.cs +++ b/src/TouchSocket.Core/Serialization/SerializerFormatter/XmlStringToClassSerializerFormatter.cs @@ -10,7 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; +using System.Diagnostics.CodeAnalysis; namespace TouchSocket.Core; @@ -18,13 +18,14 @@ namespace TouchSocket.Core; /// Xml字符串转换器 /// /// +[RequiresUnreferencedCode("Members from deserialized types may be trimmed if not referenced directly")] public class XmlStringToClassSerializerFormatter : ISerializerFormatter { /// public int Order { get; set; } /// - public virtual bool TryDeserialize(TState state, in string source, Type targetType, out object target) + public virtual bool TryDeserialize(TState state, in string source, [DynamicallyAccessedMembers(AOT.SerializerFormatterMemberType)] Type targetType, out object target) { try { @@ -39,7 +40,7 @@ public class XmlStringToClassSerializerFormatter : ISerializerFormatter< } /// - public virtual bool TrySerialize(TState state, in object target, out string source) + public virtual bool TrySerialize(TState state, in TTarget target, out string source) { try { diff --git a/src/TouchSocket.Core/Text/TextValues.cs b/src/TouchSocket.Core/Text/TextValues.cs new file mode 100644 index 000000000..bbe35e748 --- /dev/null +++ b/src/TouchSocket.Core/Text/TextValues.cs @@ -0,0 +1,348 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ +using System.Collections; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace TouchSocket.Core; + +/// +/// 表示多文本值集合,可包含单个或多个值。 +/// +[DebuggerDisplay("{ToString()}")] +[DebuggerTypeProxy(typeof(TextValuesDebugView))] +public readonly struct TextValues : IEnumerable, IEquatable +{ + /// + /// 空集合。 + /// + public static readonly TextValues Empty = default; + + private readonly object m_values; // null|string|string[] + + /// + /// 使用单个值初始化。 + /// + public TextValues(string value) + { + m_values = value; + } + + /// + /// 使用多个值初始化。 + /// + public TextValues(string[] values) + { + m_values = values; + } + + /// + /// 获取值数量。 + /// + public int Count + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + var value = m_values; + if (value is null) + { + return 0; + } + if (value is string) + { + return 1; + } + return Unsafe.As(value).Length; + } + } + + /// + /// 获取第一个值,如果不存在则返回。 + /// + public string First + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + object value = m_values; + if (value is null) return null; + if (value is string s) return s; + var arr = Unsafe.As(value); + return arr.Length > 0 ? arr[0] : null; + } + } + + /// + /// 指示是否为空集合。 + /// + public bool IsEmpty => Count == 0; + + /// + /// 通过索引获取指定值。 + /// + public string this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + object value = m_values; + if (value is null) + { + throw new IndexOutOfRangeException(); + } + if (value is string s) + { + if (index == 0) return s; + throw new IndexOutOfRangeException(); + } + return Unsafe.As(value)[index]; + } + } + + /// + /// 隐式转换:到字符串,返回第一个值。 + /// + public static implicit operator string(TextValues values) => values.First; + + /// + /// 隐式转换:到字符串数组。 + /// + public static implicit operator string[](TextValues values) => values.ToArray(); + + /// + /// 隐式转换:字符串到。 + /// + public static implicit operator TextValues(string value) => new TextValues(value); + + /// + /// 隐式转换:字符串数组到。 + /// + public static implicit operator TextValues(string[] values) => new TextValues(values); + + /// + /// 判断是否为空。 + /// + public static bool IsNullOrEmpty(TextValues values) => values.Count == 0; + + /// + /// 不相等运算符。 + /// + public static bool operator !=(TextValues left, TextValues right) => !left.Equals(right); + + /// + /// 相等运算符。 + /// + public static bool operator ==(TextValues left, TextValues right) => left.Equals(right); + + /// + /// 添加一个值,返回新集合。 + /// + public TextValues Add(string value) + { + if (value is null) + { + return this; + } + var current = m_values; + if (current is null) + { + return new TextValues(value); + } + if (current is string s) + { + return new TextValues(new[] { s, value }); + } + var arr = Unsafe.As(current); + var newArr = new string[arr.Length + 1]; + Array.Copy(arr, newArr, arr.Length); + newArr[arr.Length] = value; + return new TextValues(newArr); + } + + /// + /// 判断相等。 + /// + public bool Equals(string other, StringComparison comparison) + { + return Equals(new TextValues(other)); + } + + /// + /// 判断相等。 + /// + public bool Equals(TextValues other) + { + if (ReferenceEquals(m_values, other.m_values)) return true; + int count = Count; + if (count != other.Count) return false; + if (count == 0) return true; + if (count == 1) + { + return string.Equals(First, other.First, StringComparison.Ordinal); + } + var a = ToArray(); + var b = other.ToArray(); + for (int i = 0; i < a.Length; i++) + { + if (!string.Equals(a[i], b[i], StringComparison.Ordinal)) return false; + } + return true; + } + + /// + /// 判断相等。 + /// + public override bool Equals(object obj) => obj is TextValues v && Equals(v); + + /// + /// 获取枚举器。 + /// + public Enumerator GetEnumerator() => new Enumerator(m_values); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + /// 获取哈希码。 + /// + public override int GetHashCode() + { + object value = m_values; + if (value is null) return 0; + if (value is string s) + { + return s?.GetHashCode() ?? 0; + } + var arr = Unsafe.As(value); + int hash = 17; + for (int i = 0; i < arr.Length; i++) + { + hash = hash * 31 + (arr[i]?.GetHashCode() ?? 0); + } + return hash; + } + + /// + /// 转换为数组。 + /// + public string[] ToArray() + { + object value = m_values; + if (value is null) + { + return Array.Empty(); + } + if (value is string s) + { + return new[] { s }; + } + return Unsafe.As(value); + } + + /// + /// 返回字符串表示。单值返回该值,多个值使用","分隔。 + /// + public override string ToString() + { + object value = m_values; + if (value is null) + { + return string.Empty; + } + if (value is string s) + { + return s ?? string.Empty; + } + var arr = Unsafe.As(value); + if (arr.Length == 0) return string.Empty; + if (arr.Length == 1) return arr[0] ?? string.Empty; + return string.Join(",", arr); + } + + /// + /// 枚举器。 + /// + public struct Enumerator : IEnumerator + { + private readonly object m_values; // null|string|string[] + private string m_current; + private int m_index; + + internal Enumerator(object values) + { + m_values = values; + m_index = -1; + m_current = null; + } + + /// + /// 当前值。 + /// + public string Current => m_current; + + object IEnumerator.Current => Current; + + /// + /// 释放资源。 + /// + public void Dispose() + { + } + + /// + /// 移动到下一项。 + /// + public bool MoveNext() + { + if (m_values is null) + { + return false; + } + if (m_values is string s) + { + if (m_index < 0) + { + m_index = 0; + m_current = s; + return true; + } + return false; + } + var arr = Unsafe.As(m_values); + int next = m_index + 1; + if ((uint)next < (uint)arr.Length) + { + m_index = next; + m_current = arr[next]; + return true; + } + return false; + } + + /// + /// 重置。 + /// + public void Reset() + { + m_index = -1; + m_current = null; + } + } + + private sealed class TextValuesDebugView(TextValues values) + { + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public string[] Items => values.ToArray(); + } +} \ No newline at end of file diff --git a/src/TouchSocket.Core/Threading/AsyncAutoResetEvent.cs b/src/TouchSocket.Core/Threading/AsyncAutoResetEvent.cs index bd1b0924a..d27da9dae 100644 --- a/src/TouchSocket.Core/Threading/AsyncAutoResetEvent.cs +++ b/src/TouchSocket.Core/Threading/AsyncAutoResetEvent.cs @@ -1,33 +1,267 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.Diagnostics; namespace TouchSocket.Core; /// -/// 异步等待的AutoResetEvent -/// WaitOneAsync方法会返回一个task,通过await方式等待 +/// 表示一个异步自动重置事件,提供基于的异步等待机制。 /// -public class AsyncAutoResetEvent : AsyncResetEvent +/// +/// AsyncAutoResetEvent是的异步版本实现, +/// 允许多个任务异步等待信号,当信号被设置时,只有一个等待者会被唤醒,然后信号自动重置。 +/// 此实现基于微软VS相关库的代码。 +/// +/// 与传统的不同,此类不会阻塞线程,而是返回可等待的, +/// 更适合在异步编程模式中使用,能够避免线程阻塞并提高系统的并发性能。 +/// 此代码摘抄自微软VS相关库。 +/// +/// +[DebuggerDisplay("Signaled: {signaled}")] +public class AsyncAutoResetEvent { /// - /// 异步等待的AutoResetEvent - /// WaitOneAsync方法会返回一个task,通过await方式等待 + /// A queue of folks awaiting signals. /// - public AsyncAutoResetEvent() : this(false) { } + private readonly Queue signalAwaiters = new Queue(); /// - /// 异步等待的AutoResetEvent - /// WaitOneAsync方法会返回一个task,通过await方式等待 + /// Whether to complete the task synchronously in the method, + /// as opposed to asynchronously. /// - /// - public AsyncAutoResetEvent(bool set) : base(set, true) { } -} \ No newline at end of file + private readonly bool allowInliningAwaiters; + + /// + /// A reusable delegate that points to the method. + /// + private readonly Action onCancellationRequestHandler; + + /// + /// A value indicating whether this event is already in a signaled state. + /// + /// + /// This should not need the volatile modifier because it is + /// always accessed within a lock. + /// + private bool signaled; + + /// + /// 初始化类的新实例,默认不允许内联等待者。 + /// + /// + /// 使用默认设置创建异步自动重置事件,等待者的完成操作将异步执行, + /// 这样能更好地模拟的行为。 + /// + public AsyncAutoResetEvent() + : this(allowInliningAwaiters: false) + { + } + + /// + /// 初始化类的新实例。 + /// + /// + /// 指示是否在方法中同步完成任务,而不是异步完成。 + /// 能更好地模拟类的行为, + /// 但可能会带来略好的性能。 + /// + public AsyncAutoResetEvent(bool allowInliningAwaiters) + { + this.allowInliningAwaiters = allowInliningAwaiters; + this.onCancellationRequestHandler = this.OnCancellationRequest; + } + + /// + /// 返回一个可等待对象,用于异步获取下一个信号。 + /// + /// 表示异步等待操作的 + /// + /// 如果当前事件已处于信号状态,则立即返回已完成的任务; + /// 否则返回一个将在信号设置时完成的任务。 + /// + public Task WaitOneAsync() + { + return this.WaitOneAsync(CancellationToken.None); + } + + /// + /// 返回一个可等待对象,用于异步获取下一个信号,并支持超时。 + /// + /// 等待超时时间。 + /// + /// 表示异步等待操作的, + /// 如果在超时时间内获得信号则返回,否则返回。 + /// + /// + /// 此方法在指定的超时时间内等待信号,如果超时则取消等待操作。 + /// 超时机制通过实现。 + /// + public async Task WaitOneAsync(TimeSpan millisecondsTimeout) + { + try + { + using (var timeoutSource = new CancellationTokenSource(millisecondsTimeout)) + { + await this.WaitOneAsync(timeoutSource.Token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return true; + } + } + catch (OperationCanceledException) + { + return false; + } + } + + /// + /// 返回一个可等待对象,用于异步获取下一个信号,并支持取消操作。 + /// + /// 用于取消等待操作的取消令牌,取消时会将调用方从等待队列中移除。 + /// 表示异步等待操作的 + /// 被取消时抛出。 + /// + /// 如果当前事件已处于信号状态,则立即返回已完成的任务; + /// 否则将调用方加入等待队列,并返回一个将在信号设置时完成的任务。 + /// 如果取消令牌被触发,等待者将从队列中移除并抛出。 + /// + public Task WaitOneAsync(CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + + lock (this.signalAwaiters) + { + if (this.signaled) + { + this.signaled = false; + return Task.CompletedTask; + } + else + { + var waiter = new WaiterCompletionSource(this, this.allowInliningAwaiters, cancellationToken); + if (cancellationToken.IsCancellationRequested) + { + waiter.TrySetCanceled(cancellationToken); + } + else + { + this.signalAwaiters.Enqueue(waiter); + } + + return waiter.Task; + } + } + } + + /// + /// 解除阻塞一个等待者,或者如果没有等待者则设置信号,使下一个等待者可以立即继续执行。 + /// + /// + /// 如果有等待者在队列中,则唤醒队列中的第一个等待者; + /// 如果没有等待者且事件未处于信号状态,则设置信号状态,使下一个调用的等待者可以立即继续。 + /// 每次调用此方法只会唤醒一个等待者,符合自动重置事件的语义。 + /// + public void Set() + { + WaiterCompletionSource toRelease = null; + lock (this.signalAwaiters) + { + if (this.signalAwaiters.Count > 0) + { + toRelease = this.signalAwaiters.Dequeue(); + } + else if (!this.signaled) + { + this.signaled = true; + } + } + + if (toRelease is not null) + { + toRelease.Registration.Dispose(); + toRelease.TrySetResult(default); + } + } + + /// + /// 解除阻塞所有当前等待的等待者。 + /// + /// + /// 此方法会唤醒队列中的所有等待者,与标准的自动重置事件语义不同。 + /// 通常用于需要同时唤醒所有等待者的特殊场景,如应用程序关闭时的清理操作。 + /// + public void SetAll() + { + lock (this.signalAwaiters) + { + for (var i = 0; i < this.signalAwaiters.Count; i++) + { + this.Set(); + } + } + } + + /// + /// Responds to cancellation requests by removing the request from the waiter queue. + /// + /// The passed in to the method. + private void OnCancellationRequest(object state) + { + var tcs = (WaiterCompletionSource)state; + bool removed; + lock (this.signalAwaiters) + { + removed = this.signalAwaiters.RemoveMidQueue(tcs); + } + + // We only cancel the task if we removed it from the queue. + // If it wasn't in the queue, either it has already been signaled + // or it hasn't even been added to the queue yet. If the latter, + // the Task will be canceled later so long as the signal hasn't been awarded + // to this Task yet. + if (removed) + { + tcs.TrySetCanceled(tcs.CancellationToken); + } + } + + /// + /// Tracks someone waiting for a signal from the event. + /// + private class WaiterCompletionSource : TaskCompletionSourceWithoutInlining + { + /// + /// Initializes a new instance of the class. + /// + /// The event that is initializing this value. + /// to allow continuations to be inlined upon the completer's callstack. + /// The cancellation cancellationToken associated with the waiter. + internal WaiterCompletionSource(AsyncAutoResetEvent owner, bool allowInliningContinuations, CancellationToken cancellationToken) + : base(allowInliningContinuations) + { + this.CancellationToken = cancellationToken; + this.Registration = cancellationToken.Register(NullableHelpers.AsNullableArgAction(owner.onCancellationRequestHandler), this); + } + + /// + /// Gets the provided by the waiter. + /// + internal CancellationToken CancellationToken { get; private set; } + + /// + /// Gets the registration to dispose of when the waiter receives their event. + /// + internal CancellationTokenRegistration Registration { get; private set; } + } +} diff --git a/src/TouchSocket.Core/Threading/AsyncExchange.cs b/src/TouchSocket.Core/Threading/AsyncExchange.cs new file mode 100644 index 000000000..21834d608 --- /dev/null +++ b/src/TouchSocket.Core/Threading/AsyncExchange.cs @@ -0,0 +1,258 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.Runtime.CompilerServices; +using System.Threading.Tasks.Sources; + +namespace TouchSocket.Core; + +/// +/// 精简版线程安全单槽异步交接(单生产者 + 单消费者,不支持并发写队列): +/// 写:一次只能有一个未消费数据,写调用返回的任务在该数据被读取并 Dispose 后完成。 +/// 读:无数据则挂起,得到 ReadLease 后需 Dispose 触发写端完成。Complete 后拒绝新写;若无数据则后续 Read 返回完成租约。 +/// +public sealed class AsyncExchange : IValueTaskSource>, IValueTaskSource +{ + private readonly Lock m_lock = new(); + private bool m_completed; + private bool m_hasItem; + private T m_item; + + private CancellationTokenRegistration m_readerCancelReg; + private ManualResetValueTaskSourceCore> m_readerCore = new() { RunContinuationsAsynchronously = true }; + private bool m_readerWaiting; + + private CancellationTokenRegistration m_writerCancelReg; + private ManualResetValueTaskSourceCore m_writerCore = new() { RunContinuationsAsynchronously = true }; + private bool m_writerWaiting; + + private static readonly Action s_cancelReader = static s => ((AsyncExchange)s!).CancelReader(); + private static readonly Action s_cancelWriter = static s => ((AsyncExchange)s!).CancelWriter(); + + /// + /// 获取当前是否已完成(即已调用 ,且没有未消费数据和挂起的读写操作)。 + /// + public bool IsCompleted + { + get + { + lock (this.m_lock) + { + return this.m_completed && !this.m_hasItem && !this.m_readerWaiting && !this.m_writerWaiting; + } + } + } + + /// + /// 标记当前交接已完成。调用后不再接受新的写入请求, + /// 若当前没有未消费数据且有挂起的读取操作,则立即完成该读取操作。 + /// + public void Complete() + { + lock (this.m_lock) + { + if (this.m_completed) + { + return; + } + this.m_completed = true; + if (!this.m_hasItem && this.m_readerWaiting) + { + this.m_readerWaiting = false; + this.m_readerCore.SetResult(this.CreateReadLease(default, true)); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ReadLease CreateReadLease(T value, bool isCompleted) + { + return new ReadLease(this.ReleaseAfterRead, value, isCompleted); + } + + /// + /// 异步读取数据。如果当前有可用数据则立即返回,否则挂起等待数据写入或交接完成。 + /// 返回的 需在读取后调用 以释放资源并通知写端完成。 + /// 若已完成交接,则返回已完成的租约。 + /// + /// 用于取消等待操作的 。 + /// 表示异步读取操作的 + public ValueTask> ReadAsync(CancellationToken cancellationToken = default) + { + lock (this.m_lock) + { + cancellationToken.ThrowIfCancellationRequested(); + if (this.m_hasItem) + { + return new ValueTask>(this.CreateReadLease(this.m_item, false)); + } + if (this.m_completed) + { + return new ValueTask>(this.CreateReadLease(default!, true)); + } + if (this.m_readerWaiting) + { + throw new InvalidOperationException("A reader is already pending."); + } + this.m_readerCore.Reset(); + this.m_readerWaiting = true; + if (cancellationToken.CanBeCanceled) + { + this.m_readerCancelReg.Dispose(); +#if NET6_0_OR_GREATER + this.m_readerCancelReg = cancellationToken.UnsafeRegister(s_cancelReader, this); +#else + this.m_readerCancelReg = cancellationToken.Register(s_cancelReader, this); +#endif + } + return new ValueTask>(this, this.m_readerCore.Version); + } + } + + /// + /// 异步写入数据。如果当前有未消费数据或有挂起的写操作,则抛出异常; + /// 否则将数据写入并挂起等待读取端消费,消费后写操作完成。 + /// 若已完成交接,则抛出异常。 + /// + /// 要写入的数据。 + /// 用于取消等待操作的 。 + /// 表示异步写入操作的 + public ValueTask WriteAsync(T value, CancellationToken cancellationToken = default) + { + lock (this.m_lock) + { + cancellationToken.ThrowIfCancellationRequested(); + if (this.m_completed) + { + throw new InvalidOperationException("Completed; cannot write."); + } + if (this.m_hasItem || this.m_writerWaiting) + { + throw new InvalidOperationException("Previous write not yet completed."); + } + this.m_item = value; + this.m_hasItem = true; + if (this.m_readerWaiting) + { + this.m_readerWaiting = false; + this.m_readerCore.SetResult(this.CreateReadLease(this.m_item, false)); + } + this.m_writerCore.Reset(); + this.m_writerWaiting = true; + if (cancellationToken.CanBeCanceled) + { + this.m_writerCancelReg.Dispose(); +#if NET6_0_OR_GREATER + this.m_writerCancelReg = cancellationToken.UnsafeRegister(s_cancelWriter, this); +#else + this.m_writerCancelReg = cancellationToken.Register(s_cancelWriter, this); +#endif + } + return new ValueTask(this, this.m_writerCore.Version); + } + } + + /// + /// 重置当前交接状态。仅在已完成且无未消费数据和挂起操作时可调用, + /// 否则会抛出异常。重置后可重新开始新的交接流程。 + /// + public void Reset() + { + lock (this.m_lock) + { + if (!this.m_completed) + { + throw new InvalidOperationException("Not completed."); + } + if (this.m_hasItem || this.m_readerWaiting || this.m_writerWaiting) + { + throw new InvalidOperationException("Pending state exists."); + } + this.m_item = default!; + this.m_completed = false; + } + } + + private void ReleaseAfterRead() + { + var completeWriter = false; + lock (this.m_lock) + { + if (!this.m_hasItem) + { + return; + } + this.m_hasItem = false; + this.m_item = default!; + if (this.m_writerWaiting) + { + this.m_writerWaiting = false; + completeWriter = true; + } + if (this.m_completed && this.m_readerWaiting) + { + this.m_readerWaiting = false; + this.m_readerCore.SetResult(this.CreateReadLease(default!, true)); + } + } + if (completeWriter) + { + this.m_writerCore.SetResult(default); + } + } + + private void CancelReader() + { + lock (this.m_lock) + { + if (!this.m_readerWaiting) + { + return; + } + this.m_readerWaiting = false; + this.m_readerCore.SetException(new OperationCanceledException()); + } + } + private void CancelWriter() + { + lock (this.m_lock) + { + if (!this.m_writerWaiting) + { + return; + } + this.m_writerWaiting = false; + this.m_writerCore.SetException(new OperationCanceledException()); + } + } + + #region IValueTaskSource + ReadLease IValueTaskSource>.GetResult(short cancellationToken) + { + var r = this.m_readerCore.GetResult(cancellationToken); + this.m_readerCancelReg.Dispose(); + return r; + } + void IValueTaskSource.GetResult(short cancellationToken) + { + this.m_writerCore.GetResult(cancellationToken); + this.m_writerCancelReg.Dispose(); + } + ValueTaskSourceStatus IValueTaskSource>.GetStatus(short cancellationToken) => this.m_readerCore.GetStatus(cancellationToken); + ValueTaskSourceStatus IValueTaskSource.GetStatus(short cancellationToken) => this.m_writerCore.GetStatus(cancellationToken); + void IValueTaskSource>.OnCompleted(Action continuation, object state, short cancellationToken, ValueTaskSourceOnCompletedFlags flags) + => this.m_readerCore.OnCompleted(continuation, state, cancellationToken, flags); + void IValueTaskSource.OnCompleted(Action continuation, object state, short cancellationToken, ValueTaskSourceOnCompletedFlags flags) + => this.m_writerCore.OnCompleted(continuation, state, cancellationToken, flags); + #endregion + +} \ No newline at end of file diff --git a/src/TouchSocket.Core/Threading/AsyncManualResetEvent.cs b/src/TouchSocket.Core/Threading/AsyncManualResetEvent.cs index 6a4d53d74..68e6d9fe1 100644 --- a/src/TouchSocket.Core/Threading/AsyncManualResetEvent.cs +++ b/src/TouchSocket.Core/Threading/AsyncManualResetEvent.cs @@ -1,30 +1,268 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.ComponentModel; +using System.Diagnostics; +using System.Runtime.CompilerServices; namespace TouchSocket.Core; /// -/// 一个手动恢复的异步通知事件 +/// A flavor of that can be asynchronously awaited on. /// -public class AsyncManualResetEvent : AsyncResetEvent +/// +/// 此代码摘抄自微软VS相关库。 +/// 这是一个可异步等待的手动重置事件实现,提供了与类似的功能, +/// 但支持异步操作模式。允许多个任务异步等待事件的设置,并在事件被设置时唤醒所有等待的任务。 +/// +[DebuggerDisplay("Signaled: {IsSet}")] +public class AsyncManualResetEvent { /// - /// 一个手动恢复的异步通知事件 + /// Whether the task completion source should allow executing continuations synchronously. /// - public AsyncManualResetEvent() : this(false) { } + private readonly bool m_allowInliningAwaiters; /// - /// 一个手动恢复的异步通知事件 + /// The object to lock when accessing fields. /// - /// - public AsyncManualResetEvent(bool set) : base(set, false) { } -} \ No newline at end of file + private readonly Lock m_syncObject = new(); + + /// + /// The source of the task to return from . + /// + /// + /// This should not need the volatile modifier because it is + /// always accessed within a lock. + /// + private TaskCompletionSourceWithoutInlining m_taskCompletionSource; + + /// + /// A flag indicating whether the event is signaled. + /// When this is set to true, it's possible that + /// .Task.IsCompleted is still false + /// if the completion has been scheduled asynchronously. + /// Thus, this field should be the definitive answer as to whether + /// the event is signaled because it is synchronously updated. + /// + /// + /// This should not need the volatile modifier because it is + /// always accessed within a lock. + /// + private bool m_isSet; + + /// + /// 初始化类的新实例。 + /// + /// 指示事件是否应初始处于已设置状态的值。 + /// + /// 指示是否允许调用者的延续在调用的线程上执行的值, + /// 在调用返回之前执行。如果此值为,则调用者不应持有私有锁以避免死锁。 + /// 当为时,从返回的任务可能在返回给其调用者时尚未完全转换到其完成状态。 + /// + public AsyncManualResetEvent(bool initialState = false, bool allowInliningAwaiters = false) + { + this.m_allowInliningAwaiters = allowInliningAwaiters; + + this.m_taskCompletionSource = this.CreateTaskSource(); + this.m_isSet = initialState; + if (initialState) + { + this.m_taskCompletionSource.SetResult(EmptyStruct.Instance); + } + } + + /// + /// 获取一个值,指示事件当前是否处于已设置状态。 + /// + /// 如果事件已设置,则为;否则为 + public bool IsSet + { + get + { + lock (this.m_syncObject) + { + return this.m_isSet; + } + } + } + + /// + /// 返回一个在此事件被设置时完成的任务。 + /// + /// 表示异步等待操作的 + /// + /// 此方法返回一个任务,当事件被设置时该任务将完成。 + /// 如果事件已经被设置,则返回一个已完成的任务。 + /// + public Task WaitOneAsync() + { + lock (this.m_syncObject) + { + return this.m_taskCompletionSource.Task; + } + } + + /// + /// 返回一个在此事件被设置或超时时完成的任务。 + /// + /// 等待的超时时间。 + /// + /// 表示异步等待操作的。 + /// 如果事件在超时前被设置,则返回;如果超时,则返回。 + /// + /// + /// 此方法在指定的超时时间内等待事件被设置。 + /// 如果在超时前事件被设置,则返回;否则返回。 + /// + public async ValueTask WaitOneAsync(TimeSpan millisecondsTimeout) + { + try + { + using (var timeoutSource = new CancellationTokenSource(millisecondsTimeout)) + { + await this.WaitOneAsync(timeoutSource.Token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return true; + } + } + catch (OperationCanceledException) + { + return false; + } + } + + /// + /// 返回一个在此事件被设置时完成的任务。 + /// + /// 取消令牌。 + /// 一个在事件被设置时完成的任务,或者在被取消时取消。 + /// + /// 此方法返回一个任务,当事件被设置时该任务将完成。 + /// 如果提供的取消令牌被取消,则返回的任务也将被取消。 + /// + public Task WaitOneAsync(CancellationToken cancellationToken) => this.WaitOneAsync().WithCancellation(cancellationToken); + + private Task SetAsync() + { + TaskCompletionSourceWithoutInlining tcs = null; + var transitionRequired = false; + lock (this.m_syncObject) + { + transitionRequired = !this.m_isSet; + tcs = this.m_taskCompletionSource; + this.m_isSet = true; + } + + // Snap the Task that is exposed to the outside so we return that one. + // Once we complete the TaskCompletionSourceWithoutInlinining's task, + // the Task property will return the inner Task. + // SetAsync should return the same Task that WaitAsync callers would have observed previously. + Task result = tcs.Task; + + if (transitionRequired) + { + tcs.TrySetResult(default(EmptyStruct)); + } + + return result; + } + + /// + /// 设置此事件以解除对调用者的阻塞。 + /// + /// + /// 此方法将事件设置为已设置状态,使所有等待此事件的任务完成。 + /// 一旦调用此方法,所有当前和将来的调用都将立即返回已完成的任务, + /// 直到调用方法。 + /// + public void Set() + { + this.SetAsync(); + } + + /// + /// 将此事件重置为将阻塞调用者的状态。 + /// + /// + /// 此方法将事件重置为未设置状态,使后续的调用将等待直到事件再次被设置。 + /// 如果事件已经处于未设置状态,则此方法不执行任何操作。 + /// + public void Reset() + { + lock (this.m_syncObject) + { + if (this.m_isSet) + { + this.m_taskCompletionSource = this.CreateTaskSource(); + this.m_isSet = false; + } + } + } + + private Task PulseAllAsync() + { + TaskCompletionSourceWithoutInlining tcs = null; + lock (this.m_syncObject) + { + // Atomically replace the completion source with a new, uncompleted source + // while capturing the previous one so we can complete it. + // This ensures that we don't leave a gap in time where WaitAsync() will + // continue to return completed Tasks due to a Pulse method which should + // execute instantaneously. + tcs = this.m_taskCompletionSource; + this.m_taskCompletionSource = this.CreateTaskSource(); + this.m_isSet = false; + } + + // Snap the Task that is exposed to the outside so we return that one. + // Once we complete the TaskCompletionSourceWithoutInlinining's task, + // the Task property will return the inner Task. + // PulseAllAsync should return the same Task that WaitAsync callers would have observed previously. + Task result = tcs.Task; + tcs.TrySetResult(default(EmptyStruct)); + return result; + } + + /// + /// 设置并立即重置此事件,允许所有当前等待者解除阻塞。 + /// + /// + /// 此方法执行一个脉冲操作:瞬间设置事件以释放所有当前等待的任务,然后立即将事件重置为未设置状态。 + /// 这确保只有在调用此方法时正在等待的任务会被释放,而在脉冲操作完成后开始等待的新任务将继续阻塞。 + /// + public void PulseAll() + { + this.PulseAllAsync(); + } + + /// + /// 获取一个在此事件被设置时完成的等待器。 + /// + /// 一个,可用于异步等待事件。 + /// + /// 此方法使能够直接在await表达式中使用。 + /// 它是编辑器隐藏的方法,通常不应直接调用。 + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public TaskAwaiter GetAwaiter() + { + return this.WaitOneAsync().GetAwaiter(); + } + + /// + /// Creates a new TaskCompletionSource to represent an unset event. + /// + private TaskCompletionSourceWithoutInlining CreateTaskSource() + { + return new TaskCompletionSourceWithoutInlining(this.m_allowInliningAwaiters); + } +} diff --git a/src/TouchSocket.Core/Threading/AsyncResetEvent.cs b/src/TouchSocket.Core/Threading/AsyncResetEvent.cs deleted file mode 100644 index 4f6bac670..000000000 --- a/src/TouchSocket.Core/Threading/AsyncResetEvent.cs +++ /dev/null @@ -1,201 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace TouchSocket.Core; - -/// -/// 异步AsyncResetEvent -/// 能够创建一个手动Reset或者自动Reset. -/// -public class AsyncResetEvent : DisposableObject -{ - private readonly bool m_autoReset; - - private readonly Lock m_locker = new Lock(); - - private readonly Queue> m_waitQueue = new Queue>(); - - private volatile bool m_eventSet; - - /// - /// 创建一个异步AsyncResetEvent - /// - /// 是否包含初始信号 - /// 是否自动重置 - public AsyncResetEvent(bool initialState, bool autoReset) - { - this.m_eventSet = initialState; - this.m_autoReset = autoReset; - } - - /// - /// 异步等待设置此事件 - /// - public Task WaitOneAsync() - { - return this.WaitOneAsync(CancellationToken.None); - } - - /// - ///异步等待指定时间 - /// - /// 超时时间 - public async Task WaitOneAsync(TimeSpan millisecondsTimeout) - { - try - { - using (var timeoutSource = new CancellationTokenSource(millisecondsTimeout)) - { - await this.WaitOneAsync(timeoutSource.Token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - return true; - } - } - catch (OperationCanceledException) - { - return false; - } - } - - /// - /// 异步等待可取消 - /// - /// 可取消令箭 - public Task WaitOneAsync(CancellationToken cancellationToken) - { - if (cancellationToken.IsCancellationRequested) - { - return EasyTask.FromCanceled(cancellationToken); - } - - lock (this.m_locker) - { - if (this.DisposedValue) - { - return EasyTask.CompletedTask; - } - - if (this.m_eventSet) - { - if (this.m_autoReset) - { - this.m_eventSet = false; - } - - return EasyTask.CompletedTask; - } - else - { -#if NET45 - var completionSource = new TaskCompletionSource(); -#else - var completionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); -#endif - - var registration = cancellationToken.Register(() => - { - lock (this.m_locker) - { -#if NET45 - completionSource.TrySetCanceled(); -#else - completionSource.TrySetCanceled(cancellationToken); -#endif - } - }, useSynchronizationContext: false); - - this.m_waitQueue.Enqueue(completionSource); - - completionSource.Task.ContinueWith( - (_) => registration.Dispose(), - CancellationToken.None, - TaskContinuationOptions.ExecuteSynchronously, - TaskScheduler.Default - ); - - return completionSource.Task; - } - } - } - - /// - /// 重置 - /// - public bool Reset() - { - lock (this.m_locker) - { - this.m_eventSet = false; - return true; - } - } - - /// - /// 设置信号 - /// - public bool Set() - { - lock (this.m_locker) - { - while (this.m_waitQueue.Count > 0) - { - var toRelease = this.m_waitQueue.Dequeue(); - - if (toRelease.Task.IsCompleted) - { - continue; - } - - var b = toRelease.TrySetResult(true); - - if (this.m_autoReset) - { - return b; - } - } - - this.m_eventSet = true; - return false; - } - } - - /// - protected override void Dispose(bool disposing) - { - if (this.DisposedValue) - { - return; - } - - if (disposing) - { - while (true) - { - lock (this.m_locker) - { - if (this.m_waitQueue.Count == 0) - { - break; - } - } - - this.Set(); - } - - } - base.Dispose(disposing); - } -} \ No newline at end of file diff --git a/src/TouchSocket.Core/Threading/EasyTask.cs b/src/TouchSocket.Core/Threading/EasyTask.cs index 48496ea1f..5e0034964 100644 --- a/src/TouchSocket.Core/Threading/EasyTask.cs +++ b/src/TouchSocket.Core/Threading/EasyTask.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading; -using System.Threading.Tasks; - namespace TouchSocket.Core; @@ -26,7 +23,7 @@ public static partial class EasyTask /// static EasyTask() { -#if NET45_OR_GREATER +#if NET462_OR_GREATER // 在 .NET 4.5 或更高版本中,直接使用 Task.FromResult 方法创建已完成的任务。 CompletedTask = Task.FromResult(0); #else @@ -47,15 +44,7 @@ public static partial class EasyTask /// 一个表示已取消任务的 Task 对象。 public static Task FromCanceled(CancellationToken cancellationToken) { -#if NET45 - // 在 .NET 4.5 版本中,使用 TaskCompletionSource 来创建并标记一个任务为已取消。 - var tcs = new TaskCompletionSource(); - tcs.TrySetCanceled(); - return tcs.Task; -#else - // 在 .NET 4.5 以上版本中,直接使用 Task.FromCanceled 方法创建已取消的任务。 return Task.FromCanceled(cancellationToken); -#endif } /// @@ -66,15 +55,7 @@ public static partial class EasyTask /// 一个表示已取消任务的 Task 对象,带有指定类型的结果。 public static Task FromCanceled(CancellationToken cancellationToken) { -#if NET45 - // 在 .NET 4.5 版本中,使用 TaskCompletionSource 来创建并标记一个带有结果类型的已取消任务。 - var tcs = new TaskCompletionSource(); - tcs.TrySetCanceled(); - return tcs.Task; -#else - // 在 .NET 4.5 以上版本中,直接使用 Task.FromCanceled 方法创建带有结果类型的已取消任务。 return Task.FromCanceled(cancellationToken); -#endif } /// diff --git a/src/TouchSocket.Core/Threading/EasyTask_Run.cs b/src/TouchSocket.Core/Threading/EasyTask_Run.cs index 062fa3c65..4ac3c292b 100644 --- a/src/TouchSocket.Core/Threading/EasyTask_Run.cs +++ b/src/TouchSocket.Core/Threading/EasyTask_Run.cs @@ -10,43 +10,126 @@ // 感谢您的下载和使用 // ------------------------------------------------------------------------------ -using System; -using System.Collections; -using System.Threading; -using System.Threading.Tasks; using TouchSocket.Resources; namespace TouchSocket.Core; public static partial class EasyTask { - /// - /// 运行一个带有状态和取消令牌的异步方法。 - /// - /// 状态的类型。 - /// 要运行的异步方法。 - /// 传递给方法的状态。 - /// 取消令牌。 - /// 表示异步操作的任务。 - public static Task Run(Func func, T status, CancellationToken ct = default) - { - ThrowHelper.ThrowArgumentNullExceptionIf(func, nameof(func)); + #region SafeRun - return Task.Run(() => func(status, ct), ct); + /// + /// 安全地运行一个异步方法。 + /// + /// 要运行的异步方法。 + /// 表示异步操作的任务。 + public static async Task SafeRun(Func func) + { + if (func is null) + { + return Result.FromFail(TouchSocketCoreResource.ArgumentIsNull.Format(nameof(func))); + } + + try + { + await func().ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return Result.Success; + } + catch (Exception ex) + { + return Result.FromException(ex); + } } /// - /// 运行一个带有状态的异步方法。 + /// 安全地运行一个支持取消令牌的异步方法。 /// - /// 状态的类型。 /// 要运行的异步方法。 - /// 传递给方法的状态。 /// 取消令牌。 /// 表示异步操作的任务。 - public static Task Run(Func func, T status, CancellationToken ct = default) + public static async Task SafeRun(Func func, CancellationToken ct) { - ThrowHelper.ThrowArgumentNullExceptionIf(func, nameof(func)); - return Task.Run(() => func(status), ct); + if (func is null) + { + return Result.FromFail(TouchSocketCoreResource.ArgumentIsNull.Format(nameof(func))); + } + + if (ct.IsCancellationRequested) + { + return Result.Canceled; + } + + try + { + await func(ct).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return Result.Success; + } + catch (OperationCanceledException) when (ct.IsCancellationRequested) + { + return Result.Canceled; + } + catch (Exception ex) + { + return Result.FromException(ex); + } + } + + /// + /// 安全地运行一个带有返回值的异步方法。 + /// + /// 返回值的类型。 + /// 要运行的异步方法。 + /// 表示异步操作的任务。 + public static async Task> SafeRun(Func> func) + { + if (func is null) + { + return new Result(ResultCode.Failure, TouchSocketCoreResource.ArgumentIsNull.Format(nameof(func))); + } + + try + { + var result = await func().ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return new Result(result); + } + catch (Exception ex) + { + return Result.FromException(ex); + } + } + + /// + /// 安全地运行一个支持取消令牌且带有返回值的异步方法。 + /// + /// 返回值的类型。 + /// 要运行的异步方法。 + /// 取消令牌。 + /// 表示异步操作的任务。 + public static async Task> SafeRun(Func> func, CancellationToken ct) + { + if (func is null) + { + return new Result(ResultCode.Failure, TouchSocketCoreResource.ArgumentIsNull.Format(nameof(func))); + } + + if (ct.IsCancellationRequested) + { + return new Result(ResultCode.Canceled, Result.Canceled.Message); + } + + try + { + var result = await func(ct).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return new Result(result); + } + catch (OperationCanceledException) when (ct.IsCancellationRequested) + { + return new Result(ResultCode.Canceled, Result.Canceled.Message); + } + catch (Exception ex) + { + return Result.FromException(ex); + } } /// @@ -55,43 +138,266 @@ public static partial class EasyTask /// 状态的类型。 /// 要运行的异步方法。 /// 传递给方法的状态。 - /// 取消令牌。 /// 表示异步操作的任务。 - public static async Task SafeRun(Func func, T1 status, CancellationToken ct = default) + public static async Task SafeRun(Func func, T1 status) { if (func is null) { - return; - } - if (ct.IsCancellationRequested) - { - return; + return Result.FromFail(TouchSocketCoreResource.ArgumentIsNull.Format(nameof(func))); } + try { - await Task.Run(() => func(status), ct).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await func(status).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return Result.Success; } - catch + catch (Exception ex) { + return Result.FromException(ex); } } + /// + /// 安全地运行一个支持取消令牌且带有状态的异步方法。 + /// + /// 状态的类型。 + /// 要运行的异步方法。 + /// 传递给方法的状态。 + /// 取消令牌。 + /// 表示异步操作的任务。 + public static async Task SafeRun(Func func, T1 status, CancellationToken ct) + { + if (func is null) + { + return Result.FromFail(TouchSocketCoreResource.ArgumentIsNull.Format(nameof(func))); + } + + if (ct.IsCancellationRequested) + { + return Result.Canceled; + } + + try + { + await func(status, ct).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return Result.Success; + } + catch (OperationCanceledException) when (ct.IsCancellationRequested) + { + return Result.Canceled; + } + catch (Exception ex) + { + return Result.FromException(ex); + } + } + + /// + /// 安全地运行一个带有状态和返回值的异步方法。 + /// + /// 状态的类型。 + /// 返回值的类型。 + /// 要运行的异步方法。 + /// 传递给方法的状态。 + /// 表示异步操作的任务。 + public static async Task> SafeRun(Func> func, T1 status) + { + if (func is null) + { + return new Result(ResultCode.Failure, TouchSocketCoreResource.ArgumentIsNull.Format(nameof(func))); + } + + try + { + var result = await func(status).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return new Result(result); + } + catch (Exception ex) + { + return Result.FromException(ex); + } + } + + /// + /// 安全地运行一个支持取消令牌且带有状态和返回值的异步方法。 + /// + /// 状态的类型。 + /// 返回值的类型。 + /// 要运行的异步方法。 + /// 传递给方法的状态。 + /// 取消令牌。 + /// 表示异步操作的任务。 + public static async Task> SafeRun(Func> func, T1 status, CancellationToken ct) + { + if (func is null) + { + return new Result(ResultCode.Failure, TouchSocketCoreResource.ArgumentIsNull.Format(nameof(func))); + } + + if (ct.IsCancellationRequested) + { + return new Result(ResultCode.Canceled, Result.Canceled.Message); + } + + try + { + var result = await func(status, ct).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return new Result(result); + } + catch (OperationCanceledException) when (ct.IsCancellationRequested) + { + return new Result(ResultCode.Canceled, Result.Canceled.Message); + } + catch (Exception ex) + { + return Result.FromException(ex); + } + } + + /// + /// 安全地运行一个带有两个状态的异步方法。 + /// + /// 第一个状态的类型。 + /// 第二个状态的类型。 + /// 要运行的异步方法。 + /// 传递给方法的第一个状态。 + /// 传递给方法的第二个状态。 + /// 表示异步操作的任务。 + public static async Task SafeRun(Func func, T1 status1, T2 status2) + { + if (func is null) + { + return Result.FromFail(TouchSocketCoreResource.ArgumentIsNull.Format(nameof(func))); + } + + try + { + await func(status1, status2).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return Result.Success; + } + catch (Exception ex) + { + return Result.FromException(ex); + } + } + + /// + /// 安全地运行一个支持取消令牌且带有两个状态的异步方法。 + /// + /// 第一个状态的类型。 + /// 第二个状态的类型。 + /// 要运行的异步方法。 + /// 传递给方法的第一个状态。 + /// 传递给方法的第二个状态。 + /// 取消令牌。 + /// 表示异步操作的任务。 + public static async Task SafeRun(Func func, T1 status1, T2 status2, CancellationToken ct) + { + if (func is null) + { + return Result.FromFail(TouchSocketCoreResource.ArgumentIsNull.Format(nameof(func))); + } + + if (ct.IsCancellationRequested) + { + return Result.Canceled; + } + + try + { + await func(status1, status2, ct).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return Result.Success; + } + catch (OperationCanceledException) when (ct.IsCancellationRequested) + { + return Result.Canceled; + } + catch (Exception ex) + { + return Result.FromException(ex); + } + } + + /// + /// 安全地运行一个带有两个状态和返回值的异步方法。 + /// + /// 第一个状态的类型。 + /// 第二个状态的类型。 + /// 返回值的类型。 + /// 要运行的异步方法。 + /// 传递给方法的第一个状态。 + /// 传递给方法的第二个状态。 + /// 表示异步操作的任务。 + public static async Task> SafeRun(Func> func, T1 status1, T2 status2) + { + if (func is null) + { + return new Result(ResultCode.Failure, TouchSocketCoreResource.ArgumentIsNull.Format(nameof(func))); + } + + try + { + var result = await func(status1, status2).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return new Result(result); + } + catch (Exception ex) + { + return Result.FromException(ex); + } + } + + /// + /// 安全地运行一个支持取消令牌且带有两个状态和返回值的异步方法。 + /// + /// 第一个状态的类型。 + /// 第二个状态的类型。 + /// 返回值的类型。 + /// 要运行的异步方法。 + /// 传递给方法的第一个状态。 + /// 传递给方法的第二个状态。 + /// 取消令牌。 + /// 表示异步操作的任务。 + public static async Task> SafeRun(Func> func, T1 status1, T2 status2, CancellationToken ct) + { + if (func is null) + { + return new Result(ResultCode.Failure, TouchSocketCoreResource.ArgumentIsNull.Format(nameof(func))); + } + + if (ct.IsCancellationRequested) + { + return new Result(ResultCode.Canceled, Result.Canceled.Message); + } + + try + { + var result = await func(status1, status2, ct).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return new Result(result); + } + catch (OperationCanceledException) when (ct.IsCancellationRequested) + { + return new Result(ResultCode.Canceled, Result.Canceled.Message); + } + catch (Exception ex) + { + return Result.FromException(ex); + } + } + + #endregion SafeRun + /// /// 安全地等待一个任务完成。 /// /// 要等待的任务。 - /// 取消令牌。 /// 表示任务结果的 对象。 - public static async Task SafeWaitAsync(this Task task, CancellationToken ct = default) + public static async Task SafeWaitAsync(this Task task) { if (task is null) { return Result.FromFail(TouchSocketCoreResource.ArgumentIsNull.Format(nameof(task))); } - if (ct.IsCancellationRequested) - { - return Result.Canceled; - } + try { await task.ConfigureAwait(EasyTask.ContinueOnCapturedContext); @@ -108,123 +414,22 @@ public static partial class EasyTask /// /// 任务结果的类型。 /// 要等待的任务。 - /// 取消令牌。 /// 表示任务结果的 对象。 - public static async Task> SafeWaitAsync(this Task task, CancellationToken ct = default) + public static async Task> SafeWaitAsync(this Task task) { if (task is null) { - return Result.FromFail(TouchSocketCoreResource.ArgumentIsNull.Format(nameof(task))); - } - if (ct.IsCancellationRequested) - { - return Result.Canceled; + return new Result(ResultCode.Failure, TouchSocketCoreResource.ArgumentIsNull.Format(nameof(task))); } + try { - return new Result(await task.ConfigureAwait(EasyTask.ContinueOnCapturedContext)); + var result = await task.ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return new Result(result); } catch (Exception ex) { return Result.FromException(ex); } } - - /// - /// 安全地运行一个带有状态的异步方法。 - /// - /// 要运行的异步方法。 - /// 取消令牌。 - /// 表示异步操作的任务。 - public static async Task SafeRun(Func func, CancellationToken ct = default) - { - if (func is null) - { - return; - } - if (ct.IsCancellationRequested) - { - return; - } - try - { - await Task.Run(() => func(), ct).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - catch - { - } - } - - /// - /// 安全地运行一个带有两个状态的异步方法。 - /// - /// 第一个状态的类型。 - /// 第二个状态的类型。 - /// 要运行的异步方法。 - /// 传递给方法的第一个状态。 - /// 传递给方法的第二个状态。 - /// 取消令牌。 - /// 表示异步操作的任务。 - public static async Task SafeRun(Func func, T1 status1, T2 status2, CancellationToken ct = default) - { - if (func is null) - { - return; - } - if (ct.IsCancellationRequested) - { - return; - } - try - { - await Task.Run(() => func(status1, status2), ct).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - catch - { - } - } - - /// - /// 运行一个无状态的异步方法。 - /// - /// 要运行的异步方法。 - /// 取消令牌。 - /// 表示异步操作的任务。 - public static Task Run(Func func, CancellationToken ct = default) - { - ThrowHelper.ThrowArgumentNullExceptionIf(func, nameof(func)); - - return Task.Run(func, ct); - } - - /// - /// 运行一个带有状态的同步方法。 - /// - /// 状态的类型。 - /// 要运行的同步方法。 - /// 传递给方法的状态。 - /// 取消令牌。 - /// 表示异步操作的任务。 - public static Task Run(Action func, T status, CancellationToken ct = default) - { - ThrowHelper.ThrowArgumentNullExceptionIf(func, nameof(func)); - - return Task.Run(() => func(status), ct); - } - - /// - /// 运行一个带有状态和取消令牌的同步方法。 - /// - /// 状态的类型。 - /// 要运行的同步方法。 - /// 传递给方法的状态。 - /// 取消令牌。 - /// 表示异步操作的任务。 - public static Task Run(Action func, T status, CancellationToken ct = default) - { - ThrowHelper.ThrowArgumentNullExceptionIf(func, nameof(func)); - - return Task.Run(() => func(status, ct), ct); - } - } \ No newline at end of file diff --git a/src/TouchSocket.Core/Threading/EasyValueTask.cs b/src/TouchSocket.Core/Threading/EasyValueTask.cs index db7118fb1..10e77304c 100644 --- a/src/TouchSocket.Core/Threading/EasyValueTask.cs +++ b/src/TouchSocket.Core/Threading/EasyValueTask.cs @@ -10,15 +10,13 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; - namespace TouchSocket.Core; /// /// 定义了一个简化版本的ValueTask工具类。 /// 该类提供了一些静态方法来创建ValueTask对象,旨在优化性能并简化异步编程。 /// -public class EasyValueTask +public static class EasyValueTask { /// /// 类的静态构造函数,用于初始化静态字段。 @@ -50,4 +48,50 @@ public class EasyValueTask { return new ValueTask(result); } + + /// + /// 安全地等待一个任务完成。 + /// + /// 要等待的任务。 + /// 取消令牌。 + /// 表示任务结果的 对象。 + public static async Task SafeWaitAsync(this ValueTask task, CancellationToken ct = default) + { + if (ct.IsCancellationRequested) + { + return Result.Canceled; + } + try + { + await task.ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return Result.Success; + } + catch (Exception ex) + { + return Result.FromException(ex); + } + } + + /// + /// 安全地等待一个任务完成并返回结果。 + /// + /// 任务结果的类型。 + /// 要等待的任务。 + /// 取消令牌。 + /// 表示任务结果的 对象。 + public static async Task> SafeWaitAsync(this ValueTask task, CancellationToken ct = default) + { + if (ct.IsCancellationRequested) + { + return Result.Canceled; + } + try + { + return new Result(await task.ConfigureAwait(EasyTask.ContinueOnCapturedContext)); + } + catch (Exception ex) + { + return Result.FromException(ex); + } + } } \ No newline at end of file diff --git a/src/TouchSocket.Core/Threading/EmptyStruct.cs b/src/TouchSocket.Core/Threading/EmptyStruct.cs new file mode 100644 index 000000000..8cbcfa24a --- /dev/null +++ b/src/TouchSocket.Core/Threading/EmptyStruct.cs @@ -0,0 +1,21 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +namespace TouchSocket.Core; + +internal readonly struct EmptyStruct +{ + /// + /// Gets an instance of the empty struct. + /// + internal static EmptyStruct Instance => default; +} diff --git a/src/TouchSocket.Core/Threading/InternalUtilities.cs b/src/TouchSocket.Core/Threading/InternalUtilities.cs new file mode 100644 index 000000000..627252df9 --- /dev/null +++ b/src/TouchSocket.Core/Threading/InternalUtilities.cs @@ -0,0 +1,48 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +namespace TouchSocket.Core; + +internal static class InternalUtilities +{ + /// + /// Removes an element from the middle of a queue without disrupting the other elements. + /// + /// The element to remove. + /// The queue to modify. + /// The value to remove. + /// + /// If a value appears multiple times in the queue, only its first entry is removed. + /// + internal static bool RemoveMidQueue(this Queue queue, T valueToRemove) + where T : class + { + var originalCount = queue.Count; + var dequeueCounter = 0; + var found = false; + while (dequeueCounter < originalCount) + { + dequeueCounter++; + var dequeued = queue.Dequeue(); + if (!found && dequeued == valueToRemove) + { // only find 1 match + found = true; + } + else + { + queue.Enqueue(dequeued); + } + } + + return found; + } +} diff --git a/src/TouchSocket.Core/Threading/ManualResetValueTaskSourceCore.cs b/src/TouchSocket.Core/Threading/ManualResetValueTaskSourceCore.cs deleted file mode 100644 index cbecea450..000000000 --- a/src/TouchSocket.Core/Threading/ManualResetValueTaskSourceCore.cs +++ /dev/null @@ -1,277 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -#if !(NET481_OR_GREATER || NET6_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER) - -using System; -using System.Diagnostics; -using System.Runtime.ExceptionServices; -using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; -using System.Threading.Tasks.Sources; - -namespace TouchSocket.Core; - -/// Provides the core logic for implementing a manual-reset or . -/// -[StructLayout(LayoutKind.Auto)] -public struct ManualResetValueTaskSourceCore -{ - /// - /// The callback to invoke when the operation completes if was called before the operation completed, - /// or if the operation completed before a callback was supplied, - /// or null if a callback hasn't yet been provided and the operation hasn't yet completed. - /// - private Action _continuation; - /// State to pass to . - private object _continuationState; - /// to flow to the callback, or null if no flowing is required. - private ExecutionContext _executionContext; - /// - /// A "captured" or with which to invoke the callback, - /// or null if no special context is required. - /// - private object _capturedContext; - /// Whether the current operation has completed. - private bool _completed; - /// The result with which the operation succeeded, or the default value if it hasn't yet completed or failed. - private TResult _result; - /// The exception with which the operation failed, or null if it hasn't yet completed or completed successfully. - private ExceptionDispatchInfo _error; - /// The current version of this value, used to help prevent misuse. - private short _version; - - /// Gets or sets whether to force continuations to run asynchronously. - /// Continuations may run asynchronously if this is false, but they'll never run synchronously if this is true. - public bool RunContinuationsAsynchronously { get; set; } - - /// Resets to prepare for the next operation. - public void Reset() - { - // Reset/update state for the next use/await of this instance. - this._version++; - this._completed = false; - this._result = default; - this._error = null; - this._executionContext = null; - this._capturedContext = null; - this._continuation = null; - this._continuationState = null; - } - - /// Completes with a successful result. - /// The result. - public void SetResult(TResult result) - { - this._result = result; - this.SignalCompletion(); - } - - /// Complets with an error. - /// - public void SetException(Exception error) - { - this._error = ExceptionDispatchInfo.Capture(error); - this.SignalCompletion(); - } - - /// Gets the operation version. - public short Version => this._version; - - /// Gets the status of the operation. - /// Opaque value that was provided to the 's constructor. - public ValueTaskSourceStatus GetStatus(short token) - { - this.ValidateToken(token); - return - this._continuation == null || !this._completed ? ValueTaskSourceStatus.Pending : - this._error == null ? ValueTaskSourceStatus.Succeeded : - this._error.SourceException is OperationCanceledException ? ValueTaskSourceStatus.Canceled : - ValueTaskSourceStatus.Faulted; - } - - /// Gets the result of the operation. - /// Opaque value that was provided to the 's constructor. - public TResult GetResult(short token) - { - this.ValidateToken(token); - if (!this._completed) - { - throw new InvalidOperationException(); - } - - this._error?.Throw(); - return this._result; - } - - /// Schedules the continuation action for this operation. - /// The continuation to invoke when the operation has completed. - /// The state object to pass to when it's invoked. - /// Opaque value that was provided to the 's constructor. - /// The flags describing the behavior of the continuation. - public void OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) - { - if (continuation is null) - { - throw new ArgumentNullException(nameof(continuation)); - } - this.ValidateToken(token); - - if ((flags & ValueTaskSourceOnCompletedFlags.FlowExecutionContext) != 0) - { - this._executionContext = ExecutionContext.Capture(); - } - - if ((flags & ValueTaskSourceOnCompletedFlags.UseSchedulingContext) != 0) - { - var sc = SynchronizationContext.Current; - if (sc != null && sc.GetType() != typeof(SynchronizationContext)) - { - this._capturedContext = sc; - } - else - { - var ts = TaskScheduler.Current; - if (ts != TaskScheduler.Default) - { - this._capturedContext = ts; - } - } - } - - // We need to set the continuation state before we swap in the delegate, so that - // if there's a race between this and SetResult/Exception and SetResult/Exception - // sees the _continuation as non-null, it'll be able to invoke it with the state - // stored here. However, this also means that if this is used incorrectly (e.g. - // awaited twice concurrently), _continuationState might get erroneously overwritten. - // To minimize the chances of that, we check preemptively whether _continuation - // is already set to something other than the completion sentinel. - - object oldContinuation = this._continuation; - if (oldContinuation == null) - { - this._continuationState = state; - oldContinuation = Interlocked.CompareExchange(ref this._continuation, continuation, null); - } - - if (oldContinuation != null) - { - // Operation already completed, so we need to queue the supplied callback. - if (!ReferenceEquals(oldContinuation, ManualResetValueTaskSourceCoreShared.s_sentinel)) - { - throw new InvalidOperationException(); - } - - switch (this._capturedContext) - { - case null: - Task.Factory.StartNew(continuation, state, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); - break; - - case SynchronizationContext sc: - sc.Post(s => - { - var tuple = (Tuple, object>)s; - tuple.Item1(tuple.Item2); - }, Tuple.Create(continuation, state)); - break; - - case TaskScheduler ts: - Task.Factory.StartNew(continuation, state, CancellationToken.None, TaskCreationOptions.DenyChildAttach, ts); - break; - } - } - } - - /// Ensures that the specified token matches the current version. - /// The token supplied by . - private void ValidateToken(short token) - { - if (token != this._version) - { - throw new InvalidOperationException(); - } - } - - /// Signals that the operation has completed. Invoked after the result or error has been set. - private void SignalCompletion() - { - if (this._completed) - { - throw new InvalidOperationException(); - } - this._completed = true; - - if (this._continuation != null || Interlocked.CompareExchange(ref this._continuation, ManualResetValueTaskSourceCoreShared.s_sentinel, null) != null) - { - if (this._executionContext != null) - { - ExecutionContext.Run( - this._executionContext, - s => ((ManualResetValueTaskSourceCore)s).InvokeContinuation(), - this); - } - else - { - this.InvokeContinuation(); - } - } - } - - /// - /// Invokes the continuation with the appropriate captured context / scheduler. - /// This assumes that if is not null we're already - /// running within that . - /// - private void InvokeContinuation() - { - Debug.Assert(this._continuation != null); - - switch (this._capturedContext) - { - case null: - if (this.RunContinuationsAsynchronously) - { - Task.Factory.StartNew(this._continuation, this._continuationState, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); - } - else - { - this._continuation(this._continuationState); - } - break; - - case SynchronizationContext sc: - sc.Post(s => - { - var state = (Tuple, object>)s; - state.Item1(state.Item2); - }, Tuple.Create(this._continuation, this._continuationState)); - break; - - case TaskScheduler ts: - Task.Factory.StartNew(this._continuation, this._continuationState, CancellationToken.None, TaskCreationOptions.DenyChildAttach, ts); - break; - } - } -} - -internal static class ManualResetValueTaskSourceCoreShared // separated out of generic to avoid unnecessary duplication -{ - internal static readonly Action s_sentinel = CompletionSentinel; - private static void CompletionSentinel(object _) // named method to aid debugging - { - Debug.Fail("The sentinel delegate should never be invoked."); - throw new InvalidOperationException(); - } -} -#endif \ No newline at end of file diff --git a/src/TouchSocket.Core/Threading/NullableHelpers.cs b/src/TouchSocket.Core/Threading/NullableHelpers.cs new file mode 100644 index 000000000..62ab8247a --- /dev/null +++ b/src/TouchSocket.Core/Threading/NullableHelpers.cs @@ -0,0 +1,43 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +namespace TouchSocket.Core; + +internal static class NullableHelpers +{ + /// + /// Converts a delegate which assumes an argument that is never null into a delegate which might be given a null value, + /// without adding an explicit null check. + /// + /// The type of argument to be passed to the delegate. + /// The delegate which, according to the signature, does not expect . + /// The exact same referenced delegate, but with a signature that may expect . + internal static Action AsNullableArgAction(Action action) + where T : class + { + return action!; + } + + /// + /// Converts a delegate which assumes an argument that is never null into a delegate which might be given a null value, + /// without adding an explicit null check. + /// + /// The type of argument to be passed to the delegate. + /// The type of value returned from the delegate. + /// The delegate which, according to the signature, does not expect . + /// The exact same referenced delegate, but with a signature that may expect . + internal static Func AsNullableArgFunc(Func func) + where TArg : class + { + return func!; + } +} diff --git a/src/TouchSocket.Core/Threading/ReadLock.cs b/src/TouchSocket.Core/Threading/ReadLock.cs index f5e93a327..f633212a1 100644 --- a/src/TouchSocket.Core/Threading/ReadLock.cs +++ b/src/TouchSocket.Core/Threading/ReadLock.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/Threading/TaskCompletionSourceWithoutInlining.cs b/src/TouchSocket.Core/Threading/TaskCompletionSourceWithoutInlining.cs new file mode 100644 index 000000000..cb7987637 --- /dev/null +++ b/src/TouchSocket.Core/Threading/TaskCompletionSourceWithoutInlining.cs @@ -0,0 +1,67 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +namespace TouchSocket.Core; + +/// +/// A -derivative that +/// does not inline continuations if so configured. +/// +/// The type of the task's resulting value. +internal class TaskCompletionSourceWithoutInlining : TaskCompletionSource +{ + /// + /// The Task that we expose to others that may not inline continuations. + /// + private readonly Task exposedTask; + + /// + /// Initializes a new instance of the class. + /// + /// + /// to allow continuations to be inlined; otherwise . + /// + /// + /// TaskCreationOptions to pass on to the base constructor. + /// + /// The state to set on the Task. + internal TaskCompletionSourceWithoutInlining(bool allowInliningContinuations, TaskCreationOptions options = TaskCreationOptions.None, object state = null) + : base(state, AdjustFlags(options, allowInliningContinuations)) + { + this.exposedTask = base.Task; + } + + /// + /// Gets the that may never complete inline with completion of this . + /// + /// + /// Return the base.Task if it is already completed since inlining continuations + /// on the completer is no longer a concern. Also, when we are not inlining continuations, + /// this.exposedTask completes slightly later than base.Task, and callers expect + /// the Task we return to be complete as soon as they call TrySetResult. + /// + internal new Task Task => base.Task.IsCompleted ? base.Task : this.exposedTask; + + /// + /// Modifies the specified flags to include RunContinuationsAsynchronously + /// if wanted by the caller and supported by the platform. + /// + /// The base options supplied by the caller. + /// to allow inlining continuations. + /// The possibly modified flags. + private static TaskCreationOptions AdjustFlags(TaskCreationOptions options, bool allowInliningContinuations) + { + return allowInliningContinuations + ? (options & ~TaskCreationOptions.RunContinuationsAsynchronously) + : (options | TaskCreationOptions.RunContinuationsAsynchronously); + } +} diff --git a/src/TouchSocket.Core/Threading/TimeoutTokenSource.cs b/src/TouchSocket.Core/Threading/TimeoutTokenSource.cs new file mode 100644 index 000000000..ff917332d --- /dev/null +++ b/src/TouchSocket.Core/Threading/TimeoutTokenSource.cs @@ -0,0 +1,191 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +namespace TouchSocket.Core; + +/// +/// 表示超时令牌源的状态。 +/// +/// +/// 此枚举用于跟踪异步操作的执行状态,特别是在涉及超时和取消的场景中。 +/// 提供了从初始化到完成或取消的完整状态转换。 +/// +public enum TimeoutTokenState : byte +{ + /// + /// 初始化状态,操作尚未开始或正在进行中。 + /// + Initialized, + + /// + /// 操作成功完成。 + /// + Completed, + + /// + /// 操作因超时而被取消。 + /// + TimedOut, + + /// + /// 操作被用户主动取消。 + /// + Cancelled +} + +/// +/// 带超时功能的取消令牌管理器。 +/// +/// +/// 此类继承自,提供了组合超时和用户取消令牌的功能。 +/// 能够区分操作是因超时还是用户主动取消而终止,并提供相应的异常处理机制。 +/// 适用于需要精确控制超时行为的异步操作场景。 +/// +public sealed class TimeoutTokenSource : DisposableObject +{ + private readonly CancellationTokenSource m_timeoutCts; + private readonly CancellationTokenSource m_combinedCts; + private readonly CancellationToken m_originalToken; + private readonly int m_timeoutMs; + private TimeoutTokenState m_state; + + /// + /// 初始化类的新实例。 + /// + /// 超时时间(以毫秒为单位)。 + /// 用户提供的取消令牌。 + /// + /// 构造函数创建一个组合的取消令牌,该令牌会在超时或用户取消时被触发。 + /// 初始状态设置为。 + /// + public TimeoutTokenSource(int timeoutMs, CancellationToken cancellationToken) + { + this.m_timeoutMs = timeoutMs; + this.m_originalToken = cancellationToken; + this.m_timeoutCts = new CancellationTokenSource(TimeSpan.FromMilliseconds(timeoutMs)); + this.m_combinedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, this.m_timeoutCts.Token); + this.m_state = TimeoutTokenState.Initialized; + } + + /// + /// 获取当前超时令牌源的状态。 + /// + /// 表示当前状态的枚举值。 + /// + /// 此属性反映了操作的当前执行状态,包括初始化、完成、超时或取消。 + /// + public TimeoutTokenState State => this.m_state; + + /// + /// 获取组合后的取消令牌。 + /// + /// 一个,当超时或用户取消时会被触发。 + /// + /// 此令牌结合了超时机制和用户取消请求,可用于需要同时响应这两种取消条件的异步操作。 + /// + public CancellationToken Token => this.m_combinedCts.Token; + + /// + /// 标记操作成功完成。 + /// + /// + /// 调用此方法将状态从更改为。 + /// 如果状态已经不是初始化状态,则不会进行任何更改。 + /// + public void MarkCompleted() + { + if (this.m_state == TimeoutTokenState.Initialized) + { + this.m_state = TimeoutTokenState.Completed; + } + } + + /// + /// 处理操作取消异常,转换为适当的异常类型并更新状态。 + /// + /// 捕获的操作取消异常。 + /// 当操作因超时而取消时抛出。 + /// 当操作被用户主动取消时抛出。 + /// + /// 此方法分析取消的原因并相应地更新状态: + /// + /// 如果超时令牌被触发但原始令牌未被触发,则认为是超时,状态更新为 + /// 如果原始令牌被触发,则认为是用户取消,状态更新为 + /// 其他情况下重新抛出原始异常。 + /// + /// + public void HandleCancellation(OperationCanceledException ex) + { + if (this.m_timeoutCts.Token.IsCancellationRequested && !this.m_originalToken.IsCancellationRequested) + { + // 如果是超时取消(超时令牌被取消,但原始令牌没有被取消) + this.m_state = TimeoutTokenState.TimedOut; + throw new TimeoutException($"操作在 {this.m_timeoutMs} 毫秒内未完成"); + } + else if (this.m_originalToken.IsCancellationRequested) + { + // 如果是用户主动取消 + this.m_state = TimeoutTokenState.Cancelled; + throw new OperationCanceledException("操作被用户取消", this.m_originalToken); + } + else + { + // 其他情况,重新抛出原异常 + throw ex; + } + } + + /// + /// 释放由使用的托管资源。 + /// + /// 如果为,则释放托管资源;否则仅释放非托管资源。 + /// + /// 此方法重写基类的方法, + /// 负责释放内部创建的实例。 + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + this.m_timeoutCts?.Dispose(); + this.m_combinedCts?.Dispose(); + } + base.Dispose(disposing); + } + + /// + /// 检查取消结果并转换为适当的结果类型。 + /// + /// 要检查的操作结果。 + /// + /// 如果结果表示取消且是由超时引起的,则返回; + /// 否则返回原始的。 + /// + /// + /// 此方法用于将通用的取消结果转换为更具体的超时结果, + /// 便于调用者区分取消的具体原因。 + /// + public Result CheckCancellationResult(Result result) + { + if (result.ResultCode == ResultCode.Canceled) + { + if (this.m_timeoutCts.Token.IsCancellationRequested && !this.m_originalToken.IsCancellationRequested) + { + // 如果是超时取消(超时令牌被取消,但原始令牌没有被取消) + this.m_state = TimeoutTokenState.TimedOut; + return Result.Overtime; + } + } + + return result; + } +} \ No newline at end of file diff --git a/src/TouchSocket.Core/Threading/ValueTaskSource.cs b/src/TouchSocket.Core/Threading/ValueTaskSource.cs deleted file mode 100644 index 9e4597016..000000000 --- a/src/TouchSocket.Core/Threading/ValueTaskSource.cs +++ /dev/null @@ -1,226 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; -using System.Runtime.ExceptionServices; -using System.Threading; -using System.Threading.Tasks; -using System.Threading.Tasks.Sources; - -namespace TouchSocket.Core; - -/// -/// 提供异步操作的值任务源抽象类。 -/// -/// 结果类型。 -public abstract class ValueTaskSource : DisposableObject, IValueTaskSource -{ - #region 字段 - - /// - /// 表示继续操作已完成的静态操作委托。 - /// - private static readonly Action s_continuationCompleted = _ => { }; - - /// - /// 异步操作继续操作委托。 - /// - private volatile Action m_continuation; - /// - /// 异常分发信息。 - /// - private ExceptionDispatchInfo m_exceptionDispatchInfo; - /// - /// 取消令牌注册。 - /// - private CancellationTokenRegistration m_tokenRegistration; - /// - /// 用户状态对象。 - /// - private object m_userState; - - #endregion 字段 - - /// - /// 获取异常分发信息。 - /// - protected ExceptionDispatchInfo ExceptionDispatchInfo => this.m_exceptionDispatchInfo; - - /// - /// 获取结果。 - /// - /// 操作结果。 - protected abstract TResult GetResult(); - - /// - /// 取消操作。 - /// - protected void Cancel() - { - this.SetException(new OperationCanceledException()); - } - - /// - /// 重置操作状态。 - /// - protected virtual void Reset() - { - //this.m_resetEventForRead.Reset(); - this.m_continuation = null; - this.m_exceptionDispatchInfo = null; - this.m_tokenRegistration = default; - this.m_userState = null; - } - - /// - /// 完成操作。 - /// - /// 是否使用调度器。 - protected void Complete(bool scheduler) - { - //this.m_resetEventForRead.Set(); - - var c = this.m_continuation; - - if (c != null || (c = Interlocked.CompareExchange(ref this.m_continuation, s_continuationCompleted, null)) != null) - { - var continuationState = this.m_userState; - this.m_userState = null; - this.m_continuation = s_continuationCompleted; // 防止有人轮询IsCompleted - - if (scheduler) - { - this.Scheduler(c, continuationState); - } - else - { - c.Invoke(continuationState); - } - } - } - - /// - protected override void Dispose(bool disposing) - { - if (this.DisposedValue) - { - return; - } - - if (disposing) - { - this.Cancel(); - } - base.Dispose(disposing); - } - - /// - /// 调度继续操作。 - /// - /// 继续操作委托。 - /// 状态对象。 - protected abstract void Scheduler(Action action, object state); - - /// - /// 设置异常。 - /// - /// 异常对象。 - protected void SetException(Exception exception) - { - this.m_exceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception); - this.Complete(false); - } - - /// - /// 值等待异步操作。 - /// - /// 取消令牌。 - /// 值任务。 - protected ValueTask ValueWaitAsync(CancellationToken token) - { - this.ThrowIfDisposed(); - token.ThrowIfCancellationRequested(); - - if (token.CanBeCanceled) - { - if (this.m_tokenRegistration == default) - { - this.m_tokenRegistration = token.Register(this.Cancel); - } - else - { - this.m_tokenRegistration.Dispose(); - this.m_tokenRegistration = token.Register(this.Cancel); - } - } - - return new ValueTask(this, 0); - } - - #region IValueTaskSource - - /// - /// 获取结果。 - /// - /// 令牌。 - /// 操作结果。 - TResult IValueTaskSource.GetResult(short token) - { - this.m_continuation = null; - if (this.m_exceptionDispatchInfo != null) - { - var exceptionDispatchInfo = this.m_exceptionDispatchInfo; - this.m_exceptionDispatchInfo = null; - exceptionDispatchInfo.Throw(); - } - - return this.GetResult(); - } - - - ValueTaskSourceStatus IValueTaskSource.GetStatus(short token) - { - return this.GetStatus(token); - } - - /// - /// 获取状态。 - /// - /// 令牌。 - /// 操作状态。 - protected virtual ValueTaskSourceStatus GetStatus(short token) - { - return !ReferenceEquals(this.m_continuation, s_continuationCompleted) ? ValueTaskSourceStatus.Pending : - ValueTaskSourceStatus.Succeeded; - } - - /// - /// 操作完成时调用。 - /// - /// 继续操作委托。 - /// 状态对象。 - /// 令牌。 - /// 标志。 - void IValueTaskSource.OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) - { - this.m_userState = state; - //Interlocked.CompareExchange(ref this.m_continuation, continuation, null); - var prevContinuation = Interlocked.CompareExchange(ref this.m_continuation, continuation, null); - if (ReferenceEquals(prevContinuation, s_continuationCompleted)) - { - this.m_userState = null; - this.Scheduler(continuation, state); - } - } - - #endregion IValueTaskSource -} \ No newline at end of file diff --git a/src/TouchSocket.Core/Threading/WriteLock.cs b/src/TouchSocket.Core/Threading/WriteLock.cs index c2370c086..7c130e86d 100644 --- a/src/TouchSocket.Core/Threading/WriteLock.cs +++ b/src/TouchSocket.Core/Threading/WriteLock.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Core/TouchSocket.Core.csproj b/src/TouchSocket.Core/TouchSocket.Core.csproj index 68a1ef0a0..3bea4bcb5 100644 --- a/src/TouchSocket.Core/TouchSocket.Core.csproj +++ b/src/TouchSocket.Core/TouchSocket.Core.csproj @@ -1,6 +1,6 @@ - net481;net45;net462;net472;netstandard2.0;netstandard2.1;net6.0;net9.0;net8.0 + net462;netstandard2.0;netstandard2.1;net6.0;net10.0;net8.0 Message;ArrayPool;Logger;Plugin;3DES;Xml;FilePool;Serialize;TouchSocket 这是一个基础服务功能的库,其中包含:内存池、对象池、文件池、流式数据解包器、等待逻辑池、AppMessenger、3DES加密、Xml快速存储、运行时间测量器、文件快捷操作、高性能二进制序列化器、规范日志接口等。 @@ -14,52 +14,45 @@ - - - - - - - - - + + - + + - + + - + - + + - + + - - diff --git a/src/TouchSocket.Core/WaitPool/AsyncWaitData.cs b/src/TouchSocket.Core/WaitPool/AsyncWaitData.cs new file mode 100644 index 000000000..f20dcf880 --- /dev/null +++ b/src/TouchSocket.Core/WaitPool/AsyncWaitData.cs @@ -0,0 +1,175 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.Runtime.CompilerServices; +using System.Threading.Tasks.Sources; + +namespace TouchSocket.Core; + +/// +/// 表示一个用于异步等待结果的容器,基于 / 实现。 +/// +/// 等待的数据类型。 +/// +/// 此类用于在等待池中挂起并等待特定签名的数据到达。它使用 +/// 来实现高性能的 ValueTask 等待,并通过构造时传入的 回调在释放时将自身从池中移除。 +/// +public sealed class AsyncWaitData : IValueTaskSource, IDisposable +{ + private readonly Action m_cancel; + private readonly Action m_remove; + private T m_completedData; + private ManualResetValueTaskSourceCore m_core; + private volatile int m_isCompleted; + private T m_pendingData; + private CancellationTokenRegistration m_registration; + + // 0 = 未完成, 1 = 已完成 + private int m_sign; + + private WaitDataStatus m_status; + + /// + /// 使用指定签名和移除回调初始化一个新的 实例。 + /// + /// 此等待项对应的签名(用于在池中查找)。 + /// 完成或释放时调用的回调,用于将此实例从等待池中移除。 + /// 可选的挂起数据,当创建时可以携带一个初始占位数据。 + internal AsyncWaitData(int sign, Action remove, T pendingData) + { + this.m_sign = sign; + this.m_remove = remove; + + this.m_pendingData = pendingData; + this.m_core.RunContinuationsAsynchronously = true; + this.m_cancel = this.Cancel; + } + + /// + /// 获取已完成时的返回数据。 + /// + public T CompletedData => this.m_completedData; + + /// + /// 获取挂起时的原始数据(如果在创建时传入)。 + /// + public T PendingData => this.m_pendingData; + + /// + /// 获取此等待项的签名标识。 + /// + public int Sign => this.m_sign; + + /// + /// 获取当前等待状态(例如:Success、Canceled 等)。 + /// + public WaitDataStatus Status => this.m_status; + + /// + /// 取消当前等待,标记为已取消并触发等待任务的异常()。 + /// + public void Cancel() + { + this.Set(WaitDataStatus.Canceled, default!); + } + + /// + public void Dispose() + { + // 确保取消令牌已释放 + this.m_registration.Dispose(); + this.m_registration = default; + + var sign = this.m_sign; + this.Clear(); + this.m_remove(sign); + } + + WaitDataStatus IValueTaskSource.GetResult(short token) + { + this.m_core.GetResult(token); + return this.m_status; + } + + ValueTaskSourceStatus IValueTaskSource.GetStatus(short token) + => this.m_core.GetStatus(token); + + void IValueTaskSource.OnCompleted(Action continuation, object state, + short token, ValueTaskSourceOnCompletedFlags flags) + => this.m_core.OnCompleted(continuation, state, token, flags); + + /// + /// 将等待项设置为成功并携带结果数据。 + /// + /// 要设置的完成数据。 + public void Set(T result) + { + this.Set(WaitDataStatus.Success, result); + } + + /// + /// 设置等待项的状态和数据,并完成对应的 ValueTask。 + /// + /// 要设置的状态。 + /// 要设置的完成数据。 + public void Set(WaitDataStatus status, T result) + { + // 使用 Interlocked 确保只设置一次 + if (Interlocked.CompareExchange(ref this.m_isCompleted, 1, 0) != 0) + { + return; // 已经完成,直接返回 + } + + this.m_status = status; + this.m_completedData = result; + + if (status == WaitDataStatus.Canceled) + { + this.m_core.SetException(new OperationCanceledException()); + } + else + { + this.m_core.SetResult(result); + } + } + + /// + /// 异步等待此项完成,返回一个 ,可传入取消令牌以取消等待。 + /// + /// 可选的取消令牌。若触发则会调用 。 + /// 表示等待状态的 ValueTask。 + public ValueTask WaitAsync(CancellationToken cancellationToken = default) + { + if (cancellationToken.CanBeCanceled) + { + this.m_registration = cancellationToken.Register(this.m_cancel); + } + + return new ValueTask(this, this.m_core.Version); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Clear() + { + this.m_pendingData = default; + this.m_completedData = default; + this.m_status = WaitDataStatus.Default; + this.m_isCompleted = 0; + } + + internal void Reset(int sign, T pendingData) + { + this.m_pendingData = pendingData; + this.m_sign = sign; + this.m_core.Reset(); + } +} \ No newline at end of file diff --git a/src/TouchSocket.Core/WaitPool/IWaitData.cs b/src/TouchSocket.Core/WaitPool/IWaitData.cs deleted file mode 100644 index 71da96604..000000000 --- a/src/TouchSocket.Core/WaitPool/IWaitData.cs +++ /dev/null @@ -1,36 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; - -namespace TouchSocket.Core; - -/// -/// 定义等待数据接口。 -/// -/// 等待结果的类型。 -public interface IWaitData : IWaitDataBase -{ - /// - /// 等待指定的时间间隔。 - /// - /// 等待的时间间隔。 - /// 等待数据的状态。 - WaitDataStatus Wait(TimeSpan timeSpan); - - /// - /// 等待指定的毫秒数。 - /// - /// 等待的毫秒数。 - /// 等待数据的状态。 - WaitDataStatus Wait(int millisecond); -} \ No newline at end of file diff --git a/src/TouchSocket.Core/WaitPool/IWaitDataAsync.cs b/src/TouchSocket.Core/WaitPool/IWaitDataAsync.cs deleted file mode 100644 index 9fa51bc7a..000000000 --- a/src/TouchSocket.Core/WaitPool/IWaitDataAsync.cs +++ /dev/null @@ -1,37 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; -using System.Threading.Tasks; - -namespace TouchSocket.Core; - -/// -/// 异步等待数据接口。 -/// -/// 等待结果的类型。 -public interface IWaitDataAsync : IWaitDataBase -{ - /// - /// 异步等待指定的时间间隔。 - /// - /// 等待的时间间隔。 - /// 等待数据的状态。 - Task WaitAsync(TimeSpan timeSpan); - - /// - /// 异步等待指定的毫秒数。 - /// - /// 等待的毫秒数。 - /// 等待数据的状态。 - Task WaitAsync(int millisecond); -} \ No newline at end of file diff --git a/src/TouchSocket.Core/WaitPool/IWaitDataBase.cs b/src/TouchSocket.Core/WaitPool/IWaitDataBase.cs deleted file mode 100644 index bd54127a9..000000000 --- a/src/TouchSocket.Core/WaitPool/IWaitDataBase.cs +++ /dev/null @@ -1,68 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System.Threading; - -namespace TouchSocket.Core; - -/// -/// 定义一个等待数据的接口。 -/// -/// 等待结果的类型。 -public interface IWaitDataBase : IDisposableObject -{ - /// - /// 获取等待对象的状态。 - /// - WaitDataStatus Status { get; } - - /// - /// 获取等待结果。 - /// - T WaitResult { get; } - - /// - /// 取消等待。 - /// - void Cancel(); - - /// - /// 重置等待对象。 - /// 设置。然后重置状态为。 - /// - void Reset(); - - /// - /// 使等待的线程继续执行。 - /// - /// 如果操作成功,则返回;否则返回 - bool Set(); - - /// - /// 使等待的线程继续执行,并设置等待结果。 - /// - /// 等待结果。 - /// 如果操作成功,则返回;否则返回 - bool Set(T waitResult); - - /// - /// 设置取消令牌。 - /// - /// 取消令牌。 - void SetCancellationToken(CancellationToken cancellationToken); - - /// - /// 设置等待结果。 - /// - /// 等待结果。 - void SetResult(T result); -} \ No newline at end of file diff --git a/src/TouchSocket.Core/WaitPool/IWaitHandlePool.cs b/src/TouchSocket.Core/WaitPool/IWaitHandlePool.cs deleted file mode 100644 index b0e91679a..000000000 --- a/src/TouchSocket.Core/WaitPool/IWaitHandlePool.cs +++ /dev/null @@ -1,139 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; - -namespace TouchSocket.Core; - -/// -/// 表示一个等待句柄池的接口。 -/// -/// 等待数据的类型。 -/// 异步等待数据的类型。 -/// 等待句柄的类型。 -public interface IWaitHandlePool : IDisposableObject - where T : IWaitHandle - where TWaitData : IWaitData - where TWaitDataAsync : IWaitDataAsync -{ - /// - /// 获取或设置最大标志值。 - /// - int MaxSign { get; set; } - - /// - /// 获取或设置最小标志值。 - /// - int MinSign { get; set; } - - /// - /// 取消池中的所有等待句柄。 - /// - void CancelAll(); - - /// - /// 销毁指定的等待数据。 - /// - /// 要销毁的等待数据。 - [Obsolete("此方法在调用时,可能导致不可控bug,已被弃用,请使用Destroy(int sign)的重载函数直接代替", true)] - void Destroy(TWaitData waitData); - - /// - /// 销毁指定的异步等待数据。 - /// - /// 要销毁的异步等待数据。 - [Obsolete("此方法在调用时,可能导致不可控bug,已被弃用,请使用Destroy(int sign)的重载函数直接代替", true)] - void Destroy(TWaitDataAsync waitData); - - /// - /// 销毁指定的等待数据。 - /// - /// 要销毁的等待令箭。 - void Destroy(int sign); - - /// - /// 获取一个等待数据对象。 - /// - /// 要设置在等待数据中的结果。 - /// 是否自动标记等待数据。 - /// 等待数据对象。 - TWaitData GetWaitData(T result, bool autoSign = true); - - /// - /// 获取一个等待数据对象并输出其标志。 - /// - /// 等待数据的标志。 - /// 等待数据对象。 - TWaitData GetWaitData(out int sign); - - /// - /// 获取一个异步等待数据对象。 - /// - /// 要设置在等待数据中的结果。 - /// 是否自动标记等待数据。 - /// 异步等待数据对象。 - TWaitDataAsync GetWaitDataAsync(T result, bool autoSign = true); - - /// - /// 获取一个异步等待数据对象并输出其标志。 - /// - /// 等待数据的标志。 - /// 异步等待数据对象。 - TWaitDataAsync GetWaitDataAsync(out int sign); - - /// - /// 设置指定标志的运行状态。 - /// - /// 要设置运行状态的标志。 - /// 如果设置成功则为 true,否则为 false。 - bool SetRun(int sign); - - /// - /// 设置指定标志和等待结果的运行状态。 - /// - /// 要设置运行状态的标志。 - /// 要设置的等待结果。 - /// 如果设置成功则为 true,否则为 false。 - bool SetRun(int sign, T waitResult); - - /// - /// 设置指定等待结果的运行状态。 - /// - /// 要设置的等待结果。 - /// 如果设置成功则为 true,否则为 false。 - bool SetRun(T waitResult); - - /// - /// 尝试获取指定标志的等待数据。 - /// - /// 要获取等待数据的标志。 - /// 如果找到则为等待数据。 - /// 如果找到等待数据则为 true,否则为 false。 - bool TryGetData(int sign, out TWaitData waitData); - - /// - /// 尝试获取指定标志的异步等待数据。 - /// - /// 要获取异步等待数据的标志。 - /// 如果找到则为异步等待数据。 - /// 如果找到异步等待数据则为 true,否则为 false。 - bool TryGetDataAsync(int sign, out TWaitDataAsync waitDataAsync); -} - -/// -/// 表示一个等待句柄池的接口。 -/// -/// 等待句柄的类型。 -public interface IWaitHandlePool : IWaitHandlePool, WaitDataAsync, T> where T : IWaitHandle -{ - -} \ No newline at end of file diff --git a/src/TouchSocket.Core/WaitPool/WaitData.cs b/src/TouchSocket.Core/WaitPool/WaitData.cs deleted file mode 100644 index 82c2c8ad2..000000000 --- a/src/TouchSocket.Core/WaitPool/WaitData.cs +++ /dev/null @@ -1,134 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; -using System.Threading; - -namespace TouchSocket.Core; - -/// -/// 等待数据对象 -/// -/// -public class WaitData : DisposableObject, IWaitData -{ - private readonly AutoResetEvent m_waitHandle; - private volatile WaitDataStatus m_status; - private CancellationTokenRegistration m_tokenRegistration; - - /// - /// WaitData - /// - public WaitData() - { - this.m_waitHandle = new AutoResetEvent(false); - } - - /// - public WaitDataStatus Status => this.m_status; - - /// - public T WaitResult { get; private set; } - - /// - public void Cancel() - { - this.m_status = WaitDataStatus.Canceled; - this.m_waitHandle.Set(); - } - - /// - public void Reset() - { - if (this.m_tokenRegistration != default) - { - this.m_tokenRegistration.Dispose(); - } - this.m_status = WaitDataStatus.Default; - this.WaitResult = default; - this.m_waitHandle.Reset(); - } - - /// - public bool Set() - { - this.m_status = WaitDataStatus.SetRunning; - return this.m_waitHandle.Set(); - } - - /// - public bool Set(T waitResult) - { - this.WaitResult = waitResult; - this.m_status = WaitDataStatus.SetRunning; - return this.m_waitHandle.Set(); - } - - /// - public void SetCancellationToken(CancellationToken cancellationToken) - { - if (cancellationToken.CanBeCanceled) - { - if (this.m_tokenRegistration == default) - { - this.m_tokenRegistration = cancellationToken.Register(this.Cancel); - } - else - { - this.m_tokenRegistration.Dispose(); - this.m_tokenRegistration = cancellationToken.Register(this.Cancel); - } - } - } - - /// - public void SetResult(T result) - { - this.WaitResult = result; - } - - /// - public WaitDataStatus Wait(TimeSpan timeSpan) - { - return this.Wait((int)timeSpan.TotalMilliseconds); - } - - /// - public WaitDataStatus Wait(int millisecond) - { - if (!this.m_waitHandle.WaitOne(millisecond)) - { - this.m_status = WaitDataStatus.Overtime; - } - return this.m_status; - } - - /// - protected override void Dispose(bool disposing) - { - if (disposing) - { - this.m_status = WaitDataStatus.Disposed; - this.WaitResult = default; - this.m_waitHandle.SafeDispose(); - this.m_tokenRegistration.Dispose(); - } - base.Dispose(disposing); - } -} - -/// -/// 等待数据对象 -/// -public class WaitData : WaitData -{ -} \ No newline at end of file diff --git a/src/TouchSocket.Core/WaitPool/WaitDataAsync.cs b/src/TouchSocket.Core/WaitPool/WaitDataAsync.cs index f56cb3da0..5d148b16d 100644 --- a/src/TouchSocket.Core/WaitPool/WaitDataAsync.cs +++ b/src/TouchSocket.Core/WaitPool/WaitDataAsync.cs @@ -10,28 +10,23 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading; -using System.Threading.Tasks; - namespace TouchSocket.Core; /// /// 等待数据对象 /// /// -public class WaitDataAsync : DisposableObject, IWaitDataAsync +public class WaitDataAsync : DisposableObject { - private readonly AsyncAutoResetEvent m_asyncWaitHandle; + private readonly AsyncManualResetEvent m_asyncWaitHandle; private volatile WaitDataStatus m_status; - private CancellationTokenRegistration m_tokenRegistration; /// /// 构造函数 /// public WaitDataAsync() { - this.m_asyncWaitHandle = new AsyncAutoResetEvent(false); + this.m_asyncWaitHandle = new AsyncManualResetEvent(false); } /// @@ -56,35 +51,18 @@ public class WaitDataAsync : DisposableObject, IWaitDataAsync } /// - public bool Set() + public void Set() { - this.m_status = WaitDataStatus.SetRunning; - return this.m_asyncWaitHandle.Set(); + this.m_status = WaitDataStatus.Success; + this.m_asyncWaitHandle.Set(); } /// - public bool Set(T waitResult) + public void Set(T waitResult) { this.WaitResult = waitResult; - this.m_status = WaitDataStatus.SetRunning; - return this.m_asyncWaitHandle.Set(); - } - - /// - public void SetCancellationToken(CancellationToken cancellationToken) - { - if (cancellationToken.CanBeCanceled) - { - if (this.m_tokenRegistration == default) - { - this.m_tokenRegistration = cancellationToken.Register(this.Cancel); - } - else - { - this.m_tokenRegistration.Dispose(); - this.m_tokenRegistration = cancellationToken.Register(this.Cancel); - } - } + this.m_status = WaitDataStatus.Success; + this.m_asyncWaitHandle.Set(); } /// @@ -94,20 +72,18 @@ public class WaitDataAsync : DisposableObject, IWaitDataAsync } /// - public async Task WaitAsync(TimeSpan timeSpan) + public async ValueTask WaitAsync(CancellationToken cancellationToken) { - if (!await this.m_asyncWaitHandle.WaitOneAsync(timeSpan).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) + try { - this.m_status = WaitDataStatus.Overtime; + await this.m_asyncWaitHandle.WaitOneAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return this.m_status; + } + catch (OperationCanceledException) + { + this.m_status = WaitDataStatus.Canceled; + return this.m_status; } - - return this.m_status; - } - - /// - public Task WaitAsync(int millisecond) - { - return this.WaitAsync(TimeSpan.FromMilliseconds(millisecond)); } /// @@ -117,8 +93,6 @@ public class WaitDataAsync : DisposableObject, IWaitDataAsync { this.m_status = WaitDataStatus.Disposed; this.WaitResult = default; - this.m_asyncWaitHandle.SafeDispose(); - this.m_tokenRegistration.Dispose(); } base.Dispose(disposing); diff --git a/src/TouchSocket.Core/WaitPool/WaitDataStatus.cs b/src/TouchSocket.Core/WaitPool/WaitDataStatus.cs index 7b740b269..77da5c51d 100644 --- a/src/TouchSocket.Core/WaitPool/WaitDataStatus.cs +++ b/src/TouchSocket.Core/WaitPool/WaitDataStatus.cs @@ -25,7 +25,7 @@ public enum WaitDataStatus : byte /// /// 收到信号运行 /// - SetRunning, + Success, /// /// 超时 diff --git a/src/TouchSocket.Core/WaitPool/WaitDataStatusExtension.cs b/src/TouchSocket.Core/WaitPool/WaitDataStatusExtension.cs index 4a58ba887..4519006e9 100644 --- a/src/TouchSocket.Core/WaitPool/WaitDataStatusExtension.cs +++ b/src/TouchSocket.Core/WaitPool/WaitDataStatusExtension.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using TouchSocket.Resources; namespace TouchSocket.Core; @@ -21,14 +20,14 @@ namespace TouchSocket.Core; public static class WaitDataStatusExtension { /// - /// 当状态不是时抛出异常。 + /// 当状态不是时抛出异常。 /// /// public static void ThrowIfNotRunning(this WaitDataStatus status) { switch (status) { - case WaitDataStatus.SetRunning: + case WaitDataStatus.Success: return; case WaitDataStatus.Canceled: throw new OperationCanceledException(); diff --git a/src/TouchSocket.Core/WaitPool/WaitHandlePool.cs b/src/TouchSocket.Core/WaitPool/WaitHandlePool.cs index 3f63cb156..ab9a031c6 100644 --- a/src/TouchSocket.Core/WaitPool/WaitHandlePool.cs +++ b/src/TouchSocket.Core/WaitPool/WaitHandlePool.cs @@ -1,525 +1,207 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ -#if !NET45 -using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; using System.Runtime.CompilerServices; -using System.Threading; namespace TouchSocket.Core; /// -/// 表示一个等待句柄池。 +/// 表示一个等待句柄池,用于管理具有等待功能的对象集合。 /// -/// 等待句柄的类型。 -public class WaitHandlePool : WaitHandlePool, WaitDataAsync, T>, IWaitHandlePool - where T : IWaitHandle +/// 等待句柄的类型,必须实现接口并且是引用类型。 +/// +/// WaitHandlePool提供了对等待句柄的集中管理,支持自动签名生成、等待数据创建和池内对象的生命周期管理。 +/// 使用线程安全的并发字典来存储等待数据,支持高并发场景下的操作。 +/// 签名生成采用原子递增方式,确保在指定范围内的唯一性。 +/// +public sealed class WaitHandlePool + where T : class, IWaitHandle { -} - -/// -/// 表示一个等待句柄池 -/// -/// 同步等待数据 -/// 异步等待数据 -/// 数据体 -public class WaitHandlePool : DisposableObject, IWaitHandlePool - where TWaitData : IWaitData, new() - where TWaitDataAsync : IWaitDataAsync, new() - where T : IWaitHandle -{ - /// - /// 不要设为readonly - /// - private SpinLock m_lock = new SpinLock(Debugger.IsAttached); - private readonly Dictionary m_waitDic = new(); - private readonly Dictionary m_waitDicAsync = new(); - private readonly Queue m_waitQueue = new(); - private readonly Queue m_waitQueueAsync = new(); + private readonly int m_maxSign; + private readonly int m_minSign; + private readonly Action m_remove; + private readonly ConcurrentDictionary> m_waitDic = new(); + private readonly ConcurrentStack> s_pool = new(); private int m_currentSign; - private int m_maxSign = int.MaxValue; - private int m_minSign = 0; - /// - public int MaxSign { get => this.m_maxSign; set => this.m_maxSign = value; } + /// + /// 初始化类的新实例。 + /// + /// 签名的最小值,默认为1。 + /// 签名的最大值,默认为。 + /// + /// 签名范围用于控制自动生成的唯一标识符的取值范围。 + /// 当签名达到最大值时,会自动重置到最小值重新开始分配。 + /// + public WaitHandlePool(int minSign = 1, int maxSign = int.MaxValue) + { + this.m_minSign = minSign; + this.m_currentSign = minSign; + this.m_maxSign = maxSign; + this.m_remove = this.Remove; + } - /// - public int MinSign { get => this.m_minSign; set => this.m_minSign = value; } + private AsyncWaitData GetOrCreate(int sign, T pending) + { + if (this.s_pool.TryPop(out var item)) + { + item.Reset(sign, pending); + return item; + } + return new AsyncWaitData(sign, this.m_remove, pending); + } - /// + + /// + /// 取消池中所有等待操作。 + /// + /// + /// 此方法会遍历池中所有的等待数据,并调用其方法来取消等待。 + /// 取消后的等待数据会从池中移除。适用于应用程序关闭或需要批量取消所有等待操作的场景。 + /// public void CancelAll() { - var lockTaken = false; - try + var signs = this.m_waitDic.Keys.ToList(); + foreach (var sign in signs) { - this.m_lock.Enter(ref lockTaken); - foreach (var item in this.m_waitDic.Values) - { - item.Cancel(); - } - foreach (var item in this.m_waitDicAsync.Values) + if (this.m_waitDic.TryRemove(sign, out var item)) { item.Cancel(); } } - finally - { - if (lockTaken) - { - this.m_lock.Exit(false); - } - } } - /// - public void Destroy(int sign) + /// + /// 获取与指定结果关联的异步等待数据。 + /// + /// 要关联的结果对象。 + /// 指示是否自动为结果对象分配签名,默认为。 + /// 创建的实例。 + /// 当指定的签名已被使用时抛出。 + /// + /// 如果,方法会自动为结果对象生成唯一签名。 + /// 创建的等待数据会被添加到池中,直到被设置结果或取消时才会移除。 + /// + public AsyncWaitData GetWaitDataAsync(T result, bool autoSign = true) { - var lockTaken = false; - try + var sign = result.Sign; + if (autoSign) { - this.m_lock.Enter(ref lockTaken); - - if (this.m_waitDicAsync.TryRemove(sign, out var waitDataAsync)) - { - if (waitDataAsync.DisposedValue) - { - return; - } - - waitDataAsync.Reset(); - this.m_waitQueueAsync.Enqueue(waitDataAsync); - return; - } - - if (this.m_waitDic.TryRemove(sign, out var waitData)) - { - if (waitData.DisposedValue) - { - return; - } - waitData.Reset(); - this.m_waitQueue.Enqueue(waitData); - } - } - finally - { - if (lockTaken) - { - this.m_lock.Exit(false); - } - } - } - - /// - [Obsolete("此方法在调用时,可能导致不可控bug,已被弃用,请使用Destroy(in sign)的重载函数直接代替", true)] - public void Destroy(TWaitData waitData) - { - var lockTaken = false; - try - { - this.m_lock.Enter(ref lockTaken); - if (waitData.WaitResult == null) - { - return; - } - if (this.m_waitDic.Remove(waitData.WaitResult.Sign)) - { - if (waitData.DisposedValue) - { - return; - } - - waitData.Reset(); - this.m_waitQueue.Enqueue(waitData); - } - } - finally - { - if (lockTaken) - { - this.m_lock.Exit(false); - } - } - } - - /// - [Obsolete("此方法在调用时,可能导致不可控bug,已被弃用,请使用Destroy(in sign)的重载函数直接代替", true)] - public void Destroy(TWaitDataAsync waitData) - { - var lockTaken = false; - try - { - this.m_lock.Enter(ref lockTaken); - - if (waitData.WaitResult == null) - { - return; - } - if (this.m_waitDicAsync.Remove(waitData.WaitResult.Sign)) - { - if (waitData.DisposedValue) - { - return; - } - - waitData.Reset(); - this.m_waitQueueAsync.Enqueue(waitData); - } - } - finally - { - if (lockTaken) - { - this.m_lock.Exit(false); - } - } - } - - /// - public TWaitData GetWaitData(T result, bool autoSign = true) - { - var lockTaken = false; - try - { - this.m_lock.Enter(ref lockTaken); - if (this.m_waitQueue.TryDequeue(out var waitData)) - { - if (autoSign) - { - result.Sign = this.GetSign(); - } - waitData.SetResult(result); - this.m_waitDic.Add(result.Sign, waitData); - return waitData; - } - - // 如果队列中没有可取出的等待数据对象,则新建一个 - waitData = new TWaitData(); - // 如果自动签名开启,则为结果对象设置签名 - if (autoSign) - { - result.Sign = this.GetSign(); - } - // 设置等待数据对象的结果 - waitData.SetResult(result); - // 将结果对象的签名和等待数据对象添加到字典中 - this.m_waitDic.Add(result.Sign, waitData); - return waitData; - } - finally - { - if (lockTaken) - { - this.m_lock.Exit(false); - } - } - } - - /// - public TWaitData GetWaitData(out int sign) - { - var lockTaken = false; - try - { - this.m_lock.Enter(ref lockTaken); - // 尝试从同步等待队列中取出一个等待数据对象 - if (this.m_waitQueue.TryDequeue(out var waitData)) - { - // 生成签名 - sign = this.GetSign(); - // 设置等待数据对象的默认结果 - waitData.SetResult(default); - // 将签名和等待数据对象添加到字典中 - this.m_waitDic.Add(sign, waitData); - return waitData; - } - - // 如果队列中没有可取出的等待数据对象,则新建一个 - waitData = new TWaitData(); - // 生成签名 sign = this.GetSign(); - // 设置等待数据对象的默认结果 - waitData.SetResult(default); - // 将签名和等待数据对象添加到字典中 - this.m_waitDic.Add(sign, waitData); - return waitData; + result.Sign = sign; } - finally + var waitData = this.GetOrCreate(sign, result); + if (!this.m_waitDic.TryAdd(sign, waitData)) { - if (lockTaken) - { - this.m_lock.Exit(false); - } + ThrowHelper.ThrowInvalidOperationException($"The sign '{result.Sign}' is already in use."); } + return waitData; + } - /// - public TWaitDataAsync GetWaitDataAsync(T result, bool autoSign = true) + /// + /// 获取具有自动生成签名的异步等待数据。 + /// + /// 输出参数,返回自动生成的签名值。 + /// 创建的实例。 + /// 当生成的签名已被使用时抛出。 + /// + /// 此方法会自动生成唯一签名,并创建不包含挂起数据的等待对象。 + /// 适用于只需要等待通知而不关心具体数据内容的场景。 + /// + public AsyncWaitData GetWaitDataAsync(out int sign) { - var lockTaken = false; - try + sign = this.GetSign(); + var waitData = this.GetOrCreate(sign, default); + if (!this.m_waitDic.TryAdd(sign, waitData)) { - this.m_lock.Enter(ref lockTaken); - // 尝试从异步等待队列中取出一个等待数据对象 - if (this.m_waitQueueAsync.TryDequeue(out var waitData)) - { - // 如果自动签名开启,则为结果对象设置签名 - if (autoSign) - { - result.Sign = this.GetSign(); - } - // 设置等待数据对象的结果 - waitData.SetResult(result); - // 将结果对象的签名和等待数据对象添加到字典中 - this.m_waitDicAsync.Add(result.Sign, waitData); - return waitData; - } - - // 如果队列中没有可取出的等待数据对象,则新建一个 - waitData = new TWaitDataAsync(); - // 如果自动签名开启,则为结果对象设置签名 - if (autoSign) - { - result.Sign = this.GetSign(); - } - // 设置等待数据对象的结果 - waitData.SetResult(result); - // 将结果对象的签名和等待数据对象添加到字典中 - this.m_waitDicAsync.Add(result.Sign, waitData); - return waitData; - } - finally - { - if (lockTaken) - { - this.m_lock.Exit(false); - } + ThrowHelper.ThrowInvalidOperationException($"The sign '{sign}' is already in use."); } + return waitData; } - /// - public TWaitDataAsync GetWaitDataAsync(out int sign) + /// + /// 使用指定结果设置对应签名的等待操作。 + /// + /// 包含签名和结果数据的对象。 + /// 如果成功设置等待操作则返回;否则返回 + /// + /// 此方法根据结果对象的签名查找对应的等待数据,并设置其结果。 + /// 设置成功后,等待数据会从池中移除,正在等待的任务会被完成。 + /// 如果找不到对应签名的等待数据,则返回。 + /// + public bool Set(T result) { - var lockTaken = false; - try + if (this.m_waitDic.TryGetValue(result.Sign, out var waitDataAsync)) { - this.m_lock.Enter(ref lockTaken); - // 尝试从异步等待队列中取出一个等待数据对象 - if (this.m_waitQueueAsync.TryDequeue(out var waitData)) - { - // 生成签名 - sign = this.GetSign(); - // 设置等待数据对象的默认结果 - waitData.SetResult(default); - // 将签名和等待数据对象添加到字典中 - this.m_waitDicAsync.Add(sign, waitData); - return waitData; - } - - // 如果队列中没有可取出的等待数据对象,则新建一个 - waitData = new TWaitDataAsync(); - // 生成签名 - sign = this.GetSign(); - // 设置等待数据对象的默认结果 - waitData.SetResult(default); - // 将签名和等待数据对象添加到字典中 - this.m_waitDicAsync.Add(sign, waitData); - return waitData; - } - finally - { - if (lockTaken) - { - this.m_lock.Exit(false); - } + waitDataAsync.Set(result); + return true; } + return false; } - /// - public bool SetRun(int sign) + /// + /// 尝试获取指定签名的异步等待数据。 + /// + /// 要查找的签名。 + /// 输出参数,如果找到则返回对应的等待数据;否则为。 + /// 如果找到指定签名的等待数据则返回;否则返回 + /// + /// 此方法允许查询池中是否存在特定签名的等待数据,而不会修改池的状态。 + /// 适用于需要检查等待状态或获取等待数据进行进一步操作的场景。 + /// + public bool TryGetDataAsync(int sign, out AsyncWaitData waitDataAsync) { - var lockTaken = false; - try - { - this.m_lock.Enter(ref lockTaken); - // 尝试从异步等待数据字典中获取并设置等待数据 - if (this.m_waitDicAsync.TryGetValue(sign, out var waitDataAsync)) - { - waitDataAsync.Set(); - return true; - } - - // 尝试从同步等待数据字典中获取并设置等待数据 - if (this.m_waitDic.TryGetValue(sign, out var waitData)) - { - waitData.Set(); - return true; - } - - return false; - } - finally - { - if (lockTaken) - { - this.m_lock.Exit(false); - } - } - } - - /// - public bool SetRun(int sign, T waitResult) - { - var lockTaken = false; - try - { - this.m_lock.Enter(ref lockTaken); - // 尝试从异步等待数据字典中获取并设置等待数据 - if (this.m_waitDicAsync.TryGetValue(sign, out var waitDataAsync)) - { - waitDataAsync.Set(waitResult); - return true; - } - // 尝试从同步等待数据字典中获取并设置等待数据 - if (this.m_waitDic.TryGetValue(sign, out var waitData)) - { - waitData.Set(waitResult); - return true; - } - - return false; - } - finally - { - if (lockTaken) - { - this.m_lock.Exit(false); - } - } - } - - /// - public bool SetRun(T waitResult) - { - var lockTaken = false; - try - { - this.m_lock.Enter(ref lockTaken); - // 尝试从异步等待数据字典中获取并设置等待数据 - if (this.m_waitDicAsync.TryGetValue(waitResult.Sign, out var waitDataAsync)) - { - waitDataAsync.Set(waitResult); - return true; - } - - // 尝试从同步等待数据字典中获取并设置等待数据 - if (this.m_waitDic.TryGetValue(waitResult.Sign, out var waitData)) - { - waitData.Set(waitResult); - return true; - } - - return false; - } - finally - { - if (lockTaken) - { - this.m_lock.Exit(false); - } - } - } - - /// - public bool TryGetData(int sign, out TWaitData waitData) - { - var lockTaken = false; - try - { - this.m_lock.Enter(ref lockTaken); - return this.m_waitDic.TryGetValue(sign, out waitData); - } - finally - { - if (lockTaken) - { - this.m_lock.Exit(false); - } - } - } - - /// - public bool TryGetDataAsync(int sign, out TWaitDataAsync waitDataAsync) - { - var lockTaken = false; - try - { - this.m_lock.Enter(ref lockTaken); - return this.m_waitDicAsync.TryGetValue(sign, out waitDataAsync); - } - finally - { - if (lockTaken) - { - this.m_lock.Exit(false); - } - } - } - - /// - protected override void Dispose(bool disposing) - { - if (disposing) - { - var lockTaken = false; - try - { - this.m_lock.Enter(ref lockTaken); - foreach (var item in this.m_waitDic.Values) - { - item.SafeDispose(); - } - foreach (var item in this.m_waitQueue) - { - item.SafeDispose(); - } - this.m_waitDic.Clear(); - - this.m_waitQueue.Clear(); - } - finally - { - if (lockTaken) - { - this.m_lock.Exit(false); - } - } - } - - base.Dispose(disposing); + return this.m_waitDic.TryGetValue(sign, out waitDataAsync); } + /// + /// 生成下一个可用的唯一签名。 + /// + /// 生成的唯一签名值。 + /// + /// 使用原子递增操作确保签名的唯一性和线程安全性。 + /// 当签名达到最大值时,会重新开始分配以避免溢出。 + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private int GetSign() { - var sign = this.m_currentSign++; - if (this.m_currentSign >= this.m_maxSign) + while (true) { - this.m_currentSign = this.m_minSign; + var currentSign = this.m_currentSign; + var nextSign = currentSign >= this.m_maxSign ? this.m_minSign : currentSign + 1; + + if (Interlocked.CompareExchange(ref this.m_currentSign, nextSign, currentSign) == currentSign) + { + return nextSign; + } + // 如果CAS失败,继续重试 } - return sign; } -} -#endif + + /// + /// 从池中移除指定签名的等待数据。 + /// + /// 要移除的签名。 + /// + /// 此方法由等待数据在释放时自动调用,确保池中不会保留已完成或已取消的等待对象。 + /// + private void Remove(int sign) + { + if (this.m_waitDic.TryRemove(sign, out var asyncWaitData)) + { + this.s_pool.Push(asyncWaitData); + } + } + +} \ No newline at end of file diff --git a/src/TouchSocket.Core/WaitPool/WaitHandlePool_Net45.cs b/src/TouchSocket.Core/WaitPool/WaitHandlePool_Net45.cs deleted file mode 100644 index 83f155b74..000000000 --- a/src/TouchSocket.Core/WaitPool/WaitHandlePool_Net45.cs +++ /dev/null @@ -1,397 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -#if NET45 -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Threading; - -namespace TouchSocket.Core; - -/// -/// 表示一个等待句柄池。 -/// -/// 等待句柄的类型。 -public class WaitHandlePool : WaitHandlePool, WaitDataAsync, T>, IWaitHandlePool - where T : IWaitHandle -{ -} - -/// -/// 表示一个等待句柄池 -/// -/// 同步等待数据 -/// 异步等待数据 -/// 数据体 -public class WaitHandlePool : DisposableObject, IWaitHandlePool - where TWaitData : IWaitData, new() - where TWaitDataAsync : IWaitDataAsync, new() - where T : IWaitHandle -{ - /// - /// 不要设为readonly - /// - private readonly Lock m_lock = new Lock(); - private readonly Dictionary m_waitDic = new(); - private readonly Dictionary m_waitDicAsync = new(); - private readonly Queue m_waitQueue = new(); - private readonly Queue m_waitQueueAsync = new(); - private int m_currentSign; - private int m_maxSign = int.MaxValue; - private int m_minSign = 0; - - /// - public int MaxSign { get => this.m_maxSign; set => this.m_maxSign = value; } - - /// - public int MinSign { get => this.m_minSign; set => this.m_minSign = value; } - - /// - public void CancelAll() - { - lock (this.m_lock) - { - foreach (var item in this.m_waitDic.Values) - { - item.Cancel(); - } - foreach (var item in this.m_waitDicAsync.Values) - { - item.Cancel(); - } - } - } - - /// - [Obsolete("此方法在调用时,可能导致不可控bug,已被弃用,请使用Destroy(in sign)的重载函数直接代替", true)] - public void Destroy(TWaitData waitData) - { - lock (this.m_lock) - { - if (waitData.WaitResult == null) - { - return; - } - if (this.m_waitDic.Remove(waitData.WaitResult.Sign)) - { - if (waitData.DisposedValue) - { - return; - } - - waitData.Reset(); - this.m_waitQueue.Enqueue(waitData); - } - } - } - - /// - [Obsolete("此方法在调用时,可能导致不可控bug,已被弃用,请使用Destroy(in sign)的重载函数直接代替", true)] - public void Destroy(TWaitDataAsync waitData) - { - lock (this.m_lock) - { - if (waitData.WaitResult == null) - { - return; - } - if (this.m_waitDicAsync.Remove(waitData.WaitResult.Sign)) - { - if (waitData.DisposedValue) - { - return; - } - - waitData.Reset(); - this.m_waitQueueAsync.Enqueue(waitData); - } - } - } - - /// - public void Destroy(int sign) - { - lock (this.m_lock) - { - if (this.m_waitDicAsync.TryRemove(sign, out var waitDataAsync)) - { - if (waitDataAsync.DisposedValue) - { - return; - } - - waitDataAsync.Reset(); - this.m_waitQueueAsync.Enqueue(waitDataAsync); - return; - } - - if (this.m_waitDic.TryRemove(sign, out var waitData)) - { - if (waitData.DisposedValue) - { - return; - } - waitData.Reset(); - this.m_waitQueue.Enqueue(waitData); - } - } - } - - /// - public TWaitData GetWaitData(T result, bool autoSign = true) - { - lock (this.m_lock) - { - if (this.m_waitQueue.TryDequeue(out var waitData)) - { - if (autoSign) - { - result.Sign = this.GetSign(); - } - waitData.SetResult(result); - this.m_waitDic.Add(result.Sign, waitData); - return waitData; - } - - // 如果队列中没有可取出的等待数据对象,则新建一个 - waitData = new TWaitData(); - // 如果自动签名开启,则为结果对象设置签名 - if (autoSign) - { - result.Sign = this.GetSign(); - } - // 设置等待数据对象的结果 - waitData.SetResult(result); - // 将结果对象的签名和等待数据对象添加到字典中 - this.m_waitDic.Add(result.Sign, waitData); - return waitData; - } - } - - /// - public TWaitData GetWaitData(out int sign) - { - lock (this.m_lock) - { - // 尝试从同步等待队列中取出一个等待数据对象 - if (this.m_waitQueue.TryDequeue(out var waitData)) - { - // 生成签名 - sign = this.GetSign(); - // 设置等待数据对象的默认结果 - waitData.SetResult(default); - // 将签名和等待数据对象添加到字典中 - this.m_waitDic.Add(sign, waitData); - return waitData; - } - - // 如果队列中没有可取出的等待数据对象,则新建一个 - waitData = new TWaitData(); - // 生成签名 - sign = this.GetSign(); - // 设置等待数据对象的默认结果 - waitData.SetResult(default); - // 将签名和等待数据对象添加到字典中 - this.m_waitDic.Add(sign, waitData); - return waitData; - } - } - - /// - public TWaitDataAsync GetWaitDataAsync(T result, bool autoSign = true) - { - lock (this.m_lock) - { - // 尝试从异步等待队列中取出一个等待数据对象 - if (this.m_waitQueueAsync.TryDequeue(out var waitData)) - { - // 如果自动签名开启,则为结果对象设置签名 - if (autoSign) - { - result.Sign = this.GetSign(); - } - // 设置等待数据对象的结果 - waitData.SetResult(result); - // 将结果对象的签名和等待数据对象添加到字典中 - this.m_waitDicAsync.Add(result.Sign, waitData); - return waitData; - } - - // 如果队列中没有可取出的等待数据对象,则新建一个 - waitData = new TWaitDataAsync(); - // 如果自动签名开启,则为结果对象设置签名 - if (autoSign) - { - result.Sign = this.GetSign(); - } - // 设置等待数据对象的结果 - waitData.SetResult(result); - // 将结果对象的签名和等待数据对象添加到字典中 - this.m_waitDicAsync.Add(result.Sign, waitData); - return waitData; - } - } - - /// - public TWaitDataAsync GetWaitDataAsync(out int sign) - { - lock (this.m_lock) - { - // 尝试从异步等待队列中取出一个等待数据对象 - if (this.m_waitQueueAsync.TryDequeue(out var waitData)) - { - // 生成签名 - sign = this.GetSign(); - // 设置等待数据对象的默认结果 - waitData.SetResult(default); - // 将签名和等待数据对象添加到字典中 - this.m_waitDicAsync.Add(sign, waitData); - return waitData; - } - - // 如果队列中没有可取出的等待数据对象,则新建一个 - waitData = new TWaitDataAsync(); - // 生成签名 - sign = this.GetSign(); - // 设置等待数据对象的默认结果 - waitData.SetResult(default); - // 将签名和等待数据对象添加到字典中 - this.m_waitDicAsync.Add(sign, waitData); - return waitData; - } - } - - /// - public bool SetRun(int sign) - { - lock (this.m_lock) - { - // 尝试从异步等待数据字典中获取并设置等待数据 - if (this.m_waitDicAsync.TryGetValue(sign, out var waitDataAsync)) - { - waitDataAsync.Set(); - return true; - } - - // 尝试从同步等待数据字典中获取并设置等待数据 - if (this.m_waitDic.TryGetValue(sign, out var waitData)) - { - waitData.Set(); - return true; - } - - return false; - } - } - - /// - public bool SetRun(int sign, T waitResult) - { - lock (this.m_lock) - { - // 尝试从异步等待数据字典中获取并设置等待数据 - if (this.m_waitDicAsync.TryGetValue(sign, out var waitDataAsync)) - { - waitDataAsync.Set(waitResult); - return true; - } - // 尝试从同步等待数据字典中获取并设置等待数据 - if (this.m_waitDic.TryGetValue(sign, out var waitData)) - { - waitData.Set(waitResult); - return true; - } - - return false; - } - } - - /// - public bool SetRun(T waitResult) - { - lock (this.m_lock) - { - // 尝试从异步等待数据字典中获取并设置等待数据 - if (this.m_waitDicAsync.TryGetValue(waitResult.Sign, out var waitDataAsync)) - { - waitDataAsync.Set(waitResult); - return true; - } - - // 尝试从同步等待数据字典中获取并设置等待数据 - if (this.m_waitDic.TryGetValue(waitResult.Sign, out var waitData)) - { - waitData.Set(waitResult); - return true; - } - - return false; - } - } - - /// - public bool TryGetData(int sign, out TWaitData waitData) - { - lock (this.m_lock) - { - return this.m_waitDic.TryGetValue(sign, out waitData); - } - } - - /// - public bool TryGetDataAsync(int sign, out TWaitDataAsync waitDataAsync) - { - lock (this.m_lock) - { - return this.m_waitDicAsync.TryGetValue(sign, out waitDataAsync); - } - } - - /// - protected override void Dispose(bool disposing) - { - if (disposing) - { - lock (this.m_lock) - { - foreach (var item in this.m_waitDic.Values) - { - item.SafeDispose(); - } - foreach (var item in this.m_waitQueue) - { - item.SafeDispose(); - } - this.m_waitDic.Clear(); - - this.m_waitQueue.Clear(); - } - } - - base.Dispose(disposing); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int GetSign() - { - var sign = this.m_currentSign++; - if (this.m_currentSign >= this.m_maxSign) - { - this.m_currentSign = this.m_minSign; - } - return sign; - } -} -#endif diff --git a/src/TouchSocket.Core/WaitPool/WaitResult.cs b/src/TouchSocket.Core/WaitPool/WaitResult.cs index 232ea33ba..e310c15d0 100644 --- a/src/TouchSocket.Core/WaitPool/WaitResult.cs +++ b/src/TouchSocket.Core/WaitPool/WaitResult.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Core; /// diff --git a/src/TouchSocket.Dmtp.SourceGenerator/DmtpRpcClientSourceGenerator.cs b/src/TouchSocket.Dmtp.SourceGenerator/DmtpRpcClientSourceGenerator.cs index 7b46ccca9..73d73dc5e 100644 --- a/src/TouchSocket.Dmtp.SourceGenerator/DmtpRpcClientSourceGenerator.cs +++ b/src/TouchSocket.Dmtp.SourceGenerator/DmtpRpcClientSourceGenerator.cs @@ -12,36 +12,53 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Collections.Generic; using System.Linq; +using TouchSocket.Rpc; namespace TouchSocket; [Generator] -public class DmtpRpcClientSourceGenerator : ISourceGenerator +public class DmtpRpcClientSourceGenerator : IIncrementalGenerator { - public void Initialize(GeneratorInitializationContext context) + public void Initialize(IncrementalGeneratorInitializationContext context) { - context.RegisterForSyntaxNotifications(() => new DmtpRpcClientSyntaxReceiver()); - } + // 第一步:收集所有接口声明语法节点 + var interfaceDeclarations = context.SyntaxProvider + .CreateSyntaxProvider( + predicate: static (s, _) => s is InterfaceDeclarationSyntax, + transform: static (ctx, _) => (InterfaceDeclarationSyntax)ctx.Node); - public void Execute(GeneratorExecutionContext context) - { - var s = context.Compilation.GetMetadataReference(context.Compilation.Assembly); - - if (context.SyntaxReceiver is DmtpRpcClientSyntaxReceiver receiver) - { - var builders = receiver - .GetRpcApiTypes(context.Compilation) - .Select(i => new DmtpRpcClientCodeBuilder(i)) - .Distinct(CodeBuilderEqualityComparer.Default); - //Debugger.Launch(); - foreach (var builder in builders) + // 第二步:将语法节点转换为符号并过滤有效接口 + var interfacesWithSymbol = context.CompilationProvider.Combine(interfaceDeclarations.Collect()) + .SelectMany(static (tuple, _) => { - var tree = CSharpSyntaxTree.ParseText(builder.ToSourceText()); - var root = tree.GetRoot().NormalizeWhitespace(); - var ret = root.ToFullString(); - context.AddSource($"{builder.GetFileName()}.g.cs", ret); - } - } + var (compilation, interfaces) = tuple; + var results = new List(); + var attributeSymbol = RpcUtils.GetGeneratorRpcProxyAttribute(compilation); + + foreach (var interfaceSyntax in interfaces) + { + var model = compilation.GetSemanticModel(interfaceSyntax.SyntaxTree); + var interfaceSymbol = model.GetDeclaredSymbol(interfaceSyntax); + + if (interfaceSymbol != null && + attributeSymbol != null && + RpcUtils.IsRpcApiInterface(interfaceSymbol)) + { + results.Add(interfaceSymbol); + } + } + return results.Distinct(SymbolEqualityComparer.Default); + }); + + // 第三步:生成源代码 + context.RegisterSourceOutput(interfacesWithSymbol, + static (productionContext, interfaceSymbol) => + { + var builder = new DmtpRpcClientCodeBuilder((INamedTypeSymbol)interfaceSymbol); + productionContext.AddSource(builder); + }); } } \ No newline at end of file diff --git a/src/TouchSocket.Dmtp.SourceGenerator/DmtpRpcClientSyntaxReceiver.cs b/src/TouchSocket.Dmtp.SourceGenerator/DmtpRpcClientSyntaxReceiver.cs deleted file mode 100644 index 4af5196a2..000000000 --- a/src/TouchSocket.Dmtp.SourceGenerator/DmtpRpcClientSyntaxReceiver.cs +++ /dev/null @@ -1,51 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using System.Collections.Generic; -using TouchSocket.Rpc; - -namespace TouchSocket; - -internal sealed class DmtpRpcClientSyntaxReceiver : ISyntaxReceiver -{ - - private readonly List interfaceSyntaxList = new List(); - - void ISyntaxReceiver.OnVisitSyntaxNode(SyntaxNode syntaxNode) - { - if (syntaxNode is InterfaceDeclarationSyntax syntax) - { - this.interfaceSyntaxList.Add(syntax); - } - } - - public IEnumerable GetRpcApiTypes(Compilation compilation) - { - //Debugger.Launch(); - var generatorRpcProxyAttribute = RpcUtils.GetGeneratorRpcProxyAttribute(compilation); - if (generatorRpcProxyAttribute == null) - { - yield break; - } - foreach (var interfaceSyntax in this.interfaceSyntaxList) - { - var @interface = compilation.GetSemanticModel(interfaceSyntax.SyntaxTree).GetDeclaredSymbol(interfaceSyntax); - if (@interface != null && RpcUtils.IsRpcApiInterface(@interface)) - { - yield return @interface; - } - } - } -} \ No newline at end of file diff --git a/src/TouchSocket.Dmtp.SourceGenerator/TouchSocket.Dmtp.SourceGenerator.csproj b/src/TouchSocket.Dmtp.SourceGenerator/TouchSocket.Dmtp.SourceGenerator.csproj index 21cf0e4da..da0ff4299 100644 --- a/src/TouchSocket.Dmtp.SourceGenerator/TouchSocket.Dmtp.SourceGenerator.csproj +++ b/src/TouchSocket.Dmtp.SourceGenerator/TouchSocket.Dmtp.SourceGenerator.csproj @@ -18,6 +18,6 @@ - + diff --git a/src/TouchSocket.Dmtp/Actor/DmtpActor.cs b/src/TouchSocket.Dmtp/Actor/DmtpActor.cs index 06291bdc0..57d7f9580 100644 --- a/src/TouchSocket.Dmtp/Actor/DmtpActor.cs +++ b/src/TouchSocket.Dmtp/Actor/DmtpActor.cs @@ -10,14 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using Newtonsoft.Json; -using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Resources; using TouchSocket.Sockets; @@ -33,42 +26,44 @@ public abstract class DmtpActor : DisposableObject, IDmtpActor /// /// 请求关闭 /// - public Func Closing { get; set; } - - /// - /// 当创建通道时 - /// - public Func CreatedChannel { get; set; } - - /// - /// 查找其他IDmtpActor - /// - public Func> FindDmtpActor { get; set; } + public Func Closing { get; init; } /// /// 在完成握手连接时 /// - public Func Handshaked { get; set; } + public Func Connected { get; init; } /// /// 握手 /// - public Func Handshaking { get; set; } + public Func Connecting { get; init; } + + /// + /// 当创建通道时 + /// + public Func CreatedChannel { get; init; } + + /// + /// 查找其他IDmtpActor + /// + public Func> FindDmtpActor { get; init; } /// /// 重设Id /// - public Func IdChanged { get; set; } - - /// - /// 当需要路由的时候 - /// - public Func Routing { get; set; } + public Func IdChanged { get; init; } /// /// 异步发送数据接口 /// - public Func, Task> OutputSendAsync { get; set; } + public Func, CancellationToken, Task> OutputSendAsync { get; init; } + + /// + /// 当需要路由的时候 + /// + public Func Routing { get; init; } + + public ITransportWriter TransportWriter { get; init; } #endregion 委托 @@ -81,10 +76,10 @@ public abstract class DmtpActor : DisposableObject, IDmtpActor public IDmtpActorObject Client { get; set; } /// - public string Id { get; set; } + public CancellationToken ClosedToken => this.m_cancellationTokenSource.Token; /// - public virtual bool Online => this.m_online; + public string Id { get => this.m_id; init => this.m_id = value; } /// public bool IsReliable { get; } @@ -96,21 +91,24 @@ public abstract class DmtpActor : DisposableObject, IDmtpActor public ILog Logger { get; set; } /// - public WaitHandlePool WaitHandlePool { get; protected set; } + public virtual bool Online => this.m_online; /// - public CancellationToken ClosedToken => this.m_cancellationTokenSource.Token; + public WaitHandlePool WaitHandlePool { get; } #endregion 属性 #region 字段 + + private readonly Dictionary m_actors = new Dictionary(); + private readonly AsyncManualResetEvent m_handshakeFinished = new(false); + private readonly Lock m_syncRoot = new Lock(); private readonly ConcurrentDictionary m_userChannels = new ConcurrentDictionary(); - private readonly AsyncResetEvent m_handshakeFinished = new AsyncResetEvent(false, false); private CancellationTokenSource m_cancellationTokenSource; private bool m_online; - private readonly Lock m_syncRoot = new Lock(); - private readonly Dictionary m_actors = new Dictionary(); - #endregion + private string m_id; + + #endregion 字段 /// /// 创建一个Dmtp协议的最基础功能件 @@ -133,15 +131,18 @@ public abstract class DmtpActor : DisposableObject, IDmtpActor { } - /// /// 建立对点 /// /// /// /// - public virtual async Task HandshakeAsync(string verifyToken, string id, int millisecondsTimeout, Metadata metadata, CancellationToken token) + public virtual async Task ConnectAsync(DmtpOption dmtpOption, CancellationToken cancellationToken) { + var verifyToken = dmtpOption.VerifyToken; + var id = dmtpOption.Id; + var metadata = dmtpOption.Metadata; + if (this.m_online) { return; @@ -155,7 +156,7 @@ public abstract class DmtpActor : DisposableObject, IDmtpActor this.m_handshakeFinished.Reset(); - await this.OnHandshaking(args).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.OnConnecting(args).ConfigureAwait(EasyTask.ContinueOnCapturedContext); var waitVerify = new WaitVerify() { @@ -163,22 +164,20 @@ public abstract class DmtpActor : DisposableObject, IDmtpActor Id = args.Id, Metadata = args.Metadata }; - var waitData = this.WaitHandlePool.GetWaitDataAsync(waitVerify); - waitData.SetCancellationToken(token); - try + using (var waitData = this.WaitHandlePool.GetWaitDataAsync(waitVerify)) { - await this.SendJsonObjectAsync(P1_Handshake_Request, waitVerify).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - switch (await waitData.WaitAsync(millisecondsTimeout).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) + await this.SendJsonObjectAsync(P1_Handshake_Request, waitVerify, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + switch (await waitData.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) { - case WaitDataStatus.SetRunning: + case WaitDataStatus.Success: { - var verifyResult = (WaitVerify)waitData.WaitResult; + var verifyResult = (WaitVerify)waitData.CompletedData; if (verifyResult.Status == 1) { - this.Id = verifyResult.Id; + this.m_id = verifyResult.Id; this.m_online = true; - _ = EasyTask.SafeRun(this.PrivateOnHandshaked, new DmtpVerifyEventArgs() + _ = EasyTask.SafeRun(this.PrivateOnConnected, new DmtpVerifyEventArgs() { Id = verifyResult.Id, Metadata = verifyResult.Metadata, @@ -204,10 +203,6 @@ public abstract class DmtpActor : DisposableObject, IDmtpActor throw new OperationCanceledException(); } } - finally - { - this.WaitHandlePool.Destroy(waitVerify.Sign); - } } #region 委托触发 @@ -237,44 +232,30 @@ public abstract class DmtpActor : DisposableObject, IDmtpActor await this.Closing.Invoke(this, msg).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - /// - /// 正在握手连接 - /// - /// - /// - protected virtual Task OnHandshaking(DmtpVerifyEventArgs e) - { - if (this.Handshaking != null) - { - return this.Handshaking.Invoke(this, e); - } - return EasyTask.CompletedTask; - } - /// /// 握手连接完成 /// /// /// - protected virtual Task OnHandshaked(DmtpVerifyEventArgs e) + protected virtual Task OnConnected(DmtpVerifyEventArgs e) { - if (this.Handshaked != null) + if (this.Connected != null) { - return this.Handshaked.Invoke(this, e); + return this.Connected.Invoke(this, e); } return EasyTask.CompletedTask; } /// - /// 当Id修改时 + /// 正在握手连接 /// /// /// - protected virtual Task OnIdChanged(IdChangedEventArgs e) + protected virtual Task OnConnecting(DmtpVerifyEventArgs e) { - if (this.IdChanged != null) + if (this.Connecting != null) { - return this.IdChanged.Invoke(this, e); + return this.Connecting.Invoke(this, e); } return EasyTask.CompletedTask; } @@ -293,9 +274,23 @@ public abstract class DmtpActor : DisposableObject, IDmtpActor return EasyTask.CompletedTask; } - private async Task PrivateOnHandshaked(DmtpVerifyEventArgs e) + /// + /// 当Id修改时 + /// + /// + /// + protected virtual Task OnIdChanged(IdChangedEventArgs e) { - await this.OnHandshaked(e); + if (this.IdChanged != null) + { + return this.IdChanged.Invoke(this, e); + } + return EasyTask.CompletedTask; + } + + private async Task PrivateOnConnected(DmtpVerifyEventArgs e) + { + await this.OnConnected(e); } private async Task PrivateOnCreatedChannel(object obj) @@ -387,7 +382,7 @@ public abstract class DmtpActor : DisposableObject, IDmtpActor public virtual async Task InputReceivedData(DmtpMessage message) { this.LastActiveTime = DateTimeOffset.UtcNow; - var byteBlock = message.BodyByteBlock; + var reader = new BytesReader(message.Memory); switch (message.ProtocolFlags) { case P0_Close: @@ -407,17 +402,17 @@ public abstract class DmtpActor : DisposableObject, IDmtpActor Metadata = waitVerify.Metadata, Id = waitVerify.Id, }; - await this.OnHandshaking(args).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.OnConnecting(args).ConfigureAwait(EasyTask.ContinueOnCapturedContext); if (args.Id.HasValue()) { - await this.OnIdChanged(new IdChangedEventArgs(this.Id, args.Id)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.Id = args.Id; + await this.OnIdChanged(new IdChangedEventArgs(this.m_id, args.Id)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + this.m_id = args.Id; } if (args.IsPermitOperation) { - waitVerify.Id = this.Id; + waitVerify.Id = this.m_id; waitVerify.Status = 1; waitVerify.Metadata = args.Metadata; waitVerify.Message = args.Message ?? TouchSocketCoreResource.OperationSuccessful; @@ -425,7 +420,7 @@ public abstract class DmtpActor : DisposableObject, IDmtpActor this.m_online = true; args.Message ??= TouchSocketCoreResource.OperationSuccessful; this.m_cancellationTokenSource = new CancellationTokenSource(); - _ = EasyTask.SafeRun(this.PrivateOnHandshaked, args); + _ = EasyTask.SafeRun(this.PrivateOnConnected, args); } else//不允许连接 { @@ -448,7 +443,7 @@ public abstract class DmtpActor : DisposableObject, IDmtpActor try { var waitVerify = ResolveJsonObject(message.GetBodyString()); - this.WaitHandlePool.SetRun(waitVerify); + this.WaitHandlePool.Set(waitVerify); await this.m_handshakeFinished.WaitOneAsync(TimeSpan.FromSeconds(5)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } catch (Exception ex) @@ -466,7 +461,7 @@ public abstract class DmtpActor : DisposableObject, IDmtpActor try { await this.OnIdChanged(new IdChangedEventArgs(waitSetId.OldId, waitSetId.NewId)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.Id = waitSetId.NewId; + this.m_id = waitSetId.NewId; waitSetId.Status = 1; } catch (Exception ex) @@ -486,7 +481,7 @@ public abstract class DmtpActor : DisposableObject, IDmtpActor { try { - this.WaitHandlePool.SetRun(ResolveJsonObject(message.GetBodyString())); + this.WaitHandlePool.Set(ResolveJsonObject(message.GetBodyString())); } catch (Exception ex) { @@ -506,7 +501,7 @@ public abstract class DmtpActor : DisposableObject, IDmtpActor { if (await this.TryFindDmtpActor(waitPing.TargetId).ConfigureAwait(EasyTask.ContinueOnCapturedContext) is DmtpActor actor) { - await actor.SendAsync(P5_Ping_Request, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await actor.SendAsync(P5_Ping_Request, message.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return true; } else @@ -542,12 +537,12 @@ public abstract class DmtpActor : DisposableObject, IDmtpActor { if (await this.TryFindDmtpActor(waitPing.TargetId).ConfigureAwait(EasyTask.ContinueOnCapturedContext) is DmtpActor actor) { - await actor.SendAsync(P6_Ping_Response, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await actor.SendAsync(P6_Ping_Response, message.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } else { - this.WaitHandlePool.SetRun(waitPing); + this.WaitHandlePool.Set(waitPing); } } catch (Exception ex) @@ -562,31 +557,31 @@ public abstract class DmtpActor : DisposableObject, IDmtpActor { var waitCreateChannel = new WaitCreateChannelPackage(); - waitCreateChannel.UnpackageRouter(ref byteBlock); + waitCreateChannel.UnpackageRouter(ref reader); if (this.AllowRoute && waitCreateChannel.Route) { if (await this.TryRouteAsync(new PackageRouterEventArgs(RouteType.CreateChannel, waitCreateChannel)).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) { if (await this.TryFindDmtpActor(waitCreateChannel.TargetId).ConfigureAwait(EasyTask.ContinueOnCapturedContext) is DmtpActor actor) { - await actor.SendAsync(P7_CreateChannel_Request, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await actor.SendAsync(P7_CreateChannel_Request, message.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return true; } else { - waitCreateChannel.UnpackageBody(ref byteBlock); + waitCreateChannel.UnpackageBody(ref reader); waitCreateChannel.Status = TouchSocketDmtpStatus.ClientNotFind.ToValue(); } } else { - waitCreateChannel.UnpackageBody(ref byteBlock); + waitCreateChannel.UnpackageBody(ref reader); waitCreateChannel.Status = TouchSocketDmtpStatus.RoutingNotAllowed.ToValue(); } } else { - waitCreateChannel.UnpackageBody(ref byteBlock); + waitCreateChannel.UnpackageBody(ref reader); while (true) { @@ -608,11 +603,8 @@ public abstract class DmtpActor : DisposableObject, IDmtpActor } } waitCreateChannel.SwitchId(); - byteBlock.Reset(); - waitCreateChannel.Package(ref byteBlock); - - await this.SendAsync(P8_CreateChannel_Response, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.SendAsync(P8_CreateChannel_Response, waitCreateChannel).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } catch (Exception ex) { @@ -626,19 +618,19 @@ public abstract class DmtpActor : DisposableObject, IDmtpActor { var waitCreateChannel = new WaitCreateChannelPackage(); - waitCreateChannel.UnpackageRouter(ref byteBlock); + waitCreateChannel.UnpackageRouter(ref reader); if (this.AllowRoute && waitCreateChannel.Route) { if (await this.TryFindDmtpActor(waitCreateChannel.TargetId).ConfigureAwait(EasyTask.ContinueOnCapturedContext) is DmtpActor actor) { - await actor.SendAsync(P8_CreateChannel_Response, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await actor.SendAsync(P8_CreateChannel_Response, message.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return true; } } else { - waitCreateChannel.UnpackageBody(ref byteBlock); - this.WaitHandlePool.SetRun(waitCreateChannel); + waitCreateChannel.UnpackageBody(ref reader); + this.WaitHandlePool.Set(waitCreateChannel); } } catch (Exception ex) @@ -653,30 +645,25 @@ public abstract class DmtpActor : DisposableObject, IDmtpActor { var channelPackage = new ChannelPackage(); - channelPackage.UnpackageRouter(ref byteBlock); + channelPackage.UnpackageRouter(ref reader); if (this.AllowRoute && channelPackage.Route) { if (await this.TryFindDmtpActor(channelPackage.TargetId).ConfigureAwait(EasyTask.ContinueOnCapturedContext) is DmtpActor actor) { - await actor.SendAsync(P9_ChannelPackage, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await actor.SendAsync(P9_ChannelPackage, message.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } else { - channelPackage.UnpackageBody(ref byteBlock); + channelPackage.UnpackageBody(ref reader); channelPackage.Message = TouchSocketDmtpStatus.ClientNotFind.GetDescription(channelPackage.TargetId); channelPackage.SwitchId(); - channelPackage.RunNow = true; - channelPackage.DataType = ChannelDataType.DisposeOrder; - byteBlock.Reset(); - - channelPackage.Package(ref byteBlock); - - await this.SendAsync(P9_ChannelPackage, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + channelPackage.DataType = ChannelDataType.Canceled; + await this.SendAsync(P9_ChannelPackage, channelPackage).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } else { - channelPackage.UnpackageBody(ref byteBlock); + channelPackage.UnpackageBody(ref reader); this.QueueChannelPackage(channelPackage); } } @@ -698,45 +685,45 @@ public abstract class DmtpActor : DisposableObject, IDmtpActor } /// - public virtual Task PingAsync(int millisecondsTimeout = 5000) + public virtual Task PingAsync(CancellationToken cancellationToken = default) { - return this.PrivatePingAsync(default, millisecondsTimeout); + return this.PrivatePingAsync(default, cancellationToken); } /// - public virtual async Task PingAsync(string targetId, int millisecondsTimeout = 5000) + public virtual async Task PingAsync(string targetId, CancellationToken cancellationToken = default) { if (this.AllowRoute && await this.TryFindDmtpActor(targetId).ConfigureAwait(EasyTask.ContinueOnCapturedContext) is DmtpActor actor) { - return await actor.PingAsync(millisecondsTimeout).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return await actor.PingAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } else { - return await this.PrivatePingAsync(targetId, millisecondsTimeout).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return await this.PrivatePingAsync(targetId, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } /// - public virtual async Task ResetIdAsync(string newId) + public virtual async Task ResetIdAsync(string newId, CancellationToken cancellationToken = default) { - var waitSetId = new WaitSetId(this.Id, newId); + var waitSetId = new WaitSetId(this.m_id, newId); var waitData = this.WaitHandlePool.GetWaitDataAsync(waitSetId); - await this.SendJsonObjectAsync(P3_ResetId_Request, waitSetId).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.SendJsonObjectAsync(P3_ResetId_Request, waitSetId, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - switch (await waitData.WaitAsync(5000).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) + switch (await waitData.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) { - case WaitDataStatus.SetRunning: + case WaitDataStatus.Success: { - if (waitData.WaitResult.Status == 1) + if (waitData.CompletedData.Status == 1) { - await this.OnIdChanged(new IdChangedEventArgs(this.Id, newId)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.Id = newId; + await this.OnIdChanged(new IdChangedEventArgs(this.m_id, newId)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + this.m_id = newId; } else { - throw new Exception(waitData.WaitResult.Message); + throw new Exception(waitData.CompletedData.Message); } break; } @@ -751,50 +738,10 @@ public abstract class DmtpActor : DisposableObject, IDmtpActor } } - private Task SendJsonObjectAsync(ushort protocol, in T obj) - { -#if SystemTextJson - var bytes = System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(obj, typeof(T), TouchSokcetDmtpSourceGenerationContext.Default); -#else - var bytes = JsonConvert.SerializeObject(obj).ToUtf8Bytes(); -#endif - - return this.SendAsync(protocol, bytes); - } - - private static T ResolveJsonObject(string json) - { -#if SystemTextJson - - return (T)System.Text.Json.JsonSerializer.Deserialize(json, typeof(T), TouchSokcetDmtpSourceGenerationContext.Default); -#else - return JsonConvert.DeserializeObject(json); -#endif - } - - /// - public virtual async Task SendPackageAsync(ushort protocol, IPackage package) - { - using (var byteBlock = new ByteBlock(1024 * 64)) - { - var block = byteBlock; - package.Package(ref block); - - await this.SendAsync(protocol, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - } - - /// - public virtual Task SendStringAsync(ushort protocol, string value) - { - var data = Encoding.UTF8.GetBytes(value); - return this.SendAsync(protocol, data); - } - /// public virtual async Task TryFindDmtpActor(string targetId) { - if (targetId == this.Id) + if (targetId == this.m_id) { return this; } @@ -827,29 +774,33 @@ public abstract class DmtpActor : DisposableObject, IDmtpActor } } - private async Task PrivatePingAsync(string targetId, int millisecondsTimeout) + private static T ResolveJsonObject(string json) + { + return (T)System.Text.Json.JsonSerializer.Deserialize(json, typeof(T), TouchSocketDmtpSourceGenerationContext.Default); + } + + private async Task PrivatePingAsync(string targetId, CancellationToken cancellationToken = default) { if (!this.Online) { - return false; + return Result.FromFail(TouchSocketResource.ClientNotConnected); } var waitPing = new WaitPing { TargetId = targetId, - SourceId = this.Id + SourceId = this.m_id }; - var waitData = this.WaitHandlePool.GetWaitDataAsync(waitPing); - try + using (var waitData = this.WaitHandlePool.GetWaitDataAsync(waitPing)) { - await this.SendJsonObjectAsync(P5_Ping_Request, waitPing).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - switch (await waitData.WaitAsync(millisecondsTimeout).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) + await this.SendJsonObjectAsync(P5_Ping_Request, waitPing, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + switch (await waitData.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) { - case WaitDataStatus.SetRunning: + case WaitDataStatus.Success: { - return waitData.WaitResult.Status.ToStatus() switch + return waitData.CompletedData.Status.ToStatus() switch { - TouchSocketDmtpStatus.Success => true, - _ => false, + TouchSocketDmtpStatus.Success => Result.Success, + _ => Result.UnknownFail, }; } case WaitDataStatus.Default: @@ -857,49 +808,23 @@ public abstract class DmtpActor : DisposableObject, IDmtpActor case WaitDataStatus.Canceled: case WaitDataStatus.Disposed: default: - return false; + return Result.UnknownFail; } } - catch - { - return false; - } - finally - { - this.WaitHandlePool.Destroy(waitPing.Sign); - } } #region Actor - /// - public bool TryAddActor(TActor actor) where TActor : class, IActor - { - ThrowHelper.ThrowArgumentNullExceptionIf(actor, nameof(actor)); - var type = typeof(TActor); - - lock (this.m_syncRoot) - { - if (this.m_actors.ContainsKey(type)) - { - return false; - } - this.m_actors.Add(type, actor); - return true; - } - - } /// public void AddActor(TActor actor) where TActor : class, IActor { - ThrowHelper.ThrowArgumentNullExceptionIf(actor, nameof(actor)); + ThrowHelper.ThrowIfNull(actor, nameof(actor)); var type = typeof(TActor); lock (this.m_syncRoot) { this.m_actors.AddOrUpdate(type, actor); } - } /// @@ -913,47 +838,29 @@ public abstract class DmtpActor : DisposableObject, IDmtpActor return default; } - #endregion + /// + public bool TryAddActor(TActor actor) where TActor : class, IActor + { + ThrowHelper.ThrowIfNull(actor, nameof(actor)); + var type = typeof(TActor); + + lock (this.m_syncRoot) + { + if (this.m_actors.ContainsKey(type)) + { + return false; + } + this.m_actors.Add(type, actor); + return true; + } + } + + #endregion Actor #region 断开 /// - protected override void Dispose(bool disposing) - { - if (disposing) - { - lock (this.m_syncRoot) - { - if (!this.m_online) - { - return; - } - this.m_online = false; - - this.Closing = null; - this.Routing = null; - this.FindDmtpActor = null; - this.Handshaked = null; - this.Handshaking = null; - this.IdChanged = null; - //this.OutputSend = null; - - foreach (var item in this.m_actors) - { - item.Value.SafeDispose(); - } - - this.m_actors.Clear(); - this.WaitHandlePool.CancelAll(); - this.WaitHandlePool.SafeDispose(); - } - } - - base.Dispose(disposing); - } - - /// - public async Task CloseAsync(string msg, CancellationToken token = default) + public async Task CloseAsync(string msg, CancellationToken cancellationToken = default) { try { @@ -970,8 +877,9 @@ public abstract class DmtpActor : DisposableObject, IDmtpActor /// 异步发送关闭消息 /// /// + /// /// - public async Task SendCloseAsync(string msg) + public async Task SendCloseAsync(string msg, CancellationToken cancellationToken = default) { if (!this.m_online) { @@ -979,7 +887,7 @@ public abstract class DmtpActor : DisposableObject, IDmtpActor } try { - await this.SendStringAsync(0, msg).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.SendAsync(0, msg, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return Result.Success; } catch (Exception ex) @@ -988,38 +896,164 @@ public abstract class DmtpActor : DisposableObject, IDmtpActor } } + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + lock (this.m_syncRoot) + { + if (!this.m_online) + { + return; + } + this.m_online = false; + foreach (var item in this.m_actors) + { + item.Value.SafeDispose(); + } + + this.m_handshakeFinished.Set(); + + this.m_actors.Clear(); + this.WaitHandlePool.CancelAll(); + } + } + + base.Dispose(disposing); + } + #endregion 断开 #region 协议异步发送 + private readonly SemaphoreSlim m_semaphoreSlim = new SemaphoreSlim(1, 1); + + // try + // { + // writer.Write(DmtpMessage.Head); + // WriterExtension.WriteValue(ref writer, protocol, EndianType.Big); + // var writerAnchor = new WriterAnchor(ref writer, 4); + // WriterExtension.WriteNormalString(ref writer, value, Encoding.UTF8); + // var lengthSpan = writerAnchor.Rewind(ref writer, out var length); + // lengthSpan.WriteValue(length, EndianType.Big); + // await writer.FlushAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + // this.LastActiveTime = DateTimeOffset.UtcNow; + // } + // finally + // { + // writer.Dispose(); + // } + //} /// - public virtual async Task SendAsync(ushort protocol, ReadOnlyMemory memory) + public virtual async Task SendAsync(ushort protocol, ReadOnlyMemory memory, CancellationToken cancellationToken = default) { - using (var byteBlock = new ValueByteBlock(memory.Length + 8)) + var writer = new SegmentedBytesWriter(memory.Length + 8); + await this.m_semaphoreSlim.WaitAsync(cancellationToken); + try { - byteBlock.Write(DmtpMessage.Head); - byteBlock.WriteUInt16(protocol, EndianType.Big); - byteBlock.WriteInt32(memory.Length, EndianType.Big); - byteBlock.Write(memory.Span); + writer.Write(DmtpMessage.Head); + writer.WriteValue(protocol, EndianType.Big); + WriterExtension.WriteValue(ref writer, memory.Length, EndianType.Big); + writer.Write(memory.Span); - await this.OutputSendAsync.Invoke(this, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + foreach (var item in writer.Sequence) + { + await this.OutputSendAsync.Invoke(this, item, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + } + finally + { + writer.Dispose(); + this.m_semaphoreSlim.Release(); } - - this.LastActiveTime = DateTimeOffset.UtcNow; } + ///// + //public async Task SendAsync(ushort protocol, string value, CancellationToken cancellationToken = default) + //{ + // var writer = await this.Transport.CreateWriter(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + public async Task SendAsync(ushort protocol, TPackage package, CancellationToken cancellationToken = default) + where TPackage : IPackage + { + var byteBlock = new ByteBlock(1024 * 64); + try + { + package.Package(ref byteBlock); + await this.SendAsync(protocol, byteBlock.Memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + finally + { + byteBlock.Dispose(); + } + } + + // try + // { + // writer.Write(DmtpMessage.Head); + // WriterExtension.WriteValue(ref writer, protocol, EndianType.Big); + // var writerAnchor = new WriterAnchor(ref writer,4); + // package.Package(ref writer); + // var lengthSpan=writerAnchor.Rewind(ref writer, out var length); + // lengthSpan.WriteValue(length, EndianType.Big); + // await writer.FlushAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + // this.LastActiveTime = DateTimeOffset.UtcNow; + // } + // finally + // { + // writer.Dispose(); + // } + //} + /// + public async Task SendAsync(ushort protocol, string value, CancellationToken cancellationToken = default) + { + var byteBlock = new ByteBlock(1024 * 64); + try + { + WriterExtension.WriteNormalString(ref byteBlock, value, Encoding.UTF8); + await this.SendAsync(protocol, byteBlock.Memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + finally + { + byteBlock.Dispose(); + } + } + + private Task SendJsonObjectAsync(ushort protocol, in T obj, CancellationToken cancellationToken = default) + { + var bytes = System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(obj, typeof(T), TouchSocketDmtpSourceGenerationContext.Default); + return this.SendAsync(protocol, bytes, cancellationToken); + } + + ///// + //public virtual async Task SendAsync(ushort protocol, ReadOnlyMemory memory, CancellationToken cancellationToken = default) + //{ + // var writer = await this.Transport.CreateWriter(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + // try + // { + // writer.Write(DmtpMessage.Head); + // WriterExtension.WriteValue(ref writer, protocol, EndianType.Big); + // WriterExtension.WriteValue(ref writer, memory.Length, EndianType.Big); + // writer.Write(memory.Span); + // await writer.FlushAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + // this.LastActiveTime = DateTimeOffset.UtcNow; + // } + // finally + // { + // writer.Dispose(); + // } + //} + + //public async Task SendAsync(ushort protocol, TPackage package, CancellationToken cancellationToken = default) + // where TPackage:IPackage + //{ + // var writer = await this.Transport.CreateWriter(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + #endregion 协议异步发送 #region IDmtpChannel - private void CheckChannelShouldBeReliable() - { - if (!this.IsReliable) - { - throw new NotSupportedException("Channel不支持在不可靠协议使用。"); - } - } - /// public virtual bool ChannelExisted(int id) { @@ -1028,19 +1062,19 @@ public abstract class DmtpActor : DisposableObject, IDmtpActor } /// - public virtual Task CreateChannelAsync(Metadata metadata = default) + public virtual Task CreateChannelAsync(Metadata metadata = default, CancellationToken cancellationToken = default) { - return this.PrivateCreateChannelAsync(default, true, 0, metadata); + return this.PrivateCreateChannelAsync(default, true, 0, metadata, cancellationToken); } /// - public virtual Task CreateChannelAsync(int id, Metadata metadata = default) + public virtual Task CreateChannelAsync(int id, Metadata metadata = default, CancellationToken cancellationToken = default) { - return this.PrivateCreateChannelAsync(default, false, id, metadata); + return this.PrivateCreateChannelAsync(default, false, id, metadata, cancellationToken); } /// - public virtual async Task CreateChannelAsync(string targetId, int id, Metadata metadata = default) + public virtual async Task CreateChannelAsync(string targetId, int id, Metadata metadata = default, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(targetId)) { @@ -1048,16 +1082,16 @@ public abstract class DmtpActor : DisposableObject, IDmtpActor } if (this.AllowRoute && await this.TryFindDmtpActor(targetId).ConfigureAwait(EasyTask.ContinueOnCapturedContext) is DmtpActor actor) { - return await actor.CreateChannelAsync(id, metadata).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return await actor.CreateChannelAsync(id, metadata, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } else { - return await this.PrivateCreateChannelAsync(targetId, false, id, metadata).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return await this.PrivateCreateChannelAsync(targetId, false, id, metadata, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } /// - public virtual async Task CreateChannelAsync(string targetId, Metadata metadata = default) + public virtual async Task CreateChannelAsync(string targetId, Metadata metadata = default, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(targetId)) { @@ -1066,11 +1100,11 @@ public abstract class DmtpActor : DisposableObject, IDmtpActor if (this.AllowRoute && await this.TryFindDmtpActor(targetId).ConfigureAwait(EasyTask.ContinueOnCapturedContext) is DmtpActor actor) { - return await actor.CreateChannelAsync(metadata).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return await actor.CreateChannelAsync(metadata, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } else { - return await this.PrivateCreateChannelAsync(targetId, true, 0, metadata).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return await this.PrivateCreateChannelAsync(targetId, true, 0, metadata, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } @@ -1097,17 +1131,25 @@ public abstract class DmtpActor : DisposableObject, IDmtpActor return this.m_userChannels.TryRemove(id, out _); } - internal async Task SendChannelPackageAsync(ChannelPackage channelPackage) + internal async Task SendChannelPackageAsync(ChannelPackage channelPackage, CancellationToken cancellationToken) { using (var byteBlock = new ByteBlock(channelPackage.GetLen())) { var block = byteBlock; channelPackage.Package(ref block); - await this.SendAsync(P9_ChannelPackage, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.SendAsync(P9_ChannelPackage, byteBlock.Memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } - private async Task PrivateCreateChannelAsync(string targetId, bool random, int id, Metadata metadata) + private void CheckChannelShouldBeReliable() + { + if (!this.IsReliable) + { + throw new NotSupportedException("Channel不支持在不可靠协议使用。"); + } + } + + private async Task PrivateCreateChannelAsync(string targetId, bool random, int id, Metadata metadata, CancellationToken cancellationToken) { this.CheckChannelShouldBeReliable(); @@ -1122,68 +1164,68 @@ public abstract class DmtpActor : DisposableObject, IDmtpActor throw new Exception(TouchSocketDmtpStatus.ChannelExisted.GetDescription(id)); } } - var byteBlock = new ByteBlock(1024 * 64); - var waitCreateChannel = new WaitCreateChannelPackage() - { - Random = random, - ChannelId = id, - SourceId = this.Id, - TargetId = targetId, - Metadata = metadata - }; - var waitData = this.WaitHandlePool.GetWaitDataAsync(waitCreateChannel); - try { - waitCreateChannel.Package(ref byteBlock); - - await this.SendAsync(P7_CreateChannel_Request, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - switch (await waitData.WaitAsync(10 * 1000).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) + var waitCreateChannel = new WaitCreateChannelPackage() { - case WaitDataStatus.SetRunning: - { - var result = (WaitCreateChannelPackage)waitData.WaitResult; - switch (result.Status.ToStatus()) + Random = random, + ChannelId = id, + SourceId = this.m_id, + TargetId = targetId, + Metadata = metadata + }; + + using (var waitData = this.WaitHandlePool.GetWaitDataAsync(waitCreateChannel)) + { + waitCreateChannel.Package(ref byteBlock); + + await this.SendAsync(P7_CreateChannel_Request, byteBlock.Memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + switch (await waitData.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) + { + case WaitDataStatus.Success: { - case TouchSocketDmtpStatus.Success: - { - var channel = new InternalChannel(this, targetId, result.Metadata); - channel.SetId(result.ChannelId); - channel.MakeUsing(); - if (this.m_userChannels.TryAdd(result.ChannelId, channel)) + var result = (WaitCreateChannelPackage)waitData.CompletedData; + switch (result.Status.ToStatus()) + { + case TouchSocketDmtpStatus.Success: { - return channel; + var channel = new InternalChannel(this, targetId, result.Metadata); + channel.SetId(result.ChannelId); + channel.MakeUsing(); + if (this.m_userChannels.TryAdd(result.ChannelId, channel)) + { + return channel; + } + else + { + throw new Exception(TouchSocketDmtpStatus.UnknownError.GetDescription()); + } } - else + case TouchSocketDmtpStatus.ClientNotFind: { - throw new Exception(TouchSocketDmtpStatus.UnknownError.GetDescription()); + throw new Exception(TouchSocketDmtpStatus.ClientNotFind.GetDescription(targetId)); } - } - case TouchSocketDmtpStatus.ClientNotFind: - { - throw new Exception(TouchSocketDmtpStatus.ClientNotFind.GetDescription(targetId)); - } - case TouchSocketDmtpStatus.RoutingNotAllowed: - default: - { - throw new Exception(result.Status.ToStatus().GetDescription(result.Message)); - } + case TouchSocketDmtpStatus.RoutingNotAllowed: + default: + { + throw new Exception(result.Status.ToStatus().GetDescription(result.Message)); + } + } } - } - case WaitDataStatus.Overtime: - { - throw new TimeoutException(TouchSocketDmtpStatus.Overtime.GetDescription()); - } - default: - { - throw new Exception(TouchSocketDmtpStatus.UnknownError.GetDescription()); - } + case WaitDataStatus.Overtime: + { + throw new TimeoutException(TouchSocketDmtpStatus.Overtime.GetDescription()); + } + default: + { + throw new Exception(TouchSocketDmtpStatus.UnknownError.GetDescription()); + } + } } } finally { - this.WaitHandlePool.Destroy(waitCreateChannel.Sign); byteBlock.Dispose(); } } @@ -1209,7 +1251,6 @@ public abstract class DmtpActor : DisposableObject, IDmtpActor } else { - channel.SafeDispose(); return false; } } diff --git a/src/TouchSocket.Dmtp/Actor/IDmtpActor.cs b/src/TouchSocket.Dmtp/Actor/IDmtpActor.cs index dc5cd771d..bb42cc33c 100644 --- a/src/TouchSocket.Dmtp/Actor/IDmtpActor.cs +++ b/src/TouchSocket.Dmtp/Actor/IDmtpActor.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Sockets; namespace TouchSocket.Dmtp; @@ -35,11 +31,6 @@ public interface IDmtpActor : IDisposableObject, IOnlineClient, IClosableClient, /// IDmtpActorObject Client { get; } - /// - /// 关闭标记 - /// - CancellationToken ClosedToken { get; } - /// /// 是否基于可靠协议构建。例如:基于Tcp则为,基于Udp则为。 /// @@ -75,16 +66,18 @@ public interface IDmtpActor : IDisposableObject, IOnlineClient, IClosableClient, /// 在当前对点创建一个随机Id的通道 /// /// 可选的元数据参数,用于传递额外的信息 + /// 可取消令箭 /// 返回一个异步任务,该任务完成后将提供创建的IDmtpChannel对象 - Task CreateChannelAsync(Metadata metadata = default); + Task CreateChannelAsync(Metadata metadata = default, CancellationToken cancellationToken = default); /// /// 在当前对点创建一个指定Id的通道 /// /// 要创建的通道的唯一标识符 /// 可选参数,提供有关通道的元数据信息 + /// 可取消令箭 /// 返回创建的通道对象,类型为IDmtpChannel - Task CreateChannelAsync(int id, Metadata metadata = default); + Task CreateChannelAsync(int id, Metadata metadata = default, CancellationToken cancellationToken = default); /// /// 在指定路由点创建一个指定Id的通道 @@ -92,16 +85,18 @@ public interface IDmtpActor : IDisposableObject, IOnlineClient, IClosableClient, /// 目标路由点的标识符 /// 要创建的通道的唯一标识符 /// 有关通道的元数据,可选,默认为default(Metadata) + /// 可取消令箭 /// 返回一个异步任务,该任务完成后将包含新创建的IDmtpChannel接口实例 - Task CreateChannelAsync(string targetId, int id, Metadata metadata = default); + Task CreateChannelAsync(string targetId, int id, Metadata metadata = default, CancellationToken cancellationToken = default); /// /// 在指定路由点创建一个随机Id的通道 /// /// 目标路由点的标识符 /// 可选参数,用于传递附加信息 + /// 可取消令箭 /// 返回一个异步任务,该任务完成后将提供创建的IDmtpChannel对象 - Task CreateChannelAsync(string targetId, Metadata metadata = default); + Task CreateChannelAsync(string targetId, Metadata metadata = default, CancellationToken cancellationToken = default); /// /// 尝试订阅已存在的通道。 @@ -122,16 +117,6 @@ public interface IDmtpActor : IDisposableObject, IOnlineClient, IClosableClient, /// 要添加的 Actor 实例。 void AddActor(TActor actor) where TActor : class, IActor; - - - /// - /// 尝试添加一个实现了 接口的 Actor 实例。 - /// - /// Actor 的具体类型,必须实现 接口。 - /// 要添加的 Actor 实例。 - /// 如果添加成功则返回 ,否则返回 - bool TryAddActor(TActor actor) where TActor : class, IActor; - /// /// 获取指定类型的 Actor 实例。 /// @@ -142,43 +127,40 @@ public interface IDmtpActor : IDisposableObject, IOnlineClient, IClosableClient, /// /// 向当前对点发送一个Ping报文,并且等待回应。 /// - /// 超时时间,单位为毫秒,默认为5000毫秒。用于控制等待回应的最大时长。 + /// 可取消令箭 /// 一般的,当返回时,则表明对点一定存在。而其他情况则返回。该方法主要用于检测对端点的可达性。 - Task PingAsync(int millisecondsTimeout = 5000); + Task PingAsync(CancellationToken cancellationToken = default); /// /// 向指定路由点发送一个Ping报文,并且等待回应。 /// /// 目标路由点的标识符。 - /// 等待回应的超时时间,单位为毫秒。默认为5000毫秒。 + /// 可取消令箭 /// 一般的,当返回时,则表明对点一定存在。而其他情况则返回 - Task PingAsync(string targetId, int millisecondsTimeout = 5000); + Task PingAsync(string targetId, CancellationToken cancellationToken = default); /// /// 异步发送数据。 /// /// 指定通信协议的标识符。 /// 待发送的数据,以只读内存形式提供。 + /// 可取消令箭 /// /// 此方法用于异步发送数据,通过指定协议标识符和数据内容,实现数据的异步传输。 /// - Task SendAsync(ushort protocol, ReadOnlyMemory memory); + Task SendAsync(ushort protocol, ReadOnlyMemory memory, CancellationToken cancellationToken = default); + + Task SendAsync(ushort protocol, TPackage package, CancellationToken cancellationToken = default) where TPackage : IPackage; + + Task SendAsync(ushort protocol, string value, CancellationToken cancellationToken = default); /// - /// 异步发送小(64K)对象的包。接收方可以通过ReadPackage来接收。 + /// 尝试添加一个实现了 接口的 Actor 实例。 /// - /// 发送包时使用的协议标识。 - /// 要发送的包实例。 - /// 返回一个Task对象,表示异步操作的完成。 - Task SendPackageAsync(ushort protocol, IPackage package); - - /// - /// 异步发送以utf-8编码的字符串。 - /// - /// 指定的协议编号。 - /// 要发送的字符串内容。 - /// 时抛出异常。 - Task SendStringAsync(ushort protocol, string value); + /// Actor 的具体类型,必须实现 接口。 + /// 要添加的 Actor 实例。 + /// 如果添加成功则返回 ,否则返回 + bool TryAddActor(TActor actor) where TActor : class, IActor; /// /// 尝试获取指定Id的DmtpActor。一般此方法仅在Service下有效。 diff --git a/src/TouchSocket.Dmtp/Adapter/DmtpAdapter.cs b/src/TouchSocket.Dmtp/Adapter/DmtpAdapter.cs index e8cf61c88..e719fedd3 100644 --- a/src/TouchSocket.Dmtp/Adapter/DmtpAdapter.cs +++ b/src/TouchSocket.Dmtp/Adapter/DmtpAdapter.cs @@ -10,59 +10,56 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Dmtp; /// /// DmtpAdapter 类,继承自 CustomFixedHeaderByteBlockDataHandlingAdapter<DmtpMessage> /// 该类用于特定地处理 DmtpMessage,通过自定义的固定头部字节块数据处理适配器实现。 /// -public class DmtpAdapter : CustomFixedHeaderByteBlockDataHandlingAdapter +public sealed class DmtpAdapter : CustomDataHandlingAdapter { /// public override bool CanSendRequestInfo => true; /// - public override bool CanSplicingSend => true; - - /// - public override int HeaderLength => 8; - - /// - /// 最大拼接 - /// - public const int MaxSplicing = 1024 * 64; - - /// - protected override DmtpMessage GetInstance() + protected override FilterResult Filter(ref TReader reader, bool beCached, ref DmtpMessage request) { - return new DmtpMessage(); + if (reader.BytesRemaining < 8) + { + request = default; + return FilterResult.Cache; + } + var header = reader.GetSpan(8); + var offset = 0; + if (header[offset++] != DmtpMessage.Head[0] || header[offset++] != DmtpMessage.Head[1]) + { + throw new Exception("这可能不是Dmtp协议数据"); + } + var protocolFlags = TouchSocketBitConverter.BigEndian.To(header.Slice(offset)); + offset += 2; + var m_bodyLength = TouchSocketBitConverter.BigEndian.To(header.Slice(offset)); + + if (reader.BytesRemaining < m_bodyLength + 8) + { + request = default; + return FilterResult.Cache; + } + + reader.Advance(8); + var bodyMemory = reader.GetMemory(m_bodyLength); + reader.Advance(m_bodyLength); + + request = new DmtpMessage(protocolFlags, bodyMemory); + return FilterResult.Success; } - /// - protected override void OnReceivedSuccess(DmtpMessage request) - { - request.SafeDispose(); - } - - /// - protected override async Task PreviewSendAsync(IRequestInfo requestInfo) + public override void SendInput(ref TWriter writer, IRequestInfo requestInfo) { if (requestInfo is not DmtpMessage message) { throw new Exception($"无法将{nameof(requestInfo)}转换为{nameof(DmtpMessage)}"); } - this.ThrowIfMoreThanMaxPackageSize(message.MaxLength); - - using (var byteBlock = new ByteBlock(message.MaxLength)) - { - var block = byteBlock; - message.Build(ref block); - await this.GoSendAsync(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } + message.Build(ref writer); } } \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Adapter/DmtpAdapterSlim.cs b/src/TouchSocket.Dmtp/Adapter/DmtpAdapterSlim.cs new file mode 100644 index 000000000..f98be84dd --- /dev/null +++ b/src/TouchSocket.Dmtp/Adapter/DmtpAdapterSlim.cs @@ -0,0 +1,52 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +//using System; +//using System.Threading; +//using System.Threading.Tasks; +//using TouchSocket.Core; + +//namespace TouchSocket.Dmtp; + +//public sealed class DmtpAdapterSlim : DataHandlingAdapterSlim +//{ +// protected override bool ParseRequestCore(ref TReader reader, out DmtpMessage request) +// { +// if (reader.BytesRemaining < 8) +// { +// request = default; +// return false; +// } +// var header = reader.GetSpan(8); +// var offset = 0; +// if (header[offset++] != DmtpMessage.Head[0] || header[offset++] != DmtpMessage.Head[1]) +// { +// throw new Exception("这可能不是Dmtp协议数据"); +// } +// var protocolFlags = TouchSocketBitConverter.BigEndian.To(header.Slice(offset)); +// offset += 2; +// var m_bodyLength = TouchSocketBitConverter.BigEndian.To(header.Slice(offset)); + +// if (reader.BytesRemaining < m_bodyLength + 8) +// { +// request = default; +// return false; +// } + +// reader.Advance(8); +// var bodyMemory = reader.GetMemory(m_bodyLength); +// reader.Advance(m_bodyLength); + +// request = new DmtpMessage(protocolFlags, bodyMemory); +// return true; +// } +//} \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Adapter/DmtpMessage.cs b/src/TouchSocket.Dmtp/Adapter/DmtpMessage.cs index 017155822..d38da61a7 100644 --- a/src/TouchSocket.Dmtp/Adapter/DmtpMessage.cs +++ b/src/TouchSocket.Dmtp/Adapter/DmtpMessage.cs @@ -10,96 +10,103 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Text; -using TouchSocket.Core; - namespace TouchSocket.Dmtp; /// -/// Dmtp协议的消息。 +/// DMTP协议的消息对象。 +/// +/// +/// 此类表示DMTP协议的消息格式,实现了接口。 +/// 消息格式如下: /// |*2*|*2*|**4**|***************n***********| /// |dm|ProtocolFlags|Length|Data| -/// |head|ushort|int32|bytes| -/// -public class DmtpMessage : DisposableObject, IFixedHeaderByteBlockRequestInfo, IRequestInfoBuilder +/// |head|||bytes| +/// 其中head为固定的"dm"标识符,ProtocolFlags为协议标志,Length为数据长度,Data为实际数据。 +/// +public sealed class DmtpMessage : IBytesBuilder, IRequestInfo { - private int m_bodyLength; + /// + /// DMTP协议的消息头部标识符。 + /// + /// 固定为"dm"的字节数组。 + public static readonly byte[] Head = "dm"u8.ToArray(); + + private readonly ReadOnlyMemory m_memory; /// - /// Dmtp协议的消息。 + /// 初始化类的新实例。 + /// + /// 协议标志。 + /// 消息主体数据。 + /// + /// 创建包含指定协议标志和主体数据的DMTP消息。 + /// 消息格式: /// |*2*|*2*|**4**|***************n***********| /// |Head|ProtocolFlags|Length|Data| - /// |dm|ushort|int32|bytes| - /// - public DmtpMessage() + /// |dm|||bytes| + /// + public DmtpMessage(ushort protocolFlags, ReadOnlyMemory body) { + this.ProtocolFlags = protocolFlags; + this.m_memory = body; } /// - /// Head + /// 初始化类的新实例。 /// - public static readonly byte[] Head = "dm"u8.ToArray(); - - /// - /// Dmtp协议的消息。 + /// 协议标志。 + /// + /// 创建只包含协议标志的DMTP消息,主体数据为空。 + /// 消息格式: /// |*2*|**4**|***************n***********| /// |ProtocolFlags|Length|Data| - /// |ushort|int32|bytes| - /// - /// + /// |||bytes| + /// public DmtpMessage(ushort protocolFlags) { this.ProtocolFlags = protocolFlags; } /// - /// 实际使用的Body数据。 + /// 获取构建数据时指示内存池的申请长度。 /// - public ByteBlock BodyByteBlock { get; set; } - - int IFixedHeaderByteBlockRequestInfo.BodyLength => this.m_bodyLength; + /// 消息的最大长度,包括头部、协议标志、长度字段和数据部分。 + /// + /// 此属性用于指示内存池应该申请的长度,建议设置得尽可能大一些以避免内存池扩容。 + /// 计算方式为:数据长度 + 8字节(2字节头部 + 2字节协议标志 + 4字节长度字段)。 + /// + public int MaxLength => this.Memory.Length + 8; /// - /// 协议标识 + /// 获取消息的主体数据内存块。 /// - public ushort ProtocolFlags { get; set; } - - /// - public int MaxLength => this.BodyByteBlock == null ? 6 : this.BodyByteBlock.Length + 6; + /// 包含消息主体数据的只读内存块。 + public ReadOnlyMemory Memory => this.m_memory; /// - /// 构建数据到 + /// 获取协议标志。 /// - /// - public void Build(ref TByteBlock byteBlock) where TByteBlock : IByteBlock - { - byteBlock.Write(Head); - byteBlock.Write(TouchSocketBitConverter.BigEndian.GetBytes(this.ProtocolFlags)); - if (this.BodyByteBlock == null) - { - byteBlock.Write(TouchSocketBitConverter.BigEndian.GetBytes(0)); - } - else - { - byteBlock.Write(TouchSocketBitConverter.BigEndian.GetBytes(this.BodyByteBlock.Length)); - byteBlock.Write(this.BodyByteBlock.Span); - } - } + /// 标识DMTP协议类型或操作类型的16位无符号整数。 + public ushort ProtocolFlags { get; private set; } /// - /// 从当前内存中解析出一个 - /// 注意: + /// 从指定的内存块中解析并创建实例。 + /// + /// 包含完整DMTP消息数据的内存块。 + /// 解析后的实例。 + /// 当内存块不包含有效的DMTP协议数据时抛出。 + /// + /// 重要注意事项: /// - /// 本解析只能解析一个完整消息。所以使用该方法时,请确认是否已经接收完成一个完整的包。 - /// 本解析所得的消息会脱离生命周期管理,所以需要手动释放。 + /// 此解析方法只能解析一个完整的消息,使用前请确认已接收到完整的数据包。 + /// 解析所得的消息会脱离生命周期管理,需要手动释放相关资源。 /// - /// - /// - /// - public static DmtpMessage CreateFrom(ReadOnlySpan span) + /// 解析过程会验证消息头部是否为"dm"标识,如果不匹配则抛出异常。 + /// + public static DmtpMessage CreateFrom(ReadOnlyMemory memory) { var offset = 0; + var span = memory.Span; if (span[offset++] != Head[0] || span[offset++] != Head[1]) { throw new Exception("这可能不是Dmtp协议数据"); @@ -108,74 +115,50 @@ public class DmtpMessage : DisposableObject, IFixedHeaderByteBlockRequestInfo, I offset += 2; var bodyLength = TouchSocketBitConverter.BigEndian.To(span.Slice(offset)); offset += 4; - var byteBlock = new ByteBlock(bodyLength); - byteBlock.Write(span.Slice(offset, bodyLength)); - byteBlock.SeekToStart(); - return new DmtpMessage() - { - m_bodyLength = bodyLength, - BodyByteBlock = byteBlock, - ProtocolFlags = protocolFlags - }; + return new DmtpMessage(protocolFlags, memory.Slice(offset, bodyLength)); } /// - /// 将的有效数据转换为utf-8的字符串。 + /// 将DMTP消息构建到指定的字节写入器中。 /// - /// - public string GetBodyString() + /// 实现接口的字节写入器类型。 + /// 要写入数据的字节写入器。 + /// + /// 此方法按照DMTP协议格式将消息数据写入到字节写入器中,写入顺序为: + /// + /// 消息头部标识符("dm") + /// 协议标志(大端序的 + /// 数据长度(大端序的 + /// 实际数据内容(如果存在) + /// + /// + public void Build(ref TWriter writer) + where TWriter : IBytesWriter + { - if (this.BodyByteBlock == null || this.BodyByteBlock.Length == 0) + writer.Write(Head); + WriterExtension.WriteValue(ref writer, this.ProtocolFlags, EndianType.Big); + if (this.Memory.IsEmpty) { - return default; + WriterExtension.WriteValue(ref writer, 0, EndianType.Big); } else { - return this.BodyByteBlock.Span.ToString(Encoding.UTF8); + WriterExtension.WriteValue(ref writer, this.Memory.Length, EndianType.Big); + writer.Write(this.Memory.Span); } } - bool IFixedHeaderByteBlockRequestInfo.OnParsingBody(ByteBlock byteBlock) + /// + /// 将消息主体数据转换为UTF-8编码的字符串。 + /// + /// 消息主体数据对应的UTF-8字符串。 + /// + /// 此方法将中的有效字节数据按照UTF-8编码转换为字符串。 + /// 如果主体数据为空,则返回空字符串。 + /// + public string GetBodyString() { - if (byteBlock.Length == this.m_bodyLength) - { - this.BodyByteBlock = byteBlock; - return true; - } - - return false; - } - - bool IFixedHeaderByteBlockRequestInfo.OnParsingHeader(ReadOnlySpan header) - { - if (header.Length == 8) - { - var offset = 0; - if (header[offset++] != Head[0] || header[offset++] != Head[1]) - { - throw new Exception("这可能不是Dmtp协议数据"); - } - this.ProtocolFlags = TouchSocketBitConverter.BigEndian.To(header.Slice(offset)); - offset += 2; - this.m_bodyLength = TouchSocketBitConverter.BigEndian.To(header.Slice(offset)); - return true; - } - return false; - } - - /// - /// - protected override void Dispose(bool disposing) - { - if (this.DisposedValue) - { - return; - } - if (disposing) - { - this.BodyByteBlock.SafeDispose(); - } - - base.Dispose(disposing); + return this.Memory.Span.ToString(Encoding.UTF8); } } \ No newline at end of file diff --git a/src/TouchSocket.Core/Reflection/Member.cs b/src/TouchSocket.Dmtp/Channel/ChannelDataType.cs similarity index 85% rename from src/TouchSocket.Core/Reflection/Member.cs rename to src/TouchSocket.Dmtp/Channel/ChannelDataType.cs index a097fdd3d..dbe69c478 100644 --- a/src/TouchSocket.Core/Reflection/Member.cs +++ b/src/TouchSocket.Dmtp/Channel/ChannelDataType.cs @@ -10,11 +10,12 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -namespace TouchSocket.Core; +namespace TouchSocket.Dmtp; -/// -/// 用于表达式树的成员 -/// -public abstract class Member +internal enum ChannelDataType : byte { -} \ No newline at end of file + Data, + Completed, + Canceled, + HoldOn +} diff --git a/src/TouchSocket.Dmtp/Channel/ChannelPackage.cs b/src/TouchSocket.Dmtp/Channel/ChannelPackage.cs index 1973eef71..8a5af80c7 100644 --- a/src/TouchSocket.Dmtp/Channel/ChannelPackage.cs +++ b/src/TouchSocket.Dmtp/Channel/ChannelPackage.cs @@ -10,49 +10,46 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; +using System.Buffers; namespace TouchSocket.Dmtp; -internal enum ChannelDataType : byte +internal class ChannelPackage : MsgRouterPackage, IDisposable { - DataOrder, - CompleteOrder, - CancelOrder, - DisposeOrder, - HoldOnOrder, - QueueRun, - QueuePause -} + private IMemoryOwner m_memoryOwner; -internal class ChannelPackage : MsgRouterPackage -{ public int ChannelId { get; set; } - public ByteBlock Data { get; set; } + public ReadOnlyMemory Data { get; set; } public ChannelDataType DataType { get; set; } - public bool RunNow { get; set; } + + public void Dispose() + { + this.m_memoryOwner?.Dispose(); + } public int GetLen() { - return this.Data == null ? 1024 : this.Data.Length + 1024; + return this.Data.Length + 1024; } - public override void PackageBody(ref TByteBlock byteBlock) + public override void PackageBody(ref TWriter writer) { - base.PackageBody(ref byteBlock); - byteBlock.WriteBoolean(this.RunNow); - byteBlock.WriteByte((byte)this.DataType); - byteBlock.WriteInt32(this.ChannelId); - - byteBlock.WriteByteBlock(this.Data); + base.PackageBody(ref writer); + WriterExtension.WriteValue(ref writer, (byte)this.DataType); + WriterExtension.WriteValue(ref writer, this.ChannelId); + WriterExtension.WriteByteSpan(ref writer, this.Data.Span); } - public override void UnpackageBody(ref TByteBlock byteBlock) + public override void UnpackageBody(ref TReader reader) { - base.UnpackageBody(ref byteBlock); - this.RunNow = byteBlock.ReadBoolean(); - this.DataType = (ChannelDataType)byteBlock.ReadByte(); - this.ChannelId = byteBlock.ReadInt32(); - this.Data = byteBlock.ReadByteBlock(); + base.UnpackageBody(ref reader); + this.DataType = (ChannelDataType)ReaderExtension.ReadValue(ref reader); + this.ChannelId = ReaderExtension.ReadValue(ref reader); + + var dataSpan = ReaderExtension.ReadByteSpan(ref reader); + + this.m_memoryOwner = MemoryPool.Shared.Rent(dataSpan.Length); + dataSpan.CopyTo(this.m_memoryOwner.Memory.Span); + this.Data = this.m_memoryOwner.Memory.Slice(0, dataSpan.Length); } } \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Channel/ChannelStatus.cs b/src/TouchSocket.Dmtp/Channel/ChannelStatus.cs index f48f6c046..6746a80ea 100644 --- a/src/TouchSocket.Dmtp/Channel/ChannelStatus.cs +++ b/src/TouchSocket.Dmtp/Channel/ChannelStatus.cs @@ -22,33 +22,18 @@ public enum ChannelStatus : byte /// Default = 0, - /// - /// 继续下移 - /// - Moving = 1, - - /// - /// 超时 - /// - Overtime = 2, - /// /// 继续 /// - HoldOn = 3, + HoldOn = 1, /// /// 取消 /// - Cancel = 4, + Cancel = 2, /// /// 完成 /// - Completed = 5, - - /// - /// 已释放 - /// - Disposed = 6 + Completed = 3 } \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Channel/IDmtpChannel.cs b/src/TouchSocket.Dmtp/Channel/IDmtpChannel.cs index 3195a76f8..e2b12c3d6 100644 --- a/src/TouchSocket.Dmtp/Channel/IDmtpChannel.cs +++ b/src/TouchSocket.Dmtp/Channel/IDmtpChannel.cs @@ -10,35 +10,21 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Dmtp; /// /// 提供一个基于Dmtp协议的,可以独立读写的通道。 /// -public partial interface IDmtpChannel : IDisposable, IEnumerable +public partial interface IDmtpChannel : IDisposableObject { /// - /// 通道传输速度限制 + /// 获取一个值,该值指示通道是否可以继续读取数据。 + /// 当通道状态为时返回 /// - long MaxSpeed { get; set; } + bool CanRead { get; } /// - /// 具有可读数据的条目数 - /// - int Available { get; } - - /// - /// 判断当前通道能否调用 - /// - bool CanMoveNext { get; } - - /// - /// 能否写入 + /// 获取一个值,该值指示通道是否可以继续写入数据。 /// bool CanWrite { get; } @@ -52,6 +38,11 @@ public partial interface IDmtpChannel : IDisposable, IEnumerable /// string LastOperationMes { get; } + /// + /// 获取上次操作的时间。 + /// + DateTimeOffset LastOperationTime { get; } + /// /// 元数据 /// @@ -67,64 +58,52 @@ public partial interface IDmtpChannel : IDisposable, IEnumerable /// string TargetId { get; } - /// - /// 超时时间,默认1000*10ms。 - /// - TimeSpan Timeout { get; set; } - - /// - /// 是否被使用 - /// - bool Using { get; } - - /// - /// 获取上次操作的时间。 - /// - DateTimeOffset LastOperationTime { get; } - /// /// 异步取消操作 /// /// 可选参数,用于提供取消操作的详细信息 + /// /// 返回一个Task对象,表示异步取消操作的完成 - Task CancelAsync(string operationMes = null); + Task CancelAsync(string operationMes = null, CancellationToken cancellationToken = default); /// /// 异步完成操作 /// /// 操作信息,可选参数,默认为 + /// /// 返回一个Task对象,表示异步操作的完成 - Task CompleteAsync(string operationMes = null); - - /// - /// 获取当前的有效数据。在使用之后,请进行显式的调用。 - /// - ByteBlock GetCurrent(); + Task CompleteAsync(string operationMes = null, CancellationToken cancellationToken = default); /// /// 异步调用继续 - /// 调用该指令时,接收方会跳出接收,但是通道依然可用,所以接收方需要重新调用 + /// 调用该指令时,接收方会获取到Msg,然后继续迭代。 /// /// + /// /// - Task HoldOnAsync(string operationMes = null); + Task HoldOnAsync(string operationMes = null, CancellationToken cancellationToken = default); /// - /// 转向下个元素 + /// 异步读取数据 /// - /// - bool MoveNext(); + /// 取消令箭 + /// 返回读取到的数据。当通道状态发生变化或出现错误时会抛出相应异常。 + /// 通道状态不允许读取数据 + /// 通道已被释放 + /// 操作被取消 + /// 其他异常 + Task> ReadAsync(CancellationToken cancellationToken = default); /// - /// 转向下个元素 + /// 异步写入数据。 + /// 将指定的字节数据写入通道。当通道状态不允许写入或出现错误时会抛出相应异常。 /// - /// - Task MoveNextAsync(); - - /// - /// 异步写入通道 - /// - /// 待写入的字节内存块 - /// 一个代表写入操作的Task对象 - Task WriteAsync(ReadOnlyMemory memory); + /// 要写入的数据内容。 + /// 取消令箭。 + /// 表示异步写入操作的任务。 + /// 通道状态不允许写入数据。 + /// 通道已被释放。 + /// 操作被取消。 + /// 其他异常。 + Task WriteAsync(ReadOnlyMemory memory, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Channel/InternalChannel.cs b/src/TouchSocket.Dmtp/Channel/InternalChannel.cs index 5055b4f4c..a553bb96b 100644 --- a/src/TouchSocket.Dmtp/Channel/InternalChannel.cs +++ b/src/TouchSocket.Dmtp/Channel/InternalChannel.cs @@ -10,14 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections; -using System.Collections.Concurrent; -using System.Collections.Generic; using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; namespace TouchSocket.Dmtp; @@ -25,11 +18,10 @@ namespace TouchSocket.Dmtp; internal sealed partial class InternalChannel : SafetyDisposableObject, IDmtpChannel { private readonly DmtpActor m_actor; - private readonly ConcurrentQueue m_dataQueue; - private readonly FlowGate m_flowGate; - private ByteBlock m_currentData; + private readonly SemaphoreSlim m_dataAvailable; + private readonly Queue m_dataQueue; private DateTimeOffset m_lastOperationTime; - private long m_maxSpeed; + private bool m_using; public InternalChannel(DmtpActor client, string targetId, Metadata metadata) { @@ -37,82 +29,108 @@ internal sealed partial class InternalChannel : SafetyDisposableObject, IDmtpCha this.m_lastOperationTime = DateTimeOffset.UtcNow; this.TargetId = targetId; this.Status = ChannelStatus.Default; - this.m_dataQueue = new ConcurrentQueue(); - this.m_maxSpeed = int.MaxValue; - this.m_flowGate = new FlowGate() { Maximum = this.m_maxSpeed }; + this.m_dataQueue = new Queue(); + this.m_dataAvailable = new SemaphoreSlim(0); this.Metadata = metadata; } - ~InternalChannel() - { - this.Dispose(false); - } - - public int Available => this.m_dataQueue.Count; - - public int CacheCapacity { get; set; } - - public bool CanMoveNext - { - get - { - return this.Available > 0 || (byte)this.Status < 4; - } - } - - public bool CanWrite => (byte)this.Status <= 3; + /// + public bool CanRead => this.Status == ChannelStatus.Default || + this.Status == ChannelStatus.HoldOn; + public bool CanWrite => this.m_actor.Online && (byte)this.Status <= 1; public int Id { get; private set; } public string LastOperationMes { get; private set; } public DateTimeOffset LastOperationTime => this.m_lastOperationTime; - public long MaxSpeed - { - get => this.m_maxSpeed; - set - { - if (value < 1024) - { - value = 1024; - } - this.m_maxSpeed = value; - this.m_flowGate.Maximum = value; - } - } - public Metadata Metadata { get; private set; } public ChannelStatus Status { get; private set; } public string TargetId { get; } - public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(10); + public bool Using => this.m_using; - public bool Using { get; private set; } + #region 读取 + + /// + public async Task> ReadAsync(CancellationToken cancellationToken = default) + { + if (!this.CanRead) + { + throw new InvalidOperationException($"通道状态为{this.Status},无法读取数据"); + } + + if (this.DisposedValue) + { + throw new ObjectDisposedException(nameof(InternalChannel), "通道已被释放"); + } + + var channelPackage = await this.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + try + { + switch (channelPackage.DataType) + { + case ChannelDataType.Data: + this.Status = ChannelStatus.Default; + this.LastOperationMes = string.Empty; + var data = channelPackage.Data; + return data; + + case ChannelDataType.Completed: + this.Status = ChannelStatus.Completed; + this.LastOperationMes = channelPackage.Message; + // 通道正常完成,返回空数据表示没有更多数据 + return ReadOnlyMemory.Empty; + + case ChannelDataType.Canceled: + this.Status = ChannelStatus.Cancel; + this.LastOperationMes = channelPackage.Message; + // 通道被取消,返回空数据,调用者可以检查状态决定是否继续 + return ReadOnlyMemory.Empty; + + case ChannelDataType.HoldOn: + this.Status = ChannelStatus.HoldOn; + this.LastOperationMes = channelPackage.Message; + // 通道暂停,返回空数据,调用者可以检查状态决定是否继续 + return ReadOnlyMemory.Empty; + + default: + throw new InvalidOperationException("未知的通道数据类型"); + } + } + finally + { + channelPackage.SafeDispose(); + } + } + + #endregion 读取 #region 操作 - public async Task CancelAsync(string operationMes = null) + public async Task CancelAsync(string operationMes = null, CancellationToken cancellationToken = default) { - if ((byte)this.Status > 3) + if ((byte)this.Status > 1) { return Result.Success; } try { - this.RequestCancel(true); + this.Status = ChannelStatus.Cancel; + this.LastOperationMes = operationMes; var channelPackage = new ChannelPackage() { ChannelId = this.Id, - RunNow = true, - DataType = ChannelDataType.CancelOrder, + DataType = ChannelDataType.Canceled, Message = operationMes, SourceId = this.m_actor.Id, TargetId = this.TargetId }; - await this.m_actor.SendChannelPackageAsync(channelPackage).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.m_actor.SendChannelPackageAsync(channelPackage, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); this.m_lastOperationTime = DateTimeOffset.UtcNow; return Result.Success; } @@ -122,25 +140,25 @@ internal sealed partial class InternalChannel : SafetyDisposableObject, IDmtpCha } } - public async Task CompleteAsync(string operationMes = null) + public async Task CompleteAsync(string operationMes = null, CancellationToken cancellationToken = default) { - if ((byte)this.Status > 3) + if ((byte)this.Status > 1) { return Result.Success; } try { - this.RequestComplete(true); + this.Status = ChannelStatus.Completed; + this.LastOperationMes = operationMes; var channelPackage = new ChannelPackage() { ChannelId = this.Id, - RunNow = true, - DataType = ChannelDataType.CompleteOrder, + DataType = ChannelDataType.Completed, Message = operationMes, SourceId = this.m_actor.Id, TargetId = this.TargetId }; - await this.m_actor.SendChannelPackageAsync(channelPackage).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.m_actor.SendChannelPackageAsync(channelPackage, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); this.m_lastOperationTime = DateTimeOffset.UtcNow; return Result.Success; } @@ -148,27 +166,27 @@ internal sealed partial class InternalChannel : SafetyDisposableObject, IDmtpCha { return Result.FromException(ex); } - } - public async Task HoldOnAsync(string operationMes = null) + public async Task HoldOnAsync(string operationMes = null, CancellationToken cancellationToken = default) { - if ((byte)this.Status > 3) + if ((byte)this.Status > 1) { return Result.Success; } try { + this.Status = ChannelStatus.HoldOn; + this.LastOperationMes = operationMes; var channelPackage = new ChannelPackage() { ChannelId = this.Id, - RunNow = true, - DataType = ChannelDataType.HoldOnOrder, + DataType = ChannelDataType.HoldOn, Message = operationMes, SourceId = this.m_actor.Id, TargetId = this.TargetId }; - await this.m_actor.SendChannelPackageAsync(channelPackage).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.m_actor.SendChannelPackageAsync(channelPackage, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); this.m_lastOperationTime = DateTimeOffset.UtcNow; return Result.Success; } @@ -176,236 +194,45 @@ internal sealed partial class InternalChannel : SafetyDisposableObject, IDmtpCha { return Result.FromException(ex); } - - } - - - protected override void SafetyDispose(bool disposing) - { - //不判断disposing,能够让GC也能发送释放指令 - try - { - this.RequestDispose(true); - - if ((byte)this.Status > 3) - { - return; - } - - var channelPackage = new ChannelPackage() - { - ChannelId = this.Id, - RunNow = true, - DataType = ChannelDataType.HoldOnOrder, - SourceId = this.m_actor.Id, - TargetId = this.TargetId - }; - this.m_actor.SendChannelPackageAsync(channelPackage).GetFalseAwaitResult(); - this.m_lastOperationTime = DateTimeOffset.UtcNow; - } - catch - { - } } #endregion 操作 - public ByteBlock GetCurrent() + public async Task WriteAsync(ReadOnlyMemory memory, CancellationToken cancellationToken = default) { - return this.m_currentData; - } - - public bool MoveNext() - { - if (!this.CanMoveNext) - { - return false; - } - if (this.m_dataQueue.TryDequeue(out var channelPackage)) - { - switch (channelPackage.DataType) - { - case ChannelDataType.DataOrder: - { - this.m_currentData = channelPackage.Data; - return true; - } - case ChannelDataType.CompleteOrder: - this.RequestComplete(true); - return false; - - case ChannelDataType.CancelOrder: - this.RequestCancel(true); - return false; - - case ChannelDataType.DisposeOrder: - this.RequestDispose(true); - return false; - - case ChannelDataType.HoldOnOrder: - this.Status = ChannelStatus.HoldOn; - return false; - - case ChannelDataType.QueueRun: - //this.m_canFree = true; - return false; - - case ChannelDataType.QueuePause: - //this.m_canFree = false; - return false; - - default: - return false; - } - } - - //this.Reset(); - if (this.Wait()) - { - return this.MoveNext(); - } - else - { - this.Status = ChannelStatus.Overtime; - return false; - } - } - - public async Task MoveNextAsync() - { - if (!this.CanMoveNext) - { - return false; - } - if (this.m_dataQueue.TryDequeue(out var channelPackage)) - { - switch (channelPackage.DataType) - { - case ChannelDataType.DataOrder: - { - this.m_currentData = channelPackage.Data; - return true; - } - case ChannelDataType.CompleteOrder: - this.RequestComplete(true); - return false; - - case ChannelDataType.CancelOrder: - this.RequestCancel(true); - return false; - - case ChannelDataType.DisposeOrder: - this.RequestDispose(true); - return false; - - case ChannelDataType.HoldOnOrder: - this.Status = ChannelStatus.HoldOn; - return false; - - case ChannelDataType.QueueRun: - //this.m_canFree = true; - return false; - - case ChannelDataType.QueuePause: - //this.m_canFree = false; - return false; - - default: - return false; - } - } - - //this.Reset(); - if (await this.WaitAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext)) - { - return await this.MoveNextAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - else - { - this.Status = ChannelStatus.Overtime; - return false; - } - } - - public async Task WriteAsync(ReadOnlyMemory memory) - { - if ((byte)this.Status > 3) + if ((byte)this.Status > 1) { throw new Exception($"通道已{this.Status}"); } - - await this.m_flowGate.AddCheckWaitAsync(memory.Length).ConfigureAwait(EasyTask.ContinueOnCapturedContext); var channelPackage = new ChannelPackage() { ChannelId = this.Id, - DataType = ChannelDataType.DataOrder, + DataType = ChannelDataType.Data, SourceId = this.m_actor.Id, - TargetId = this.TargetId + TargetId = this.TargetId, + Data = memory }; + await this.m_actor.SendChannelPackageAsync(channelPackage, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + this.m_lastOperationTime = DateTimeOffset.UtcNow; + } - var byteBlock = new ByteBlock(memory.Length); - byteBlock.Write(memory.Span); - channelPackage.Data = byteBlock; - - using (channelPackage.Data) - { - await this.m_actor.SendChannelPackageAsync(channelPackage).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.m_lastOperationTime = DateTimeOffset.UtcNow; - } + internal void MakeUsing() + { + this.m_using = true; } internal void ReceivedData(ChannelPackage channelPackage) { - this.m_lastOperationTime = DateTimeOffset.UtcNow; - if (channelPackage.RunNow) - { - switch (channelPackage.DataType) - { - case ChannelDataType.CompleteOrder: - this.LastOperationMes = channelPackage.Message; - this.RequestComplete(false); - break; - - case ChannelDataType.CancelOrder: - this.LastOperationMes = channelPackage.Message; - this.RequestCancel(false); - break; - - case ChannelDataType.DisposeOrder: - this.RequestDispose(false); - break; - - case ChannelDataType.HoldOnOrder: - this.LastOperationMes = channelPackage.Message; - this.Status = ChannelStatus.HoldOn; - break; - - case ChannelDataType.QueueRun: - //this.m_canFree = true; - return; - - case ChannelDataType.QueuePause: - //this.m_canFree = false; - return; - - default: - return; - } - } - this.m_dataQueue.Enqueue(channelPackage); - } - - internal void RequestDispose(bool clear) - { - if (clear) - { - this.Clear(); - } - if ((byte)this.Status > 3) + if (this.DisposedValue) { return; } - this.Status = ChannelStatus.Disposed; + this.m_lastOperationTime = DateTimeOffset.UtcNow; + lock (this.m_dataQueue) + { + this.m_dataQueue.Enqueue(channelPackage); + } + this.m_dataAvailable.Release(); } internal void SetId(int id) @@ -413,97 +240,32 @@ internal sealed partial class InternalChannel : SafetyDisposableObject, IDmtpCha this.Id = id; } - internal void MakeUsing() + protected override void SafetyDispose(bool disposing) { - this.Using = true; - } - - private void Clear() - { - try + if (disposing) { - this.m_dataQueue.Clear(package => + this.m_dataAvailable.SafeDispose(); + lock (this.m_dataQueue) { - package.Data.SafeDispose(); - }); - this.m_actor.RemoveChannel(this.Id); - } - catch - { - } - } - - private void RequestCancel(bool clear) - { - this.Status = ChannelStatus.Cancel; - if (clear) - { - this.Clear(); - } - } - - private void RequestComplete(bool clear) - { - this.Status = ChannelStatus.Completed; - if (clear) - { - this.Clear(); - } - } - - private bool Wait() - { - var spinWait = new SpinWait(); - var now = DateTimeOffset.UtcNow; - while (true) - { - if (!this.m_dataQueue.IsEmpty) - { - return true; + while (this.m_dataQueue.Count > 0) + { + this.m_dataQueue.Dequeue().SafeDispose(); + } } - if (DateTimeOffset.UtcNow - now > this.Timeout) - { - return false; - } - spinWait.SpinOnce(); } } - private async Task WaitAsync() + private async Task WaitAsync(CancellationToken cancellationToken) { - var now = DateTimeOffset.UtcNow; - while (true) + await this.m_dataAvailable.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + lock (this.m_dataQueue) { - if (!this.m_dataQueue.IsEmpty) // Replaced Count with IsEmpty + if (this.m_dataQueue.Count > 0) { - return true; + return this.m_dataQueue.Dequeue(); } - if (DateTimeOffset.UtcNow - now > this.Timeout) - { - return false; - } - await Task.Delay(1).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } + // 理论上不会走到这里 + throw new InvalidOperationException("信号量与队列不同步"); } - - #region 迭代器 - - public IEnumerator GetEnumerator() - { - ByteBlock byteBlock = null; - while (this.MoveNext()) - { - byteBlock.SafeDispose(); - byteBlock = this.GetCurrent(); - yield return byteBlock; - } - byteBlock.SafeDispose(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return this.GetEnumerator(); - } - - #endregion 迭代器 } \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Channel/InternalChannelAsync.cs b/src/TouchSocket.Dmtp/Channel/InternalChannelAsync.cs deleted file mode 100644 index 79cabc471..000000000 --- a/src/TouchSocket.Dmtp/Channel/InternalChannelAsync.cs +++ /dev/null @@ -1,37 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System.Collections.Generic; -using System.Threading; -using TouchSocket.Core; - -namespace TouchSocket.Dmtp; - -#if AsyncEnumerable - -internal partial class InternalChannel -{ - public async IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) - { - ByteBlock byteBlock = null; - while (await this.MoveNextAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext)) - { - cancellationToken.ThrowIfCancellationRequested(); - byteBlock.SafeDispose(); - byteBlock = this.GetCurrent(); - yield return byteBlock; - } - byteBlock.SafeDispose(); - } -} - -#endif \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Channel/WaitCreateChannelPackage.cs b/src/TouchSocket.Dmtp/Channel/WaitCreateChannelPackage.cs index 04b588aee..a7007eb98 100644 --- a/src/TouchSocket.Dmtp/Channel/WaitCreateChannelPackage.cs +++ b/src/TouchSocket.Dmtp/Channel/WaitCreateChannelPackage.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Dmtp; /// @@ -34,19 +32,37 @@ internal class WaitCreateChannelPackage : WaitRouterPackage /// public bool Random { get; set; } - public override void PackageBody(ref TByteBlock byteBlock) + public override void PackageBody(ref TWriter writer) { - base.PackageBody(ref byteBlock); - byteBlock.WriteBoolean(this.Random); - byteBlock.WriteInt32(this.ChannelId); - byteBlock.WritePackage(this.Metadata); + base.PackageBody(ref writer); + WriterExtension.WriteValue(ref writer, this.Random); + WriterExtension.WriteValue(ref writer, this.ChannelId); + + if (this.Metadata is null) + { + WriterExtension.WriteNull(ref writer); + } + else + { + WriterExtension.WriteNotNull(ref writer); + this.Metadata.Package(ref writer); + } } - public override void UnpackageBody(ref TByteBlock byteBlock) + public override void UnpackageBody(ref TReader reader) { - base.UnpackageBody(ref byteBlock); - this.Random = byteBlock.ReadBoolean(); - this.ChannelId = byteBlock.ReadInt32(); - this.Metadata = byteBlock.ReadPackage(); + base.UnpackageBody(ref reader); + this.Random = ReaderExtension.ReadValue(ref reader); + this.ChannelId = ReaderExtension.ReadValue(ref reader); + if (ReaderExtension.ReadIsNull(ref reader)) + { + this.Metadata = null; + } + else + { + var metadata = new Metadata(); + metadata.Unpackage(ref reader); + this.Metadata = metadata; + } } } \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Common/WaitPing.cs b/src/TouchSocket.Dmtp/Common/WaitPing.cs index 3de381045..6e0c057c5 100644 --- a/src/TouchSocket.Dmtp/Common/WaitPing.cs +++ b/src/TouchSocket.Dmtp/Common/WaitPing.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Dmtp; /// diff --git a/src/TouchSocket.Dmtp/Common/WaitSetID.cs b/src/TouchSocket.Dmtp/Common/WaitSetID.cs index 5dfd104b6..3470cbcf6 100644 --- a/src/TouchSocket.Dmtp/Common/WaitSetID.cs +++ b/src/TouchSocket.Dmtp/Common/WaitSetID.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Dmtp; /// diff --git a/src/TouchSocket.Dmtp/Common/WaitVerify.cs b/src/TouchSocket.Dmtp/Common/WaitVerify.cs index 35977a8af..43be2de2f 100644 --- a/src/TouchSocket.Dmtp/Common/WaitVerify.cs +++ b/src/TouchSocket.Dmtp/Common/WaitVerify.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Dmtp; /// diff --git a/src/TouchSocket.Dmtp/Components/Http/HttpDmtpClient.cs b/src/TouchSocket.Dmtp/Components/Http/HttpDmtpClient.cs index 1fb7a43ac..6acc61038 100644 --- a/src/TouchSocket.Dmtp/Components/Http/HttpDmtpClient.cs +++ b/src/TouchSocket.Dmtp/Components/Http/HttpDmtpClient.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Http; using TouchSocket.Sockets; @@ -30,6 +26,7 @@ public partial class HttpDmtpClient : HttpClientBase, IHttpDmtpClient private readonly SemaphoreSlim m_semaphoreForConnect = new SemaphoreSlim(1, 1); private bool m_allowRoute; + private DmtpAdapter m_adapter; private SealedDmtpActor m_dmtpActor; private Func> m_findDmtpActor; @@ -49,14 +46,13 @@ public partial class HttpDmtpClient : HttpClientBase, IHttpDmtpClient /// /// 异步使用基于Http升级的协议,连接Dmtp服务器 /// - /// 连接超时时间,单位为毫秒 - /// 用于取消异步操作的取消令牌 + /// 用于取消异步操作的取消令牌 /// 异步操作任务 /// 在连接过程中遇到错误时抛出异常 - public async Task ConnectAsync(int millisecondsTimeout, CancellationToken token) + public async Task ConnectAsync(CancellationToken cancellationToken) { // 等待信号量,以确保同时只有一个连接操作 - await this.m_semaphoreForConnect.WaitTimeAsync(millisecondsTimeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.m_semaphoreForConnect.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); try { @@ -68,21 +64,20 @@ public partial class HttpDmtpClient : HttpClientBase, IHttpDmtpClient // 如果基础连接不在状态,则尝试建立TCP连接 if (!base.Online) { - await base.TcpConnectAsync(millisecondsTimeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await base.HttpConnectAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } // 创建并配置HttpRequest,为升级到Dmtp协议做准备 - var request = new HttpRequest() - .SetHost(this.RemoteIPHost.Host); + var request = new HttpRequest(); request.Headers.Add(HttpHeaders.Connection, "upgrade"); request.Headers.Add(HttpHeaders.Upgrade, DmtpUtility.Dmtp.ToLower()); - request.AsMethod(DmtpUtility.Dmtp); // 发送请求并处理响应 - using (var responseResult = await this.ProtectedRequestContentAsync(request).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) + using (var responseResult = await this.ProtectedRequestAsync(request, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) { var response = responseResult.Response; + await response.GetContentAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); // 如果状态码为101,则切换协议为Dmtp if (response.StatusCode == 101) { @@ -91,12 +86,15 @@ public partial class HttpDmtpClient : HttpClientBase, IHttpDmtpClient else { // 其他状态码视为错误,抛出异常 - throw new Exception(response.StatusMessage); + ThrowHelper.ThrowException($"无法升级协议,状态码:{response.StatusCode},内容:{response.StatusMessage}"); } } + var dmtpOption = this.Config.GetValue(DmtpConfigExtension.DmtpOptionProperty); + ThrowHelper.ThrowIfNull(dmtpOption, nameof(dmtpOption)); + // 与Dmtp服务器进行握手操作,完成连接 - await this.m_dmtpActor.HandshakeAsync(this.Config.GetValue(DmtpConfigExtension.DmtpOptionProperty).VerifyToken, this.Config.GetValue(DmtpConfigExtension.DmtpOptionProperty).Id, millisecondsTimeout, this.Config.GetValue(DmtpConfigExtension.DmtpOptionProperty).Metadata, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.m_dmtpActor.ConnectAsync(dmtpOption, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } finally { @@ -113,9 +111,9 @@ public partial class HttpDmtpClient : HttpClientBase, IHttpDmtpClient /// 发送关闭消息。 /// /// 关闭消息的内容 - /// + /// 可取消令箭 /// 异步任务 - public override async Task CloseAsync(string msg, CancellationToken token = default) + public override async Task CloseAsync(string msg, CancellationToken cancellationToken = default) { // 检查是否已初始化IDmtpActor对象 if (this.m_dmtpActor != null) @@ -123,11 +121,11 @@ public partial class HttpDmtpClient : HttpClientBase, IHttpDmtpClient // 向IDmtpActor对象发送关闭消息 await this.m_dmtpActor.SendCloseAsync(msg).ConfigureAwait(EasyTask.ContinueOnCapturedContext); // 关闭IDmtpActor对象 - await this.m_dmtpActor.CloseAsync(msg, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.m_dmtpActor.CloseAsync(msg, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } // 调用基类的关闭方法 - return await base.CloseAsync(msg, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return await base.CloseAsync(msg, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } #endregion 断开 @@ -135,10 +133,10 @@ public partial class HttpDmtpClient : HttpClientBase, IHttpDmtpClient #region Override /// - protected override void Dispose(bool disposing) + protected override void SafetyDispose(bool disposing) { this.m_dmtpActor?.SafeDispose(); - base.Dispose(disposing); + base.SafetyDispose(disposing); } /// @@ -190,9 +188,9 @@ public partial class HttpDmtpClient : HttpClientBase, IHttpDmtpClient #region ResetId /// - public Task ResetIdAsync(string newId) + public Task ResetIdAsync(string newId, CancellationToken cancellationToken = default) { - return this.m_dmtpActor.ResetIdAsync(newId); + return this.m_dmtpActor.ResetIdAsync(newId, cancellationToken); } #endregion ResetId @@ -201,34 +199,91 @@ public partial class HttpDmtpClient : HttpClientBase, IHttpDmtpClient { this.Protocol = DmtpUtility.DmtpProtocol; var adapter = new DmtpAdapter(); - base.SetWarpAdapter(adapter); this.SetAdapter(adapter); + this.m_adapter = adapter; this.m_dmtpActor = new SealedDmtpActor(this.m_allowRoute) { //OutputSend = this.DmtpActorSend, OutputSendAsync = this.DmtpActorSendAsync, Routing = this.OnDmtpActorRouting, - Handshaking = this.OnDmtpActorHandshaking, - Handshaked = this.OnDmtpActorHandshaked, + Connecting = this.OnDmtpActorConnecting, + Connected = this.OnDmtpActorConnected, Closing = this.OnDmtpActorClose, CreatedChannel = this.OnDmtpActorCreateChannel, Logger = this.Logger, Client = this, FindDmtpActor = this.m_findDmtpActor }; + + var transport = base.Transport; + _ = EasyTask.SafeRun(this.DmtpReceiveLoopAsync, transport); + } + + private async Task DmtpReceiveLoopAsync(ITransport transport) + { + var cancellationToken = transport.ClosedToken; + using var reader = new PooledBytesReader(); + await transport.ReadLocker.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + try + { + while (true) + { + if (this.DisposedValue || cancellationToken.IsCancellationRequested) + { + return; + } + var result = await transport.Reader.ReadAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + if (result.Buffer.Length == 0) + { + break; + } + + try + { + reader.Reset(result.Buffer); + if (!await this.OnTcpReceiving(reader).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) + { + await this.m_adapter.ReceivedInputAsync(reader).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + var position = result.Buffer.GetPosition(reader.BytesRead); + transport.Reader.AdvanceTo(position, result.Buffer.End); + + if (result.IsCompleted || result.IsCanceled) + { + return; + } + reader.Clear(); + + } + catch (Exception ex) + { + this.Logger?.Exception(this, ex); + await transport.CloseAsync(ex.Message).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + } + } + catch (Exception ex) + { + // 如果发生异常,记录日志并退出接收循环 + this.Logger?.Debug(this, ex); + } + finally + { + transport.ReadLocker.Release(); + } } #region 内部委托绑定 - private Task DmtpActorSendAsync(DmtpActor actor, ReadOnlyMemory memory) + private Task DmtpActorSendAsync(DmtpActor actor, ReadOnlyMemory memory, CancellationToken cancellationToken) { - return base.ProtectedDefaultSendAsync(memory); + return base.ProtectedSendAsync(memory, cancellationToken); } private async Task OnDmtpActorClose(DmtpActor actor, string msg) { await this.OnDmtpClosing(new ClosingEventArgs(msg)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.Abort(false, msg); + //this.Abort(false, msg); } private Task OnDmtpActorCreateChannel(DmtpActor actor, CreateChannelEventArgs e) @@ -236,14 +291,14 @@ public partial class HttpDmtpClient : HttpClientBase, IHttpDmtpClient return this.OnCreateChannel(e); } - private Task OnDmtpActorHandshaked(DmtpActor actor, DmtpVerifyEventArgs e) + private Task OnDmtpActorConnected(DmtpActor actor, DmtpVerifyEventArgs e) { - return this.OnHandshaked(e); + return this.OnConnected(e); } - private Task OnDmtpActorHandshaking(DmtpActor actor, DmtpVerifyEventArgs e) + private Task OnDmtpActorConnecting(DmtpActor actor, DmtpVerifyEventArgs e) { - return this.OnHandshaking(e); + return this.OnConnecting(e); } private Task OnDmtpActorRouting(DmtpActor actor, PackageRouterEventArgs e) @@ -315,7 +370,7 @@ public partial class HttpDmtpClient : HttpClientBase, IHttpDmtpClient /// 在完成握手连接时 /// /// 包含握手信息的事件参数 - protected virtual async Task OnHandshaked(DmtpVerifyEventArgs e) + protected virtual async Task OnConnected(DmtpVerifyEventArgs e) { // 如果握手已经被处理,则不再执行后续操作 if (e.Handled) @@ -323,14 +378,14 @@ public partial class HttpDmtpClient : HttpClientBase, IHttpDmtpClient return; } // 触发插件管理器中的握手完成插件事件 - await this.PluginManager.RaiseAsync(typeof(IDmtpHandshakedPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.PluginManager.RaiseAsync(typeof(IDmtpConnectedPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// /// 即将握手连接时 /// /// 参数 - protected virtual async Task OnHandshaking(DmtpVerifyEventArgs e) + protected virtual async Task OnConnecting(DmtpVerifyEventArgs e) { // 如果握手已经被处理,则直接返回 if (e.Handled) @@ -338,7 +393,7 @@ public partial class HttpDmtpClient : HttpClientBase, IHttpDmtpClient return; } // 触发握手过程的插件事件 - await this.PluginManager.RaiseAsync(typeof(IDmtpHandshakingPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.PluginManager.RaiseAsync(typeof(IDmtpConnectingPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// diff --git a/src/TouchSocket.Dmtp/Components/Http/HttpDmtpService.cs b/src/TouchSocket.Dmtp/Components/Http/HttpDmtpService.cs index d22781ec2..17f8c512f 100644 --- a/src/TouchSocket.Dmtp/Components/Http/HttpDmtpService.cs +++ b/src/TouchSocket.Dmtp/Components/Http/HttpDmtpService.cs @@ -10,11 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Http; -using TouchSocket.Sockets; namespace TouchSocket.Dmtp; @@ -45,54 +41,4 @@ public abstract partial class HttpDmtpService : HttpService, I /// 连接令箭 /// public string VerifyToken => this.Config.GetValue(DmtpConfigExtension.DmtpOptionProperty).VerifyToken; - - #region 字段 - - private bool m_allowRoute; - private Func> m_findDmtpActor; - - #endregion 字段 - - /// - protected override void ClientInitialized(TClient client) - { - base.ClientInitialized(client); - client.m_internalOnRpcActorInit = this.PrivateOnRpcActorInit; - } - - /// - protected override void LoadConfig(TouchSocketConfig config) - { - base.LoadConfig(config); - var dmtpRouteService = this.Resolver.Resolve(); - if (dmtpRouteService != null) - { - this.m_allowRoute = true; - this.m_findDmtpActor = dmtpRouteService.FindDmtpActor; - } - } - - private async Task FindDmtpActor(string id) - { - if (this.m_allowRoute) - { - if (this.m_findDmtpActor != null) - { - return await this.m_findDmtpActor.Invoke(id).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - return this.TryGetClient(id, out var client) ? client.DmtpActor : null; - } - else - { - return null; - } - } - - private SealedDmtpActor PrivateOnRpcActorInit() - { - return new SealedDmtpActor(this.m_allowRoute) - { - FindDmtpActor = this.FindDmtpActor - }; - } } \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Components/Http/HttpDmtpSessionClient.cs b/src/TouchSocket.Dmtp/Components/Http/HttpDmtpSessionClient.cs index eb854b817..93fc5ed0a 100644 --- a/src/TouchSocket.Dmtp/Components/Http/HttpDmtpSessionClient.cs +++ b/src/TouchSocket.Dmtp/Components/Http/HttpDmtpSessionClient.cs @@ -10,22 +10,16 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Http; using TouchSocket.Sockets; namespace TouchSocket.Dmtp; /// -/// 抽象类 HttpDmtpSessionClient 继承自 HttpSessionClient,并实现 IHttpDmtpSessionClient 接口。 -/// 该类提供了与 HTTP DMTP 协议相关的会话客户端功能。 +/// 表示基于HTTP协议的Dmtp会话客户端,继承自,实现接口。 /// public abstract class HttpDmtpSessionClient : HttpSessionClient, IHttpDmtpSessionClient { - internal Func m_internalOnRpcActorInit; private SealedDmtpActor m_dmtpActor; /// @@ -47,24 +41,24 @@ public abstract class HttpDmtpSessionClient : HttpSessionClient, IHttpDmtpSessio #region 断开 /// - public override async Task CloseAsync(string msg, CancellationToken token = default) + public override async Task CloseAsync(string msg, CancellationToken cancellationToken = default) { if (this.m_dmtpActor != null) { - await this.m_dmtpActor.CloseAsync(msg, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.m_dmtpActor.CloseAsync(msg, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - return await base.CloseAsync(msg, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return await base.CloseAsync(msg, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// - protected override void Dispose(bool disposing) + protected override void SafetyDispose(bool disposing) { if (this.DisposedValue) { return; } - base.Dispose(disposing); + base.SafetyDispose(disposing); if (disposing && this.m_dmtpActor != null) { this.m_dmtpActor.Dispose(); @@ -76,14 +70,14 @@ public abstract class HttpDmtpSessionClient : HttpSessionClient, IHttpDmtpSessio #region ResetId /// - public override async Task ResetIdAsync(string newId) + public override async Task ResetIdAsync(string newId, CancellationToken cancellationToken = default) { if (this.m_dmtpActor == null) { - await base.ResetIdAsync(newId).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await base.ResetIdAsync(newId, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return; } - await this.m_dmtpActor.ResetIdAsync(newId).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.m_dmtpActor.ResetIdAsync(newId, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } #endregion ResetId @@ -93,27 +87,61 @@ public abstract class HttpDmtpSessionClient : HttpSessionClient, IHttpDmtpSessio await this.ProtectedResetIdAsync(e.NewId).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - private void SetRpcActor(SealedDmtpActor actor) + private void InitDmtpActor() { - actor.Id = this.Id; - actor.IdChanged = this.OnDmtpIdChanged; - actor.OutputSendAsync = this.ThisDmtpActorOutputSendAsync; - actor.Client = this; - actor.Closing = this.OnDmtpActorClose; - actor.Routing = this.OnDmtpActorRouting; - actor.Handshaked = this.OnDmtpActorHandshaked; - actor.Handshaking = this.OnDmtpActorHandshaking; - actor.CreatedChannel = this.OnDmtpActorCreatedChannel; - actor.Logger = this.Logger; + var allowRoute = false; + Func> findDmtpActor = default; + var dmtpRouteService = this.Resolver.Resolve(); + if (dmtpRouteService != null) + { + allowRoute = true; + findDmtpActor = dmtpRouteService.FindDmtpActor; + } + + async Task FindDmtpActor(string id) + { + if (allowRoute) + { + if (findDmtpActor != null) + { + return await findDmtpActor.Invoke(id).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + if (this.ProtectedTryGetClient(id, out var client) && client is IDmtpActorObject dmtpActorObject) + { + return dmtpActorObject.DmtpActor; + } + return null; + } + else + { + return null; + } + } + + var actor = new SealedDmtpActor(allowRoute, true) + { + Id = this.Id, + FindDmtpActor = FindDmtpActor, + IdChanged = this.OnDmtpIdChanged, + OutputSendAsync = this.ThisDmtpActorOutputSendAsync, + Client = this, + Closing = this.OnDmtpActorClose, + Routing = this.OnDmtpActorRouting, + Connected = this.OnDmtpActorConnected, + Connecting = this.OnDmtpActorConnecting, + CreatedChannel = this.OnDmtpActorCreatedChannel, + Logger = this.Logger + }; + this.m_dmtpActor = actor; this.Protocol = DmtpUtility.DmtpProtocol; this.SetAdapter(new DmtpAdapter()); } - private Task ThisDmtpActorOutputSendAsync(DmtpActor actor, ReadOnlyMemory memory) + private Task ThisDmtpActorOutputSendAsync(DmtpActor actor, ReadOnlyMemory memory, CancellationToken cancellationToken) { - return base.ProtectedDefaultSendAsync(memory); + return base.ProtectedSendAsync(memory, cancellationToken); } #region Override @@ -124,12 +152,15 @@ public abstract class HttpDmtpSessionClient : HttpSessionClient, IHttpDmtpSessio var request = httpContext.Request; var response = httpContext.Response; - if (request.IsMethod(DmtpUtility.Dmtp) && request.IsUpgrade() && - string.Equals(request.Headers.Get(HttpHeaders.Upgrade), DmtpUtility.Dmtp, StringComparison.OrdinalIgnoreCase)) + if (request.IsMethod(DmtpUtility.Dmtp) + && request.IsUpgrade() + && request.Headers.Contains(HttpHeaders.Upgrade, DmtpUtility.Dmtp)) { - this.SetRpcActor(this.m_internalOnRpcActorInit.Invoke()); + this.InitDmtpActor(); - await response.SetStatus(101, "Switching Protocols").AnswerAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await response.SetStatus(101, "Switching Protocols") + .AnswerAsync() + .ConfigureAwait(EasyTask.ContinueOnCapturedContext); return; } await base.OnReceivedHttpRequest(httpContext).ConfigureAwait(EasyTask.ContinueOnCapturedContext); @@ -139,7 +170,7 @@ public abstract class HttpDmtpSessionClient : HttpSessionClient, IHttpDmtpSessio protected override async Task OnTcpClosed(ClosedEventArgs e) { await this.OnDmtpClosed(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - await base.OnTcpClosed(e); + await base.OnTcpClosed(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// @@ -168,7 +199,7 @@ public abstract class HttpDmtpSessionClient : HttpSessionClient, IHttpDmtpSessio private Task OnDmtpActorClose(DmtpActor actor, string msg) { - base.Abort(false, msg); + //base.Abort(false, msg); return EasyTask.CompletedTask; } @@ -177,12 +208,12 @@ public abstract class HttpDmtpSessionClient : HttpSessionClient, IHttpDmtpSessio return this.OnCreatedChannel(e); } - private Task OnDmtpActorHandshaked(DmtpActor actor, DmtpVerifyEventArgs e) + private Task OnDmtpActorConnected(DmtpActor actor, DmtpVerifyEventArgs e) { - return this.OnHandshaked(e); + return this.OnConnected(e); } - private Task OnDmtpActorHandshaking(DmtpActor actor, DmtpVerifyEventArgs e) + private Task OnDmtpActorConnecting(DmtpActor actor, DmtpVerifyEventArgs e) { if (e.Token == this.VerifyToken) { @@ -193,7 +224,7 @@ public abstract class HttpDmtpSessionClient : HttpSessionClient, IHttpDmtpSessio e.Message = "Token不受理"; } - return this.OnHandshaking(e); + return this.OnConnecting(e); } private Task OnDmtpActorRouting(DmtpActor actor, PackageRouterEventArgs e) @@ -264,7 +295,7 @@ public abstract class HttpDmtpSessionClient : HttpSessionClient, IHttpDmtpSessio /// 在完成握手连接时 /// /// 包含握手信息的事件参数 - protected virtual async Task OnHandshaked(DmtpVerifyEventArgs e) + protected virtual async Task OnConnected(DmtpVerifyEventArgs e) { // 如果握手已经被处理,则不再执行后续操作 if (e.Handled) @@ -272,20 +303,20 @@ public abstract class HttpDmtpSessionClient : HttpSessionClient, IHttpDmtpSessio return; } // 触发插件管理器中的握手完成插件事件 - await this.PluginManager.RaiseAsync(typeof(IDmtpHandshakedPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.PluginManager.RaiseAsync(typeof(IDmtpConnectedPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// /// 在验证Token时 /// /// 参数 - protected virtual async Task OnHandshaking(DmtpVerifyEventArgs e) + protected virtual async Task OnConnecting(DmtpVerifyEventArgs e) { if (e.Handled) { return; } - await this.PluginManager.RaiseAsync(typeof(IDmtpHandshakingPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.PluginManager.RaiseAsync(typeof(IDmtpConnectingPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// diff --git a/src/TouchSocket.Dmtp/Components/Tcp/Interface/ITcpDmtpSessionClient.cs b/src/TouchSocket.Dmtp/Components/Tcp/Interface/ITcpDmtpSessionClient.cs index 5c13e1981..5dec8b080 100644 --- a/src/TouchSocket.Dmtp/Components/Tcp/Interface/ITcpDmtpSessionClient.cs +++ b/src/TouchSocket.Dmtp/Components/Tcp/Interface/ITcpDmtpSessionClient.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; using TouchSocket.Sockets; namespace TouchSocket.Dmtp; diff --git a/src/TouchSocket.Dmtp/Components/Tcp/TcpDmtpClient.cs b/src/TouchSocket.Dmtp/Components/Tcp/TcpDmtpClient.cs index eb4976ae8..d514f3f14 100644 --- a/src/TouchSocket.Dmtp/Components/Tcp/TcpDmtpClient.cs +++ b/src/TouchSocket.Dmtp/Components/Tcp/TcpDmtpClient.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Sockets; namespace TouchSocket.Dmtp; @@ -58,9 +54,9 @@ public partial class TcpDmtpClient : TcpClientBase, ITcpDmtpClient /// 发送关闭消息。 /// /// 关闭消息的内容 - /// + /// 可取消令箭 /// 异步任务 - public override async Task CloseAsync(string msg, CancellationToken token = default) + public override async Task CloseAsync(string msg, CancellationToken cancellationToken = default) { // 检查是否已初始化IDmtpActor对象 if (this.m_dmtpActor != null) @@ -68,11 +64,11 @@ public partial class TcpDmtpClient : TcpClientBase, ITcpDmtpClient // 向IDmtpActor对象发送关闭消息 await this.m_dmtpActor.SendCloseAsync(msg).ConfigureAwait(EasyTask.ContinueOnCapturedContext); // 关闭IDmtpActor对象 - await this.m_dmtpActor.CloseAsync(msg, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.m_dmtpActor.CloseAsync(msg, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } // 调用基类的CloseAsync方法完成后续关闭操作 - return await base.CloseAsync(msg, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return await base.CloseAsync(msg, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } #endregion 断开 @@ -80,9 +76,9 @@ public partial class TcpDmtpClient : TcpClientBase, ITcpDmtpClient #region 连接 /// - public virtual async Task ConnectAsync(int millisecondsTimeout, CancellationToken token) + public virtual async Task ConnectAsync(CancellationToken cancellationToken) { - await this.m_semaphoreForConnect.WaitTimeAsync(millisecondsTimeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.m_semaphoreForConnect.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); try { @@ -92,10 +88,13 @@ public partial class TcpDmtpClient : TcpClientBase, ITcpDmtpClient } if (!base.Online) { - await base.TcpConnectAsync(millisecondsTimeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await base.TcpConnectAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - await this.m_dmtpActor.HandshakeAsync(this.Config.GetValue(DmtpConfigExtension.DmtpOptionProperty).VerifyToken, this.Config.GetValue(DmtpConfigExtension.DmtpOptionProperty).Id, millisecondsTimeout, this.Config.GetValue(DmtpConfigExtension.DmtpOptionProperty).Metadata, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var dmtpOption = this.Config.GetValue(DmtpConfigExtension.DmtpOptionProperty); + ThrowHelper.ThrowIfNull(dmtpOption, nameof(dmtpOption)); + + await this.m_dmtpActor.ConnectAsync(dmtpOption, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } finally { @@ -108,9 +107,9 @@ public partial class TcpDmtpClient : TcpClientBase, ITcpDmtpClient #region ResetId /// - public Task ResetIdAsync(string newId) + public Task ResetIdAsync(string newId, CancellationToken cancellationToken = default) { - return this.m_dmtpActor.ResetIdAsync(newId); + return this.m_dmtpActor.ResetIdAsync(newId, cancellationToken); } #endregion ResetId @@ -129,8 +128,8 @@ public partial class TcpDmtpClient : TcpClientBase, ITcpDmtpClient { OutputSendAsync = this.DmtpActorSendAsync, Routing = this.OnDmtpActorRouting, - Handshaking = this.OnDmtpActorHandshaking, - Handshaked = this.OnDmtpActorHandshaked, + Connecting = this.OnDmtpActorConnecting, + Connected = this.OnDmtpActorConnected, Closing = this.OnDmtpActorClose, Logger = this.Logger, Client = this, @@ -141,20 +140,20 @@ public partial class TcpDmtpClient : TcpClientBase, ITcpDmtpClient #region 内部委托绑定 - private Task DmtpActorSendAsync(DmtpActor actor, ReadOnlyMemory memory) + private Task DmtpActorSendAsync(DmtpActor actor, ReadOnlyMemory memory, CancellationToken cancellationToken) { - this.ThrowIfTcpClientNotConnected(); + this.ThrowIfClientNotConnected(); if (memory.Length > this.m_dmtpAdapter.MaxPackageSize) { ThrowHelper.ThrowArgumentOutOfRangeException_MoreThan(nameof(memory.Length), memory.Length, this.m_dmtpAdapter.MaxPackageSize); } - return base.ProtectedDefaultSendAsync(memory); + return base.ProtectedSendAsync(memory, cancellationToken); } private async Task OnDmtpActorClose(DmtpActor actor, string msg) { await this.OnDmtpClosing(new ClosingEventArgs(msg)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.Abort(false, msg); + //this.Abort(false, msg); } private Task OnDmtpActorCreateChannel(DmtpActor actor, CreateChannelEventArgs e) @@ -162,14 +161,14 @@ public partial class TcpDmtpClient : TcpClientBase, ITcpDmtpClient return this.OnDmtpCreatedChannel(e); } - private Task OnDmtpActorHandshaked(DmtpActor actor, DmtpVerifyEventArgs e) + private Task OnDmtpActorConnected(DmtpActor actor, DmtpVerifyEventArgs e) { - return this.OnDmtpHandshaked(e); + return this.OnDmtpConnected(e); } - private Task OnDmtpActorHandshaking(DmtpActor actor, DmtpVerifyEventArgs e) + private Task OnDmtpActorConnecting(DmtpActor actor, DmtpVerifyEventArgs e) { - return this.OnDmtpHandshaking(e); + return this.OnDmtpConnecting(e); } private Task OnDmtpActorRouting(DmtpActor actor, PackageRouterEventArgs e) @@ -240,7 +239,7 @@ public partial class TcpDmtpClient : TcpClientBase, ITcpDmtpClient /// 在完成握手连接时 /// /// 包含握手信息的事件参数 - protected virtual async Task OnDmtpHandshaked(DmtpVerifyEventArgs e) + protected virtual async Task OnDmtpConnected(DmtpVerifyEventArgs e) { // 如果握手已经被处理,则不再执行后续操作 if (e.Handled) @@ -248,14 +247,14 @@ public partial class TcpDmtpClient : TcpClientBase, ITcpDmtpClient return; } // 触发插件管理器中的握手完成插件事件 - await this.PluginManager.RaiseAsync(typeof(IDmtpHandshakedPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.PluginManager.RaiseAsync(typeof(IDmtpConnectedPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// /// 即将握手连接时 /// /// 参数 - protected virtual async Task OnDmtpHandshaking(DmtpVerifyEventArgs e) + protected virtual async Task OnDmtpConnecting(DmtpVerifyEventArgs e) { //如果参数已经被处理,则直接返回,不再执行后续操作 if (e.Handled) @@ -263,7 +262,7 @@ public partial class TcpDmtpClient : TcpClientBase, ITcpDmtpClient return; } //调用插件管理器,触发即将握手连接的事件 - await this.PluginManager.RaiseAsync(typeof(IDmtpHandshakingPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.PluginManager.RaiseAsync(typeof(IDmtpConnectingPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// @@ -286,10 +285,10 @@ public partial class TcpDmtpClient : TcpClientBase, ITcpDmtpClient #region Override /// - protected override void Dispose(bool disposing) + protected override void SafetyDispose(bool disposing) { this.m_dmtpActor?.SafeDispose(); - base.Dispose(disposing); + base.SafetyDispose(disposing); } /// @@ -310,39 +309,93 @@ public partial class TcpDmtpClient : TcpClientBase, ITcpDmtpClient /// protected sealed override async Task OnTcpConnecting(ConnectingEventArgs e) { - this.m_dmtpAdapter = new DmtpAdapter(); - this.SetAdapter(this.m_dmtpAdapter); + var adapter = new DmtpAdapter(); + adapter.Config(this.Config); + this.SetAdapter(adapter); + this.m_dmtpAdapter = adapter; await base.OnTcpConnecting(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - /// - protected sealed override async Task OnTcpReceived(ReceivedDataEventArgs e) - { - var message = (DmtpMessage)e.RequestInfo; - if (!await this.m_dmtpActor.InputReceivedData(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) - { - await this.PluginManager.RaiseAsync(typeof(IDmtpReceivedPlugin), this.Resolver, this, new DmtpMessageEventArgs(message)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - } + ///// + //protected sealed override async Task OnTcpReceived(ReceivedDataEventArgs e) + //{ + // var message = (DmtpMessage)e.RequestInfo; + // if (!await this.m_dmtpActor.InputReceivedData(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) + // { + // await this.PluginManager.RaiseAsync(typeof(IDmtpReceivedPlugin), this.Resolver, this, new DmtpMessageEventArgs(message)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + // } + //} /// - protected override async ValueTask OnTcpReceiving(ByteBlock byteBlock) + protected override async ValueTask OnTcpReceiving(IBytesReader reader) { - while (byteBlock.CanRead) + while (reader.BytesRemaining > 0) { - if (this.m_dmtpAdapter.TryParseRequest(ref byteBlock, out var message)) + if (this.m_dmtpAdapter.TryParseRequest(ref reader, out var message)) { - using (message) + if (!await this.m_dmtpActor.InputReceivedData(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) { - if (!await this.m_dmtpActor.InputReceivedData(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) - { - await this.PluginManager.RaiseAsync(typeof(IDmtpReceivedPlugin), this.Resolver, this, new DmtpMessageEventArgs(message)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } + await this.PluginManager.RaiseAsync(typeof(IDmtpReceivedPlugin), this.Resolver, this, new DmtpMessageEventArgs(message)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } + else + { + break; + } } return true; } + protected async Task ReceiveLoopAsync1111(ITransport transport) + { + var dmtpAdapter2 = new DmtpAdapter(); + var cancellationToken = transport.ClosedToken; + while (!cancellationToken.IsCancellationRequested) + { + try + { + // 等待接收数据 + var readResult = await transport.Reader.ReadAsync(cancellationToken); + + var sequence = readResult.Buffer; + var reader = new BytesReader(sequence); + try + { + while (true) + { + if (!dmtpAdapter2.TryParseRequest(ref reader, out var dmtpMessage)) + { + break; + } + + await this.ProcessDmtpMessageAsync(dmtpMessage); + } + } + finally + { + transport.Reader.AdvanceTo(sequence.GetPosition(reader.BytesRead), sequence.End); + reader.Dispose(); + } + } + catch (OperationCanceledException) + { + // 如果操作被取消,则退出循环 + break; + } + catch (Exception ex) + { + // 处理接收过程中发生的异常 + this.Logger.Error(ex, "接收数据时发生错误"); + break; + } + } + } + + private Task ProcessDmtpMessageAsync(DmtpMessage message) + { + // 处理Dmtp消息的逻辑 + // 这里可以根据需要实现具体的处理逻辑 + return Task.CompletedTask; + } #endregion Override } \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Components/Tcp/TcpDmtpService.cs b/src/TouchSocket.Dmtp/Components/Tcp/TcpDmtpService.cs index e7c07a693..e3eb492bf 100644 --- a/src/TouchSocket.Dmtp/Components/Tcp/TcpDmtpService.cs +++ b/src/TouchSocket.Dmtp/Components/Tcp/TcpDmtpService.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Sockets; namespace TouchSocket.Dmtp; @@ -42,60 +39,6 @@ public class TcpDmtpService : TcpDmtpService, ITcpDmtpServ /// 客户端会话类型,必须继承自 public abstract class TcpDmtpService : TcpServiceBase, ITcpDmtpService where TClient : TcpDmtpSessionClient { - #region 字段 - - private bool m_allowRoute; - private Func> m_findDmtpActor; - - #endregion 字段 - /// public string VerifyToken => this.Config.GetValue(DmtpConfigExtension.DmtpOptionProperty).VerifyToken; - - /// - protected override void ClientInitialized(TClient client) - { - base.ClientInitialized(client); - client.InternalSetAction(this.PrivateConnecting); - } - - /// - protected override void LoadConfig(TouchSocketConfig config) - { - config.SetTcpDataHandlingAdapter(default); - base.LoadConfig(config); - - var dmtpRouteService = this.Resolver.Resolve(); - if (dmtpRouteService != null) - { - this.m_allowRoute = true; - this.m_findDmtpActor = dmtpRouteService.FindDmtpActor; - } - } - - private async Task FindDmtpActor(string id) - { - if (this.m_allowRoute) - { - if (this.m_findDmtpActor != null) - { - return await this.m_findDmtpActor.Invoke(id).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - return this.TryGetClient(id, out var client) ? client.DmtpActor : null; - } - else - { - return null; - } - } - - private Task PrivateConnecting(TcpDmtpSessionClient sessionClient, ConnectingEventArgs e) - { - sessionClient.InternalSetDmtpActor(new SealedDmtpActor(this.m_allowRoute) - { - Id = e.Id, - FindDmtpActor = this.FindDmtpActor - }); - return EasyTask.CompletedTask; - } } \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Components/Tcp/TcpDmtpSessionClient.cs b/src/TouchSocket.Dmtp/Components/Tcp/TcpDmtpSessionClient.cs index c999c3cea..3dc18bccc 100644 --- a/src/TouchSocket.Dmtp/Components/Tcp/TcpDmtpSessionClient.cs +++ b/src/TouchSocket.Dmtp/Components/Tcp/TcpDmtpSessionClient.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Sockets; namespace TouchSocket.Dmtp; @@ -24,10 +20,8 @@ namespace TouchSocket.Dmtp; /// public abstract class TcpDmtpSessionClient : TcpSessionClientBase, ITcpDmtpSessionClient { - private readonly DmtpAdapter m_dmtpAdapter = new DmtpAdapter(); - private DmtpActor m_dmtpActor; - private Func m_privateConnecting; - + private readonly DmtpAdapter m_dmtpAdapter = new(); + private SealedDmtpActor m_dmtpActor; /// /// 构造函数:初始化TcpDmtpSessionClient实例 /// @@ -59,7 +53,7 @@ public abstract class TcpDmtpSessionClient : TcpSessionClientBase, ITcpDmtpSessi private Task OnDmtpActorClose(DmtpActor actor, string msg) { - base.Abort(false, msg); + //base.Abort(false, msg); return EasyTask.CompletedTask; } @@ -68,12 +62,12 @@ public abstract class TcpDmtpSessionClient : TcpSessionClientBase, ITcpDmtpSessi return this.OnCreateChannel(e); } - private Task OnDmtpActorHandshaked(DmtpActor actor, DmtpVerifyEventArgs e) + private Task OnDmtpActorConnected(DmtpActor actor, DmtpVerifyEventArgs e) { - return this.OnHandshaked(e); + return this.OnConnected(e); } - private async Task OnDmtpActorHandshaking(DmtpActor actor, DmtpVerifyEventArgs e) + private async Task OnDmtpActorConnecting(DmtpActor actor, DmtpVerifyEventArgs e) { if (e.Token == this.VerifyToken) { @@ -84,7 +78,7 @@ public abstract class TcpDmtpSessionClient : TcpSessionClientBase, ITcpDmtpSessi e.Message = "Token不受理"; } - await this.OnHandshaking(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.OnConnecting(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } private Task OnDmtpActorRouting(DmtpActor actor, PackageRouterEventArgs e) @@ -156,7 +150,7 @@ public abstract class TcpDmtpSessionClient : TcpSessionClientBase, ITcpDmtpSessi /// 在完成握手连接时 /// /// 包含握手信息的事件参数 - protected virtual async Task OnHandshaked(DmtpVerifyEventArgs e) + protected virtual async Task OnConnected(DmtpVerifyEventArgs e) { // 如果握手已经被处理,则不再继续执行 if (e.Handled) @@ -164,14 +158,14 @@ public abstract class TcpDmtpSessionClient : TcpSessionClientBase, ITcpDmtpSessi return; } // 触发插件管理器中的握手完成插件事件 - await this.PluginManager.RaiseAsync(typeof(IDmtpHandshakedPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.PluginManager.RaiseAsync(typeof(IDmtpConnectedPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// /// 在验证Token时 /// /// 参数 - protected virtual async Task OnHandshaking(DmtpVerifyEventArgs e) + protected virtual async Task OnConnecting(DmtpVerifyEventArgs e) { // 如果事件已经被处理,则直接返回 if (e.Handled) @@ -179,7 +173,7 @@ public abstract class TcpDmtpSessionClient : TcpSessionClientBase, ITcpDmtpSessi return; } // 异步调用插件管理器,执行握手验证插件 - await this.PluginManager.RaiseAsync(typeof(IDmtpHandshakingPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.PluginManager.RaiseAsync(typeof(IDmtpConnectingPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// @@ -205,26 +199,26 @@ public abstract class TcpDmtpSessionClient : TcpSessionClientBase, ITcpDmtpSessi /// 发送关闭消息。 /// /// 关闭消息的内容 - /// + /// 可取消令箭 /// 异步任务 - public override async Task CloseAsync(string msg, CancellationToken token = default) + public override async Task CloseAsync(string msg, CancellationToken cancellationToken = default) { // 检查是否已初始化IDmtpActor接口 if (this.m_dmtpActor != null) { // 如果已初始化,则调用IDmtpActor的CloseAsync方法发送关闭消息 - await this.m_dmtpActor.CloseAsync(msg, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.m_dmtpActor.CloseAsync(msg, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } // 调用基类的CloseAsync方法发送关闭消息 - return await base.CloseAsync(msg, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return await base.CloseAsync(msg, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// - protected override void Dispose(bool disposing) + protected override void SafetyDispose(bool disposing) { this.m_dmtpActor?.SafeDispose(); - base.Dispose(disposing); + base.SafetyDispose(disposing); } #endregion 断开 @@ -232,47 +226,71 @@ public abstract class TcpDmtpSessionClient : TcpSessionClientBase, ITcpDmtpSessi #region ResetId /// - public override Task ResetIdAsync(string newId) + public override Task ResetIdAsync(string newId, CancellationToken cancellationToken = default) { - return this.m_dmtpActor.ResetIdAsync(newId); + return this.m_dmtpActor.ResetIdAsync(newId, cancellationToken); } #endregion ResetId - #region Internal - - internal void InternalSetAction(Func privateConnecting) + private void InitDmtpActor(string id) { - this.m_privateConnecting = privateConnecting; - } + var allowRoute = false; + Func> findDmtpActor = default; + var dmtpRouteService = this.Resolver.Resolve(); + if (dmtpRouteService != null) + { + allowRoute = true; + findDmtpActor = dmtpRouteService.FindDmtpActor; + } + + async Task FindDmtpActor(string id) + { + if (allowRoute) + { + if (findDmtpActor != null) + { + return await findDmtpActor.Invoke(id).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + if (this.ProtectedTryGetClient(id, out var client) && client is IDmtpActorObject dmtpActorObject) + { + return dmtpActorObject.DmtpActor; + } + return null; + } + else + { + return null; + } + } + + var actor = new SealedDmtpActor(allowRoute, true) + { + Id = id, + FindDmtpActor = FindDmtpActor, + IdChanged = this.ThisOnResetId, + OutputSendAsync = this.ThisDmtpActorOutputSendAsync, + Client = this, + Closing = this.OnDmtpActorClose, + Routing = this.OnDmtpActorRouting, + Connected = this.OnDmtpActorConnected, + Connecting = this.OnDmtpActorConnecting, + CreatedChannel = this.OnDmtpActorCreateChannel, + Logger = this.Logger + }; - internal void InternalSetDmtpActor(DmtpActor actor) - { - actor.IdChanged = this.ThisOnResetId; - //actor.OutputSend = this.ThisDmtpActorOutputSend; - actor.OutputSendAsync = this.ThisDmtpActorOutputSendAsync; - actor.Client = this; - actor.Closing = this.OnDmtpActorClose; - actor.Routing = this.OnDmtpActorRouting; - actor.Handshaked = this.OnDmtpActorHandshaked; - actor.Handshaking = this.OnDmtpActorHandshaking; - actor.CreatedChannel = this.OnDmtpActorCreateChannel; - actor.Logger = this.Logger; this.m_dmtpActor = actor; - - this.SetAdapter(this.m_dmtpAdapter); + this.m_dmtpAdapter.Config(this.Config); } - #endregion Internal - - private Task ThisDmtpActorOutputSendAsync(DmtpActor actor, ReadOnlyMemory memory) + private Task ThisDmtpActorOutputSendAsync(DmtpActor actor, ReadOnlyMemory memory, CancellationToken cancellationToken) { if (memory.Length > this.m_dmtpAdapter.MaxPackageSize) { ThrowHelper.ThrowArgumentOutOfRangeException_MoreThan(nameof(memory.Length), memory.Length, this.m_dmtpAdapter.MaxPackageSize); } - return base.ProtectedDefaultSendAsync(memory); + return base.ProtectedSendAsync(memory, cancellationToken); } private async Task ThisOnResetId(DmtpActor rpcActor, IdChangedEventArgs e) @@ -299,13 +317,11 @@ public abstract class TcpDmtpSessionClient : TcpSessionClientBase, ITcpDmtpSessi /// protected override async Task OnTcpConnected(ConnectedEventArgs e) { - this.m_dmtpActor.Id = this.Id; - _ = Task.Run(async () => + _ = EasyTask.SafeRun(async () => { await Task.Delay(this.VerifyTimeout).ConfigureAwait(EasyTask.ContinueOnCapturedContext); if (!this.Online) { - await this.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both); await base.CloseAsync("握手验证超时").ConfigureAwait(EasyTask.ContinueOnCapturedContext); } }); @@ -316,35 +332,36 @@ public abstract class TcpDmtpSessionClient : TcpSessionClientBase, ITcpDmtpSessi /// protected override async Task OnTcpConnecting(ConnectingEventArgs e) { - await this.m_privateConnecting(this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); await base.OnTcpConnecting(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + this.InitDmtpActor(e.Id); } - /// - protected override async Task OnTcpReceived(ReceivedDataEventArgs e) - { - var message = (DmtpMessage)e.RequestInfo; - if (!await this.m_dmtpActor.InputReceivedData(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) - { - await this.PluginManager.RaiseAsync(typeof(IDmtpReceivedPlugin), this.Resolver, this, new DmtpMessageEventArgs(message)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - } + ///// + //protected override async Task OnTcpReceived(ReceivedDataEventArgs e) + //{ + // var message = (DmtpMessage)e.RequestInfo; + // if (!await this.m_dmtpActor.InputReceivedData(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) + // { + // await this.PluginManager.RaiseAsync(typeof(IDmtpReceivedPlugin), this.Resolver, this, new DmtpMessageEventArgs(message)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + // } + //} /// - protected override async ValueTask OnTcpReceiving(ByteBlock byteBlock) + protected override async ValueTask OnTcpReceiving(IBytesReader reader) { - while (byteBlock.CanRead) + while (reader.BytesRemaining > 0) { - if (this.m_dmtpAdapter.TryParseRequest(ref byteBlock, out var message)) + if (this.m_dmtpAdapter.TryParseRequest(ref reader, out var message)) { - using (message) + if (!await this.m_dmtpActor.InputReceivedData(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) { - if (!await this.m_dmtpActor.InputReceivedData(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) - { - await this.PluginManager.RaiseAsync(typeof(IDmtpReceivedPlugin), this.Resolver, this, new DmtpMessageEventArgs(message)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } + await this.PluginManager.RaiseAsync(typeof(IDmtpReceivedPlugin), this.Resolver, this, new DmtpMessageEventArgs(message)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } + else + { + break; + } } return true; } diff --git a/src/TouchSocket.Dmtp/Components/Udp/UdpDmtp.cs b/src/TouchSocket.Dmtp/Components/Udp/UdpDmtp.cs index 3188fe2dc..ee38eff48 100644 --- a/src/TouchSocket.Dmtp/Components/Udp/UdpDmtp.cs +++ b/src/TouchSocket.Dmtp/Components/Udp/UdpDmtp.cs @@ -10,12 +10,8 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Collections.Concurrent; using System.Net; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Sockets; namespace TouchSocket.Dmtp; @@ -65,17 +61,17 @@ public partial class UdpDmtp : UdpSessionBase, IUdpDmtp return await this.PrivateGetUdpDmtpClientAsync(endPoint).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - internal Task InternalSendAsync(EndPoint m_endPoint, ReadOnlyMemory memory) + internal Task InternalSendAsync(EndPoint m_endPoint, ReadOnlyMemory memory, CancellationToken cancellationToken = default) { - return this.ProtectedSendAsync(m_endPoint, memory); + return this.ProtectedSendAsync(m_endPoint, memory, cancellationToken); } /// - protected override void Dispose(bool disposing) + protected override void SafetyDispose(bool disposing) { this.m_timer.SafeDispose(); this.m_udpDmtpClients.Clear(); - base.Dispose(disposing); + base.SafetyDispose(disposing); } /// @@ -93,7 +89,7 @@ public partial class UdpDmtp : UdpSessionBase, IUdpDmtp return; } - var message = DmtpMessage.CreateFrom(e.ByteBlock.Span); + var message = DmtpMessage.CreateFrom(e.Memory); if (!await client.InputReceivedData(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) { if (this.PluginManager.Enable) diff --git a/src/TouchSocket.Dmtp/Components/Udp/UdpDmtpClient.cs b/src/TouchSocket.Dmtp/Components/Udp/UdpDmtpClient.cs index e5bb56441..942a7fb9f 100644 --- a/src/TouchSocket.Dmtp/Components/Udp/UdpDmtpClient.cs +++ b/src/TouchSocket.Dmtp/Components/Udp/UdpDmtpClient.cs @@ -10,10 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Net; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Sockets; namespace TouchSocket.Dmtp; @@ -55,7 +52,7 @@ internal sealed class UdpDmtpClient : DmtpActor, IUdpDmtpClient Id = this.Id, IsPermitOperation = true }; - await pluginManager.RaiseAsync(typeof(IDmtpHandshakingPlugin), this.m_udpSession.Resolver, this, args).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await pluginManager.RaiseAsync(typeof(IDmtpConnectingPlugin), this.m_udpSession.Resolver, this, args).ConfigureAwait(EasyTask.ContinueOnCapturedContext); if (args.IsPermitOperation == false) { @@ -66,7 +63,7 @@ internal sealed class UdpDmtpClient : DmtpActor, IUdpDmtpClient { Id = this.Id }; - await pluginManager.RaiseAsync(typeof(IDmtpHandshakedPlugin), this.m_udpSession.Resolver, this, args).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await pluginManager.RaiseAsync(typeof(IDmtpConnectedPlugin), this.m_udpSession.Resolver, this, args).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return true; } @@ -86,21 +83,14 @@ internal sealed class UdpDmtpClient : DmtpActor, IUdpDmtpClient /// /// 不支持该操作 /// - /// - /// /// 该客户端的Id为实际通信EndPoint值,所以不支持重置Id的操作。 - public override Task ResetIdAsync(string id) + public override Task ResetIdAsync(string id, CancellationToken cancellationToken = default) { throw new NotSupportedException("该客户端的Id为实际通信EndPoint值,所以不支持重置Id的操作。"); } - //private void RpcActorSend(DmtpActor actor, ArraySegment[] transferBytes) - //{ - // this.m_udpSession.InternalSend(this.m_endPoint, transferBytes); - //} - - private Task RpcActorSendAsync(DmtpActor actor, ReadOnlyMemory memory) + private Task RpcActorSendAsync(DmtpActor actor, ReadOnlyMemory memory, CancellationToken cancellationToken) { - return this.m_udpSession.InternalSendAsync(this.m_endPoint, memory); + return this.m_udpSession.InternalSendAsync(this.m_endPoint, memory, cancellationToken); } } \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Components/WebSocket/Interface/IWebSocketDmtpClient.cs b/src/TouchSocket.Dmtp/Components/WebSocket/Interface/IWebSocketDmtpClient.cs index a1ea8018c..e1b8e135f 100644 --- a/src/TouchSocket.Dmtp/Components/WebSocket/Interface/IWebSocketDmtpClient.cs +++ b/src/TouchSocket.Dmtp/Components/WebSocket/Interface/IWebSocketDmtpClient.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; using TouchSocket.Sockets; namespace TouchSocket.Dmtp; diff --git a/src/TouchSocket.Dmtp/Components/WebSocket/WebSocketDmtpClient.cs b/src/TouchSocket.Dmtp/Components/WebSocket/WebSocketDmtpClient.cs index 64a6a823f..f4f14e4b5 100644 --- a/src/TouchSocket.Dmtp/Components/WebSocket/WebSocketDmtpClient.cs +++ b/src/TouchSocket.Dmtp/Components/WebSocket/WebSocketDmtpClient.cs @@ -10,11 +10,8 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; +using System.Buffers; using System.Net.WebSockets; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Http.WebSockets; using TouchSocket.Sockets; @@ -48,9 +45,9 @@ public class WebSocketDmtpClient : SetupClientWebSocket, IWebSocketDmtpClient #region 连接 /// - public override async Task ConnectAsync(int millisecondsTimeout, CancellationToken token) + public override async Task ConnectAsync(CancellationToken cancellationToken) { - await this.m_connectionSemaphore.WaitTimeAsync(millisecondsTimeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.m_connectionSemaphore.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); try { if (this.Online) @@ -60,15 +57,15 @@ public class WebSocketDmtpClient : SetupClientWebSocket, IWebSocketDmtpClient if (!base.Online) { - await base.ConnectAsync(millisecondsTimeout, token); + await base.ConnectAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } this.m_dmtpActor = new SealedDmtpActor(this.m_allowRoute) { OutputSendAsync = this.OnDmtpActorSendAsync, Routing = this.OnDmtpActorRouting, - Handshaking = this.OnDmtpActorHandshaking, - Handshaked = this.OnDmtpActorHandshaked, + Connecting = this.OnDmtpActorConnecting, + Connected = this.OnDmtpActorConnected, Closing = this.OnDmtpActorClose, Logger = this.Logger, Client = this, @@ -80,9 +77,10 @@ public class WebSocketDmtpClient : SetupClientWebSocket, IWebSocketDmtpClient this.m_dmtpAdapter.Config(this.Config); this.m_tokenSourceForReceive = new CancellationTokenSource(); - var option = this.Config.GetValue(DmtpConfigExtension.DmtpOptionProperty); + var dmtpOption = this.Config.GetValue(DmtpConfigExtension.DmtpOptionProperty); + ThrowHelper.ThrowIfNull(dmtpOption, nameof(dmtpOption)); - await this.m_dmtpActor.HandshakeAsync(option.VerifyToken, option.Id, millisecondsTimeout, option.Metadata, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.m_dmtpActor.ConnectAsync(dmtpOption, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } finally { @@ -106,7 +104,7 @@ public class WebSocketDmtpClient : SetupClientWebSocket, IWebSocketDmtpClient /// - public override async Task CloseAsync(string msg, CancellationToken token = default) + public override async Task CloseAsync(string msg, CancellationToken cancellationToken = default) { try { @@ -119,10 +117,10 @@ public class WebSocketDmtpClient : SetupClientWebSocket, IWebSocketDmtpClient await dmtpActor.SendCloseAsync(msg).ConfigureAwait(EasyTask.ContinueOnCapturedContext); // 关闭IDmtpActor对象 - await dmtpActor.CloseAsync(msg, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await dmtpActor.CloseAsync(msg, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - await base.CloseAsync(msg, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await base.CloseAsync(msg, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return Result.Success; } @@ -133,24 +131,9 @@ public class WebSocketDmtpClient : SetupClientWebSocket, IWebSocketDmtpClient } /// - public Task ResetIdAsync(string newId) + public Task ResetIdAsync(string newId, CancellationToken cancellationToken = default) { - return this.m_dmtpActor.ResetIdAsync(newId); - } - - /// - protected override void Dispose(bool disposing) - { - if (this.DisposedValue) - { - return; - } - if (disposing) - { - this.Abort(true, $"调用{nameof(Dispose)}"); - } - - base.Dispose(disposing); + return this.m_dmtpActor.ResetIdAsync(newId, cancellationToken); } /// @@ -165,22 +148,16 @@ public class WebSocketDmtpClient : SetupClientWebSocket, IWebSocketDmtpClient } /// - protected override async Task OnReceived(WebSocketReceiveResult result, ByteBlock byteBlock) + protected override async Task OnWebSocketReceived(WebSocketMessageType messageType, ReadOnlySequence sequence) { try { - //处理数据 - while (byteBlock.CanRead) + using (var buffer = new ContiguousMemoryBuffer(sequence)) { - if (this.m_dmtpAdapter.TryParseRequest(ref byteBlock, out var message)) + var message = DmtpMessage.CreateFrom(buffer.Memory); + if (!await this.m_dmtpActor.InputReceivedData(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) { - using (message) - { - if (!await this.m_dmtpActor.InputReceivedData(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) - { - await this.PluginManager.RaiseAsync(typeof(IDmtpReceivedPlugin), this.Resolver, this, new DmtpMessageEventArgs(message)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - } + await this.PluginManager.RaiseAsync(typeof(IDmtpReceivedPlugin), this.Resolver, this, new DmtpMessageEventArgs(message)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } } @@ -201,8 +178,6 @@ public class WebSocketDmtpClient : SetupClientWebSocket, IWebSocketDmtpClient private async Task OnDmtpActorClose(DmtpActor actor, string msg) { await this.OnDmtpClosing(new ClosingEventArgs(msg)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - - this.Abort(false, msg); } private Task OnDmtpActorCreateChannel(DmtpActor actor, CreateChannelEventArgs e) @@ -210,14 +185,14 @@ public class WebSocketDmtpClient : SetupClientWebSocket, IWebSocketDmtpClient return this.OnCreateChannel(e); } - private Task OnDmtpActorHandshaked(DmtpActor actor, DmtpVerifyEventArgs e) + private Task OnDmtpActorConnected(DmtpActor actor, DmtpVerifyEventArgs e) { - return this.OnHandshaked(e); + return this.OnConnected(e); } - private Task OnDmtpActorHandshaking(DmtpActor actor, DmtpVerifyEventArgs e) + private Task OnDmtpActorConnecting(DmtpActor actor, DmtpVerifyEventArgs e) { - return this.OnHandshaking(e); + return this.OnConnecting(e); } private Task OnDmtpActorRouting(DmtpActor actor, PackageRouterEventArgs e) @@ -225,9 +200,9 @@ public class WebSocketDmtpClient : SetupClientWebSocket, IWebSocketDmtpClient return this.OnRouting(e); } - private async Task OnDmtpActorSendAsync(DmtpActor actor, ReadOnlyMemory memory) + private async Task OnDmtpActorSendAsync(DmtpActor actor, ReadOnlyMemory memory, CancellationToken cancellationToken) { - await base.ProtectedSendAsync(memory, WebSocketMessageType.Binary, true, CancellationToken.None).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await base.ProtectedSendAsync(memory, WebSocketMessageType.Binary, true, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } #endregion 内部委托绑定 @@ -292,7 +267,7 @@ public class WebSocketDmtpClient : SetupClientWebSocket, IWebSocketDmtpClient /// 在完成握手连接时 /// /// 包含握手信息的事件参数 - protected virtual async Task OnHandshaked(DmtpVerifyEventArgs e) + protected virtual async Task OnConnected(DmtpVerifyEventArgs e) { // 如果握手已经被处理,则不再执行后续操作 if (e.Handled) @@ -300,14 +275,14 @@ public class WebSocketDmtpClient : SetupClientWebSocket, IWebSocketDmtpClient return; } // 触发插件管理器中的握手完成插件事件 - await this.PluginManager.RaiseAsync(typeof(IDmtpHandshakedPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.PluginManager.RaiseAsync(typeof(IDmtpConnectedPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// /// 即将握手连接时 /// /// 参数 - protected virtual async Task OnHandshaking(DmtpVerifyEventArgs e) + protected virtual async Task OnConnecting(DmtpVerifyEventArgs e) { // 如果握手已经被处理,则直接返回 if (e.Handled) @@ -315,7 +290,7 @@ public class WebSocketDmtpClient : SetupClientWebSocket, IWebSocketDmtpClient return; } // 触发握手过程的插件事件 - await this.PluginManager.RaiseAsync(typeof(IDmtpHandshakingPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.PluginManager.RaiseAsync(typeof(IDmtpConnectingPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// diff --git a/src/TouchSocket.Dmtp/Enum/RouteType.cs b/src/TouchSocket.Dmtp/Enum/RouteType.cs index 9e22b5f04..d46a64421 100644 --- a/src/TouchSocket.Dmtp/Enum/RouteType.cs +++ b/src/TouchSocket.Dmtp/Enum/RouteType.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Dmtp; /// diff --git a/src/TouchSocket.Dmtp/EventArgs/CreateChannelEventArgs.cs b/src/TouchSocket.Dmtp/EventArgs/CreateChannelEventArgs.cs index c29b6b7e5..1547524a6 100644 --- a/src/TouchSocket.Dmtp/EventArgs/CreateChannelEventArgs.cs +++ b/src/TouchSocket.Dmtp/EventArgs/CreateChannelEventArgs.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Dmtp; /// diff --git a/src/TouchSocket.Dmtp/EventArgs/DmtpMessageEventArgs.cs b/src/TouchSocket.Dmtp/EventArgs/DmtpMessageEventArgs.cs index 866301f5e..7e14459be 100644 --- a/src/TouchSocket.Dmtp/EventArgs/DmtpMessageEventArgs.cs +++ b/src/TouchSocket.Dmtp/EventArgs/DmtpMessageEventArgs.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Dmtp; /// diff --git a/src/TouchSocket.Dmtp/EventArgs/DmtpVerifyEventArgs.cs b/src/TouchSocket.Dmtp/EventArgs/DmtpVerifyEventArgs.cs index 9761b67dc..f2a16b4e9 100644 --- a/src/TouchSocket.Dmtp/EventArgs/DmtpVerifyEventArgs.cs +++ b/src/TouchSocket.Dmtp/EventArgs/DmtpVerifyEventArgs.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Dmtp; /// diff --git a/src/TouchSocket.Dmtp/EventArgs/PackageRouterEventArgs.cs b/src/TouchSocket.Dmtp/EventArgs/PackageRouterEventArgs.cs index 841ddebb0..21f52c89c 100644 --- a/src/TouchSocket.Dmtp/EventArgs/PackageRouterEventArgs.cs +++ b/src/TouchSocket.Dmtp/EventArgs/PackageRouterEventArgs.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Dmtp; /// diff --git a/src/TouchSocket.Dmtp/Exceptions/TokenVerifyException.cs b/src/TouchSocket.Dmtp/Exceptions/TokenVerifyException.cs index 36074da5c..f08960d88 100644 --- a/src/TouchSocket.Dmtp/Exceptions/TokenVerifyException.cs +++ b/src/TouchSocket.Dmtp/Exceptions/TokenVerifyException.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using TouchSocket.Core; - namespace TouchSocket.Dmtp; /// diff --git a/src/TouchSocket.Dmtp/Extensions/DmtpActorExtension.cs b/src/TouchSocket.Dmtp/Extensions/DmtpActorExtension.cs index 4cf86bd92..2d3b61e52 100644 --- a/src/TouchSocket.Dmtp/Extensions/DmtpActorExtension.cs +++ b/src/TouchSocket.Dmtp/Extensions/DmtpActorExtension.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Dmtp; /// @@ -23,30 +19,16 @@ public static class DmtpActorExtension { #region Ping - /// - [AsyncToSyncWarning] - public static bool Ping(this IDmtpActorObject client, int millisecondsTimeout = 5000) + /// + public static Task PingAsync(this IDmtpActorObject client, CancellationToken cancellationToken = default) { - return client.DmtpActor.PingAsync(millisecondsTimeout).GetFalseAwaitResult(); + return client.DmtpActor.PingAsync(cancellationToken); } - /// - [AsyncToSyncWarning] - public static bool Ping(this IDmtpActorObject client, string targetId, int millisecondsTimeout = 5000) + /// + public static Task PingAsync(this IDmtpActorObject client, string targetId, CancellationToken cancellationToken = default) { - return client.DmtpActor.PingAsync(targetId, millisecondsTimeout).GetFalseAwaitResult(); - } - - /// - public static Task PingAsync(this IDmtpActorObject client, int millisecondsTimeout = 5000) - { - return client.DmtpActor.PingAsync(millisecondsTimeout); - } - - /// - public static Task PingAsync(this IDmtpActorObject client, string targetId, int millisecondsTimeout = 5000) - { - return client.DmtpActor.PingAsync(targetId, millisecondsTimeout); + return client.DmtpActor.PingAsync(targetId, cancellationToken); } #endregion Ping @@ -59,56 +41,56 @@ public static class DmtpActorExtension return client.DmtpActor.ChannelExisted(id); } - /// + /// [AsyncToSyncWarning] - public static IDmtpChannel CreateChannel(this IDmtpActorObject client, Metadata metadata = default) + public static IDmtpChannel CreateChannel(this IDmtpActorObject client, Metadata metadata = default, CancellationToken cancellationToken = default) { - return client.DmtpActor.CreateChannelAsync(metadata).GetFalseAwaitResult(); + return client.DmtpActor.CreateChannelAsync(metadata, cancellationToken).GetFalseAwaitResult(); } - /// + /// [AsyncToSyncWarning] - public static IDmtpChannel CreateChannel(this IDmtpActorObject client, int id, Metadata metadata = default) + public static IDmtpChannel CreateChannel(this IDmtpActorObject client, int id, Metadata metadata = default, CancellationToken cancellationToken = default) { - return client.DmtpActor.CreateChannelAsync(id, metadata).GetFalseAwaitResult(); + return client.DmtpActor.CreateChannelAsync(id, metadata, cancellationToken).GetFalseAwaitResult(); } - /// + /// [AsyncToSyncWarning] - public static IDmtpChannel CreateChannel(this IDmtpActorObject client, string targetId, int id, Metadata metadata = default) + public static IDmtpChannel CreateChannel(this IDmtpActorObject client, string targetId, int id, Metadata metadata = default, CancellationToken cancellationToken = default) { - return client.DmtpActor.CreateChannelAsync(targetId, id, metadata).GetFalseAwaitResult(); + return client.DmtpActor.CreateChannelAsync(targetId, id, metadata, cancellationToken).GetFalseAwaitResult(); } - /// + /// [AsyncToSyncWarning] - public static IDmtpChannel CreateChannel(this IDmtpActorObject client, string targetId, Metadata metadata = default) + public static IDmtpChannel CreateChannel(this IDmtpActorObject client, string targetId, Metadata metadata = default, CancellationToken cancellationToken = default) { - return client.DmtpActor.CreateChannelAsync(targetId, metadata).GetFalseAwaitResult(); + return client.DmtpActor.CreateChannelAsync(targetId, metadata, cancellationToken).GetFalseAwaitResult(); } - /// - public static Task CreateChannelAsync(this IDmtpActorObject client, Metadata metadata = default) + /// + public static Task CreateChannelAsync(this IDmtpActorObject client, Metadata metadata = default, CancellationToken cancellationToken = default) { - return client.DmtpActor.CreateChannelAsync(metadata); + return client.DmtpActor.CreateChannelAsync(metadata, cancellationToken); } - /// - public static Task CreateChannelAsync(this IDmtpActorObject client, int id, Metadata metadata = default) + /// + public static Task CreateChannelAsync(this IDmtpActorObject client, int id, Metadata metadata = default, CancellationToken cancellationToken = default) { - return client.DmtpActor.CreateChannelAsync(id, metadata); + return client.DmtpActor.CreateChannelAsync(id, metadata, cancellationToken); } - /// - public static Task CreateChannelAsync(this IDmtpActorObject client, string targetId, int id, Metadata metadata = default) + /// + public static Task CreateChannelAsync(this IDmtpActorObject client, string targetId, int id, Metadata metadata = default, CancellationToken cancellationToken = default) { - return client.DmtpActor.CreateChannelAsync(targetId, id, metadata); + return client.DmtpActor.CreateChannelAsync(targetId, id, metadata, cancellationToken); } - /// - public static Task CreateChannelAsync(this IDmtpActorObject client, string targetId, Metadata metadata = default) + /// + public static Task CreateChannelAsync(this IDmtpActorObject client, string targetId, Metadata metadata = default, CancellationToken cancellationToken = default) { - return client.DmtpActor.CreateChannelAsync(targetId, metadata); + return client.DmtpActor.CreateChannelAsync(targetId, metadata, cancellationToken); } /// @@ -177,7 +159,8 @@ public static class DmtpActorExtension /// 发送数据包所使用的协议。 /// 要发送的数据包实例。 /// 数据包的预估最大大小,用于指导内存的分配。 - public static async Task SendAsync(this IDmtpActorObject client, ushort protocol, IPackage package, int maxSize) + /// 可取消令箭 + public static async Task SendAsync(this IDmtpActorObject client, ushort protocol, IPackage package, int maxSize, CancellationToken cancellationToken = default) { // 使用ByteBlock管理内存,根据预估的最大大小来分配内存。 using (var byteBlock = new ByteBlock(maxSize)) @@ -187,7 +170,7 @@ public static class DmtpActorExtension // 准备数据包,将数据写入到block中。 package.Package(ref block); // 使用异步方法发送数据包和协议。 - await client.DmtpActor.SendAsync(protocol, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await client.DmtpActor.SendAsync(protocol, byteBlock.Memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } @@ -198,11 +181,12 @@ public static class DmtpActorExtension /// 要发送包的客户端对象。 /// 发送包所使用的协议。 /// 要发送的包实例。 + /// 可取消令箭 /// 返回一个Task对象,表示异步操作的结果。 - public static Task SendAsync(this IDmtpActorObject client, ushort protocol, IPackage package) + public static Task SendAsync(this IDmtpActorObject client, ushort protocol, IPackage package, CancellationToken cancellationToken = default) { // 调用重载的SendAsync方法,使用默认的最大传输单元大小64K - return SendAsync(client, protocol, package, 1024 * 64); + return SendAsync(client, protocol, package, 1024 * 64, cancellationToken); } #endregion 发送Package @@ -267,10 +251,11 @@ public static class DmtpActorExtension /// 客户端对象,实现IDmtpActorObject接口 /// 协议标识符 /// 待发送的数据,以只读内存块形式提供 + /// 可取消令箭 /// 返回一个Task对象,标识异步操作 - public static Task SendAsync(this IDmtpActorObject client, ushort protocol, ReadOnlyMemory memory) + public static Task SendAsync(this IDmtpActorObject client, ushort protocol, ReadOnlyMemory memory, CancellationToken cancellationToken = default) { - return client.DmtpActor.SendAsync(protocol, memory); + return client.DmtpActor.SendAsync(protocol, memory, cancellationToken); } /// @@ -278,10 +263,11 @@ public static class DmtpActorExtension /// /// 客户端对象,实现IDmtpActorObject接口 /// 协议标识符 + /// 可取消令箭 /// 返回一个Task对象,标识异步操作 - public static Task SendAsync(this IDmtpActorObject client, ushort protocol) + public static Task SendAsync(this IDmtpActorObject client, ushort protocol, CancellationToken cancellationToken = default) { - return client.DmtpActor.SendAsync(protocol, ReadOnlyMemory.Empty); + return client.DmtpActor.SendAsync(protocol, ReadOnlyMemory.Empty, cancellationToken); } #endregion 发送 @@ -297,7 +283,7 @@ public static class DmtpActorExtension return channelStatus switch { ChannelStatus.Default => ResultCode.Default, - ChannelStatus.Overtime => ResultCode.Overtime, + ChannelStatus.HoldOn => ResultCode.Default, ChannelStatus.Cancel => ResultCode.Canceled, ChannelStatus.Completed => ResultCode.Success, _ => ResultCode.Error, diff --git a/src/TouchSocket.Dmtp/Extensions/DmtpConfigExtension.cs b/src/TouchSocket.Dmtp/Extensions/DmtpConfigExtension.cs index 11b455c8d..afe3a44d0 100644 --- a/src/TouchSocket.Dmtp/Extensions/DmtpConfigExtension.cs +++ b/src/TouchSocket.Dmtp/Extensions/DmtpConfigExtension.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Dmtp; /// @@ -22,18 +20,7 @@ public static class DmtpConfigExtension /// /// 设置Dmtp相关配置。 /// + [GeneratorProperty(TargetType = typeof(TouchSocketConfig), ActionMode = true)] public static readonly DependencyProperty DmtpOptionProperty = new("DmtpOption", new DmtpOption()); - - /// - /// 设置Dmtp相关配置。 - /// - /// 待设置的配置对象 - /// 要设置的Dmtp配置值 - /// 返回更新后的配置对象 - public static TouchSocketConfig SetDmtpOption(this TouchSocketConfig config, DmtpOption value) - { - config.SetValue(DmtpOptionProperty, value); - return config; - } } \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Extensions/DmtpPluginRaiseExtension.cs b/src/TouchSocket.Dmtp/Extensions/DmtpPluginRaiseExtension.cs new file mode 100644 index 000000000..a4eb3bf2e --- /dev/null +++ b/src/TouchSocket.Dmtp/Extensions/DmtpPluginRaiseExtension.cs @@ -0,0 +1,26 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using TouchSocket.Dmtp; + +namespace TouchSocket.Core; + +[PluginRaise(typeof(IDmtpConnectingPlugin))] +[PluginRaise(typeof(IDmtpConnectedPlugin))] +[PluginRaise(typeof(IDmtpReceivedPlugin))] +[PluginRaise(typeof(IDmtpRoutingPlugin))] +[PluginRaise(typeof(IDmtpCreatedChannelPlugin))] +[PluginRaise(typeof(IDmtpClosingPlugin))] +[PluginRaise(typeof(IDmtpClosedPlugin))] +internal static partial class DmtpPluginRaiseExtension +{ +} diff --git a/src/TouchSocket.Dmtp/Extensions/DmtpPluginsManagerExtension.cs b/src/TouchSocket.Dmtp/Extensions/DmtpPluginsManagerExtension.cs index 33b4dcc67..f8495f3e6 100644 --- a/src/TouchSocket.Dmtp/Extensions/DmtpPluginsManagerExtension.cs +++ b/src/TouchSocket.Dmtp/Extensions/DmtpPluginsManagerExtension.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; -using TouchSocket.Sockets; - namespace TouchSocket.Dmtp; /// @@ -20,47 +17,5 @@ namespace TouchSocket.Dmtp; /// public static class DmtpPluginManagerExtension { - /// - /// 启用DmtpRpc心跳功能。该功能既可用于客户端,也可用于服务器端,但通常建议仅在客户端使用。 - /// - /// 心跳默认每3秒发送一次。当心跳失败次数达到最大值(默认为3次)时,将判定为连接断开。 - /// - /// - /// 插件管理器,用于管理包括心跳插件在内的各种插件。 - /// 返回新创建并已添加到插件管理器的DmtpHeartbeatPlugin实例。 - public static DmtpHeartbeatPlugin UseDmtpHeartbeat(this IPluginManager pluginManager) - { - var heartbeat = new DmtpHeartbeatPlugin(); - pluginManager.Add(heartbeat); - return heartbeat; - } - #region WebSocketReconnection - - /// - /// 使用断线重连。 - /// - /// 要重连的客户端类型,必须实现接口。 - /// 插件管理器,用于添加重连插件。 - /// 返回创建的重连插件实例。 - public static ReconnectionPlugin UseDmtpReconnection(this IPluginManager pluginManager) where TClient : IDmtpClient - { - var reconnectionPlugin = new DmtpReconnectionPlugin(); - pluginManager.Add(reconnectionPlugin); - return reconnectionPlugin; - } - - /// - /// 使用断线重连。 - /// - /// 插件管理器,用于添加重连插件。 - /// 返回创建的重连插件实例。 - public static ReconnectionPlugin UseWebSocketReconnection(this IPluginManager pluginManager) - { - var reconnectionPlugin = new DmtpReconnectionPlugin(); - pluginManager.Add(reconnectionPlugin); - return reconnectionPlugin; - } - - #endregion WebSocketReconnection } \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Extensions/ReconnectionOptionsExtension.cs b/src/TouchSocket.Dmtp/Extensions/ReconnectionOptionsExtension.cs new file mode 100644 index 000000000..809f2a300 --- /dev/null +++ b/src/TouchSocket.Dmtp/Extensions/ReconnectionOptionsExtension.cs @@ -0,0 +1,115 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using TouchSocket.Sockets; + +namespace TouchSocket.Dmtp; + +/// +/// 重连选项扩展类,提供DMTP协议特定的检活策略配置 +/// +public static class ReconnectionOptionsExtension +{ + /// + /// 配置DMTP检活策略 + /// 此方法为DMTP客户端配置智能的连接检查策略,支持三级检查: + /// 1. 在线状态检查 - 快速判断连接是否断开 + /// 2. 活动时间检查 - 避免频繁检查,优化性能 + /// 3. Ping心跳检查 - 主动验证连接可用性 + /// + /// 客户端类型,必须实现相关接口以支持连接、在线状态和DMTP功能 + /// 重连选项实例,不能为 + /// + /// 活动时间检查间隔,用于避免频繁的心跳检查 + /// 如果客户端在此时间内有活动,则跳过本次检查 + /// 默认值:3秒 + /// 建议范围:1-10秒 + /// + /// + /// Ping操作的超时时间,防止长时间等待 + /// 默认值:5秒 + /// 建议范围:3-10秒 + /// + /// 时抛出 + /// 小于等于零时抛出 + /// 小于等于零时抛出 + public static void UseDmtpCheckAction( + this ReconnectionOption reconnectionOption, + TimeSpan? activeTimeSpan = null, + TimeSpan? pingTimeout = null) + where TClient : IConnectableClient, IOnlineClient, IDependencyClient, IDmtpClient + { + ThrowHelper.ThrowIfNull(reconnectionOption, nameof(reconnectionOption)); + var span = activeTimeSpan ?? TimeSpan.FromSeconds(3); + var timeout = pingTimeout ?? TimeSpan.FromSeconds(5); + + // 验证时间参数的有效性 + if (span <= TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(activeTimeSpan), "活动时间间隔必须大于零"); + } + + if (timeout <= TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(pingTimeout), "Ping超时时间必须大于零"); + } + + reconnectionOption.CheckAction = async (client) => + { + // 第1步:快速在线状态检查 + // 如果客户端已经离线,无需进一步检查,直接返回Dead状态 + if (!client.Online) + { + return ConnectionCheckResult.Dead; + } + + // 第2步:活动时间检查 + // 如果客户端在指定时间内有活动,说明连接正常,跳过本次心跳检查 + var lastActiveTime = client.GetLastActiveTime(); + var timeSinceLastActivity = DateTimeOffset.UtcNow - lastActiveTime; + + if (timeSinceLastActivity < span) + { + return ConnectionCheckResult.Skip; + } + + // 第3步:主动心跳检查 + // 通过Ping操作验证连接的实际可用性 + try + { + using var pingCts = new CancellationTokenSource(timeout); + var pingResult = await client.PingAsync(pingCts.Token).ConfigureAwait(false); + + if (pingResult.IsSuccess) + { + return ConnectionCheckResult.Alive; + } + + using var closeCts = new CancellationTokenSource(timeout); + + var closeResult = await client.CloseAsync("心跳插件ping失败主动断开连接", closeCts.Token).ConfigureAwait(false); + + return ConnectionCheckResult.Dead; + } + catch (OperationCanceledException) + { + // Ping超时,认为连接已死 + return ConnectionCheckResult.Dead; + } + catch + { + // 其他异常也认为连接不可用 + return ConnectionCheckResult.Dead; + } + }; + } +} diff --git a/src/TouchSocket.Dmtp/Features/FileTransfer/Actor/DmtpFileTransferActor.cs b/src/TouchSocket.Dmtp/Features/FileTransfer/Actor/DmtpFileTransferActor.cs index bc7d6df9b..f3603c09f 100644 --- a/src/TouchSocket.Dmtp/Features/FileTransfer/Actor/DmtpFileTransferActor.cs +++ b/src/TouchSocket.Dmtp/Features/FileTransfer/Actor/DmtpFileTransferActor.cs @@ -10,12 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Buffers; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Resources; namespace TouchSocket.Dmtp.FileTransfer; @@ -72,21 +67,21 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe /// public async Task InputReceivedData(DmtpMessage message) { - var byteBlock = message.BodyByteBlock; + var reader = new BytesReader(message.Memory); if (message.ProtocolFlags == this.m_pullFileResourceInfo_Request) { try { var fileTransferRouterPackage = new FileTransferRouterPackage(); - fileTransferRouterPackage.Unpackage(ref byteBlock); + fileTransferRouterPackage.Unpackage(ref reader); if (fileTransferRouterPackage.Route && this.DmtpActor.AllowRoute) { if (await this.DmtpActor.TryRouteAsync(new PackageRouterEventArgs(RouteType.PullFile, fileTransferRouterPackage)).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) { if (await this.DmtpActor.TryFindDmtpActor(fileTransferRouterPackage.TargetId).ConfigureAwait(EasyTask.ContinueOnCapturedContext) is DmtpActor actor) { - await actor.SendAsync(this.m_pullFileResourceInfo_Request, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await actor.SendAsync(this.m_pullFileResourceInfo_Request, message.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return true; } else @@ -99,17 +94,14 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe fileTransferRouterPackage.Status = TouchSocketDmtpStatus.RoutingNotAllowed.ToValue(); } fileTransferRouterPackage.SwitchId(); - byteBlock.Reset(); - fileTransferRouterPackage.Package(ref byteBlock); - - await this.DmtpActor.SendAsync(this.m_pullFileResourceInfo_Response, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.DmtpActor.SendAsync(this.m_pullFileResourceInfo_Response, fileTransferRouterPackage).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } else { //fileTransferRouterPackage.UnpackageBody(byteBlock); - _ = EasyTask.Run(this.RequestPullFileResourceInfo, fileTransferRouterPackage); + _ = EasyTask.SafeRun(this.RequestPullFileResourceInfo, fileTransferRouterPackage); } } catch (Exception ex) @@ -124,18 +116,18 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe { var waitFileResource = new FileTransferRouterPackage(); - waitFileResource.UnpackageRouter(ref byteBlock); + waitFileResource.UnpackageRouter(ref reader); if (this.DmtpActor.AllowRoute && waitFileResource.Route) { if (await this.DmtpActor.TryFindDmtpActor(waitFileResource.TargetId).ConfigureAwait(EasyTask.ContinueOnCapturedContext) is DmtpActor actor) { - await actor.SendAsync(this.m_pullFileResourceInfo_Response, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await actor.SendAsync(this.m_pullFileResourceInfo_Response, message.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } else { - waitFileResource.UnpackageBody(ref byteBlock); - this.DmtpActor.WaitHandlePool.SetRun(waitFileResource); + waitFileResource.UnpackageBody(ref reader); + this.DmtpActor.WaitHandlePool.Set(waitFileResource); } } catch (Exception ex) @@ -150,27 +142,24 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe { var waitFileSection = new WaitFileSection(); - waitFileSection.UnpackageRouter(ref byteBlock); + waitFileSection.UnpackageRouter(ref reader); if (this.DmtpActor.AllowRoute && waitFileSection.Route) { if (await this.DmtpActor.TryFindDmtpActor(waitFileSection.TargetId).ConfigureAwait(EasyTask.ContinueOnCapturedContext) is DmtpActor actor) { - await actor.SendAsync(this.m_pullFileSection_Request, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await actor.SendAsync(this.m_pullFileSection_Request, message.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } else { waitFileSection.Status = TouchSocketDmtpStatus.ClientNotFind.ToValue(); waitFileSection.SwitchId(); - byteBlock.Reset(); - waitFileSection.Package(ref byteBlock); - - await this.DmtpActor.SendAsync(this.m_pullFileSection_Response, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.DmtpActor.SendAsync(this.m_pullFileSection_Response, waitFileSection).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } else { - waitFileSection.UnpackageBody(ref byteBlock); + waitFileSection.UnpackageBody(ref reader); await this.RequestPullFileSection(waitFileSection).ConfigureAwait(EasyTask.ContinueOnCapturedContext); //EasyTask.Run(this.RequestPullFileSection, waitFileSection); } @@ -187,18 +176,18 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe { var waitFileSection = new WaitFileSection(); - waitFileSection.UnpackageRouter(ref byteBlock); + waitFileSection.UnpackageRouter(ref reader); if (this.DmtpActor.AllowRoute && waitFileSection.Route) { if (await this.DmtpActor.TryFindDmtpActor(waitFileSection.TargetId).ConfigureAwait(EasyTask.ContinueOnCapturedContext) is DmtpActor actor) { - await actor.SendAsync(this.m_pullFileSection_Response, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await actor.SendAsync(this.m_pullFileSection_Response, message.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } else { - waitFileSection.UnpackageBody(ref byteBlock); - this.DmtpActor.WaitHandlePool.SetRun(waitFileSection); + waitFileSection.UnpackageBody(ref reader); + this.DmtpActor.WaitHandlePool.Set(waitFileSection); } } catch (Exception ex) @@ -213,14 +202,14 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe { var fileTransferRouterPackage = new FileTransferRouterPackage(); - fileTransferRouterPackage.Unpackage(ref byteBlock); + fileTransferRouterPackage.Unpackage(ref reader); if (fileTransferRouterPackage.Route && this.DmtpActor.AllowRoute) { if (await this.DmtpActor.TryRouteAsync(new PackageRouterEventArgs(RouteType.PullFile, fileTransferRouterPackage)).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) { if (await this.DmtpActor.TryFindDmtpActor(fileTransferRouterPackage.TargetId).ConfigureAwait(EasyTask.ContinueOnCapturedContext) is DmtpActor actor) { - await actor.SendAsync(this.m_pushFileResourceInfo_Request, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await actor.SendAsync(this.m_pushFileResourceInfo_Request, message.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return true; } else @@ -232,12 +221,9 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe { fileTransferRouterPackage.Status = TouchSocketDmtpStatus.RoutingNotAllowed.ToValue(); } - byteBlock.Reset(); fileTransferRouterPackage.SwitchId(); - fileTransferRouterPackage.Package(ref byteBlock); - - await this.DmtpActor.SendAsync(this.m_pushFileResourceInfo_Response, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.DmtpActor.SendAsync(this.m_pushFileResourceInfo_Response, fileTransferRouterPackage).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } else { @@ -257,18 +243,18 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe { var waitFileResource = new FileTransferRouterPackage(); - waitFileResource.UnpackageRouter(ref byteBlock); + waitFileResource.UnpackageRouter(ref reader); if (this.DmtpActor.AllowRoute && waitFileResource.Route) { if (await this.DmtpActor.TryFindDmtpActor(waitFileResource.TargetId).ConfigureAwait(EasyTask.ContinueOnCapturedContext) is DmtpActor actor) { - await actor.SendAsync(this.m_pushFileResourceInfo_Response, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await actor.SendAsync(this.m_pushFileResourceInfo_Response, message.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } else { - waitFileResource.UnpackageBody(ref byteBlock); - this.DmtpActor.WaitHandlePool.SetRun(waitFileResource); + waitFileResource.UnpackageBody(ref reader); + this.DmtpActor.WaitHandlePool.Set(waitFileResource); } } catch (Exception ex) @@ -283,26 +269,23 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe { var waitFileSection = new WaitFileSection(); - waitFileSection.UnpackageRouter(ref byteBlock); + waitFileSection.UnpackageRouter(ref reader); if (this.DmtpActor.AllowRoute && waitFileSection.Route) { if (await this.DmtpActor.TryFindDmtpActor(waitFileSection.TargetId).ConfigureAwait(EasyTask.ContinueOnCapturedContext) is DmtpActor actor) { - await actor.SendAsync(this.m_pushFileSection_Request, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await actor.SendAsync(this.m_pushFileSection_Request, message.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } else { waitFileSection.Status = TouchSocketDmtpStatus.ClientNotFind.ToValue(); waitFileSection.SwitchId(); - byteBlock.Reset(); - waitFileSection.Package(ref byteBlock); - - await this.DmtpActor.SendAsync(this.m_pushFileSection_Response, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.DmtpActor.SendAsync(this.m_pushFileSection_Response, waitFileSection).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } else { - waitFileSection.UnpackageBody(ref byteBlock); + waitFileSection.UnpackageBody(ref reader); //this.RequestPushFileSection(waitFileSection); //EasyTask.Run(this.RequestPushFileSection, waitFileSection); await this.RequestPushFileSection(waitFileSection).ConfigureAwait(EasyTask.ContinueOnCapturedContext); @@ -320,18 +303,18 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe { var waitFileSection = new WaitFileSection(); - waitFileSection.UnpackageRouter(ref byteBlock); + waitFileSection.UnpackageRouter(ref reader); if (this.DmtpActor.AllowRoute && waitFileSection.Route) { if (await this.DmtpActor.TryFindDmtpActor(waitFileSection.TargetId).ConfigureAwait(EasyTask.ContinueOnCapturedContext) is DmtpActor actor) { - await actor.SendAsync(this.m_pushFileSection_Response, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await actor.SendAsync(this.m_pushFileSection_Response, message.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } else { - waitFileSection.UnpackageBody(ref byteBlock); - this.DmtpActor.WaitHandlePool.SetRun(waitFileSection); + waitFileSection.UnpackageBody(ref reader); + this.DmtpActor.WaitHandlePool.Set(waitFileSection); } } catch (Exception ex) @@ -346,26 +329,24 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe { var waitFinishedPackage = new WaitFinishedPackage(); - waitFinishedPackage.UnpackageRouter(ref byteBlock); + waitFinishedPackage.UnpackageRouter(ref reader); if (this.DmtpActor.AllowRoute && waitFinishedPackage.Route) { if (await this.DmtpActor.TryFindDmtpActor(waitFinishedPackage.TargetId).ConfigureAwait(EasyTask.ContinueOnCapturedContext) is DmtpActor actor) { - await actor.SendAsync(this.m_finishedFileResourceInfo_Request, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await actor.SendAsync(this.m_finishedFileResourceInfo_Request, message.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } else { waitFinishedPackage.Status = TouchSocketDmtpStatus.ClientNotFind.ToValue(); waitFinishedPackage.SwitchId(); - byteBlock.Reset(); - waitFinishedPackage.Package(ref byteBlock); - await this.DmtpActor.SendAsync(this.m_finishedFileResourceInfo_Response, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.DmtpActor.SendAsync(this.m_finishedFileResourceInfo_Response, waitFinishedPackage).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } else { - waitFinishedPackage.UnpackageBody(ref byteBlock); - _ = EasyTask.Run(this.RequestFinishedFileResourceInfo, waitFinishedPackage); + waitFinishedPackage.UnpackageBody(ref reader); + _ = EasyTask.SafeRun(this.RequestFinishedFileResourceInfo, waitFinishedPackage); } } catch (Exception ex) @@ -379,18 +360,18 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe try { var waitFinishedPackage = new WaitFinishedPackage(); - waitFinishedPackage.UnpackageRouter(ref byteBlock); + waitFinishedPackage.UnpackageRouter(ref reader); if (this.DmtpActor.AllowRoute && waitFinishedPackage.Route) { if (await this.DmtpActor.TryFindDmtpActor(waitFinishedPackage.TargetId).ConfigureAwait(EasyTask.ContinueOnCapturedContext) is DmtpActor actor) { - await actor.SendAsync(this.m_finishedFileResourceInfo_Response, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await actor.SendAsync(this.m_finishedFileResourceInfo_Response, message.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } else { - waitFinishedPackage.UnpackageBody(ref byteBlock); - this.DmtpActor.WaitHandlePool.SetRun(waitFinishedPackage); + waitFinishedPackage.UnpackageBody(ref reader); + this.DmtpActor.WaitHandlePool.Set(waitFinishedPackage); } } catch (Exception ex) @@ -405,14 +386,14 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe { var waitSmallFilePackage = new WaitSmallFilePackage(); - waitSmallFilePackage.UnpackageRouter(ref byteBlock); + waitSmallFilePackage.UnpackageRouter(ref reader); if (waitSmallFilePackage.Route && this.DmtpActor.AllowRoute) { if (await this.DmtpActor.TryRouteAsync(new PackageRouterEventArgs(RouteType.PullFile, waitSmallFilePackage)).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) { if (await this.DmtpActor.TryFindDmtpActor(waitSmallFilePackage.TargetId).ConfigureAwait(EasyTask.ContinueOnCapturedContext) is DmtpActor actor) { - await actor.SendAsync(this.m_pullSmallFile_Request, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await actor.SendAsync(this.m_pullSmallFile_Request, message.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return true; } else @@ -424,17 +405,13 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe { waitSmallFilePackage.Status = TouchSocketDmtpStatus.RoutingNotAllowed.ToValue(); } - byteBlock.Reset(); waitSmallFilePackage.SwitchId(); - - waitSmallFilePackage.Package(ref byteBlock); - - await this.DmtpActor.SendAsync(this.m_pullSmallFile_Response, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.DmtpActor.SendAsync(this.m_pullSmallFile_Response, waitSmallFilePackage).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } else { - waitSmallFilePackage.UnpackageBody(ref byteBlock); - _ = EasyTask.Run(this.RequestPullSmallFile, waitSmallFilePackage); + waitSmallFilePackage.UnpackageBody(ref reader); + _ = EasyTask.SafeRun(this.RequestPullSmallFile, waitSmallFilePackage); } } catch (Exception ex) @@ -449,18 +426,18 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe { var waitSmallFilePackage = new WaitSmallFilePackage(); - waitSmallFilePackage.UnpackageRouter(ref byteBlock); + waitSmallFilePackage.UnpackageRouter(ref reader); if (this.DmtpActor.AllowRoute && waitSmallFilePackage.Route) { if (await this.DmtpActor.TryFindDmtpActor(waitSmallFilePackage.TargetId).ConfigureAwait(EasyTask.ContinueOnCapturedContext) is DmtpActor actor) { - await actor.SendAsync(this.m_pullSmallFile_Response, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await actor.SendAsync(this.m_pullSmallFile_Response, message.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } else { - waitSmallFilePackage.UnpackageBody(ref byteBlock); - this.DmtpActor.WaitHandlePool.SetRun(waitSmallFilePackage); + waitSmallFilePackage.UnpackageBody(ref reader); + this.DmtpActor.WaitHandlePool.Set(waitSmallFilePackage); } } catch (Exception ex) @@ -475,14 +452,14 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe { var waitSmallFilePackage = new WaitSmallFilePackage(); - waitSmallFilePackage.UnpackageRouter(ref byteBlock); + waitSmallFilePackage.UnpackageRouter(ref reader); if (waitSmallFilePackage.Route && this.DmtpActor.AllowRoute) { if (await this.DmtpActor.TryRouteAsync(new PackageRouterEventArgs(RouteType.PullFile, waitSmallFilePackage)).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) { if (await this.DmtpActor.TryFindDmtpActor(waitSmallFilePackage.TargetId).ConfigureAwait(EasyTask.ContinueOnCapturedContext) is DmtpActor actor) { - await actor.SendAsync(this.m_pushSmallFile_Request, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await actor.SendAsync(this.m_pushSmallFile_Request, message.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return true; } else @@ -495,16 +472,12 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe waitSmallFilePackage.Status = TouchSocketDmtpStatus.RoutingNotAllowed.ToValue(); } - byteBlock.Reset(); waitSmallFilePackage.SwitchId(); - - waitSmallFilePackage.Package(ref byteBlock); - - await this.DmtpActor.SendAsync(this.m_pushSmallFile_Response, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.DmtpActor.SendAsync(this.m_pushSmallFile_Response, waitSmallFilePackage).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } else { - waitSmallFilePackage.UnpackageBody(ref byteBlock); + waitSmallFilePackage.UnpackageBody(ref reader); _ = this.RequestPushSmallFile(waitSmallFilePackage); } } @@ -520,19 +493,19 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe { var waitSmallFilePackage = new WaitSmallFilePackage(); - waitSmallFilePackage.UnpackageRouter(ref byteBlock); + waitSmallFilePackage.UnpackageRouter(ref reader); if (this.DmtpActor.AllowRoute && waitSmallFilePackage.Route) { if (await this.DmtpActor.TryFindDmtpActor(waitSmallFilePackage.TargetId).ConfigureAwait(EasyTask.ContinueOnCapturedContext) is DmtpActor actor) { - await actor.SendAsync(this.m_pushSmallFile_Response, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await actor.SendAsync(this.m_pushSmallFile_Response, message.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } else { - waitSmallFilePackage.UnpackageBody(ref byteBlock); - this.DmtpActor.WaitHandlePool.SetRun(waitSmallFilePackage); + waitSmallFilePackage.UnpackageBody(ref reader); + this.DmtpActor.WaitHandlePool.Set(waitSmallFilePackage); } } catch (Exception ex) @@ -616,92 +589,92 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe #region Id传输 /// - public Task FinishedFileResourceInfoAsync(string targetId, FileResourceInfo fileResourceInfo, ResultCode code, Metadata metadata = default, int millisecondsTimeout = 5000, CancellationToken token = default) + public async Task FinishedFileResourceInfoAsync(string targetId, FileResourceInfo fileResourceInfo, ResultCode code, Metadata metadata, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(targetId)) { - return this.PrivateFinishedFileResourceInfoAsync(targetId, fileResourceInfo, code, metadata, millisecondsTimeout, token); + return await this.PrivateFinishedFileResourceInfoAsync(targetId, fileResourceInfo, code, metadata, cancellationToken); } - if (this.DmtpActor.AllowRoute && this.TryFindDmtpFileTransferActor(targetId).GetFalseAwaitResult() is DmtpFileTransferActor actor) + if (this.DmtpActor.AllowRoute && (await this.TryFindDmtpFileTransferActor(targetId)) is DmtpFileTransferActor actor) { - return actor.FinishedFileResourceInfoAsync(fileResourceInfo, code, metadata, millisecondsTimeout, token); + return await actor.FinishedFileResourceInfoAsync(fileResourceInfo, code, metadata, cancellationToken); } else { - return this.PrivateFinishedFileResourceInfoAsync(targetId, fileResourceInfo, code, metadata, millisecondsTimeout, token); + return await this.PrivateFinishedFileResourceInfoAsync(targetId, fileResourceInfo, code, metadata, cancellationToken); } } /// - public Task PullFileResourceInfoAsync(string targetId, string path, Metadata metadata = default, int fileSectionSize = 1024 * 512, int millisecondsTimeout = 5000, CancellationToken token = default) + public async Task PullFileResourceInfoAsync(string targetId, string path, Metadata metadata, int fileSectionSize, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(targetId)) { - return this.PrivatePullFileResourceInfoAsync(targetId, path, metadata, fileSectionSize, millisecondsTimeout, token); + return await this.PrivatePullFileResourceInfoAsync(targetId, path, metadata, fileSectionSize, cancellationToken); } - if (this.DmtpActor.AllowRoute && this.TryFindDmtpFileTransferActor(targetId).GetFalseAwaitResult() is DmtpFileTransferActor actor) + if (this.DmtpActor.AllowRoute && (await this.TryFindDmtpFileTransferActor(targetId)) is DmtpFileTransferActor actor) { - return actor.PullFileResourceInfoAsync(path, metadata, fileSectionSize, millisecondsTimeout, token); + return await actor.PullFileResourceInfoAsync(path, metadata, fileSectionSize, cancellationToken); } else { - return this.PrivatePullFileResourceInfoAsync(targetId, path, metadata, fileSectionSize, millisecondsTimeout, token); + return await this.PrivatePullFileResourceInfoAsync(targetId, path, metadata, fileSectionSize, cancellationToken); } } /// - public Task PullFileSectionAsync(string targetId, FileSection fileSection, int millisecondsTimeout = 5000, CancellationToken token = default) + public async Task PullFileSectionAsync(string targetId, FileSection fileSection, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(targetId)) { - return this.PrivatePullFileSectionAsync(targetId, fileSection, millisecondsTimeout, token); + return await this.PrivatePullFileSectionAsync(targetId, fileSection, cancellationToken); } - if (this.DmtpActor.AllowRoute && this.TryFindDmtpFileTransferActor(targetId).GetFalseAwaitResult() is DmtpFileTransferActor actor) + if (this.DmtpActor.AllowRoute && (await this.TryFindDmtpFileTransferActor(targetId)) is DmtpFileTransferActor actor) { - return actor.PullFileSectionAsync(fileSection, millisecondsTimeout, token); + return await actor.PullFileSectionAsync(fileSection, cancellationToken); } else { - return this.PrivatePullFileSectionAsync(targetId, fileSection, millisecondsTimeout, token); + return await this.PrivatePullFileSectionAsync(targetId, fileSection, cancellationToken); } } /// - public Task PushFileResourceInfoAsync(string targetId, string savePath, FileResourceLocator fileResourceLocator, Metadata metadata = default, int millisecondsTimeout = 5000, CancellationToken token = default) + public async Task PushFileResourceInfoAsync(string targetId, string savePath, FileResourceLocator fileResourceLocator, Metadata metadata, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(targetId)) { - return this.PrivatePushFileResourceInfoAsync(targetId, savePath, fileResourceLocator, metadata, millisecondsTimeout, token); + return await this.PrivatePushFileResourceInfoAsync(targetId, savePath, fileResourceLocator, metadata, cancellationToken); } - if (this.DmtpActor.AllowRoute && this.TryFindDmtpFileTransferActor(targetId).GetFalseAwaitResult() is DmtpFileTransferActor actor) + if (this.DmtpActor.AllowRoute && (await this.TryFindDmtpFileTransferActor(targetId)) is DmtpFileTransferActor actor) { - return actor.PushFileResourceInfoAsync(savePath, fileResourceLocator, metadata, millisecondsTimeout, token); + return await actor.PushFileResourceInfoAsync(savePath, fileResourceLocator, metadata, cancellationToken); } else { - return this.PrivatePushFileResourceInfoAsync(targetId, savePath, fileResourceLocator, metadata, millisecondsTimeout, token); + return await this.PrivatePushFileResourceInfoAsync(targetId, savePath, fileResourceLocator, metadata, cancellationToken); } } /// - public Task PushFileSectionAsync(string targetId, FileResourceLocator fileResourceLocator, FileSection fileSection, int millisecondsTimeout = 5000, CancellationToken token = default) + public async Task PushFileSectionAsync(string targetId, FileResourceLocator fileResourceLocator, FileSection fileSection, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(targetId)) { - return this.PrivatePushFileSectionAsync(targetId, fileResourceLocator, fileSection, millisecondsTimeout, token); + return await this.PrivatePushFileSectionAsync(targetId, fileResourceLocator, fileSection, cancellationToken); } - if (this.DmtpActor.AllowRoute && this.TryFindDmtpFileTransferActor(targetId).GetFalseAwaitResult() is DmtpFileTransferActor actor) + if (this.DmtpActor.AllowRoute && (await this.TryFindDmtpFileTransferActor(targetId)) is DmtpFileTransferActor actor) { - return actor.PushFileSectionAsync(fileResourceLocator, fileSection, millisecondsTimeout, token); + return await actor.PushFileSectionAsync(fileResourceLocator, fileSection, cancellationToken); } else { - return this.PrivatePushFileSectionAsync(targetId, fileResourceLocator, fileSection, millisecondsTimeout, token); + return await this.PrivatePushFileSectionAsync(targetId, fileResourceLocator, fileSection, cancellationToken); } } @@ -709,69 +682,39 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe #region 传输 - ///// - //public FinishedResult FinishedFileResourceInfo(FileResourceInfo fileResourceInfo, ResultCode code, Metadata metadata = default, int millisecondsTimeout = 5000, CancellationToken token = default) - //{ - // return this.PrivateFinishedFileResourceInfo(default, fileResourceInfo, code, metadata, millisecondsTimeout, token); - //} - /// - public Task FinishedFileResourceInfoAsync(FileResourceInfo fileResourceInfo, ResultCode code, Metadata metadata = default, int millisecondsTimeout = 5000, CancellationToken token = default) + public Task FinishedFileResourceInfoAsync(FileResourceInfo fileResourceInfo, ResultCode code, Metadata metadata, CancellationToken cancellationToken) { - return this.PrivateFinishedFileResourceInfoAsync(default, fileResourceInfo, code, metadata, millisecondsTimeout, token); + return this.PrivateFinishedFileResourceInfoAsync(default, fileResourceInfo, code, metadata, cancellationToken); } - ///// - //public FileResourceInfoResult PullFileResourceInfo(string path, Metadata metadata = default, int fileSectionSize = 1024 * 512, int millisecondsTimeout = 5000, CancellationToken token = default) - //{ - // return this.PrivatePullFileResourceInfoAsync(default, path, metadata, fileSectionSize, millisecondsTimeout, token); - //} - /// - public Task PullFileResourceInfoAsync(string path, Metadata metadata = default, int fileSectionSize = 1024 * 512, int millisecondsTimeout = 5000, CancellationToken token = default) + public Task PullFileResourceInfoAsync(string path, Metadata metadata, int fileSectionSize, CancellationToken cancellationToken) { - return this.PrivatePullFileResourceInfoAsync(default, path, metadata, fileSectionSize, millisecondsTimeout, token); + return this.PrivatePullFileResourceInfoAsync(default, path, metadata, fileSectionSize, cancellationToken); } - ///// - //public FileSectionResult PullFileSection(FileSection fileSection, int millisecondsTimeout = 5000, CancellationToken token = default) - //{ - // return this.PrivatePullFileSectionAsync(default, fileSection, millisecondsTimeout, token); - //} - /// - public Task PullFileSectionAsync(FileSection fileSection, int millisecondsTimeout = 5000, CancellationToken token = default) + public Task PullFileSectionAsync(FileSection fileSection, CancellationToken cancellationToken) { - return this.PrivatePullFileSectionAsync(default, fileSection, millisecondsTimeout, token); + return this.PrivatePullFileSectionAsync(default, fileSection, cancellationToken); } - ///// - //public Result PushFileResourceInfo(string savePath, FileResourceLocator fileResourceLocator, Metadata metadata = default, int millisecondsTimeout = 5000, CancellationToken token = default) - //{ - // return this.PrivatePushFileResourceInfoAsync(default, savePath, fileResourceLocator, metadata, millisecondsTimeout, token); - //} - /// - public Task PushFileResourceInfoAsync(string savePath, FileResourceLocator fileResourceLocator, Metadata metadata = default, int millisecondsTimeout = 5000, CancellationToken token = default) + public Task PushFileResourceInfoAsync(string savePath, FileResourceLocator fileResourceLocator, Metadata metadata, CancellationToken cancellationToken) { - return this.PrivatePushFileResourceInfoAsync(default, savePath, fileResourceLocator, metadata, millisecondsTimeout, token); + return this.PrivatePushFileResourceInfoAsync(default, savePath, fileResourceLocator, metadata, cancellationToken); } - ///// - //public Result PushFileSection(FileResourceLocator fileResourceLocator, FileSection fileSection, int millisecondsTimeout = 5000, CancellationToken token = default) - //{ - // return this.PrivatePushFileSectionAsync(default, fileResourceLocator, fileSection, millisecondsTimeout, token); - //} - /// - public Task PushFileSectionAsync(FileResourceLocator fileResourceLocator, FileSection fileSection, int millisecondsTimeout = 5000, CancellationToken token = default) + public Task PushFileSectionAsync(FileResourceLocator fileResourceLocator, FileSection fileSection, CancellationToken cancellationToken) { - return this.PrivatePushFileSectionAsync(default, fileResourceLocator, fileSection, millisecondsTimeout, token); + return this.PrivatePushFileSectionAsync(default, fileResourceLocator, fileSection, cancellationToken); } #region Private - private async Task PrivateFinishedFileResourceInfoAsync(string targetId, FileResourceInfo fileResourceInfo, ResultCode code, Metadata metadata = default, int millisecondsTimeout = 5000, CancellationToken token = default) + private async Task PrivateFinishedFileResourceInfoAsync(string targetId, FileResourceInfo fileResourceInfo, ResultCode code, Metadata metadata, CancellationToken cancellationToken) { var waitFinishedPackage = new WaitFinishedPackage() { @@ -789,16 +732,15 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe { var block = byteBlock; waitFinishedPackage.Package(ref block); - await this.DmtpActor.SendAsync(this.m_finishedFileResourceInfo_Request, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - waitData.SetCancellationToken(token); + await this.DmtpActor.SendAsync(this.m_finishedFileResourceInfo_Request, byteBlock.Memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - await waitData.WaitAsync(millisecondsTimeout).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await waitData.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); switch (waitData.Status) { - case WaitDataStatus.SetRunning: + case WaitDataStatus.Success: { - var waitFile = (WaitFinishedPackage)waitData.WaitResult; + var waitFile = (WaitFinishedPackage)waitData.CompletedData; switch (waitFile.Status.ToStatus()) { case TouchSocketDmtpStatus.Success: @@ -836,12 +778,12 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe } finally { - this.DmtpActor.WaitHandlePool.Destroy(waitFinishedPackage.Sign); + waitData.Dispose(); byteBlock.Dispose(); } } - private async Task PrivatePullFileResourceInfoAsync(string targetId, string path, Metadata metadata = default, int fileSectionSize = 1024 * 512, int millisecondsTimeout = 5000, CancellationToken token = default) + private async Task PrivatePullFileResourceInfoAsync(string targetId, string path, Metadata metadata, int fileSectionSize, CancellationToken cancellationToken) { var waitFileResource = new FileTransferRouterPackage() { @@ -859,16 +801,14 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe { var block = byteBlock; waitFileResource.Package(ref block); - await this.DmtpActor.SendAsync(this.m_pullFileResourceInfo_Request, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - waitData.SetCancellationToken(token); - - await waitData.WaitAsync(millisecondsTimeout).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.DmtpActor.SendAsync(this.m_pullFileResourceInfo_Request, byteBlock.Memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await waitData.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); switch (waitData.Status) { - case WaitDataStatus.SetRunning: + case WaitDataStatus.Success: { - var waitFile = (FileTransferRouterPackage)waitData.WaitResult; + var waitFile = (FileTransferRouterPackage)waitData.CompletedData; switch (waitFile.Status.ToStatus()) { case TouchSocketDmtpStatus.Success: @@ -905,12 +845,12 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe } finally { - this.DmtpActor.WaitHandlePool.Destroy(waitFileResource.Sign); + waitData.Dispose(); byteBlock.Dispose(); } } - private async Task PrivatePullFileSectionAsync(string targetId, FileSection fileSection, int millisecondsTimeout = 5000, CancellationToken token = default) + private async Task PrivatePullFileSectionAsync(string targetId, FileSection fileSection, CancellationToken cancellationToken) { fileSection.Status = FileSectionStatus.Transferring; var waitFileSection = new WaitFileSection() @@ -927,16 +867,15 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe { var block = byteBlock; waitFileSection.Package(ref block); - await this.DmtpActor.SendAsync(this.m_pullFileSection_Request, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - waitData.SetCancellationToken(token); + await this.DmtpActor.SendAsync(this.m_pullFileSection_Request, byteBlock.Memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - await waitData.WaitAsync(millisecondsTimeout).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await waitData.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); switch (waitData.Status) { - case WaitDataStatus.SetRunning: + case WaitDataStatus.Success: { - var waitFile = (WaitFileSection)waitData.WaitResult; + var waitFile = (WaitFileSection)waitData.CompletedData; switch (waitFile.Status.ToStatus()) { case TouchSocketDmtpStatus.Success: @@ -977,12 +916,12 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe finally { fileSection.Status = FileSectionStatus.Transferred; - this.DmtpActor.WaitHandlePool.Destroy(waitFileSection.Sign); + waitData.Dispose(); byteBlock.Dispose(); } } - private async Task PrivatePushFileResourceInfoAsync(string targetId, string savePath, FileResourceLocator fileResourceLocator, Metadata metadata = default, int millisecondsTimeout = 5000, CancellationToken token = default) + private async Task PrivatePushFileResourceInfoAsync(string targetId, string savePath, FileResourceLocator fileResourceLocator, Metadata metadata, CancellationToken cancellationToken) { var fileResourceInfo = fileResourceLocator.FileResourceInfo; @@ -1005,16 +944,15 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe { var block = byteBlock; waitFileResource.Package(ref block); - await this.DmtpActor.SendAsync(this.m_pushFileResourceInfo_Request, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - waitData.SetCancellationToken(token); + await this.DmtpActor.SendAsync(this.m_pushFileResourceInfo_Request, byteBlock.Memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - await waitData.WaitAsync(millisecondsTimeout).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await waitData.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); switch (waitData.Status) { - case WaitDataStatus.SetRunning: + case WaitDataStatus.Success: { - var waitFile = (FileTransferRouterPackage)waitData.WaitResult; + var waitFile = (FileTransferRouterPackage)waitData.CompletedData; switch (waitFile.Status.ToStatus()) { case TouchSocketDmtpStatus.Success: @@ -1051,19 +989,19 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe } finally { - this.DmtpActor.WaitHandlePool.Destroy(waitFileResource.Sign); + waitData.Dispose(); byteBlock.Dispose(); } } - private async Task PrivatePushFileSectionAsync(string targetId, FileResourceLocator fileResourceLocator, FileSection fileSection, int millisecondsTimeout = 5000, CancellationToken token = default) + private async Task PrivatePushFileSectionAsync(string targetId, FileResourceLocator fileResourceLocator, FileSection fileSection, CancellationToken cancellationToken) { fileSection.Status = FileSectionStatus.Transferring; using var fileSectionResult = fileResourceLocator.ReadFileSection(fileSection); if (!fileSectionResult.IsSuccess) { fileSection.Status = FileSectionStatus.Fail; - return new Result(fileSectionResult); + return new Result(fileSectionResult.ResultCode, fileSectionResult.Message); } using var waitFileSection = new WaitFileSection() { @@ -1080,16 +1018,15 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe { var block = byteBlock; waitFileSection.Package(ref block); - await this.DmtpActor.SendAsync(this.m_pushFileSection_Request, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - waitData.SetCancellationToken(token); + await this.DmtpActor.SendAsync(this.m_pushFileSection_Request, byteBlock.Memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - await waitData.WaitAsync(millisecondsTimeout).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await waitData.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); switch (waitData.Status) { - case WaitDataStatus.SetRunning: + case WaitDataStatus.Success: { - var waitFile = (WaitFileSection)waitData.WaitResult; + var waitFile = (WaitFileSection)waitData.CompletedData; switch (waitFile.Status.ToStatus()) { case TouchSocketDmtpStatus.Success: @@ -1127,7 +1064,7 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe } finally { - this.DmtpActor.WaitHandlePool.Destroy(waitFileSection.Sign); + waitData.Dispose(); byteBlock.Dispose(); } } @@ -1227,11 +1164,9 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe SavePath = savePath }; - //触发事件的时机调到SendAsync前面,目的是保证调用方在收到回复时,响应方已经完成事件处理。 await this.OnFileTransferred.Invoke(this.DmtpActor, args).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - using (var byteBlock = new ByteBlock(1024 * 64)) { waitFinishedPackage.SwitchId(); @@ -1498,63 +1433,44 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe #region 小文件 /// - public Task PullSmallFileAsync(string targetId, string path, Metadata metadata = null, int millisecondsTimeout = 5000, CancellationToken token = default) + public async Task PullSmallFileAsync(string targetId, string path, Metadata metadata = default, CancellationToken cancellationToken = default) { - if (this.DmtpActor.AllowRoute && this.TryFindDmtpFileTransferActor(targetId).GetFalseAwaitResult() is DmtpFileTransferActor actor) + if (this.DmtpActor.AllowRoute && (await this.TryFindDmtpFileTransferActor(targetId)) is DmtpFileTransferActor actor) { - return actor.PullSmallFileAsync(path, metadata, millisecondsTimeout, token); + return await actor.PullSmallFileAsync(path, metadata, cancellationToken); } else { - return this.PrivatePullSmallFileAsync(targetId, path, metadata, millisecondsTimeout, token); + return await this.PrivatePullSmallFileAsync(targetId, path, metadata, cancellationToken); } } /// - public Task PullSmallFileAsync(string path, Metadata metadata = null, int millisecondsTimeout = 5000, CancellationToken token = default) + public Task PullSmallFileAsync(string path, Metadata metadata = default, CancellationToken cancellationToken = default) { - return this.PrivatePullSmallFileAsync(default, path, metadata, millisecondsTimeout, token); + return this.PrivatePullSmallFileAsync(default, path, metadata, cancellationToken); } - ///// - //public Result PushSmallFile(string targetId, string savePath, FileInfo fileInfo, Metadata metadata = null, int millisecondsTimeout = 5000, CancellationToken token = default) - //{ - // if (this.DmtpActor.AllowRoute && this.TryFindDmtpFileTransferActor(targetId).GetFalseAwaitResult() is DmtpFileTransferActor actor) - // { - // return actor.PushSmallFile(savePath, fileInfo, metadata, millisecondsTimeout, token); - // } - // else - // { - // return this.PrivatePushSmallFile(targetId, savePath, fileInfo, metadata, millisecondsTimeout, token); - // } - //} - - ///// - //public Result PushSmallFile(string savePath, FileInfo fileInfo, Metadata metadata = null, int millisecondsTimeout = 5000, CancellationToken token = default) - //{ - // return this.PrivatePushSmallFile(default, savePath, fileInfo, metadata, millisecondsTimeout, token); - //} - /// - public Task PushSmallFileAsync(string targetId, string savePath, FileInfo fileInfo, Metadata metadata = null, int millisecondsTimeout = 5000, CancellationToken token = default) + public async Task PushSmallFileAsync(string targetId, string savePath, FileInfo fileInfo, Metadata metadata = default, CancellationToken cancellationToken = default) { - if (this.DmtpActor.AllowRoute && this.TryFindDmtpFileTransferActor(targetId).GetFalseAwaitResult() is DmtpFileTransferActor actor) + if (this.DmtpActor.AllowRoute && (await this.TryFindDmtpFileTransferActor(targetId)) is DmtpFileTransferActor actor) { - return actor.PushSmallFileAsync(savePath, fileInfo, metadata, millisecondsTimeout, token); + return await actor.PushSmallFileAsync(savePath, fileInfo, metadata, cancellationToken); } else { - return this.PrivatePushSmallFileAsync(targetId, savePath, fileInfo, metadata, millisecondsTimeout, token); + return await this.PrivatePushSmallFileAsync(targetId, savePath, fileInfo, metadata, cancellationToken); } } /// - public Task PushSmallFileAsync(string savePath, FileInfo fileInfo, Metadata metadata = null, int millisecondsTimeout = 5000, CancellationToken token = default) + public Task PushSmallFileAsync(string savePath, FileInfo fileInfo, Metadata metadata = default, CancellationToken cancellationToken = default) { - return this.PrivatePushSmallFileAsync(default, savePath, fileInfo, metadata, millisecondsTimeout, token); + return this.PrivatePushSmallFileAsync(default, savePath, fileInfo, metadata, cancellationToken); } - private async Task PrivatePullSmallFileAsync(string targetId, string path, Metadata metadata = null, int millisecondsTimeout = 5000, CancellationToken token = default) + private async Task PrivatePullSmallFileAsync(string targetId, string path, Metadata metadata = default, CancellationToken cancellationToken = default) { var waitSmallFilePackage = new WaitSmallFilePackage() { @@ -1572,18 +1488,16 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe { var block = byteBlock; waitSmallFilePackage.Package(ref block); - await this.DmtpActor.SendAsync(this.m_pullSmallFile_Request, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.DmtpActor.SendAsync(this.m_pullSmallFile_Request, byteBlock.Memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - waitData.SetCancellationToken(token); + var status = await waitData.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - await waitData.WaitAsync(millisecondsTimeout).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - - switch (waitData.Status) + switch (status) { - case WaitDataStatus.SetRunning: + case WaitDataStatus.Success: { - var waitFile = (WaitSmallFilePackage)waitData.WaitResult; + var waitFile = (WaitSmallFilePackage)waitData.CompletedData; switch (waitFile.Status.ToStatus()) { case TouchSocketDmtpStatus.Success: @@ -1621,11 +1535,11 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe } finally { - this.DmtpActor.WaitHandlePool.Destroy(waitSmallFilePackage.Sign); + waitData.Dispose(); } } - private async Task PrivatePushSmallFileAsync(string targetId, string savePath, FileInfo fileInfo, Metadata metadata = null, int millisecondsTimeout = 5000, CancellationToken token = default) + private async Task PrivatePushSmallFileAsync(string targetId, string savePath, FileInfo fileInfo, Metadata metadata, CancellationToken cancellationToken) { if (!File.Exists(fileInfo.FullName)) { @@ -1661,15 +1575,14 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe waitSmallFilePackage.Package(ref byteBlock); await this.DmtpActor.SendAsync(this.m_pushSmallFile_Request, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - waitData.SetCancellationToken(token); - await waitData.WaitAsync(millisecondsTimeout).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await waitData.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); switch (waitData.Status) { - case WaitDataStatus.SetRunning: + case WaitDataStatus.Success: { - var waitFile = (WaitSmallFilePackage)waitData.WaitResult; + var waitFile = (WaitSmallFilePackage)waitData.CompletedData; switch (waitFile.Status.ToStatus()) { case TouchSocketDmtpStatus.Success: @@ -1705,7 +1618,7 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe } finally { - this.DmtpActor.WaitHandlePool.Destroy(waitSmallFilePackage.Sign); + waitData.Dispose(); byteBlock.Dispose(); ArrayPool.Shared.Return(buffer); } diff --git a/src/TouchSocket.Dmtp/Features/FileTransfer/Actor/IDmtpFileTransferActor.cs b/src/TouchSocket.Dmtp/Features/FileTransfer/Actor/IDmtpFileTransferActor.cs index 98d89bbac..fc51a2092 100644 --- a/src/TouchSocket.Dmtp/Features/FileTransfer/Actor/IDmtpFileTransferActor.cs +++ b/src/TouchSocket.Dmtp/Features/FileTransfer/Actor/IDmtpFileTransferActor.cs @@ -10,11 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Dmtp.FileTransfer; /// @@ -47,10 +42,9 @@ public interface IDmtpFileTransferActor : IActor /// 目标客户端Id /// 请求路径 /// 元数据 - /// 超时设置 - /// 可取消令箭 + /// 可取消令箭 /// - Task PullSmallFileAsync(string targetId, string path, Metadata metadata = null, int millisecondsTimeout = 5000, CancellationToken token = default); + Task PullSmallFileAsync(string targetId, string path, Metadata metadata = default, CancellationToken cancellationToken = default); /// /// 推送小文件。默认设置1024*1024字节大小。 @@ -59,10 +53,9 @@ public interface IDmtpFileTransferActor : IActor /// 保存路径 /// 推送的文件信息 /// 元数据 - /// 超时设置 - /// 可取消令箭 + /// 可取消令箭 /// - Task PushSmallFileAsync(string targetId, string savePath, FileInfo fileInfo, Metadata metadata = null, int millisecondsTimeout = 5000, CancellationToken token = default); + Task PushSmallFileAsync(string targetId, string savePath, FileInfo fileInfo, Metadata metadata = default, CancellationToken cancellationToken = default); #endregion Id小文件 @@ -74,10 +67,10 @@ public interface IDmtpFileTransferActor : IActor /// /// 请求路径 /// 元数据 - /// 超时设置 - /// 可取消令箭 + + /// 可取消令箭 /// - Task PullSmallFileAsync(string path, Metadata metadata = null, int millisecondsTimeout = 5000, CancellationToken token = default); + Task PullSmallFileAsync(string path, Metadata metadata = default, CancellationToken cancellationToken = default); /// @@ -86,10 +79,10 @@ public interface IDmtpFileTransferActor : IActor /// 保存路径 /// 推送的文件信息 /// 元数据 - /// 超时设置 - /// 可取消令箭 + + /// 可取消令箭 /// - Task PushSmallFileAsync(string savePath, FileInfo fileInfo, Metadata metadata = null, int millisecondsTimeout = 5000, CancellationToken token = default); + Task PushSmallFileAsync(string savePath, FileInfo fileInfo, Metadata metadata = default, CancellationToken cancellationToken = default); #endregion 小文件 @@ -105,10 +98,10 @@ public interface IDmtpFileTransferActor : IActor /// 文件资源信息 /// 状态代码 /// 元数据 - /// 超时设置 - /// 可取消令箭 + + /// 可取消令箭 /// - Task FinishedFileResourceInfoAsync(string targetId, FileResourceInfo fileResourceInfo, ResultCode code, Metadata metadata = null, int millisecondsTimeout = 5000, CancellationToken token = default); + Task FinishedFileResourceInfoAsync(string targetId, FileResourceInfo fileResourceInfo, ResultCode code, Metadata metadata, CancellationToken cancellationToken); /// @@ -116,17 +109,17 @@ public interface IDmtpFileTransferActor : IActor /// 注意: /// /// 完成该操作后,必须在设定时间(60秒)内至少完成一次文件块访问,不然该信息将变得无效, - /// 每次该操作,都应该对应一次 + /// 每次该操作,都应该对应一次 /// /// /// 目标客户端Id /// 资源路径 /// 元数据 - /// 超时设置 + /// 文件分块尺寸。 - /// 可取消令箭 + /// 可取消令箭 /// - Task PullFileResourceInfoAsync(string targetId, string path, Metadata metadata = null, int fileSectionSize = 1024 * 512, int millisecondsTimeout = 5000, CancellationToken token = default); + Task PullFileResourceInfoAsync(string targetId, string path, Metadata metadata, int fileSectionSize, CancellationToken cancellationToken); /// @@ -135,10 +128,10 @@ public interface IDmtpFileTransferActor : IActor /// /// 目标客户端Id /// 文件块 - /// 超时设置 - /// 可取消令箭 + + /// 可取消令箭 /// - Task PullFileSectionAsync(string targetId, FileSection fileSection, int millisecondsTimeout = 5000, CancellationToken token = default); + Task PullFileSectionAsync(string targetId, FileSection fileSection, CancellationToken cancellationToken); /// @@ -146,17 +139,17 @@ public interface IDmtpFileTransferActor : IActor /// 注意: /// /// 完成该操作后,必须在设定时间(60秒)内至少完成一次文件块访问,不然该信息将变得无效, - /// 每次该操作,都必须对应一次 + /// 每次该操作,都必须对应一次 /// /// /// 目标客户端Id /// 保存路径 /// 文件资源定位器 /// 元数据 - /// 超时设置 - /// 可取消令箭 + + /// 可取消令箭 /// - Task PushFileResourceInfoAsync(string targetId, string savePath, FileResourceLocator fileResourceLocator, Metadata metadata = null, int millisecondsTimeout = 5000, CancellationToken token = default); + Task PushFileResourceInfoAsync(string targetId, string savePath, FileResourceLocator fileResourceLocator, Metadata metadata, CancellationToken cancellationToken); /// @@ -166,10 +159,10 @@ public interface IDmtpFileTransferActor : IActor /// 目标客户端Id /// 文件资源定位器 /// 文件块 - /// 超时设置 - /// 可取消令箭 + + /// 可取消令箭 /// - Task PushFileSectionAsync(string targetId, FileResourceLocator fileResourceLocator, FileSection fileSection, int millisecondsTimeout = 5000, CancellationToken token = default); + Task PushFileSectionAsync(string targetId, FileResourceLocator fileResourceLocator, FileSection fileSection, CancellationToken cancellationToken); #endregion Id @@ -184,10 +177,9 @@ public interface IDmtpFileTransferActor : IActor /// 文件资源信息 /// 状态代码 /// 元数据 - /// 超时设置 - /// 可取消令箭 + /// 可取消令箭 /// - Task FinishedFileResourceInfoAsync(FileResourceInfo fileResourceInfo, ResultCode code, Metadata metadata = null, int millisecondsTimeout = 5000, CancellationToken token = default); + Task FinishedFileResourceInfoAsync(FileResourceInfo fileResourceInfo, ResultCode code, Metadata metadata, CancellationToken cancellationToken); /// @@ -195,16 +187,15 @@ public interface IDmtpFileTransferActor : IActor /// 注意: /// /// 完成该操作后,必须在设定时间(60秒)内至少完成一次文件块访问,不然该信息将变得无效, - /// 每次该操作,都应该对应一次 + /// 每次该操作,都应该对应一次 /// /// /// 资源路径 /// 元数据 /// 文件分块尺寸 - /// 超时设置 - /// 可取消令箭 + /// 可取消令箭 /// - Task PullFileResourceInfoAsync(string path, Metadata metadata = null, int fileSectionSize = 1024 * 512, int millisecondsTimeout = 5000, CancellationToken token = default); + Task PullFileResourceInfoAsync(string path, Metadata metadata, int fileSectionSize, CancellationToken cancellationToken); /// @@ -212,10 +203,9 @@ public interface IDmtpFileTransferActor : IActor /// 注意:拉取文件块时,两个成功块之间的时间应该在设定时间(60秒)内完成。 /// /// 文件块 - /// 超时设置 - /// 可取消令箭 + /// 可取消令箭 /// - Task PullFileSectionAsync(FileSection fileSection, int millisecondsTimeout = 5000, CancellationToken token = default); + Task PullFileSectionAsync(FileSection fileSection, CancellationToken cancellationToken); /// @@ -223,16 +213,15 @@ public interface IDmtpFileTransferActor : IActor /// 注意: /// /// 完成该操作后,必须在设定时间(60秒)内至少完成一次文件块访问,不然该信息将变得无效, - /// 每次该操作,都必须对应一次 + /// 每次该操作,都必须对应一次 /// /// /// 保存路径 /// 文件资源定位器 /// 元数据 - /// 超时设置 - /// 可取消令箭 + /// 可取消令箭 /// - Task PushFileResourceInfoAsync(string savePath, FileResourceLocator fileResourceLocator, Metadata metadata = null, int millisecondsTimeout = 5000, CancellationToken token = default); + Task PushFileResourceInfoAsync(string savePath, FileResourceLocator fileResourceLocator, Metadata metadata, CancellationToken cancellationToken); /// /// 推送文件块。 @@ -240,10 +229,9 @@ public interface IDmtpFileTransferActor : IActor /// /// 文件资源定位器 /// 文件块 - /// 超时设置 - /// 可取消令箭 + /// 可取消令箭 /// - Task PushFileSectionAsync(FileResourceLocator fileResourceLocator, FileSection fileSection, int millisecondsTimeout = 5000, CancellationToken token = default); + Task PushFileSectionAsync(FileResourceLocator fileResourceLocator, FileSection fileSection, CancellationToken cancellationToken); #endregion 文件传输 } \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Features/FileTransfer/Common/CancellationFileOperator.cs b/src/TouchSocket.Dmtp/Features/FileTransfer/Common/CancellationFileOperator.cs index a0e9c8b9a..41ce1f32b 100644 --- a/src/TouchSocket.Dmtp/Features/FileTransfer/Common/CancellationFileOperator.cs +++ b/src/TouchSocket.Dmtp/Features/FileTransfer/Common/CancellationFileOperator.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading; - namespace TouchSocket.Dmtp.FileTransfer; /// diff --git a/src/TouchSocket.Dmtp/Features/FileTransfer/Common/FileOperator.cs b/src/TouchSocket.Dmtp/Features/FileTransfer/Common/FileOperator.cs index 14cb834e5..b0cf2e0e8 100644 --- a/src/TouchSocket.Dmtp/Features/FileTransfer/Common/FileOperator.cs +++ b/src/TouchSocket.Dmtp/Features/FileTransfer/Common/FileOperator.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Dmtp.FileTransfer; /// diff --git a/src/TouchSocket.Dmtp/Features/FileTransfer/Common/FileResourceInfo.cs b/src/TouchSocket.Dmtp/Features/FileTransfer/Common/FileResourceInfo.cs index 011f423d2..843a92e9d 100644 --- a/src/TouchSocket.Dmtp/Features/FileTransfer/Common/FileResourceInfo.cs +++ b/src/TouchSocket.Dmtp/Features/FileTransfer/Common/FileResourceInfo.cs @@ -10,12 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using TouchSocket.Core; - namespace TouchSocket.Dmtp.FileTransfer; /// @@ -40,7 +34,7 @@ public class FileResourceInfo : PackageBase } // 使用FileInfo对象创建RemoteFileInfo对象,并用其初始化FileResourceInfo对象 - this.PrivateCreate(fileInfo.Map(), fileSectionSize); + this.PrivateCreate(Mapper.Trans(fileInfo), fileSectionSize); } /// @@ -64,34 +58,6 @@ public class FileResourceInfo : PackageBase this.PrivateCreate(fileInfo, fileSectionSize); } - /// - /// 从内存初始化资源 - /// - /// 包含资源信息的字节块 - [Obsolete($"此方法已被弃用,请使用{nameof(FileResourceInfo.Create)}")] - public FileResourceInfo(in IByteBlock byteBlock) - { - // 读取文件区块大小 - this.FileSectionSize = byteBlock.ReadInt32(); - // 读取资源句柄 - this.ResourceHandle = byteBlock.ReadInt32(); - // 读取文件信息 - this.FileInfo = byteBlock.ReadPackage(); - // 读取文件区块数量 - var len = byteBlock.ReadInt32(); - - // 根据读取的文件区块数量,创建相应的FileSection数组 - var fileSections = new FileSection[len]; - // 遍历每个文件区块,并从字节块中读取具体信息 - for (var i = 0; i < len; i++) - { - fileSections[i] = byteBlock.ReadPackage(); - } - - // 将读取的文件区块信息数组赋值给成员变量 - this.m_fileSections = fileSections; - } - private FileResourceInfo() { } @@ -139,27 +105,48 @@ public class FileResourceInfo : PackageBase /// /// 从字节块创建一个新的 实例。 /// - /// 字节块的类型,必须实现 接口。 - /// 引用的字节块,用于读取文件资源信息。 + /// 字节块的类型,必须实现 接口。 + /// 引用的字节块,用于读取文件资源信息。 /// 返回一个新的 实例。 - public static FileResourceInfo Create(ref TByteBlock byteBlock) where TByteBlock : IByteBlock + public static FileResourceInfo Create(ref TReader reader) where TReader : IBytesReader { var fileResourceInfo = new FileResourceInfo(); // 读取文件区块大小 - fileResourceInfo.FileSectionSize = byteBlock.ReadInt32(); + fileResourceInfo.FileSectionSize = ReaderExtension.ReadValue(ref reader); // 读取资源句柄 - fileResourceInfo.ResourceHandle = byteBlock.ReadInt32(); + fileResourceInfo.ResourceHandle = ReaderExtension.ReadValue(ref reader); // 读取文件信息 - fileResourceInfo.FileInfo = byteBlock.ReadPackage(); + + RemoteFileInfo remoteFileInfo; + if (ReaderExtension.ReadIsNull(ref reader)) + { + remoteFileInfo = null; + } + else + { + remoteFileInfo = new RemoteFileInfo(); + remoteFileInfo.Unpackage(ref reader); + } + fileResourceInfo.FileInfo = remoteFileInfo; // 读取文件区块数量 - var len = byteBlock.ReadInt32(); + var len = ReaderExtension.ReadValue(ref reader); // 根据读取的文件区块数量,创建相应的 FileSection 数组 var fileSections = new FileSection[len]; // 遍历每个文件区块,并从字节块中读取具体信息 for (var i = 0; i < len; i++) { - fileSections[i] = byteBlock.ReadPackage(); + FileSection fileSection; + if (ReaderExtension.ReadIsNull(ref reader)) + { + fileSection = default; + } + else + { + fileSection = new FileSection(); + fileSection.Unpackage(ref reader); + } + fileSections[i] = fileSection; } // 将读取的文件区块信息数组赋值给成员变量 @@ -209,10 +196,18 @@ public class FileResourceInfo : PackageBase } /// - public override void Package(ref TByteBlock byteBlock) + public override void Package(ref TWriter writer) { - byteBlock.WriteInt32(this.ResourceHandle); - byteBlock.WritePackage(this.FileInfo); + WriterExtension.WriteValue(ref writer, this.ResourceHandle); + if (this.FileInfo is null) + { + WriterExtension.WriteNull(ref writer); + } + else + { + WriterExtension.WriteNotNull(ref writer); + this.FileInfo.Package(ref writer); + } } /// @@ -233,21 +228,37 @@ public class FileResourceInfo : PackageBase /// /// 将对象保存到内存。 /// - /// 用于存储文件资源信息的字节块参数。 - public void Save(ref TByteBlock byteBlock) where TByteBlock : IByteBlock + /// 用于存储文件资源信息的字节块参数。 + public void Save(ref TWriter writer) where TWriter : IBytesWriter { - // 写入文件分区大小,以便在加载时正确分配内存。 - byteBlock.WriteInt32(this.FileSectionSize); - // 写入资源句柄,用于标识文件资源。 - byteBlock.WriteInt32(this.ResourceHandle); - // 写入文件信息包,包含文件的基本信息。 - byteBlock.WritePackage(this.FileInfo); - // 写入文件分区数组的长度,以便在加载时正确创建分区。 - byteBlock.WriteInt32(this.m_fileSections.Length); - // 遍历文件分区数组,将每个分区的信息写入字节块。 + WriterExtension.WriteValue(ref writer, this.FileSectionSize); + + WriterExtension.WriteValue(ref writer, this.ResourceHandle); + + if (this.FileInfo is null) + { + WriterExtension.WriteNull(ref writer); + } + else + { + WriterExtension.WriteNotNull(ref writer); + this.FileInfo.Package(ref writer); + } + + WriterExtension.WriteValue(ref writer, this.m_fileSections.Length); + foreach (var item in this.m_fileSections) { - byteBlock.WritePackage(item); + + if (item is null) + { + WriterExtension.WriteNull(ref writer); + } + else + { + WriterExtension.WriteNotNull(ref writer); + item.Package(ref writer); + } } } @@ -275,10 +286,18 @@ public class FileResourceInfo : PackageBase } /// - public override void Unpackage(ref TByteBlock byteBlock) + public override void Unpackage(ref TReader reader) { - this.ResourceHandle = byteBlock.ReadInt32(); - this.FileInfo = byteBlock.ReadPackage(); + this.ResourceHandle = ReaderExtension.ReadValue(ref reader); + if (ReaderExtension.ReadIsNull(ref reader)) + { + this.FileInfo = null; + } + else + { + this.FileInfo = new RemoteFileInfo(); + this.FileInfo.Unpackage(ref reader); + } } private void PrivateCreate(RemoteFileInfo fileInfo, long fileSectionSize) diff --git a/src/TouchSocket.Dmtp/Features/FileTransfer/Common/FileResourceInfoResult.cs b/src/TouchSocket.Dmtp/Features/FileTransfer/Common/FileResourceInfoResult.cs index a48b6db41..57a577aa1 100644 --- a/src/TouchSocket.Dmtp/Features/FileTransfer/Common/FileResourceInfoResult.cs +++ b/src/TouchSocket.Dmtp/Features/FileTransfer/Common/FileResourceInfoResult.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Dmtp.FileTransfer; /// diff --git a/src/TouchSocket.Dmtp/Features/FileTransfer/Common/FileResourceLocator.cs b/src/TouchSocket.Dmtp/Features/FileTransfer/Common/FileResourceLocator.cs index ebf35bd44..5b58ac1e5 100644 --- a/src/TouchSocket.Dmtp/Features/FileTransfer/Common/FileResourceLocator.cs +++ b/src/TouchSocket.Dmtp/Features/FileTransfer/Common/FileResourceLocator.cs @@ -10,11 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.IO; -using System.Linq; -using TouchSocket.Core; - namespace TouchSocket.Dmtp.FileTransfer; /// @@ -342,7 +337,7 @@ public class FileResourceLocator : DisposableObject // 如果文件部分的结果不成功,则返回该结果 if (!fileSectionResult.IsSuccess) { - return new Result(fileSectionResult); + return new Result(fileSectionResult.ResultCode, fileSectionResult.Message); } else { diff --git a/src/TouchSocket.Dmtp/Features/FileTransfer/Common/FileSection.cs b/src/TouchSocket.Dmtp/Features/FileTransfer/Common/FileSection.cs index 80d28583d..27d4d3281 100644 --- a/src/TouchSocket.Dmtp/Features/FileTransfer/Common/FileSection.cs +++ b/src/TouchSocket.Dmtp/Features/FileTransfer/Common/FileSection.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Dmtp.FileTransfer; /// @@ -45,23 +43,23 @@ public class FileSection : PackageBase public int Length { get; internal set; } /// - public override void Package(ref TByteBlock byteBlock) + public override void Package(ref TWriter writer) { - byteBlock.WriteByte((byte)this.Status); - byteBlock.WriteInt32(this.ResourceHandle); - byteBlock.WriteInt64(this.Offset); - byteBlock.WriteInt32(this.Length); - byteBlock.WriteInt32(this.Index); + WriterExtension.WriteValue(ref writer, (byte)this.Status); + WriterExtension.WriteValue(ref writer, this.ResourceHandle); + WriterExtension.WriteValue(ref writer, this.Offset); + WriterExtension.WriteValue(ref writer, this.Length); + WriterExtension.WriteValue(ref writer, this.Index); } /// - public override void Unpackage(ref TByteBlock byteBlock) + public override void Unpackage(ref TReader reader) { - this.Status = (FileSectionStatus)byteBlock.ReadByte(); - this.ResourceHandle = byteBlock.ReadInt32(); - this.Offset = byteBlock.ReadInt64(); - this.Length = byteBlock.ReadInt32(); - this.Index = byteBlock.ReadInt32(); + this.Status = (FileSectionStatus)ReaderExtension.ReadValue(ref reader); + this.ResourceHandle = ReaderExtension.ReadValue(ref reader); + this.Offset = ReaderExtension.ReadValue(ref reader); + this.Length = ReaderExtension.ReadValue(ref reader); + this.Index = ReaderExtension.ReadValue(ref reader); } /// diff --git a/src/TouchSocket.Dmtp/Features/FileTransfer/Common/FileSectionResult.cs b/src/TouchSocket.Dmtp/Features/FileTransfer/Common/FileSectionResult.cs index 587703ad8..ca5f7d83d 100644 --- a/src/TouchSocket.Dmtp/Features/FileTransfer/Common/FileSectionResult.cs +++ b/src/TouchSocket.Dmtp/Features/FileTransfer/Common/FileSectionResult.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using TouchSocket.Core; - namespace TouchSocket.Dmtp.FileTransfer; /// diff --git a/src/TouchSocket.Dmtp/Features/FileTransfer/Common/FinishedResult.cs b/src/TouchSocket.Dmtp/Features/FileTransfer/Common/FinishedResult.cs index 00eee2d77..645bb425c 100644 --- a/src/TouchSocket.Dmtp/Features/FileTransfer/Common/FinishedResult.cs +++ b/src/TouchSocket.Dmtp/Features/FileTransfer/Common/FinishedResult.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Dmtp.FileTransfer; /// diff --git a/src/TouchSocket.Dmtp/Features/FileTransfer/Common/PullSmallFileResult.cs b/src/TouchSocket.Dmtp/Features/FileTransfer/Common/PullSmallFileResult.cs index 774ca0cb7..1f7c77fc9 100644 --- a/src/TouchSocket.Dmtp/Features/FileTransfer/Common/PullSmallFileResult.cs +++ b/src/TouchSocket.Dmtp/Features/FileTransfer/Common/PullSmallFileResult.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using TouchSocket.Core; - namespace TouchSocket.Dmtp.FileTransfer; /// @@ -57,7 +54,7 @@ public class PullSmallFileResult : ResultBase /// 将拉取的数据保存为文件。 /// /// 要保存文件的路径。 - /// 是否覆盖同名文件,默认为true。 + /// 是否覆盖同名文件,默认为。 /// 返回保存结果。 public Result Save(string path, bool overwrite = true) { diff --git a/src/TouchSocket.Dmtp/Features/FileTransfer/Common/RemoteFileInfo.cs b/src/TouchSocket.Dmtp/Features/FileTransfer/Common/RemoteFileInfo.cs index 568bf082d..c6bf4037f 100644 --- a/src/TouchSocket.Dmtp/Features/FileTransfer/Common/RemoteFileInfo.cs +++ b/src/TouchSocket.Dmtp/Features/FileTransfer/Common/RemoteFileInfo.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.IO; - namespace TouchSocket.Dmtp.FileTransfer; @@ -54,18 +52,18 @@ public class RemoteFileInfo : RemoteFileSystemInfo public string MD5 { get; set; } /// - public override void Package(ref TByteBlock byteBlock) + public override void Package(ref TWriter writer) { - base.Package(ref byteBlock); - byteBlock.WriteString(this.MD5); - byteBlock.WriteInt64(this.Length); + base.Package(ref writer); + WriterExtension.WriteString(ref writer, this.MD5); + WriterExtension.WriteValue(ref writer, this.Length); } /// - public override void Unpackage(ref TByteBlock byteBlock) + public override void Unpackage(ref TReader reader) { - base.Unpackage(ref byteBlock); - this.MD5 = byteBlock.ReadString(); - this.Length = byteBlock.ReadInt64(); + base.Unpackage(ref reader); + this.MD5 = ReaderExtension.ReadString(ref reader); + this.Length = ReaderExtension.ReadValue(ref reader); } } \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Features/FileTransfer/Common/RemoteFileInfoResult.cs b/src/TouchSocket.Dmtp/Features/FileTransfer/Common/RemoteFileInfoResult.cs index 7da00c6f9..e094ed9b6 100644 --- a/src/TouchSocket.Dmtp/Features/FileTransfer/Common/RemoteFileInfoResult.cs +++ b/src/TouchSocket.Dmtp/Features/FileTransfer/Common/RemoteFileInfoResult.cs @@ -10,14 +10,12 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Dmtp.FileTransfer; /// /// 远程访问结果 /// -public struct RemoteFileInfoResult : IResult +public struct RemoteFileInfoResult { /// /// 构造函数:初始化远程访问结果对象 diff --git a/src/TouchSocket.Dmtp/Features/FileTransfer/Common/RemoteFileSystemInfo.cs b/src/TouchSocket.Dmtp/Features/FileTransfer/Common/RemoteFileSystemInfo.cs index 2dd954332..3bdc3073c 100644 --- a/src/TouchSocket.Dmtp/Features/FileTransfer/Common/RemoteFileSystemInfo.cs +++ b/src/TouchSocket.Dmtp/Features/FileTransfer/Common/RemoteFileSystemInfo.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.IO; -using TouchSocket.Core; - namespace TouchSocket.Dmtp.FileTransfer; /// @@ -52,24 +48,24 @@ public abstract class RemoteFileSystemInfo : PackageBase public string Name { get; set; } /// - public override void Package(ref TByteBlock byteBlock) + public override void Package(ref TWriter writer) { - byteBlock.WriteDateTime(this.LastWriteTime); - byteBlock.WriteDateTime(this.LastAccessTime); - byteBlock.WriteDateTime(this.CreationTime); - byteBlock.WriteInt32((int)this.Attributes); - byteBlock.WriteString(this.FullName); - byteBlock.WriteString(this.Name); + WriterExtension.WriteValue(ref writer, this.LastWriteTime); + WriterExtension.WriteValue(ref writer, this.LastAccessTime); + WriterExtension.WriteValue(ref writer, this.CreationTime); + WriterExtension.WriteValue(ref writer, (int)this.Attributes); + WriterExtension.WriteString(ref writer, this.FullName); + WriterExtension.WriteString(ref writer, this.Name); } /// - public override void Unpackage(ref TByteBlock byteBlock) + public override void Unpackage(ref TReader reader) { - this.LastWriteTime = byteBlock.ReadDateTime(); - this.LastAccessTime = byteBlock.ReadDateTime(); - this.CreationTime = byteBlock.ReadDateTime(); - this.Attributes = (FileAttributes)byteBlock.ReadInt32(); - this.FullName = byteBlock.ReadString(); - this.Name = byteBlock.ReadString(); + this.LastWriteTime = ReaderExtension.ReadValue(ref reader); + this.LastAccessTime = ReaderExtension.ReadValue(ref reader); + this.CreationTime = ReaderExtension.ReadValue(ref reader); + this.Attributes = (FileAttributes)ReaderExtension.ReadValue(ref reader); + this.FullName = ReaderExtension.ReadString(ref reader); + this.Name = ReaderExtension.ReadString(ref reader); } } \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Features/FileTransfer/Common/WaitFileSection.cs b/src/TouchSocket.Dmtp/Features/FileTransfer/Common/WaitFileSection.cs index 157219b8e..700249568 100644 --- a/src/TouchSocket.Dmtp/Features/FileTransfer/Common/WaitFileSection.cs +++ b/src/TouchSocket.Dmtp/Features/FileTransfer/Common/WaitFileSection.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using TouchSocket.Core; - namespace TouchSocket.Dmtp.FileTransfer; internal class WaitFileSection : WaitRouterPackage, IDisposable @@ -25,17 +22,33 @@ internal class WaitFileSection : WaitRouterPackage, IDisposable this.Value?.Dispose(); } - public override void PackageBody(ref TByteBlock byteBlock) + public override void PackageBody(ref TWriter writer) { - base.PackageBody(ref byteBlock); - byteBlock.WritePackage(this.FileSection); - byteBlock.WriteByteBlock(this.Value); + base.PackageBody(ref writer); + if (this.FileSection is null) + { + WriterExtension.WriteNull(ref writer); + } + else + { + WriterExtension.WriteNotNull(ref writer); + this.FileSection.Package(ref writer); + } + WriterExtension.WriteByteBlock(ref writer, this.Value); } - public override void UnpackageBody(ref TByteBlock byteBlock) + public override void UnpackageBody(ref TReader reader) { - base.UnpackageBody(ref byteBlock); - this.FileSection = byteBlock.ReadPackage(); - this.Value = byteBlock.ReadByteBlock(); + base.UnpackageBody(ref reader); + if (ReaderExtension.ReadIsNull(ref reader)) + { + this.FileSection = null; + } + else + { + this.FileSection = new FileSection(); + this.FileSection.Unpackage(ref reader); + } + this.Value = ReaderExtension.ReadByteBlock(ref reader); } } \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Features/FileTransfer/Common/WaitFinishedPackage.cs b/src/TouchSocket.Dmtp/Features/FileTransfer/Common/WaitFinishedPackage.cs index 54a3b23d3..cffa5e331 100644 --- a/src/TouchSocket.Dmtp/Features/FileTransfer/Common/WaitFinishedPackage.cs +++ b/src/TouchSocket.Dmtp/Features/FileTransfer/Common/WaitFinishedPackage.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Dmtp.FileTransfer; internal class WaitFinishedPackage : WaitRouterPackage @@ -20,19 +18,35 @@ internal class WaitFinishedPackage : WaitRouterPackage public Metadata Metadata { get; set; } public int ResourceHandle { get; set; } - public override void PackageBody(ref TByteBlock byteBlock) + public override void PackageBody(ref TWriter writer) { - base.PackageBody(ref byteBlock); - byteBlock.WriteInt32(this.ResourceHandle); - byteBlock.WritePackage(this.Metadata); - byteBlock.WriteByte((byte)this.Code); + base.PackageBody(ref writer); + WriterExtension.WriteValue(ref writer, this.ResourceHandle); + if (this.Metadata is null) + { + WriterExtension.WriteNull(ref writer); + } + else + { + WriterExtension.WriteNotNull(ref writer); + this.Metadata.Package(ref writer); + } + WriterExtension.WriteValue(ref writer, (byte)this.Code); } - public override void UnpackageBody(ref TByteBlock byteBlock) + public override void UnpackageBody(ref TReader reader) { - base.UnpackageBody(ref byteBlock); - this.ResourceHandle = byteBlock.ReadInt32(); - this.Metadata = byteBlock.ReadPackage(); - this.Code = (ResultCode)byteBlock.ReadByte(); + base.UnpackageBody(ref reader); + this.ResourceHandle = ReaderExtension.ReadValue(ref reader); + if (ReaderExtension.ReadIsNull(ref reader)) + { + this.Metadata = null; + } + else + { + this.Metadata = new Metadata(); + this.Metadata.Unpackage(ref reader); + } + this.Code = (ResultCode)ReaderExtension.ReadValue(ref reader); } } \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Features/FileTransfer/Common/WaitSmallFilePackage.cs b/src/TouchSocket.Dmtp/Features/FileTransfer/Common/WaitSmallFilePackage.cs index 28687f0df..00f9ce2c4 100644 --- a/src/TouchSocket.Dmtp/Features/FileTransfer/Common/WaitSmallFilePackage.cs +++ b/src/TouchSocket.Dmtp/Features/FileTransfer/Common/WaitSmallFilePackage.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Dmtp.FileTransfer; internal class WaitSmallFilePackage : WaitRouterPackage @@ -23,21 +21,56 @@ internal class WaitSmallFilePackage : WaitRouterPackage public Metadata Metadata { get; set; } public string Path { get; set; } - public override void PackageBody(ref TByteBlock byteBlock) + public override void PackageBody(ref TWriter writer) { - base.PackageBody(ref byteBlock); - byteBlock.WriteString(this.Path); - byteBlock.WritePackage(this.Metadata); - byteBlock.WritePackage(this.FileInfo); - byteBlock.WriteBytesPackage(this.Data, 0, this.Len); + base.PackageBody(ref writer); + WriterExtension.WriteString(ref writer, this.Path); + if (this.Metadata is null) + { + WriterExtension.WriteNull(ref writer); + } + else + { + WriterExtension.WriteNotNull(ref writer); + this.Metadata.Package(ref writer); + } + + if (this.FileInfo is null) + { + WriterExtension.WriteNull(ref writer); + } + else + { + WriterExtension.WriteNotNull(ref writer); + this.FileInfo.Package(ref writer); + } + + WriterExtension.WriteByteSpan(ref writer, new System.ReadOnlySpan(this.Data, 0, this.Len)); } - public override void UnpackageBody(ref TByteBlock byteBlock) + public override void UnpackageBody(ref TReader reader) { - base.UnpackageBody(ref byteBlock); - this.Path = byteBlock.ReadString(); - this.Metadata = byteBlock.ReadPackage(); - this.FileInfo = byteBlock.ReadPackage(); - this.Data = byteBlock.ReadBytesPackage(); + base.UnpackageBody(ref reader); + this.Path = ReaderExtension.ReadString(ref reader); + if (ReaderExtension.ReadIsNull(ref reader)) + { + this.Metadata = null; + } + else + { + this.Metadata = new Metadata(); + this.Metadata.Unpackage(ref reader); + } + + if (ReaderExtension.ReadIsNull(ref reader)) + { + this.FileInfo = null; + } + else + { + this.FileInfo = new RemoteFileInfo(); + this.FileInfo.Unpackage(ref reader); + } + this.Data = ReaderExtension.ReadByteSpan(ref reader).ToArray(); } } \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Features/FileTransfer/DmtpFileTransferFeature.cs b/src/TouchSocket.Dmtp/Features/FileTransfer/DmtpFileTransferFeature.cs index bb0442f07..81a001849 100644 --- a/src/TouchSocket.Dmtp/Features/FileTransfer/DmtpFileTransferFeature.cs +++ b/src/TouchSocket.Dmtp/Features/FileTransfer/DmtpFileTransferFeature.cs @@ -10,28 +10,26 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Dmtp.FileTransfer; /// /// 能够基于Dmtp协议,提供文件传输的能力 /// -public sealed class DmtpFileTransferFeature : PluginBase, IDmtpHandshakingPlugin, IDmtpReceivedPlugin, IDmtpFeature +public sealed class DmtpFileTransferFeature : PluginBase, IDmtpConnectingPlugin, IDmtpReceivedPlugin, IDmtpFeature { - private readonly IFileResourceController m_fileResourceController; + private readonly DmtpFileTransferOption m_option; private IPluginManager m_pluginManager; /// /// 能够基于Dmtp协议,提供文件传输的能力 /// - /// - public DmtpFileTransferFeature(IResolver resolver) + /// 服务解析器 + /// 配置选项 + public DmtpFileTransferFeature(IResolver resolver, DmtpFileTransferOption option) { - this.m_fileResourceController = resolver.Resolve() ?? FileResourceController.Default; - this.MaxSmallFileLength = 1024 * 1024; - this.SetProtocolFlags(30); + ThrowHelper.ThrowIfNull(option, nameof(option)); + this.m_option = option; + this.m_option.FileResourceController ??= resolver.Resolve() ?? FileResourceController.Default; } /// @@ -41,29 +39,28 @@ public sealed class DmtpFileTransferFeature : PluginBase, IDmtpHandshakingPlugin this.m_pluginManager = pluginManager; } - /// - public int MaxSmallFileLength { get; set; } - /// public ushort ReserveProtocolSize => 20; - /// - public string RootPath { get; set; } + /// + public ushort StartProtocol => this.m_option.StartProtocol; + + /// + /// 获取配置选项 + /// + public DmtpFileTransferOption Option => this.m_option; /// - public ushort StartProtocol { get; set; } - - /// - public async Task OnDmtpHandshaking(IDmtpActorObject client, DmtpVerifyEventArgs e) + public async Task OnDmtpConnecting(IDmtpActorObject client, DmtpVerifyEventArgs e) { - var dmtpFileTransferActor = new DmtpFileTransferActor(client.DmtpActor, this.m_fileResourceController) + var dmtpFileTransferActor = new DmtpFileTransferActor(client.DmtpActor, this.m_option.FileResourceController) { OnFileTransferring = this.OnFileTransfering, OnFileTransferred = this.OnFileTransfered, - RootPath = this.RootPath, - MaxSmallFileLength = this.MaxSmallFileLength + RootPath = this.m_option.RootPath, + MaxSmallFileLength = this.m_option.MaxSmallFileLength }; - dmtpFileTransferActor.SetProtocolFlags(this.StartProtocol); + dmtpFileTransferActor.SetProtocolFlags(this.m_option.StartProtocol); client.DmtpActor.TryAddActor(dmtpFileTransferActor); await e.InvokeNext().ConfigureAwait(EasyTask.ContinueOnCapturedContext); } @@ -83,34 +80,6 @@ public sealed class DmtpFileTransferFeature : PluginBase, IDmtpHandshakingPlugin await e.InvokeNext().ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - /// - public DmtpFileTransferFeature SetMaxSmallFileLength(int maxSmallFileLength) - { - this.MaxSmallFileLength = maxSmallFileLength; - return this; - } - - /// - /// 设置的起始协议。 - /// - /// 默认起始为:30,保留20个协议长度。 - /// - /// - /// - /// - public DmtpFileTransferFeature SetProtocolFlags(ushort start) - { - this.StartProtocol = start; - return this; - } - - /// - public DmtpFileTransferFeature SetRootPath(string rootPath) - { - this.RootPath = rootPath; - return this; - } - private async Task OnFileTransfered(IDmtpActor actor, FileTransferredEventArgs e) { await this.m_pluginManager.RaiseAsync(typeof(IDmtpFileTransferredPlugin), actor.Client.Resolver, actor.Client, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); diff --git a/src/TouchSocket.Dmtp/Features/FileTransfer/EventArgs/FileTransferredEventArgs.cs b/src/TouchSocket.Dmtp/Features/FileTransfer/EventArgs/FileTransferredEventArgs.cs index f49db6e3b..faacc8612 100644 --- a/src/TouchSocket.Dmtp/Features/FileTransfer/EventArgs/FileTransferredEventArgs.cs +++ b/src/TouchSocket.Dmtp/Features/FileTransfer/EventArgs/FileTransferredEventArgs.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Dmtp.FileTransfer; /// diff --git a/src/TouchSocket.Dmtp/Features/FileTransfer/EventArgs/FileTransferringEventArgs.cs b/src/TouchSocket.Dmtp/Features/FileTransfer/EventArgs/FileTransferringEventArgs.cs index ed26135fb..a27e02f95 100644 --- a/src/TouchSocket.Dmtp/Features/FileTransfer/EventArgs/FileTransferringEventArgs.cs +++ b/src/TouchSocket.Dmtp/Features/FileTransfer/EventArgs/FileTransferringEventArgs.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Dmtp.FileTransfer; diff --git a/src/TouchSocket.Dmtp/Features/FileTransfer/Extensions/DmtpFileTransferActorExtension.cs b/src/TouchSocket.Dmtp/Features/FileTransfer/Extensions/DmtpFileTransferActorExtension.cs index e7361a537..d1844a5bf 100644 --- a/src/TouchSocket.Dmtp/Features/FileTransfer/Extensions/DmtpFileTransferActorExtension.cs +++ b/src/TouchSocket.Dmtp/Features/FileTransfer/Extensions/DmtpFileTransferActorExtension.cs @@ -10,10 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Collections.Concurrent; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Resources; namespace TouchSocket.Dmtp.FileTransfer; @@ -23,21 +20,6 @@ namespace TouchSocket.Dmtp.FileTransfer; /// public static class DmtpFileTransferActorExtension { - #region 插件扩展 - - /// - /// 使用DmtpFileTransfer插件 - /// - /// 插件管理器实例,用于管理插件的加载和执行 - /// 返回DmtpFileTransferFeature实例,以便进一步操作或配置 - public static DmtpFileTransferFeature UseDmtpFileTransfer(this IPluginManager pluginManager) - { - // 通过插件管理器添加DmtpFileTransfer插件,并返回插件实例 - return pluginManager.Add(); - } - - #endregion 插件扩展 - #region DependencyProperty ///// @@ -67,7 +49,7 @@ public static class DmtpFileTransferActorExtension public static IDmtpFileTransferActor GetDmtpFileTransferActor(this IDmtpActorObject client) { var actor = client.DmtpActor.GetDmtpFileTransferActor(); - ThrowHelper.ThrowArgumentNullExceptionIf(actor, nameof(actor), TouchSocketDmtpResource.DmtpFileTransferActorNull); + ThrowHelper.ThrowIfNull(actor, nameof(actor), TouchSocketDmtpResource.DmtpFileTransferActorNull); return actor; } @@ -111,10 +93,10 @@ public static class DmtpFileTransferActorExtension try { - var resourceInfoResult = await actor.PullFileResourceInfoAsync(targetId, fileOperator.ResourcePath, fileOperator.Metadata, fileOperator.FileSectionSize, (int)fileOperator.Timeout.TotalMilliseconds, fileOperator.Token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var resourceInfoResult = await actor.PullFileResourceInfoAsync(targetId, fileOperator.ResourcePath, fileOperator.Metadata, fileOperator.FileSectionSize, fileOperator.Token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); if (!resourceInfoResult.IsSuccess) { - return fileOperator.SetResult(new Result(resourceInfoResult)); + return fileOperator.SetResult(new Result(resourceInfoResult.ResultCode, resourceInfoResult.Message)); } var resourceInfo = resourceInfoResult.FileResourceInfo; if (fileOperator.ResourceInfo == null) @@ -147,7 +129,7 @@ public static class DmtpFileTransferActorExtension } var failResult = Result.UnknownFail; - await Task.Run(async () => + await EasyTask.SafeRun(async () => { var failed = 0; while (true) @@ -162,7 +144,7 @@ public static class DmtpFileTransferActorExtension } try { - using (var result = await actor.PullFileSectionAsync(targetId, fileSection, (int)fileOperator.Timeout.TotalMilliseconds, fileOperator.Token).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) + using (var result = await actor.PullFileSectionAsync(targetId, fileSection, fileOperator.Token).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) { if (result.IsSuccess) { @@ -179,7 +161,7 @@ public static class DmtpFileTransferActorExtension } else { - failResult = new Result(result); + failResult = new Result(result.ResultCode, result.Message); if (result.ResultCode == ResultCode.Canceled) { return; @@ -202,14 +184,14 @@ public static class DmtpFileTransferActorExtension { if (actor.DmtpActor.Online) { - await actor.FinishedFileResourceInfoAsync(targetId, resourceInfo, ResultCode.Canceled, fileOperator.Metadata, (int)fileOperator.Timeout.TotalMilliseconds, fileOperator.Token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await actor.FinishedFileResourceInfoAsync(targetId, resourceInfo, ResultCode.Canceled, fileOperator.Metadata, fileOperator.Token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } return fileOperator.SetResult(Result.Canceled); } var result1 = locator.TryFinished(); if (actor.DmtpActor.Online) { - await actor.FinishedFileResourceInfoAsync(targetId, resourceInfo, ResultCode.Success, fileOperator.Metadata, (int)fileOperator.Timeout.TotalMilliseconds, fileOperator.Token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await actor.FinishedFileResourceInfoAsync(targetId, resourceInfo, ResultCode.Success, fileOperator.Metadata, fileOperator.Token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } return result1.IsSuccess ? fileOperator.SetResult(Result.Success) : fileOperator.SetResult(failResult); @@ -259,7 +241,7 @@ public static class DmtpFileTransferActorExtension fileOperator.SetLength(fileOperator.ResourceInfo.FileInfo.Length); using var locator = new FileResourceLocator(fileOperator.ResourceInfo); - var resultInfo = await actor.PushFileResourceInfoAsync(targetId, fileOperator.SavePath, locator, fileOperator.Metadata, (int)fileOperator.Timeout.TotalMilliseconds, fileOperator.Token); + var resultInfo = await actor.PushFileResourceInfoAsync(targetId, fileOperator.SavePath, locator, fileOperator.Metadata, fileOperator.Token); if (!resultInfo.IsSuccess) { return fileOperator.SetResult(resultInfo); @@ -282,7 +264,7 @@ public static class DmtpFileTransferActorExtension } var failResult = Result.UnknownFail; - await Task.Run(async () => + await EasyTask.SafeRun(async () => { var failed = 0; while (true) @@ -297,14 +279,14 @@ public static class DmtpFileTransferActorExtension } try { - var result = await actor.PushFileSectionAsync(targetId, locator, fileSection, (int)fileOperator.Timeout.TotalMilliseconds, fileOperator.Token); + var result = await actor.PushFileSectionAsync(targetId, locator, fileSection, fileOperator.Token); if (result.IsSuccess) { await fileOperator.AddFlowAsync(fileSection.Length); } else { - failResult = new Result(result); + failResult = new Result(result.ResultCode, result.Message); if (result.ResultCode == ResultCode.Canceled) { return; @@ -325,13 +307,13 @@ public static class DmtpFileTransferActorExtension if (fileOperator.Token.IsCancellationRequested) { - await actor.FinishedFileResourceInfoAsync(targetId, fileOperator.ResourceInfo, ResultCode.Canceled, fileOperator.Metadata, (int)fileOperator.Timeout.TotalMilliseconds, fileOperator.Token); + await actor.FinishedFileResourceInfoAsync(targetId, fileOperator.ResourceInfo, ResultCode.Canceled, fileOperator.Metadata, fileOperator.Token); return fileOperator.SetResult(Result.Canceled); } else { - var res = await actor.FinishedFileResourceInfoAsync(targetId, fileOperator.ResourceInfo, ResultCode.Success, fileOperator.Metadata, (int)fileOperator.Timeout.TotalMilliseconds, fileOperator.Token); - return fileOperator.SetResult(res.ToResult()); + var res = await actor.FinishedFileResourceInfoAsync(targetId, fileOperator.ResourceInfo, ResultCode.Success, fileOperator.Metadata, fileOperator.Token); + return fileOperator.SetResult(new Result(res.ResultCode, res.Message)); } } catch (Exception ex) diff --git a/src/TouchSocket.Dmtp/Features/FileTransfer/Extensions/DmtpFileTransferPluginManagerExtension.cs b/src/TouchSocket.Dmtp/Features/FileTransfer/Extensions/DmtpFileTransferPluginManagerExtension.cs new file mode 100644 index 000000000..042b35d69 --- /dev/null +++ b/src/TouchSocket.Dmtp/Features/FileTransfer/Extensions/DmtpFileTransferPluginManagerExtension.cs @@ -0,0 +1,47 @@ +//------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +//------------------------------------------------------------------------------ + +namespace TouchSocket.Dmtp.FileTransfer; + +/// +/// 的扩展方法,用于使用DmtpFileTransfer插件。 +/// +public static class DmtpFileTransferPluginManagerExtension +{ + /// + /// 使用DmtpFileTransfer插件。 + /// + /// 插件管理器实例。 + /// 配置选项的委托。 + /// 返回实例。 + public static DmtpFileTransferFeature UseDmtpFileTransfer(this IPluginManager pluginManager, Action options) + { + var option = new DmtpFileTransferOption(); + + options.Invoke(option); + + var dmtpFileTransferFeature = new DmtpFileTransferFeature(pluginManager.Resolver, option); + pluginManager.Add(dmtpFileTransferFeature); + + return dmtpFileTransferFeature; + } + + /// + /// 使用DmtpFileTransfer插件。 + /// + /// 插件管理器实例。 + /// 返回实例。 + public static DmtpFileTransferFeature UseDmtpFileTransfer(this IPluginManager pluginManager) + { + return UseDmtpFileTransfer(pluginManager, options => { }); + } +} \ No newline at end of file diff --git a/src/TouchSocket.Core/Results/IResult.cs b/src/TouchSocket.Dmtp/Features/FileTransfer/Options/DmtpFileTransferOption.cs similarity index 62% rename from src/TouchSocket.Core/Results/IResult.cs rename to src/TouchSocket.Dmtp/Features/FileTransfer/Options/DmtpFileTransferOption.cs index 5a0bdfa20..97e5ba51b 100644 --- a/src/TouchSocket.Core/Results/IResult.cs +++ b/src/TouchSocket.Dmtp/Features/FileTransfer/Options/DmtpFileTransferOption.cs @@ -10,28 +10,29 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Diagnostics.CodeAnalysis; - -namespace TouchSocket.Core; +namespace TouchSocket.Dmtp.FileTransfer; /// -/// 表示结果的接口 +/// Dmtp文件传输配置选项 /// -public interface IResult +public class DmtpFileTransferOption : DmtpFeatureOption { + public DmtpFileTransferOption() + { + this.StartProtocol = 30; + } /// - /// 结果代码 + /// 文件资源控制器 /// - ResultCode ResultCode { get; } + public IFileResourceController FileResourceController { get; set; } = TouchSocket.Dmtp.FileTransfer.FileResourceController.Default; /// - /// 结果附加消息 + /// 小文件最大长度 /// - string Message { get; } + public int MaxSmallFileLength { get; set; } = 1024 * 1024; /// - /// 是否成功。一般的当时会返回。其余情况返回 + /// 根路径 /// - [MemberNotNullWhen(true, nameof(IResult.Value))] - bool IsSuccess { get; } + public string RootPath { get; set; } } \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Features/FileTransfer/Plugins/IDmtpFileTransferingPlugin.cs b/src/TouchSocket.Dmtp/Features/FileTransfer/Plugins/IDmtpFileTransferingPlugin.cs index a5c3254ff..72c82cda2 100644 --- a/src/TouchSocket.Dmtp/Features/FileTransfer/Plugins/IDmtpFileTransferingPlugin.cs +++ b/src/TouchSocket.Dmtp/Features/FileTransfer/Plugins/IDmtpFileTransferingPlugin.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Dmtp.FileTransfer; diff --git a/src/TouchSocket.Dmtp/Features/FileTransfer/Plugins/IDmtpFileTransferredPlugin.cs b/src/TouchSocket.Dmtp/Features/FileTransfer/Plugins/IDmtpFileTransferredPlugin.cs index a1437cf97..9c7791142 100644 --- a/src/TouchSocket.Dmtp/Features/FileTransfer/Plugins/IDmtpFileTransferredPlugin.cs +++ b/src/TouchSocket.Dmtp/Features/FileTransfer/Plugins/IDmtpFileTransferredPlugin.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Dmtp.FileTransfer; diff --git a/src/TouchSocket.Dmtp/Features/FileTransfer/RouterPackages/FileTransferRouterPackage.cs b/src/TouchSocket.Dmtp/Features/FileTransfer/RouterPackages/FileTransferRouterPackage.cs index fa079b003..e5eade28f 100644 --- a/src/TouchSocket.Dmtp/Features/FileTransfer/RouterPackages/FileTransferRouterPackage.cs +++ b/src/TouchSocket.Dmtp/Features/FileTransfer/RouterPackages/FileTransferRouterPackage.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Dmtp.FileTransfer; /// @@ -53,26 +51,61 @@ public class FileTransferRouterPackage : WaitRouterPackage protected override bool IncludedRouter => true; /// - public override void PackageBody(ref TByteBlock byteBlock) + public override void PackageBody(ref TWriter writer) { - base.PackageBody(ref byteBlock); - byteBlock.WriteInt32(this.ContinuationIndex); - byteBlock.WriteString(this.Path); - byteBlock.WriteInt32(this.ResourceHandle); - byteBlock.WriteInt32(this.FileSectionSize); - byteBlock.WritePackage(this.FileInfo); - byteBlock.WritePackage(this.Metadata); + base.PackageBody(ref writer); + WriterExtension.WriteValue(ref writer, this.ContinuationIndex); + WriterExtension.WriteString(ref writer, this.Path); + WriterExtension.WriteValue(ref writer, this.ResourceHandle); + WriterExtension.WriteValue(ref writer, this.FileSectionSize); + if (this.FileInfo is null) + { + WriterExtension.WriteNull(ref writer); + } + else + { + WriterExtension.WriteNotNull(ref writer); + this.FileInfo.Package(ref writer); + } + + if (this.Metadata is null) + { + WriterExtension.WriteNull(ref writer); + } + else + { + WriterExtension.WriteNotNull(ref writer); + this.Metadata.Package(ref writer); + } } /// - public override void UnpackageBody(ref TByteBlock byteBlock) + public override void UnpackageBody(ref TReader reader) { - base.UnpackageBody(ref byteBlock); - this.ContinuationIndex = byteBlock.ReadInt32(); - this.Path = byteBlock.ReadString(); - this.ResourceHandle = byteBlock.ReadInt32(); - this.FileSectionSize = byteBlock.ReadInt32(); - this.FileInfo = byteBlock.ReadPackage(); - this.Metadata = byteBlock.ReadPackage(); + base.UnpackageBody(ref reader); + this.ContinuationIndex = ReaderExtension.ReadValue(ref reader); + this.Path = ReaderExtension.ReadString(ref reader); + this.ResourceHandle = ReaderExtension.ReadValue(ref reader); + this.FileSectionSize = ReaderExtension.ReadValue(ref reader); + + if (ReaderExtension.ReadIsNull(ref reader)) + { + this.FileInfo = null; + } + else + { + this.FileInfo = new RemoteFileInfo(); + this.FileInfo.Unpackage(ref reader); + } + + if (ReaderExtension.ReadIsNull(ref reader)) + { + this.Metadata = null; + } + else + { + this.Metadata = new Metadata(); + this.Metadata.Unpackage(ref reader); + } } } \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Features/FileTransfer/Services/FileResourceController.cs b/src/TouchSocket.Dmtp/Features/FileTransfer/Services/FileResourceController.cs index 9dc57d4a3..5d0433a9a 100644 --- a/src/TouchSocket.Dmtp/Features/FileTransfer/Services/FileResourceController.cs +++ b/src/TouchSocket.Dmtp/Features/FileTransfer/Services/FileResourceController.cs @@ -10,13 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using TouchSocket.Core; namespace TouchSocket.Dmtp.FileTransfer; diff --git a/src/TouchSocket.Dmtp/Features/FileTransfer/Services/IFileResourceController.cs b/src/TouchSocket.Dmtp/Features/FileTransfer/Services/IFileResourceController.cs index d27bd71ff..f4d6a7079 100644 --- a/src/TouchSocket.Dmtp/Features/FileTransfer/Services/IFileResourceController.cs +++ b/src/TouchSocket.Dmtp/Features/FileTransfer/Services/IFileResourceController.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.IO; - namespace TouchSocket.Dmtp.FileTransfer; /// diff --git a/src/TouchSocket.Dmtp/Features/Redis/Actor/DmtpRedisActor.cs b/src/TouchSocket.Dmtp/Features/Redis/Actor/DmtpRedisActor.cs index a72eb6f60..74e28d8e0 100644 --- a/src/TouchSocket.Dmtp/Features/Redis/Actor/DmtpRedisActor.cs +++ b/src/TouchSocket.Dmtp/Features/Redis/Actor/DmtpRedisActor.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Resources; namespace TouchSocket.Dmtp.Redis; @@ -39,33 +36,30 @@ internal sealed class DmtpRedisActor : DisposableObject, IDmtpRedisActor public IDmtpActor DmtpActor { get; } /// - public ICache ICache { get; set; } + public ICache> ICache { get; set; } /// - public int Timeout { get; set; } = 30 * 1000; - - /// - public async Task AddAsync(string key, TValue value, int duration = 60000) + public async Task AddAsync(string key, TValue value, int duration, CancellationToken cancellationToken) { - var cache = new CacheEntry(key) + var cache = new CacheEntry>(key) { Duration = TimeSpan.FromSeconds(duration) }; - if (!(value is byte[])) + if (value is not ReadOnlyMemory) { cache.Value = this.Converter.Serialize(null, value); } - return await this.AddCacheAsync(cache).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return await this.AddCacheAsync(cache, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// - public async Task AddCacheAsync(ICacheEntry entity) + public async Task AddCacheAsync(ICacheEntry> entity, CancellationToken cancellationToken) { - return !await this.ContainsCacheAsync(entity.Key).ConfigureAwait(EasyTask.ContinueOnCapturedContext) && await this.SetCacheAsync(entity).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return !await this.ContainsCacheAsync(entity.Key, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext) && await this.SetCacheAsync(entity, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// - public async Task ClearCacheAsync() + public async Task ClearCacheAsync(CancellationToken cancellationToken) { var package = new RedisRequestWaitPackage { @@ -81,17 +75,17 @@ internal sealed class DmtpRedisActor : DisposableObject, IDmtpRedisActor package.Package(ref block); await this.DmtpActor.SendAsync(this.m_redis_Request, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - switch (await waitData.WaitAsync(this.Timeout).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) + switch (await waitData.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) { - case WaitDataStatus.SetRunning: + case WaitDataStatus.Success: { - if (waitData.WaitResult.Status == 1) + if (waitData.CompletedData.Status == 1) { return; } else { - throw new Exception(waitData.WaitResult.Message); + throw new Exception(waitData.CompletedData.Message); } } case WaitDataStatus.Overtime: throw new TimeoutException(TouchSocketDmtpStatus.Overtime.GetDescription()); @@ -104,12 +98,12 @@ internal sealed class DmtpRedisActor : DisposableObject, IDmtpRedisActor } finally { - this.DmtpActor.WaitHandlePool.Destroy(package.Sign); + waitData.Dispose(); } } /// - public async Task ContainsCacheAsync(string key) + public async Task ContainsCacheAsync(string key, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(key)) { @@ -131,13 +125,13 @@ internal sealed class DmtpRedisActor : DisposableObject, IDmtpRedisActor package.Package(ref block); await this.DmtpActor.SendAsync(this.m_redis_Request, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - switch (await waitData.WaitAsync(this.Timeout).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) + switch (await waitData.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) { - case WaitDataStatus.SetRunning: + case WaitDataStatus.Success: { - return waitData.WaitResult.Status == 1 + return waitData.CompletedData.Status == 1 ? true - : waitData.WaitResult.Status == byte.MaxValue ? false : throw new Exception(waitData.WaitResult.Message); + : waitData.CompletedData.Status == byte.MaxValue ? false : throw new Exception(waitData.CompletedData.Message); } case WaitDataStatus.Overtime: throw new TimeoutException(TouchSocketDmtpStatus.Overtime.GetDescription()); case WaitDataStatus.Canceled: @@ -149,18 +143,18 @@ internal sealed class DmtpRedisActor : DisposableObject, IDmtpRedisActor } finally { - this.DmtpActor.WaitHandlePool.Destroy(package.Sign); + waitData.Dispose(); } } /// - public async Task GetAsync(string key) + public async Task GetAsync(string key, CancellationToken cancellationToken = default) { - var cache = await this.GetCacheAsync(key).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var cache = await this.GetCacheAsync(key, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); if (cache != null) { - if (cache.Value is null) + if (cache.Value.IsEmpty) { return default; } @@ -175,7 +169,7 @@ internal sealed class DmtpRedisActor : DisposableObject, IDmtpRedisActor } /// - public async Task> GetCacheAsync(string key) + public async Task>> GetCacheAsync(string key, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(key)) { @@ -191,23 +185,23 @@ internal sealed class DmtpRedisActor : DisposableObject, IDmtpRedisActor var waitData = this.DmtpActor.WaitHandlePool.GetWaitDataAsync(package); try { - using (var byteBlock = new ByteBlock((package.value == null ? 0 : package.value.Length) + 1024)) + using (var byteBlock = new ByteBlock((package.value.Length) + 1024)) { var block = byteBlock; package.Package(ref block); await this.DmtpActor.SendAsync(this.m_redis_Request, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - switch (await waitData.WaitAsync(this.Timeout).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) + switch (await waitData.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) { - case WaitDataStatus.SetRunning: + case WaitDataStatus.Success: { - var responsePackage = (RedisResponseWaitPackage)waitData.WaitResult; + var responsePackage = (RedisResponseWaitPackage)waitData.CompletedData; return responsePackage.Status == 1 - ? new CacheEntry(key) + ? new CacheEntry>(key) { Value = responsePackage.value } - : responsePackage.Status == byte.MaxValue ? new CacheEntry(key) : (ICacheEntry)default; + : responsePackage.Status == byte.MaxValue ? new CacheEntry>(key) : (ICacheEntry>)default; } case WaitDataStatus.Overtime: throw new TimeoutException(TouchSocketDmtpStatus.Overtime.GetDescription()); case WaitDataStatus.Canceled: @@ -219,7 +213,7 @@ internal sealed class DmtpRedisActor : DisposableObject, IDmtpRedisActor } finally { - this.DmtpActor.WaitHandlePool.Destroy(package.Sign); + waitData.Dispose(); } } @@ -236,15 +230,15 @@ internal sealed class DmtpRedisActor : DisposableObject, IDmtpRedisActor try { var package = new RedisRequestWaitPackage(); - var block = message.BodyByteBlock; - package.Unpackage(ref block); + var reader = new BytesReader(message.Memory); + package.Unpackage(ref reader); waitResult.Sign = package.Sign; switch (package.packageType) { case RedisPackageType.Set: { - var success = this.ICache.SetCache(new CacheEntry(package.key) + var success = this.ICache.SetCache(new CacheEntry>(package.key) { Duration = package.timeSpan.Value, Value = package.value @@ -307,16 +301,16 @@ internal sealed class DmtpRedisActor : DisposableObject, IDmtpRedisActor else if (message.ProtocolFlags == this.m_redis_Response) { var waitResult = new RedisResponseWaitPackage(); - var block = message.BodyByteBlock; - waitResult.Unpackage(ref block); - this.DmtpActor.WaitHandlePool.SetRun(waitResult); + var reader = new BytesReader(message.Memory); + waitResult.Unpackage(ref reader); + this.DmtpActor.WaitHandlePool.Set(waitResult); return true; } return false; } /// - public async Task RemoveCacheAsync(string key) + public async Task RemoveCacheAsync(string key, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(key)) { @@ -332,19 +326,19 @@ internal sealed class DmtpRedisActor : DisposableObject, IDmtpRedisActor var waitData = this.DmtpActor.WaitHandlePool.GetWaitDataAsync(package); try { - using (var byteBlock = new ByteBlock((package.value == null ? 0 : package.value.Length) + 1024)) + using (var byteBlock = new ByteBlock((package.value.Length) + 1024)) { var block = byteBlock; package.Package(ref block); - await this.DmtpActor.SendAsync(this.m_redis_Request, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.DmtpActor.SendAsync(this.m_redis_Request, byteBlock.Memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - switch (await waitData.WaitAsync(this.Timeout).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) + switch (await waitData.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) { - case WaitDataStatus.SetRunning: + case WaitDataStatus.Success: { - return waitData.WaitResult.Status == 1 + return waitData.CompletedData.Status == 1 ? true - : waitData.WaitResult.Status == byte.MaxValue ? false : throw new Exception(waitData.WaitResult.Message); + : waitData.CompletedData.Status == byte.MaxValue ? false : throw new Exception(waitData.CompletedData.Message); } case WaitDataStatus.Overtime: throw new TimeoutException(Resources.TouchSocketDmtpStatus.Overtime.GetDescription()); case WaitDataStatus.Canceled: return false; @@ -356,23 +350,23 @@ internal sealed class DmtpRedisActor : DisposableObject, IDmtpRedisActor } finally { - this.DmtpActor.WaitHandlePool.Destroy(package.Sign); + waitData.Dispose(); } } /// - public async Task SetAsync(string key, TValue value, int duration = 60000) + public async Task SetAsync(string key, TValue value, int duration = 60000, CancellationToken cancellationToken = default) { - var cache = new CacheEntry(key) + var cache = new CacheEntry>(key) { Duration = TimeSpan.FromSeconds(duration), - Value = value is byte[] bytes ? bytes : this.Converter.Serialize(null, value) + Value = value is ReadOnlyMemory bytes ? bytes : this.Converter.Serialize(null, value) }; - return await this.SetCacheAsync(cache).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return await this.SetCacheAsync(cache, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// - public async Task SetCacheAsync(ICacheEntry cache) + public async Task SetCacheAsync(ICacheEntry> cache, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(cache.Key)) { @@ -395,17 +389,17 @@ internal sealed class DmtpRedisActor : DisposableObject, IDmtpRedisActor var waitData = this.DmtpActor.WaitHandlePool.GetWaitDataAsync(package); try { - using (var byteBlock = new ByteBlock((package.value == null ? 0 : package.value.Length) + 1024)) + using (var byteBlock = new ByteBlock((package.value.Length) + 1024)) { var block = byteBlock; package.Package(ref block); await this.DmtpActor.SendAsync(this.m_redis_Request, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - switch (await waitData.WaitAsync(this.Timeout).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) + switch (await waitData.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) { - case WaitDataStatus.SetRunning: + case WaitDataStatus.Success: { - return waitData.WaitResult.Status == 1 || (waitData.WaitResult.Status == byte.MaxValue ? false : throw new Exception(waitData.WaitResult.Message)); + return waitData.CompletedData.Status == 1 || (waitData.CompletedData.Status == byte.MaxValue ? false : throw new Exception(waitData.CompletedData.Message)); } case WaitDataStatus.Overtime: throw new TimeoutException(Resources.TouchSocketDmtpStatus.Overtime.GetDescription()); case WaitDataStatus.Canceled: return false; @@ -417,7 +411,7 @@ internal sealed class DmtpRedisActor : DisposableObject, IDmtpRedisActor } finally { - this.DmtpActor.WaitHandlePool.Destroy(package.Sign); + waitData.Dispose(); } } diff --git a/src/TouchSocket.Dmtp/Features/Redis/Actor/IDmtpRedisActor.cs b/src/TouchSocket.Dmtp/Features/Redis/Actor/IDmtpRedisActor.cs index 3f33b9f80..e5b9e1fe5 100644 --- a/src/TouchSocket.Dmtp/Features/Redis/Actor/IDmtpRedisActor.cs +++ b/src/TouchSocket.Dmtp/Features/Redis/Actor/IDmtpRedisActor.cs @@ -10,16 +10,12 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Dmtp.Redis; /// /// 具有远程键值存贮的操作端。 /// -public interface IDmtpRedisActor : ICacheAsync, IActor +public interface IDmtpRedisActor : ICacheAsync>, IActor { /// /// 序列化转换器。 @@ -29,12 +25,7 @@ public interface IDmtpRedisActor : ICacheAsync, IActor /// /// 实际储存缓存。 /// - ICache ICache { get; set; } - - /// - /// 超时设定。默认30000ms - /// - int Timeout { get; set; } + ICache> ICache { get; set; } /// /// 添加一个缓存项到缓存中,如果键已经存在,则不进行任何操作。 @@ -44,22 +35,24 @@ public interface IDmtpRedisActor : ICacheAsync, IActor /// 缓存项的键。 /// 缓存项的值。 /// 缓存项的过期时间,单位为毫秒。默认为60000毫秒(1分钟)。 - /// 一个Task对象,表示异步操作的结果。结果为true表示添加成功,false表示失败(例如,键已经存在)。 + /// + /// 一个Task对象,表示异步操作的结果。结果为表示添加成功,false表示失败(例如,键已经存在)。 /// 如果键或值为,则抛出该异常。 /// 如果异步操作超时,则抛出该异常。 /// 如果发生其他异常,则抛出该异常。 - Task AddAsync(string key, TValue value, int duration = 60000); + Task AddAsync(string key, TValue value, int duration, CancellationToken cancellationToken); /// /// 异步获取缓存的键值对。 /// /// 缓存值的类型 /// 缓存的键 + /// /// 缓存的值 /// 如果 为空或为 null,则抛出此异常。 /// 如果获取操作超时,则抛出此异常。 /// 如果发生其他异常,则抛出此异常。 - Task GetAsync(string key); + Task GetAsync(string key, CancellationToken cancellationToken); /// /// 设置缓存值 @@ -69,9 +62,10 @@ public interface IDmtpRedisActor : ICacheAsync, IActor /// 缓存的键 /// 缓存的值 /// 缓存的持续时间 + /// /// 操作是否成功 /// 当参数为空时抛出 /// 当操作超时时抛出 /// 当发生其他异常时抛出 - Task SetAsync(string key, TValue value, int duration = 60000); + Task SetAsync(string key, TValue value, int duration, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Features/Redis/DmtpRedisActorExtensions.cs b/src/TouchSocket.Dmtp/Features/Redis/DmtpRedisActorExtensions.cs index 0e4a372ac..c87cb4ba0 100644 --- a/src/TouchSocket.Dmtp/Features/Redis/DmtpRedisActorExtensions.cs +++ b/src/TouchSocket.Dmtp/Features/Redis/DmtpRedisActorExtensions.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using TouchSocket.Core; using TouchSocket.Resources; namespace TouchSocket.Dmtp.Redis; @@ -21,12 +19,6 @@ namespace TouchSocket.Dmtp.Redis; /// public static class DmtpRedisActorExtensions { - ///// - ///// 获取或设置RedisActor的注入键。 - ///// - //public static readonly DependencyProperty DmtpRedisActorProperty = - // new("DmtpRedisActor", null); - /// /// 获取 /// @@ -36,7 +28,7 @@ public static class DmtpRedisActorExtensions public static IDmtpRedisActor GetDmtpRedisActor(this IDmtpActorObject client) { var actor = client.DmtpActor.GetDmtpRedisActor(); - ThrowHelper.ThrowArgumentNullExceptionIf(actor, nameof(actor), TouchSocketDmtpResource.RedisActorNull); + ThrowHelper.ThrowIfNull(actor, nameof(actor), TouchSocketDmtpResource.RedisActorNull); return actor; } @@ -49,15 +41,4 @@ public static class DmtpRedisActorExtensions { return dmtpActor.GetActor(); } - - /// - /// 使用Redis插件。仅:Dmtp端会生效。 - /// - /// 插件管理器,用于管理插件。 - /// 返回Redis功能插件。 - public static RedisFeature UseDmtpRedis(this IPluginManager pluginManager) - { - // 添加RedisFeature到插件管理器,使Dmtp端能够使用Redis插件。 - return pluginManager.Add(); - } } \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Features/Redis/Options/DmtpRedisOption.cs b/src/TouchSocket.Dmtp/Features/Redis/Options/DmtpRedisOption.cs new file mode 100644 index 000000000..753d9001f --- /dev/null +++ b/src/TouchSocket.Dmtp/Features/Redis/Options/DmtpRedisOption.cs @@ -0,0 +1,43 @@ +//------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +//------------------------------------------------------------------------------ + +namespace TouchSocket.Dmtp.Redis; + +/// +/// Redis配置选项 +/// +public class DmtpRedisOption : DmtpFeatureOption +{ + public DmtpRedisOption() + { + this.StartProtocol = 25; + } + /// + /// 元素序列化和反序列化转换器 + /// + public BytesSerializerConverter Converter { get; set; } = new BytesSerializerConverter(); + + /// + /// 实际储存缓存 + /// + public ICache> Cache { get; set; } = new MemoryCache>(); + + + /// + /// 配置元素的序列化和反序列化转换器 + /// + /// 配置操作 + public void ConfigureConverter(Action action) + { + action.Invoke(this.Converter); + } +} \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Features/Redis/RedisFeature.cs b/src/TouchSocket.Dmtp/Features/Redis/RedisFeature.cs index 7f841ab09..4b33933c5 100644 --- a/src/TouchSocket.Dmtp/Features/Redis/RedisFeature.cs +++ b/src/TouchSocket.Dmtp/Features/Redis/RedisFeature.cs @@ -10,55 +10,52 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Dmtp.Redis; /// /// RedisFeature /// -public class RedisFeature : PluginBase, IDmtpHandshakingPlugin, IDmtpReceivedPlugin, IDmtpFeature +public class RedisFeature : PluginBase, IDmtpConnectingPlugin, IDmtpReceivedPlugin, IDmtpFeature { + private readonly DmtpRedisOption m_option; + /// /// RedisFeature /// - public RedisFeature() + /// 配置选项 + public RedisFeature(DmtpRedisOption option) { - this.SetProtocolFlags(25); + ThrowHelper.ThrowIfNull(option, nameof(option)); + this.m_option = option; - this.Converter = new BytesSerializerConverter(); - this.Converter.Add(new JsonBytesToClassSerializerFormatter()); + // 设置默认的JSON转换器 + if (this.m_option.Converter != null) + { + this.m_option.Converter.Add(new JsonBytesToClassSerializerFormatter()); + } } - /// - /// 定义元素的序列化和反序列化。 - /// 注意:Byte[]类型不用考虑。内部单独会做处理。 - /// - public BytesSerializerConverter Converter { get; private set; } - - /// - /// 实际储存缓存。 - /// - public ICache ICache { get; set; } = new MemoryCache(); - /// public ushort ReserveProtocolSize => 5; /// - public ushort StartProtocol { get; set; } + public ushort StartProtocol => this.m_option.StartProtocol; + + /// + /// 获取配置选项 + /// + public DmtpRedisOption Option => this.m_option; /// - public async Task OnDmtpHandshaking(IDmtpActorObject client, DmtpVerifyEventArgs e) + public async Task OnDmtpConnecting(IDmtpActorObject client, DmtpVerifyEventArgs e) { var dmtpRedisActor = new DmtpRedisActor(client.DmtpActor) { - ICache = this.ICache, - Converter = this.Converter + ICache = this.m_option.Cache, + Converter = this.m_option.Converter }; - dmtpRedisActor.SetProtocolFlags(this.StartProtocol); + dmtpRedisActor.SetProtocolFlags(this.m_option.StartProtocol); client.DmtpActor.TryAddActor(dmtpRedisActor); await e.InvokeNext().ConfigureAwait(EasyTask.ContinueOnCapturedContext); @@ -77,39 +74,4 @@ public class RedisFeature : PluginBase, IDmtpHandshakingPlugin, IDmtpReceivedPlu } await e.InvokeNext().ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - - /// - /// 设置实际储存缓存。默认使用 - /// - /// - public RedisFeature SetCache(ICache cache) - { - this.ICache = cache; - return this; - } - - /// - /// 定义元素的序列化和反序列化。 - /// 注意:Byte[]类型不用考虑。内部单独会做处理。 - /// - /// - public RedisFeature ConfigureConverter(Action action) - { - action.Invoke(this.Converter); - return this; - } - - /// - /// 设置的起始协议。 - /// - /// 默认起始为:25,保留5个协议长度。 - /// - /// - /// - /// - public RedisFeature SetProtocolFlags(ushort start) - { - this.StartProtocol = start; - return this; - } } \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Features/Redis/RedisPluginManagerExtension.cs b/src/TouchSocket.Dmtp/Features/Redis/RedisPluginManagerExtension.cs new file mode 100644 index 000000000..1cc6c9395 --- /dev/null +++ b/src/TouchSocket.Dmtp/Features/Redis/RedisPluginManagerExtension.cs @@ -0,0 +1,47 @@ +//------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +//------------------------------------------------------------------------------ + +namespace TouchSocket.Dmtp.Redis; + +/// +/// 的扩展方法,用于使用Redis插件。 +/// +public static class RedisPluginManagerExtension +{ + /// + /// 使用Redis插件。 + /// + /// 插件管理器实例。 + /// 配置选项的委托。 + /// 返回实例。 + public static RedisFeature UseDmtpRedis(this IPluginManager pluginManager, Action options) + { + var option = new DmtpRedisOption(); + + options.Invoke(option); + + var redisFeature = new RedisFeature(option); + pluginManager.Add(redisFeature); + + return redisFeature; + } + + /// + /// 使用Redis插件。 + /// + /// 插件管理器实例。 + /// 返回实例。 + public static RedisFeature UseDmtpRedis(this IPluginManager pluginManager) + { + return UseDmtpRedis(pluginManager, options => { }); + } +} \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Features/Redis/RedisRequestWaitPackage.cs b/src/TouchSocket.Dmtp/Features/Redis/RedisRequestWaitPackage.cs index 4fe09a5a1..5f2ba2de9 100644 --- a/src/TouchSocket.Dmtp/Features/Redis/RedisRequestWaitPackage.cs +++ b/src/TouchSocket.Dmtp/Features/Redis/RedisRequestWaitPackage.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Dmtp.Redis; internal class RedisRequestWaitPackage : RedisResponseWaitPackage @@ -20,30 +18,30 @@ internal class RedisRequestWaitPackage : RedisResponseWaitPackage public TimeSpan? timeSpan; public RedisPackageType packageType; - public override void Package(ref TByteBlock byteBlock) + public override void Package(ref TWriter writer) { - base.Package(ref byteBlock); - byteBlock.WriteString(this.key); - byteBlock.WriteByte((byte)this.packageType); + base.Package(ref writer); + WriterExtension.WriteString(ref writer, this.key); + WriterExtension.WriteValue(ref writer, (byte)this.packageType); if (this.timeSpan.HasValue) { - byteBlock.WriteByte(1); - byteBlock.WriteTimeSpan(this.timeSpan.Value); + WriterExtension.WriteValue(ref writer, 1); + WriterExtension.WriteValue(ref writer, this.timeSpan.Value); } else { - byteBlock.WriteByte(0); + WriterExtension.WriteValue(ref writer, 0); } } - public override void Unpackage(ref TByteBlock byteBlock) + public override void Unpackage(ref TReader reader) { - base.Unpackage(ref byteBlock); - this.key = byteBlock.ReadString(); - this.packageType = (RedisPackageType)byteBlock.ReadByte(); - if (byteBlock.ReadByte() == 1) + base.Unpackage(ref reader); + this.key = ReaderExtension.ReadString(ref reader); + this.packageType = (RedisPackageType)ReaderExtension.ReadValue(ref reader); + if (ReaderExtension.ReadValue(ref reader) == 1) { - this.timeSpan = byteBlock.ReadTimeSpan(); + this.timeSpan = ReaderExtension.ReadValue(ref reader); } } } \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Features/Redis/RedisResponseWaitPackage.cs b/src/TouchSocket.Dmtp/Features/Redis/RedisResponseWaitPackage.cs index 642b5ad13..a95d56659 100644 --- a/src/TouchSocket.Dmtp/Features/Redis/RedisResponseWaitPackage.cs +++ b/src/TouchSocket.Dmtp/Features/Redis/RedisResponseWaitPackage.cs @@ -10,23 +10,21 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Dmtp.Redis; internal class RedisResponseWaitPackage : WaitPackage { - public byte[] value; + public ReadOnlyMemory value; - public override void Package(ref TByteBlock byteBlock) + public override void Package(ref TWriter writer) { - base.Package(ref byteBlock); - byteBlock.WriteBytesPackage(this.value); + base.Package(ref writer); + WriterExtension.WriteByteSpan(ref writer, this.value.Span); } - public override void Unpackage(ref TByteBlock byteBlock) + public override void Unpackage(ref TReader reader) { - base.Unpackage(ref byteBlock); - this.value = byteBlock.ReadBytesPackage(); + base.Unpackage(ref reader); + this.value = ReaderExtension.ReadByteSpan(ref reader).ToArray(); } } \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Features/Rpc/Actor/DmtpRpcActor.cs b/src/TouchSocket.Dmtp/Features/Rpc/Actor/DmtpRpcActor.cs index 28b467a15..3043dd58e 100644 --- a/src/TouchSocket.Dmtp/Features/Rpc/Actor/DmtpRpcActor.cs +++ b/src/TouchSocket.Dmtp/Features/Rpc/Actor/DmtpRpcActor.cs @@ -10,10 +10,8 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Collections.Concurrent; -using System.Threading.Tasks; -using TouchSocket.Core; +using System.Diagnostics.CodeAnalysis; using TouchSocket.Resources; using TouchSocket.Rpc; @@ -74,7 +72,7 @@ public class DmtpRpcActor : DisposableObject, IDmtpRpcActor /// 返回一个异步任务,指示处理是否成功 public async Task InputReceivedData(DmtpMessage message) { - var byteBlock = message.BodyByteBlock; + var reader = new BytesReader(message.Memory); if (message.ProtocolFlags == this.m_invoke_Request) { @@ -82,14 +80,14 @@ public class DmtpRpcActor : DisposableObject, IDmtpRpcActor { var rpcPackage = new DmtpRpcRequestPackage(); - rpcPackage.UnpackageRouter(ref byteBlock); + rpcPackage.UnpackageRouter(ref reader); if (rpcPackage.Route && this.DmtpActor.AllowRoute) { if (await this.DmtpActor.TryRouteAsync(new PackageRouterEventArgs(RouteType.Rpc, rpcPackage)).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) { if (await this.DmtpActor.TryFindDmtpActor(rpcPackage.TargetId).ConfigureAwait(EasyTask.ContinueOnCapturedContext) is DmtpActor actor) { - await actor.SendAsync(this.m_invoke_Request, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await actor.SendAsync(this.m_invoke_Request, message.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return true; } else @@ -102,13 +100,8 @@ public class DmtpRpcActor : DisposableObject, IDmtpRpcActor rpcPackage.Status = TouchSocketDmtpStatus.RoutingNotAllowed.ToValue(); } - byteBlock.Reset(); rpcPackage.SwitchId(); - - var block = byteBlock; - rpcPackage.Package(ref block); - - await this.DmtpActor.SendAsync(this.m_invoke_Response, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.DmtpActor.SendAsync(this.m_invoke_Response, rpcPackage).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } else { @@ -125,7 +118,7 @@ public class DmtpRpcActor : DisposableObject, IDmtpRpcActor } rpcPackage.LoadInfo(callContext, this.m_serializationSelector); - rpcPackage.UnpackageBody(ref byteBlock); + rpcPackage.UnpackageBody(ref reader); //await this.InvokeThisAsync(callContext).ConfigureAwait(EasyTask.ContinueOnCapturedContext); //await EasyTask.Run(this.InvokeThisAsync, callContext).ConfigureAwait(EasyTask.ContinueOnCapturedContext); await this.Dispatcher.Dispatcher(this.DmtpActor, callContext, this.InvokeThisAsync).ConfigureAwait(EasyTask.ContinueOnCapturedContext); @@ -143,21 +136,21 @@ public class DmtpRpcActor : DisposableObject, IDmtpRpcActor { var rpcPackage = new DmtpRpcResponsePackage(); - rpcPackage.UnpackageRouter(ref byteBlock); + rpcPackage.UnpackageRouter(ref reader); if (this.DmtpActor.AllowRoute && rpcPackage.Route) { if (await this.DmtpActor.TryFindDmtpActor(rpcPackage.TargetId).ConfigureAwait(EasyTask.ContinueOnCapturedContext) is DmtpActor actor) { - await actor.SendAsync(this.m_invoke_Response, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await actor.SendAsync(this.m_invoke_Response, message.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } else { if (this.DmtpActor.WaitHandlePool.TryGetDataAsync(rpcPackage.Sign, out var waitDataAsync)) { - var sourcePackage = (DmtpRpcRequestPackage)waitDataAsync.WaitResult; + var sourcePackage = (DmtpRpcRequestPackage)waitDataAsync.PendingData; rpcPackage.LoadInfo(sourcePackage.ReturnType, this.m_serializationSelector, sourcePackage.SerializationType); - rpcPackage.UnpackageBody(ref byteBlock); + rpcPackage.UnpackageBody(ref reader); waitDataAsync.Set(rpcPackage); } @@ -175,17 +168,17 @@ public class DmtpRpcActor : DisposableObject, IDmtpRpcActor { var canceledPackage = new CanceledPackage(); - canceledPackage.UnpackageRouter(ref byteBlock); + canceledPackage.UnpackageRouter(ref reader); if (this.DmtpActor.AllowRoute && canceledPackage.Route) { if (await this.DmtpActor.TryFindDmtpActor(canceledPackage.TargetId).ConfigureAwait(EasyTask.ContinueOnCapturedContext) is DmtpActor actor) { - await actor.SendAsync(this.m_cancelInvoke, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await actor.SendAsync(this.m_cancelInvoke, message.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } else { - canceledPackage.UnpackageBody(ref byteBlock); + canceledPackage.UnpackageBody(ref reader); if (this.m_callContextDic.TryGetValue(canceledPackage.Sign, out var context)) { context.Cancel(); @@ -222,7 +215,7 @@ public class DmtpRpcActor : DisposableObject, IDmtpRpcActor { switch (status) { - case WaitDataStatus.SetRunning: + case WaitDataStatus.Success: return; case WaitDataStatus.Canceled: throw new OperationCanceledException(); @@ -236,7 +229,7 @@ public class DmtpRpcActor : DisposableObject, IDmtpRpcActor } } - private async Task CanceledInvokeAsync(CanceledPackage canceled) + private async Task CanceledInvokeAsync(CanceledPackage canceled, CancellationToken cancellationToken) { using (var byteBlock = new ByteBlock(1024 * 64)) { @@ -245,13 +238,12 @@ public class DmtpRpcActor : DisposableObject, IDmtpRpcActor try { - await this.DmtpActor.SendAsync(this.m_cancelInvoke, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.DmtpActor.SendAsync(this.m_cancelInvoke, byteBlock.Memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } catch { //不关心异常 } - } } @@ -393,10 +385,17 @@ public class DmtpRpcActor : DisposableObject, IDmtpRpcActor #region Rpc /// - public async Task InvokeAsync(string invokeKey, Type returnType, IInvokeOption invokeOption, params object[] parameters) + public async Task InvokeAsync(string invokeKey, Type returnType, InvokeOption invokeOption, params object[] parameters) { invokeOption ??= InvokeOption.WaitInvoke; + var cancellationToken = invokeOption.Token; + CancellationTokenSource cts = null; + if (!cancellationToken.CanBeCanceled) + { + cts = new CancellationTokenSource(invokeOption.Timeout); + cancellationToken = cts.Token; + } var rpcPackage = new DmtpRpcRequestPackage(invokeKey, invokeOption, parameters, returnType, this.m_serializationSelector) { SourceId = this.DmtpActor.Id @@ -405,11 +404,6 @@ public class DmtpRpcActor : DisposableObject, IDmtpRpcActor try { - if (invokeOption.Token.CanBeCanceled) - { - waitData.SetCancellationToken(invokeOption.Token); - } - var byteBlock = new ByteBlock(1024 * 64); try { @@ -430,18 +424,18 @@ public class DmtpRpcActor : DisposableObject, IDmtpRpcActor } case FeedbackType.WaitSend: { - ThrowExceptionIfNotSetRunning(await waitData.WaitAsync(invokeOption.Timeout).ConfigureAwait(EasyTask.ContinueOnCapturedContext)); + ThrowExceptionIfNotSetRunning(await waitData.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext)); return returnType?.GetDefault(); } case FeedbackType.WaitInvoke: { - var waitDataStatus = await waitData.WaitAsync(invokeOption.Timeout).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var waitDataStatus = await waitData.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); if (waitDataStatus == WaitDataStatus.Canceled) { - await this.CanceledInvokeAsync(new CanceledPackage() { SourceId = this.DmtpActor.Id, Sign = rpcPackage.Sign }).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.CanceledInvokeAsync(new CanceledPackage() { SourceId = this.DmtpActor.Id, Sign = rpcPackage.Sign }, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } ThrowExceptionIfNotSetRunning(waitDataStatus); - var resultRpcPackage = (DmtpRpcResponsePackage)waitData.WaitResult; + var resultRpcPackage = (DmtpRpcResponsePackage)waitData.CompletedData; resultRpcPackage.ThrowStatus(); return resultRpcPackage.ReturnParameter; } @@ -451,12 +445,13 @@ public class DmtpRpcActor : DisposableObject, IDmtpRpcActor } finally { - this.DmtpActor.WaitHandlePool.Destroy(rpcPackage.Sign); + waitData.Dispose(); + cts?.Dispose(); } } /// - public async Task InvokeAsync(string targetId, string invokeKey, Type returnType, IInvokeOption invokeOption, params object[] parameters) + public async Task InvokeAsync(string targetId, string invokeKey, [DynamicallyAccessedMembers(AOT.RpcInvoke)] Type returnType, InvokeOption invokeOption, params object[] parameters) { if (string.IsNullOrEmpty(targetId)) { @@ -470,6 +465,14 @@ public class DmtpRpcActor : DisposableObject, IDmtpRpcActor invokeOption ??= InvokeOption.WaitInvoke; + var cancellationToken = invokeOption.Token; + CancellationTokenSource cts = null; + if (!cancellationToken.CanBeCanceled) + { + cts = new CancellationTokenSource(invokeOption.Timeout); + cancellationToken = cts.Token; + } + var rpcPackage = new DmtpRpcRequestPackage(invokeKey, invokeOption, parameters, returnType, this.m_serializationSelector) { SourceId = this.DmtpActor.Id, @@ -485,11 +488,6 @@ public class DmtpRpcActor : DisposableObject, IDmtpRpcActor try { - if (invokeOption.Token.CanBeCanceled) - { - waitData.SetCancellationToken(invokeOption.Token); - } - var byteBlock = new ByteBlock(1024 * 64); try { @@ -510,20 +508,20 @@ public class DmtpRpcActor : DisposableObject, IDmtpRpcActor } case FeedbackType.WaitSend: { - ThrowExceptionIfNotSetRunning(await waitData.WaitAsync(invokeOption.Timeout).ConfigureAwait(EasyTask.ContinueOnCapturedContext)); + ThrowExceptionIfNotSetRunning(await waitData.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext)); return returnType?.GetDefault(); } case FeedbackType.WaitInvoke: { - var waitDataStatus = await waitData.WaitAsync(invokeOption.Timeout).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var waitDataStatus = await waitData.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); if (waitDataStatus == WaitDataStatus.Canceled) { - await this.CanceledInvokeAsync(new CanceledPackage() { SourceId = this.DmtpActor.Id, Sign = rpcPackage.Sign }).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.CanceledInvokeAsync(new CanceledPackage() { SourceId = this.DmtpActor.Id, Sign = rpcPackage.Sign }, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } ThrowExceptionIfNotSetRunning(waitDataStatus); - var resultRpcPackage = (DmtpRpcResponsePackage)waitData.WaitResult; + var resultRpcPackage = (DmtpRpcResponsePackage)waitData.CompletedData; resultRpcPackage.ThrowStatus(); return resultRpcPackage.ReturnParameter; @@ -534,7 +532,8 @@ public class DmtpRpcActor : DisposableObject, IDmtpRpcActor } finally { - this.DmtpActor.WaitHandlePool.Destroy(rpcPackage.Sign); + waitData.Dispose(); + cts?.Dispose(); } } diff --git a/src/TouchSocket.Dmtp/Features/Rpc/Actor/TargetDmtpRpcActor.cs b/src/TouchSocket.Dmtp/Features/Rpc/Actor/TargetDmtpRpcActor.cs index b69df6ab8..1e9087c2f 100644 --- a/src/TouchSocket.Dmtp/Features/Rpc/Actor/TargetDmtpRpcActor.cs +++ b/src/TouchSocket.Dmtp/Features/Rpc/Actor/TargetDmtpRpcActor.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; using TouchSocket.Rpc; namespace TouchSocket.Dmtp.Rpc; @@ -51,12 +49,12 @@ internal class TargetDmtpRpcActor : IDmtpRpcActor return this.m_rpcActor.InputReceivedData(message); } - public Task InvokeAsync(string invokeKey, Type returnType, IInvokeOption invokeOption, params object[] parameters) + public Task InvokeAsync(string invokeKey, Type returnType, InvokeOption invokeOption, params object[] parameters) { return this.m_rpcActor.InvokeAsync(this.m_targetId, invokeKey, returnType, invokeOption, parameters); } - public Task InvokeAsync(string targetId, string invokeKey, Type returnType, IInvokeOption invokeOption, params object[] parameters) + public Task InvokeAsync(string targetId, string invokeKey, Type returnType, InvokeOption invokeOption, params object[] parameters) { return this.m_rpcActor.InvokeAsync(targetId, invokeKey, returnType, invokeOption, parameters); } diff --git a/src/TouchSocket.Dmtp/Features/Rpc/Attribute/DmtpRpcAttribute.cs b/src/TouchSocket.Dmtp/Features/Rpc/Attribute/DmtpRpcAttribute.cs index 4cfbb6cae..2195fd8c9 100644 --- a/src/TouchSocket.Dmtp/Features/Rpc/Attribute/DmtpRpcAttribute.cs +++ b/src/TouchSocket.Dmtp/Features/Rpc/Attribute/DmtpRpcAttribute.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using TouchSocket.Core; using TouchSocket.Rpc; namespace TouchSocket.Dmtp.Rpc; diff --git a/src/TouchSocket.Dmtp/Features/Rpc/DmtpInvokeOption.cs b/src/TouchSocket.Dmtp/Features/Rpc/DmtpInvokeOption.cs index ff0be4b68..72ee02a34 100644 --- a/src/TouchSocket.Dmtp/Features/Rpc/DmtpInvokeOption.cs +++ b/src/TouchSocket.Dmtp/Features/Rpc/DmtpInvokeOption.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; using TouchSocket.Rpc; namespace TouchSocket.Dmtp.Rpc; @@ -34,16 +33,16 @@ public class DmtpInvokeOption : InvokeOption /// 执行操作的超时时间,以毫秒为单位。 public DmtpInvokeOption(int millisecondsTimeout) : base(millisecondsTimeout) { - this.Timeout = millisecondsTimeout; + } /// /// DmtpRpc序列化类型 /// - public SerializationType SerializationType { get; set; } = SerializationType.FastBinary; + public SerializationType SerializationType { get; init; } = SerializationType.FastBinary; /// /// 元数据 /// - public Metadata Metadata { get; set; } + public Metadata Metadata { get; init; } } \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Features/Rpc/DmtpRpcCallContext.cs b/src/TouchSocket.Dmtp/Features/Rpc/DmtpRpcCallContext.cs index 0a4f6f01c..8fa9a927d 100644 --- a/src/TouchSocket.Dmtp/Features/Rpc/DmtpRpcCallContext.cs +++ b/src/TouchSocket.Dmtp/Features/Rpc/DmtpRpcCallContext.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; using TouchSocket.Rpc; namespace TouchSocket.Dmtp.Rpc; @@ -49,7 +48,7 @@ internal sealed class DmtpRpcCallContext : CallContext, IDmtpRpcCallContext } /// - protected override void Dispose(bool disposing) + protected override void SafetyDispose(bool disposing) { if (this.DisposedValue) { @@ -60,6 +59,6 @@ internal sealed class DmtpRpcCallContext : CallContext, IDmtpRpcCallContext { this.m_scopedResolver?.Dispose(); } - base.Dispose(disposing); + base.SafetyDispose(disposing); } } \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Features/Rpc/DmtpRpcFeature.cs b/src/TouchSocket.Dmtp/Features/Rpc/DmtpRpcFeature.cs index 64d75fd1d..7fcbf10ea 100644 --- a/src/TouchSocket.Dmtp/Features/Rpc/DmtpRpcFeature.cs +++ b/src/TouchSocket.Dmtp/Features/Rpc/DmtpRpcFeature.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Rpc; namespace TouchSocket.Dmtp.Rpc; @@ -20,16 +17,27 @@ namespace TouchSocket.Dmtp.Rpc; /// /// 能够基于Dmtp协议,提供Rpc的功能 /// -public class DmtpRpcFeature : PluginBase, IDmtpFeature, IDmtpHandshakingPlugin, IDmtpReceivedPlugin +public class DmtpRpcFeature : PluginBase, IDmtpFeature, IDmtpConnectingPlugin, IDmtpReceivedPlugin { private readonly IRpcServerProvider m_rpcServerProvider; + private readonly DmtpRpcOption m_option; /// /// 能够基于Dmtp协议,提供Rpc的功能 /// - /// - public DmtpRpcFeature(IServiceProvider resolver) + /// 服务解析器 + /// 配置选项 + public DmtpRpcFeature(IServiceProvider resolver, DmtpRpcOption option) { + ThrowHelper.ThrowIfNull(option, nameof(option)); + this.m_option = option; + + // 设置默认的创建DmtpRpcActor委托 + this.m_option.CreateDmtpRpcActor ??= PrivateCreateDmtpRpcActor; + + // 设置默认的调度器为并发调度器 + this.m_option.CreateDispatcher ??= (actor) => new ConcurrencyRpcDispatcher(); + var rpcServerProvider = resolver.Resolve(); if (rpcServerProvider != null) @@ -37,11 +45,6 @@ public class DmtpRpcFeature : PluginBase, IDmtpFeature, IDmtpHandshakingPlugin, this.RegisterServer(rpcServerProvider.GetMethods()); this.m_rpcServerProvider = rpcServerProvider; } - - this.CreateDmtpRpcActor = PrivateCreateDmtpRpcActor; - this.SetProtocolFlags(20); - - this.UseConcurrencyDispatcher(); } /// @@ -49,151 +52,24 @@ public class DmtpRpcFeature : PluginBase, IDmtpFeature, IDmtpHandshakingPlugin, /// public ActionMap ActionMap { get; } = new ActionMap(false); - /// - /// 获取或设置一个函数,该函数创建一个RPC调度器,用于处理IDmtpActor的RPC调用。 - /// - /// - /// 一个函数,接受一个IDmtpActor实例作为参数,并返回一个IRpcDispatcher接口,该接口泛型化于IDmtpActor和IDmtpRpcCallContext。 - /// - public Func> CreateDispatcher { get; set; } - - /// - /// 创建DmtpRpc实例 - /// - public Func, DmtpRpcActor> CreateDmtpRpcActor { get; set; } - /// public ushort ReserveProtocolSize => 5; - /// - /// 序列化选择器 - /// - public ISerializationSelector SerializationSelector { get; set; } = new DefaultSerializationSelector(); - /// - public ushort StartProtocol { get; set; } + public ushort StartProtocol => this.m_option.StartProtocol; /// - /// 配置默认的序列化选择器。 + /// 获取配置选项 /// - /// 用于配置默认序列化选择器的操作。 - /// 返回当前的 实例,以支持链式调用。 - public DmtpRpcFeature ConfigureDefaultSerializationSelector(Action selector) - { - var serializationSelector = new DefaultSerializationSelector(); - selector.Invoke(serializationSelector); - this.SerializationSelector = serializationSelector; - return this; - } - - /// - /// 设置创建DmtpRpc实例 - /// - /// - /// - public DmtpRpcFeature SetCreateDmtpRpcActor(Func, DmtpRpcActor> createDmtpRpcActor) - { - this.CreateDmtpRpcActor = createDmtpRpcActor; - return this; - } - - #region Dispatcher - - private readonly GlobalQueueRpcDispatcher m_globalQueueRpcDispatcher = new(); - - /// - /// 使用并发调度器处理请求 - /// - /// 返回当前实例,以支持链式调用 - public DmtpRpcFeature UseConcurrencyDispatcher() - { - // 设置创建调度器的委托,使用支持并发的 Rpc 调度器 - this.CreateDispatcher = (actor) => new ConcurrencyRpcDispatcher(); - // 支持链式调用,返回当前实例 - return this; - } - - /// - /// 使用全局队列RPC调度器配置RPC特性。 - /// - /// 返回配置了全局队列RPC调度器的DmtpRpcFeature实例。 - public DmtpRpcFeature UseGlobalQueueRpcDispatcher() - { - // 设置创建调度器的委托,使用GlobalQueueRpcDispatcher实现 - this.CreateDispatcher = (actor) => this.m_globalQueueRpcDispatcher; - return this; - } - - /// - /// 使用即时RPC调度器配置RPC特性 - /// - /// 返回配置了即时RPC调度器的DmtpRpcFeature实例 - public DmtpRpcFeature UseImmediateRpcDispatcher() - { - // 设置创建调度器的委托,使用ImmediateRpcDispatcher实现 - this.CreateDispatcher = (actor) => new ImmediateRpcDispatcher(); - return this; - } - - /// - /// 使用队列RPC调度器配置RPC特性 - /// - /// 返回配置了队列RPC调度器的DmtpRpcFeature实例 - public DmtpRpcFeature UseQueueRpcDispatcher() - { - // 设置创建调度器的委托,使用QueueRpcDispatcher实现 - this.CreateDispatcher = (actor) => new QueueRpcDispatcher(); - return this; - } - - private class GlobalQueueRpcDispatcher : QueueRpcDispatcher - { - public bool Pin { get; set; } = true; - - protected override void Dispose(bool disposing) - { - if (this.Pin) - { - return; - } - base.Dispose(disposing); - } - } - - #endregion Dispatcher - - /// - /// 设置的起始协议。 - /// - /// 默认起始为:20,保留5个协议长度。 - /// - /// - /// - /// - public DmtpRpcFeature SetProtocolFlags(ushort start) - { - this.StartProtocol = start; - return this; - } - - /// - /// 设置序列化选择器。默认使用 - /// - /// - /// - public DmtpRpcFeature SetSerializationSelector(ISerializationSelector selector) - { - this.SerializationSelector = selector; - return this; - } + public DmtpRpcOption Option => this.m_option; /// protected override void Dispose(bool disposing) { if (disposing) { - this.m_globalQueueRpcDispatcher.Pin = false; - this.m_globalQueueRpcDispatcher.Dispose(); + this.m_option.m_globalQueueRpcDispatcher.Pin = false; + this.m_option.m_globalQueueRpcDispatcher.Dispose(); } base.Dispose(disposing); } @@ -222,14 +98,14 @@ public class DmtpRpcFeature : PluginBase, IDmtpFeature, IDmtpHandshakingPlugin, #region Config /// - public async Task OnDmtpHandshaking(IDmtpActorObject client, DmtpVerifyEventArgs e) + public async Task OnDmtpConnecting(IDmtpActorObject client, DmtpVerifyEventArgs e) { - var dmtpRpcActor = this.CreateDmtpRpcActor(client.DmtpActor, this.m_rpcServerProvider, this.CreateDispatcher.Invoke(client.DmtpActor)); + var dmtpRpcActor = this.m_option.CreateDmtpRpcActor(client.DmtpActor, this.m_rpcServerProvider, this.m_option.CreateDispatcher.Invoke(client.DmtpActor)); - dmtpRpcActor.SerializationSelector = this.SerializationSelector; + dmtpRpcActor.SerializationSelector = this.m_option.SerializationSelector; dmtpRpcActor.GetInvokeMethod = this.GetInvokeMethod; - dmtpRpcActor.SetProtocolFlags(this.StartProtocol); + dmtpRpcActor.SetProtocolFlags(this.m_option.StartProtocol); client.DmtpActor.TryAddActor(dmtpRpcActor); await e.InvokeNext().ConfigureAwait(EasyTask.ContinueOnCapturedContext); diff --git a/src/TouchSocket.Dmtp/Features/Rpc/DmtpRpcPluginManagerExtension.cs b/src/TouchSocket.Dmtp/Features/Rpc/DmtpRpcPluginManagerExtension.cs new file mode 100644 index 000000000..1bfa37c89 --- /dev/null +++ b/src/TouchSocket.Dmtp/Features/Rpc/DmtpRpcPluginManagerExtension.cs @@ -0,0 +1,41 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +namespace TouchSocket.Dmtp.Rpc; + +/// +/// 的扩展方法,用于使用DmtpRpc插件。 +/// +public static class DmtpRpcPluginManagerExtension +{ + /// + /// 使用DmtpRpc插件。 + /// + /// 插件管理器实例。 + /// 配置选项的委托。 + /// 返回实例。 + public static DmtpRpcFeature UseDmtpRpc(this IPluginManager pluginManager, Action options) + { + var option = new DmtpRpcOption(); + + options.Invoke(option); + + var dmtpRpcFeature = new DmtpRpcFeature(pluginManager.Resolver, option); + pluginManager.Add(dmtpRpcFeature); + + return dmtpRpcFeature; + } + public static DmtpRpcFeature UseDmtpRpc(this IPluginManager pluginManager) + { + return UseDmtpRpc(pluginManager, options => { }); + } +} diff --git a/src/TouchSocket.Dmtp/Features/Rpc/Exceptions/RpcNoRegisterException.cs b/src/TouchSocket.Dmtp/Features/Rpc/Exceptions/RpcNoRegisterException.cs index cd0666fb9..a8e5b1a8a 100644 --- a/src/TouchSocket.Dmtp/Features/Rpc/Exceptions/RpcNoRegisterException.cs +++ b/src/TouchSocket.Dmtp/Features/Rpc/Exceptions/RpcNoRegisterException.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Dmtp.Rpc; /// diff --git a/src/TouchSocket.Dmtp/Features/Rpc/Exceptions/RpcSerializationException.cs b/src/TouchSocket.Dmtp/Features/Rpc/Exceptions/RpcSerializationException.cs index c12119830..f43c8ea69 100644 --- a/src/TouchSocket.Dmtp/Features/Rpc/Exceptions/RpcSerializationException.cs +++ b/src/TouchSocket.Dmtp/Features/Rpc/Exceptions/RpcSerializationException.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Dmtp.Rpc; /// diff --git a/src/TouchSocket.Dmtp/Features/Rpc/DmtpRpcActorExtension.cs b/src/TouchSocket.Dmtp/Features/Rpc/Extensions/DmtpRpcActorExtension.cs similarity index 70% rename from src/TouchSocket.Dmtp/Features/Rpc/DmtpRpcActorExtension.cs rename to src/TouchSocket.Dmtp/Features/Rpc/Extensions/DmtpRpcActorExtension.cs index badee09b6..6bb2207f7 100644 --- a/src/TouchSocket.Dmtp/Features/Rpc/DmtpRpcActorExtension.cs +++ b/src/TouchSocket.Dmtp/Features/Rpc/Extensions/DmtpRpcActorExtension.cs @@ -14,8 +14,6 @@ using System.Diagnostics.CodeAnalysis; #endif -using System; -using TouchSocket.Core; using TouchSocket.Resources; namespace TouchSocket.Dmtp.Rpc; @@ -66,7 +64,7 @@ public static class DmtpRpcActorExtension public static IDmtpRpcActor GetDmtpRpcActor(this IDmtpActorObject client) { var actor = client.DmtpActor.GetDmtpRpcActor(); - ThrowHelper.ThrowArgumentNullExceptionIf(actor, nameof(actor), TouchSocketDmtpResource.DmtpRpcActorArgumentNull); + ThrowHelper.ThrowIfNull(actor, nameof(actor), TouchSocketDmtpResource.DmtpRpcActorArgumentNull); return actor; } @@ -79,42 +77,7 @@ public static class DmtpRpcActorExtension public static TDmtpRpcActor GetDmtpRpcActor(this IDmtpActorObject client) where TDmtpRpcActor : IDmtpRpcActor { var actor = client.DmtpActor.GetDmtpRpcActor(); - ThrowHelper.ThrowArgumentNullExceptionIf(actor, nameof(actor), TouchSocketDmtpResource.DmtpRpcActorArgumentNull); + ThrowHelper.ThrowIfNull(actor, nameof(actor), TouchSocketDmtpResource.DmtpRpcActorArgumentNull); return (TDmtpRpcActor)actor; } - - - #region 插件扩展 - - /// - /// 使用DmtpRpc插件 - /// - /// 插件管理器实例 - /// 返回DmtpRpcFeature实例 -#if NET6_0_OR_GREATER - public static DmtpRpcFeature UseDmtpRpc(this IPluginManager pluginManager) -#else - public static DmtpRpcFeature UseDmtpRpc(this IPluginManager pluginManager) -#endif - { - // 添加DmtpRpcFeature到插件管理器中,并返回其实例 - return pluginManager.Add(); - } - - /// - /// 使用自定义的DmtpRpc插件。 - /// - /// 插件管理器,用于管理插件。 - /// 返回配置的DmtpRpcFeature实例。 -#if NET6_0_OR_GREATER - public static DmtpRpcFeature UseDmtpRpc<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TDmtpRpcFeature>(this IPluginManager pluginManager) where TDmtpRpcFeature : DmtpRpcFeature -#else - public static DmtpRpcFeature UseDmtpRpc(this IPluginManager pluginManager) where TDmtpRpcFeature : DmtpRpcFeature -#endif - - { - // 添加并返回自定义的DmtpRpc插件 - return pluginManager.Add(); - } - #endregion 插件扩展 } \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Features/Rpc/IDmtpRpcCallContext.cs b/src/TouchSocket.Dmtp/Features/Rpc/IDmtpRpcCallContext.cs index 0334d2a52..c1f85f012 100644 --- a/src/TouchSocket.Dmtp/Features/Rpc/IDmtpRpcCallContext.cs +++ b/src/TouchSocket.Dmtp/Features/Rpc/IDmtpRpcCallContext.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; using TouchSocket.Rpc; namespace TouchSocket.Dmtp.Rpc; diff --git a/src/TouchSocket.Dmtp/Features/Rpc/Options/DmtpRpcOption.cs b/src/TouchSocket.Dmtp/Features/Rpc/Options/DmtpRpcOption.cs new file mode 100644 index 000000000..357b2b8a0 --- /dev/null +++ b/src/TouchSocket.Dmtp/Features/Rpc/Options/DmtpRpcOption.cs @@ -0,0 +1,109 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using TouchSocket.Rpc; + +namespace TouchSocket.Dmtp.Rpc; + +/// +/// DmtpRpc配置选项 +/// +public class DmtpRpcOption : DmtpFeatureOption +{ + internal readonly GlobalQueueRpcDispatcher m_globalQueueRpcDispatcher = new(); + + public DmtpRpcOption() + { + this.StartProtocol = 20; + } + + /// + /// 创建RPC调度器的委托 + /// + public Func> CreateDispatcher { get; set; } + + /// + /// 创建DmtpRpc实例的委托 + /// + public Func, DmtpRpcActor> CreateDmtpRpcActor { get; set; } + + /// + /// 序列化选择器,默认使用 + /// + public ISerializationSelector SerializationSelector { get; set; } = new DefaultSerializationSelector(); + + /// + /// 配置默认的序列化选择器 + /// + /// 用于配置默认序列化选择器的操作 + public void ConfigureDefaultSerializationSelector(Action selector) + { + var serializationSelector = new DefaultSerializationSelector(); + selector.Invoke(serializationSelector); + this.SerializationSelector = serializationSelector; + } + + /// + /// 设置创建DmtpRpc实例的委托 + /// + /// 创建DmtpRpc实例的委托 + public void SetCreateDmtpRpcActor(Func, DmtpRpcActor> createDmtpRpcActor) + { + this.CreateDmtpRpcActor = createDmtpRpcActor; + } + + /// + /// 使用并发调度器处理请求 + /// + public void UseConcurrencyDispatcher() + { + this.CreateDispatcher = (actor) => new ConcurrencyRpcDispatcher(); + } + + /// + /// 使用全局队列RPC调度器配置RPC特性 + /// + public void UseGlobalQueueRpcDispatcher() + { + this.CreateDispatcher = (actor) => this.m_globalQueueRpcDispatcher; + } + + /// + /// 使用即时RPC调度器配置RPC特性 + /// + public void UseImmediateRpcDispatcher() + { + this.CreateDispatcher = (actor) => new ImmediateRpcDispatcher(); + } + + /// + /// 使用队列RPC调度器配置RPC特性 + /// + public void UseQueueRpcDispatcher() + { + this.CreateDispatcher = (actor) => new QueueRpcDispatcher(); + } + + internal class GlobalQueueRpcDispatcher : QueueRpcDispatcher + { + public bool Pin { get; set; } = true; + + protected override void Dispose(bool disposing) + { + if (this.Pin) + { + return; + } + base.Dispose(disposing); + } + } +} \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Features/Rpc/Proxy/DmtpRpcDispatchProxy.cs b/src/TouchSocket.Dmtp/Features/Rpc/Proxy/DmtpRpcDispatchProxy.cs index ff94bcee5..b9314bfc9 100644 --- a/src/TouchSocket.Dmtp/Features/Rpc/Proxy/DmtpRpcDispatchProxy.cs +++ b/src/TouchSocket.Dmtp/Features/Rpc/Proxy/DmtpRpcDispatchProxy.cs @@ -10,13 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -#if NET6_0_OR_GREATER || NET481_OR_GREATER -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; +using System.Diagnostics.CodeAnalysis; using TouchSocket.Rpc; namespace TouchSocket.Dmtp.Rpc; @@ -25,6 +19,7 @@ namespace TouchSocket.Dmtp.Rpc; /// DmtpRpcDispatchProxy /// /// +[RequiresUnreferencedCode("动态代理不支持AOT环境")] public abstract class DmtpRpcDispatchProxy : RpcDispatchProxy where TClient : IDmtpRpcActor { @@ -33,10 +28,8 @@ public abstract class DmtpRpcDispatchProxy : RpcDispatchProxy /// DmtpRpcDispatchProxy /// +[RequiresUnreferencedCode("动态代理不支持AOT环境")] public abstract class DmtpRpcDispatchProxy : DmtpRpcDispatchProxy { } - - -#endif \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Features/Rpc/Proxy/DmtpRpcRealityProxy.cs b/src/TouchSocket.Dmtp/Features/Rpc/Proxy/DmtpRpcRealityProxy.cs deleted file mode 100644 index 2a892c664..000000000 --- a/src/TouchSocket.Dmtp/Features/Rpc/Proxy/DmtpRpcRealityProxy.cs +++ /dev/null @@ -1,42 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -#if NET45_OR_GREATER -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using TouchSocket.Rpc; - -namespace TouchSocket.Dmtp.Rpc; - -/// -/// DmtpRpcRealityProxy -/// -/// -/// -public abstract class DmtpRpcRealityProxy : RpcRealityProxy where TClient : IDmtpRpcActor -{ - -} - -/// -/// DmtpRpcRealityProxy -/// -/// -public abstract class DmtpRpcRealityProxy : DmtpRpcRealityProxy -{ - -} - -#endif \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Features/Rpc/Serialization/DefaultSerializationSelector.cs b/src/TouchSocket.Dmtp/Features/Rpc/Serialization/DefaultSerializationSelector.cs index 437a18acb..24d21331d 100644 --- a/src/TouchSocket.Dmtp/Features/Rpc/Serialization/DefaultSerializationSelector.cs +++ b/src/TouchSocket.Dmtp/Features/Rpc/Serialization/DefaultSerializationSelector.cs @@ -11,21 +11,21 @@ //------------------------------------------------------------------------------ using Newtonsoft.Json; -using System; using System.Runtime.Serialization; -using TouchSocket.Core; -using TouchSocket.Rpc; -#if SystemTextJson using System.Text.Json; -#endif +using TouchSocket.Rpc; namespace TouchSocket.Dmtp.Rpc; /// /// 默认序列化选择器,实现了接口 /// -public sealed class DefaultSerializationSelector : ISerializationSelector +public class DefaultSerializationSelector : ISerializationSelector { + private JsonSerializerOptions m_jsonSerializerOptions; + + private bool m_useSystemTextJson; + /// /// 快速序列化上下文属性 /// @@ -41,10 +41,135 @@ public sealed class DefaultSerializationSelector : ISerializationSelector /// public SerializationBinder SerializationBinder { get; set; } -#if SystemTextJson + /// + /// 根据指定的序列化类型反序列化字节块中的数据。 + /// + /// 包含序列化数据的字节块。 + /// 指定的序列化类型。 + /// 预期反序列化出的对象类型。 + /// 反序列化后的对象。 + /// 抛出当未识别序列化类型时。 + public virtual object DeserializeParameter(ref TReader reader, SerializationType serializationType, Type parameterType) where TReader : IBytesReader - private bool m_useSystemTextJson; - private JsonSerializerOptions m_jsonSerializerOptions; + { + switch (serializationType) + { + case SerializationType.FastBinary: + return FastBinaryFormatter.Deserialize(ref reader, parameterType, this.FastSerializerContext); + + case SerializationType.SystemBinary: + if (ReaderExtension.ReadIsNull(ref reader)) + { + return parameterType.GetDefault(); + } + + using (var block = ReaderExtension.ReadByteBlock(ref reader)) + { + return SerializeConvert.BinaryDeserialize(block.AsStream(), this.SerializationBinder); + } + case SerializationType.Json: + { + if (ReaderExtension.ReadIsNull(ref reader)) + { + return parameterType.GetDefault(); + } + + if (this.m_useSystemTextJson) + { + return System.Text.Json.JsonSerializer.Deserialize(ReaderExtension.ReadString(ref reader), parameterType, this.m_jsonSerializerOptions); + } + + return JsonConvert.DeserializeObject(ReaderExtension.ReadString(ref reader), parameterType, this.JsonSerializerSettings); + } + case SerializationType.Xml: + if (ReaderExtension.ReadIsNull(ref reader)) + { + return parameterType.GetDefault(); + } + return SerializeConvert.XmlDeserializeFromBytes(ReaderExtension.ReadByteSpan(ref reader).ToArray(), parameterType); + + default: + throw new RpcException("未指定的反序列化方式"); + } + } + + /// + /// 序列化参数 + /// + /// 字节块引用,用于存储序列化后的数据 + /// 序列化类型,决定了使用哪种方式序列化 + /// 待序列化的参数对象 + /// 字节块类型,必须实现IByteBlock接口 + public virtual void SerializeParameter(ref TWriter writer, SerializationType serializationType, in object parameter) where TWriter : IBytesWriter + + { + switch (serializationType) + { + case SerializationType.FastBinary: + { + FastBinaryFormatter.Serialize(ref writer, parameter, this.FastSerializerContext); + break; + } + case SerializationType.SystemBinary: + { + if (parameter is null) + { + WriterExtension.WriteNull(ref writer); + } + else + { + WriterExtension.WriteNotNull(ref writer); + using (var block = new ByteBlock(1024 * 64)) + { + SerializeConvert.BinarySerialize(block.AsStream(), parameter); + WriterExtension.WriteByteBlock(ref writer, block); + } + } + break; + } + case SerializationType.Json: + { + if (this.m_useSystemTextJson) + { + if (parameter is null) + { + WriterExtension.WriteNull(ref writer); + } + else + { + WriterExtension.WriteNotNull(ref writer); + WriterExtension.WriteString(ref writer, System.Text.Json.JsonSerializer.Serialize(parameter, parameter.GetType(), this.m_jsonSerializerOptions)); + } + return; + } + if (parameter is null) + { + WriterExtension.WriteNull(ref writer); + } + else + { + WriterExtension.WriteNotNull(ref writer); + WriterExtension.WriteString(ref writer, JsonConvert.SerializeObject(parameter, this.JsonSerializerSettings)); + } + break; + } + case SerializationType.Xml: + { + if (parameter is null) + { + WriterExtension.WriteNull(ref writer); + } + else + { + WriterExtension.WriteNotNull(ref writer); + WriterExtension.WriteByteSpan(ref writer, SerializeConvert.XmlSerializeToBytes(parameter)); + } + break; + } + default: + throw new RpcException("未指定的序列化方式"); + } + } /// /// 使用System.Text.Json进行序列化 @@ -57,162 +182,4 @@ public sealed class DefaultSerializationSelector : ISerializationSelector this.m_useSystemTextJson = true; this.m_jsonSerializerOptions = serializerOptions; } -#endif - - /// - /// 根据指定的序列化类型反序列化字节块中的数据。 - /// - /// 包含序列化数据的字节块。 - /// 指定的序列化类型。 - /// 预期反序列化出的对象类型。 - /// 反序列化后的对象。 - /// 抛出当未识别序列化类型时。 - public object DeserializeParameter(ref TByteBlock byteBlock, SerializationType serializationType, Type parameterType) where TByteBlock : IByteBlock - { - // 根据序列化类型选择不同的反序列化方式 - switch (serializationType) - { - case SerializationType.FastBinary: - // 使用FastBinary格式进行反序列化 - return FastBinaryFormatter.Deserialize(ref byteBlock, parameterType, this.FastSerializerContext); - case SerializationType.SystemBinary: - // 检查字节块是否为 - if (byteBlock.ReadIsNull()) - { - // 如果为,则返回该类型的默认值 - return parameterType.GetDefault(); - } - - // 使用SystemBinary格式进行反序列化 - using (var block = byteBlock.ReadByteBlock()) - { - // 将字节块转换为流并进行反序列化 - return SerializeConvert.BinaryDeserialize(block.AsStream(), this.SerializationBinder); - } - case SerializationType.Json: - { - // 检查字节块是否为 - if (byteBlock.ReadIsNull()) - { - // 如果为,则返回该类型的默认值 - return parameterType.GetDefault(); - } - -#if SystemTextJson - if (this.m_useSystemTextJson) - { - return System.Text.Json.JsonSerializer.Deserialize(byteBlock.ReadString(), parameterType, this.m_jsonSerializerOptions); - } -#endif - - // 使用Json格式进行反序列化 - return JsonConvert.DeserializeObject(byteBlock.ReadString(), parameterType, this.JsonSerializerSettings); - - } - case SerializationType.Xml: - // 检查字节块是否为 - if (byteBlock.ReadIsNull()) - { - // 如果为,则返回该类型的默认值 - return parameterType.GetDefault(); - } - // 使用Xml格式进行反序列化 - return SerializeConvert.XmlDeserializeFromBytes(byteBlock.ReadBytesPackage(), parameterType); - default: - // 如果序列化类型未识别,则抛出异常 - throw new RpcException("未指定的反序列化方式"); - } - } - - /// - /// 序列化参数 - /// - /// 字节块引用,用于存储序列化后的数据 - /// 序列化类型,决定了使用哪种方式序列化 - /// 待序列化的参数对象 - /// 字节块类型,必须实现IByteBlock接口 - public void SerializeParameter(ref TByteBlock byteBlock, SerializationType serializationType, in object parameter) where TByteBlock : IByteBlock - { - // 根据序列化类型选择不同的序列化方法 - switch (serializationType) - { - case SerializationType.FastBinary: - { - // 使用FastBinaryFormatter进行序列化 - FastBinaryFormatter.Serialize(ref byteBlock, parameter, this.FastSerializerContext); - break; - } - case SerializationType.SystemBinary: - { - // 参数为时,写入空值标记 - if (parameter is null) - { - byteBlock.WriteNull(); - } - else - { - // 参数不为时,标记并序列化参数 - byteBlock.WriteNotNull(); - using (var block = new ByteBlock(1024 * 64)) - { - // 使用System.Runtime.Serialization.BinaryFormatter进行序列化 - SerializeConvert.BinarySerialize(block.AsStream(), parameter); - // 将序列化后的字节块写入byteBlock - byteBlock.WriteByteBlock(block); - } - } - break; - } - case SerializationType.Json: - { -#if SystemTextJson - if (this.m_useSystemTextJson) - { - // 参数为时,写入空值标记 - if (parameter is null) - { - byteBlock.WriteNull(); - } - else - { - // 参数不为时,标记并转换为JSON字符串 - byteBlock.WriteNotNull(); - byteBlock.WriteString(System.Text.Json.JsonSerializer.Serialize(parameter, parameter.GetType(), this.m_jsonSerializerOptions)); - } - return; - } -#endif - // 参数为时,写入空值标记 - if (parameter is null) - { - byteBlock.WriteNull(); - } - else - { - // 参数不为时,标记并转换为JSON字符串 - byteBlock.WriteNotNull(); - byteBlock.WriteString(JsonConvert.SerializeObject(parameter, this.JsonSerializerSettings)); - } - break; - } - case SerializationType.Xml: - { - // 参数为时,写入空值标记 - if (parameter is null) - { - byteBlock.WriteNull(); - } - else - { - // 参数不为时,标记并转换为Xml字节 - byteBlock.WriteNotNull(); - byteBlock.WriteBytesPackage(SerializeConvert.XmlSerializeToBytes(parameter)); - } - break; - } - default: - // 抛出异常,提示未指定的序列化方式 - throw new RpcException("未指定的序列化方式"); - } - } } \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Features/Rpc/Serialization/ISerializationSelector.cs b/src/TouchSocket.Dmtp/Features/Rpc/Serialization/ISerializationSelector.cs index c7544b556..9300545a4 100644 --- a/src/TouchSocket.Dmtp/Features/Rpc/Serialization/ISerializationSelector.cs +++ b/src/TouchSocket.Dmtp/Features/Rpc/Serialization/ISerializationSelector.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using TouchSocket.Core; - namespace TouchSocket.Dmtp.Rpc; /// @@ -23,19 +20,23 @@ public interface ISerializationSelector /// /// 反序列化字节块中的参数。 /// - /// 字节块的类型,必须实现IByteBlock接口。 - /// 包含序列化参数的字节块。 + /// 字节块的类型,必须实现IByteBlock接口。 + /// 包含序列化参数的字节块。 /// 指定的序列化类型。 /// 预期反序列化参数的类型。 /// 反序列化后的参数对象。 - object DeserializeParameter(ref TByteBlock byteBlock, SerializationType serializationType, Type parameterType) where TByteBlock : IByteBlock; + object DeserializeParameter(ref TReader reader, SerializationType serializationType, Type parameterType) where TReader : IBytesReader + + ; /// /// 序列化参数并将其添加到字节块中。 /// - /// 字节块的类型,必须实现IByteBlock接口。 - /// 将要包含序列化参数的字节块。 + /// 字节块的类型,必须实现IByteBlock接口。 + /// 将要包含序列化参数的字节块。 /// 要使用的序列化类型。 /// 要序列化的参数对象。 - void SerializeParameter(ref TByteBlock byteBlock, SerializationType serializationType, in object parameter) where TByteBlock : IByteBlock; + void SerializeParameter(ref TWriter wirter, SerializationType serializationType, in object parameter) where TWriter : IBytesWriter + + ; } \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Features/Rpc/_Packages/CanceledPackage.cs b/src/TouchSocket.Dmtp/Features/Rpc/_Packages/CanceledPackage.cs index 6dcce69fd..67aba5598 100644 --- a/src/TouchSocket.Dmtp/Features/Rpc/_Packages/CanceledPackage.cs +++ b/src/TouchSocket.Dmtp/Features/Rpc/_Packages/CanceledPackage.cs @@ -10,21 +10,19 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Dmtp.Rpc; internal class CanceledPackage : RouterPackage { public long Sign { get; set; } - public override void PackageBody(ref TByteBlock byteBlock) + public override void PackageBody(ref TWriter writer) { - byteBlock.WriteInt64(this.Sign); + WriterExtension.WriteValue(ref writer, this.Sign); } - public override void UnpackageBody(ref TByteBlock byteBlock) + public override void UnpackageBody(ref TReader reader) { - this.Sign = byteBlock.ReadInt64(); + this.Sign = ReaderExtension.ReadValue(ref reader); } } \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Features/Rpc/_Packages/DmtpRpcRequestPackage.cs b/src/TouchSocket.Dmtp/Features/Rpc/_Packages/DmtpRpcRequestPackage.cs index a0e59a100..53d7694b2 100644 --- a/src/TouchSocket.Dmtp/Features/Rpc/_Packages/DmtpRpcRequestPackage.cs +++ b/src/TouchSocket.Dmtp/Features/Rpc/_Packages/DmtpRpcRequestPackage.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using TouchSocket.Core; using TouchSocket.Rpc; namespace TouchSocket.Dmtp.Rpc; @@ -34,7 +32,7 @@ internal class DmtpRpcRequestPackage : WaitRouterPackage, IDmtpRpcRequestPackage } - public DmtpRpcRequestPackage(string invokeKey, IInvokeOption option, object[] parameters, Type returnType, ISerializationSelector selector) + public DmtpRpcRequestPackage(string invokeKey, InvokeOption option, object[] parameters, Type returnType, ISerializationSelector selector) { this.m_invokeKey = invokeKey; @@ -99,39 +97,47 @@ internal class DmtpRpcRequestPackage : WaitRouterPackage, IDmtpRpcRequestPackage this.m_selector = selector; } /// - public override void PackageBody(ref TByteBlock byteBlock) + public override void PackageBody(ref TWriter writer) { - base.PackageBody(ref byteBlock); + base.PackageBody(ref writer); if (this.m_parameters != null && this.m_parameters.Length > 0) { - byteBlock.WriteByte((byte)this.m_parameters.Length); + WriterExtension.WriteValue(ref writer, (byte)this.m_parameters.Length); foreach (var item in this.m_parameters) { - this.m_selector.SerializeParameter(ref byteBlock, this.SerializationType, item); + this.m_selector.SerializeParameter(ref writer, this.SerializationType, item); } } else { - byteBlock.WriteByte(0); + WriterExtension.WriteValue(ref writer, 0); } } /// - public override void PackageRouter(ref TByteBlock byteBlock) + public override void PackageRouter(ref TWriter writer) { - base.PackageRouter(ref byteBlock); - byteBlock.WriteByte((byte)this.m_serializationType); - byteBlock.WriteString(this.InvokeKey, FixedHeaderType.Byte); - byteBlock.WriteByte((byte)this.m_feedback); - byteBlock.WritePackage(this.Metadata); + base.PackageRouter(ref writer); + WriterExtension.WriteValue(ref writer, (byte)this.m_serializationType); + WriterExtension.WriteString(ref writer, this.InvokeKey, FixedHeaderType.Byte); + WriterExtension.WriteValue(ref writer, (byte)this.m_feedback); + if (this.Metadata is null) + { + WriterExtension.WriteNull(ref writer); + } + else + { + WriterExtension.WriteNotNull(ref writer); + this.Metadata.Package(ref writer); + } } /// - public override void UnpackageBody(ref TByteBlock byteBlock) + public override void UnpackageBody(ref TReader reader) { - base.UnpackageBody(ref byteBlock); - var countPar = byteBlock.ReadByte(); + base.UnpackageBody(ref reader); + var countPar = ReaderExtension.ReadValue(ref reader); var ps = new object[this.RpcParameters.Length]; var index = 0; @@ -148,7 +154,7 @@ internal class DmtpRpcRequestPackage : WaitRouterPackage, IDmtpRpcRequestPackage } else if (index < countPar) { - ps[i] = this.m_selector.DeserializeParameter(ref byteBlock, this.SerializationType, parameter.Type); + ps[i] = this.m_selector.DeserializeParameter(ref reader, this.SerializationType, parameter.Type); index++; } @@ -166,16 +172,16 @@ internal class DmtpRpcRequestPackage : WaitRouterPackage, IDmtpRpcRequestPackage } /// - public override void UnpackageRouter(ref TByteBlock byteBlock) + public override void UnpackageRouter(ref TReader reader) { - base.UnpackageRouter(ref byteBlock); - this.m_serializationType = (SerializationType)byteBlock.ReadByte(); - this.m_invokeKey = byteBlock.ReadString(FixedHeaderType.Byte); - this.m_feedback = (FeedbackType)byteBlock.ReadByte(); - if (!byteBlock.ReadIsNull()) + base.UnpackageRouter(ref reader); + this.m_serializationType = (SerializationType)ReaderExtension.ReadValue(ref reader); + this.m_invokeKey = ReaderExtension.ReadString(ref reader, FixedHeaderType.Byte); + this.m_feedback = (FeedbackType)ReaderExtension.ReadValue(ref reader); + if (!ReaderExtension.ReadIsNull(ref reader)) { var package = new Metadata(); - package.Unpackage(ref byteBlock); + package.Unpackage(ref reader); this.m_metadata = package; } } diff --git a/src/TouchSocket.Dmtp/Features/Rpc/_Packages/DmtpRpcResponsePackage.cs b/src/TouchSocket.Dmtp/Features/Rpc/_Packages/DmtpRpcResponsePackage.cs index 51aca6030..6576951f0 100644 --- a/src/TouchSocket.Dmtp/Features/Rpc/_Packages/DmtpRpcResponsePackage.cs +++ b/src/TouchSocket.Dmtp/Features/Rpc/_Packages/DmtpRpcResponsePackage.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using TouchSocket.Core; using TouchSocket.Resources; using TouchSocket.Rpc; using TouchSocket.Sockets; @@ -70,35 +68,35 @@ internal class DmtpRpcResponsePackage : WaitRouterPackage } /// - public override void PackageBody(ref TByteBlock byteBlock) + public override void PackageBody(ref TWriter writer) { - base.PackageBody(ref byteBlock); + base.PackageBody(ref writer); - this.m_selector.SerializeParameter(ref byteBlock, this.m_serializationType, this.ReturnParameter); + this.m_selector.SerializeParameter(ref writer, this.m_serializationType, this.ReturnParameter); } /// - public override void PackageRouter(ref TByteBlock byteBlock) + public override void PackageRouter(ref TWriter writer) { - base.PackageRouter(ref byteBlock); - byteBlock.WriteByte((byte)this.m_serializationType); + base.PackageRouter(ref writer); + WriterExtension.WriteValue(ref writer, (byte)this.m_serializationType); } /// - public override void UnpackageBody(ref TByteBlock byteBlock) + public override void UnpackageBody(ref TReader reader) { - base.UnpackageBody(ref byteBlock); + base.UnpackageBody(ref reader); if (this.m_returnType != null) { - this.m_returnParameter = this.m_selector.DeserializeParameter(ref byteBlock, this.m_serializationType, this.m_returnType); + this.m_returnParameter = this.m_selector.DeserializeParameter(ref reader, this.m_serializationType, this.m_returnType); } } /// - public override void UnpackageRouter(ref TByteBlock byteBlock) + public override void UnpackageRouter(ref TReader reader) { - base.UnpackageRouter(ref byteBlock); - this.m_serializationType = (SerializationType)byteBlock.ReadByte(); + base.UnpackageRouter(ref reader); + this.m_serializationType = (SerializationType)ReaderExtension.ReadValue(ref reader); } internal void ThrowStatus() diff --git a/src/TouchSocket.Dmtp/Features/Rpc/_Packages/IDmtpRpcRequestPackage.cs b/src/TouchSocket.Dmtp/Features/Rpc/_Packages/IDmtpRpcRequestPackage.cs index dc4cdb39f..b3365e279 100644 --- a/src/TouchSocket.Dmtp/Features/Rpc/_Packages/IDmtpRpcRequestPackage.cs +++ b/src/TouchSocket.Dmtp/Features/Rpc/_Packages/IDmtpRpcRequestPackage.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; using TouchSocket.Rpc; namespace TouchSocket.Dmtp.Rpc; diff --git a/src/TouchSocket.Dmtp/Interfaces/IActor.cs b/src/TouchSocket.Dmtp/Interfaces/IActor.cs index 150e86a36..2487e80a4 100644 --- a/src/TouchSocket.Dmtp/Interfaces/IActor.cs +++ b/src/TouchSocket.Dmtp/Interfaces/IActor.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Dmtp; /// diff --git a/src/TouchSocket.Dmtp/Interfaces/IDmtpActorObject.cs b/src/TouchSocket.Dmtp/Interfaces/IDmtpActorObject.cs index 0dbaeb6bd..ddaaf42f6 100644 --- a/src/TouchSocket.Dmtp/Interfaces/IDmtpActorObject.cs +++ b/src/TouchSocket.Dmtp/Interfaces/IDmtpActorObject.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Dmtp; /// diff --git a/src/TouchSocket.Dmtp/Interfaces/IDmtpClient.cs b/src/TouchSocket.Dmtp/Interfaces/IDmtpClient.cs index 13bc9fee4..6ea9cdf43 100644 --- a/src/TouchSocket.Dmtp/Interfaces/IDmtpClient.cs +++ b/src/TouchSocket.Dmtp/Interfaces/IDmtpClient.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; using TouchSocket.Sockets; namespace TouchSocket.Dmtp; diff --git a/src/TouchSocket.Dmtp/Interfaces/IDmtpFeature.cs b/src/TouchSocket.Dmtp/Interfaces/IDmtpFeature.cs index 280d06f2a..eb7ba1aec 100644 --- a/src/TouchSocket.Dmtp/Interfaces/IDmtpFeature.cs +++ b/src/TouchSocket.Dmtp/Interfaces/IDmtpFeature.cs @@ -20,7 +20,7 @@ public interface IDmtpFeature /// /// 起始协议 /// - ushort StartProtocol { get; set; } + ushort StartProtocol { get; } /// /// 保留协议长度 diff --git a/src/TouchSocket.Dmtp/Json/TouchSokcetDmtpSourceGenerationContext.cs b/src/TouchSocket.Dmtp/Json/TouchSocketDmtpSourceGenerationContext.cs similarity index 84% rename from src/TouchSocket.Dmtp/Json/TouchSokcetDmtpSourceGenerationContext.cs rename to src/TouchSocket.Dmtp/Json/TouchSocketDmtpSourceGenerationContext.cs index 07e94d22a..4be381cc3 100644 --- a/src/TouchSocket.Dmtp/Json/TouchSokcetDmtpSourceGenerationContext.cs +++ b/src/TouchSocket.Dmtp/Json/TouchSocketDmtpSourceGenerationContext.cs @@ -9,24 +9,16 @@ // 交流QQ群:234762506 // 感谢您的下载和使用 //------------------------------------------------------------------------------ - -#if SystemTextJson using System.Text.Json.Serialization; -using TouchSocket.Core; namespace TouchSocket.Dmtp; -/// -/// TouchSokcetDmtpSourceGenerationContext -/// [JsonSourceGenerationOptions(WriteIndented = true)] [JsonSerializable(typeof(WaitVerify))] [JsonSerializable(typeof(Metadata))] [JsonSerializable(typeof(WaitSetId))] [JsonSerializable(typeof(WaitPing))] -internal partial class TouchSokcetDmtpSourceGenerationContext : JsonSerializerContext +internal partial class TouchSocketDmtpSourceGenerationContext : JsonSerializerContext { } - -#endif \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/ObsoleteClass.cs b/src/TouchSocket.Dmtp/ObsoleteClass.cs index 5e98e6f2f..b915aa414 100644 --- a/src/TouchSocket.Dmtp/ObsoleteClass.cs +++ b/src/TouchSocket.Dmtp/ObsoleteClass.cs @@ -10,36 +10,5 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Dmtp; -/// -[Obsolete($"此接口由于表述不清,已被弃用,请使用{nameof(TcpDmtpSessionClient)}代替。", true)] -public class TcpDmtpSocketClient -{ -} - -/// -[Obsolete($"此接口由于表述不清,已被弃用,请使用{nameof(ITcpDmtpSessionClient)}代替。", true)] -public interface ITcpDmtpSocketClient -{ -} - -/// -[Obsolete($"此接口由于表述不清,已被弃用,请使用{nameof(HttpDmtpSessionClient)}代替。", true)] -public class HttpDmtpSocketClient -{ -} - -/// -[Obsolete($"此接口由于表述不清,已被弃用,请使用{nameof(IHttpDmtpSessionClient)}代替。", true)] -public interface IHttpDmtpSocketClient -{ -} - -/// -[Obsolete($"此接口由于表述不清,已被弃用,请使用{nameof(IDmtpCreatedChannelPlugin)}代替。", true)] -public interface IDmtpCreateChannelPlugin -{ -} \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Options/DmtpFeatureOption.cs b/src/TouchSocket.Dmtp/Options/DmtpFeatureOption.cs new file mode 100644 index 000000000..009545d85 --- /dev/null +++ b/src/TouchSocket.Dmtp/Options/DmtpFeatureOption.cs @@ -0,0 +1,21 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +namespace TouchSocket.Dmtp; + +public class DmtpFeatureOption +{ + /// + /// 起始协议标识 + /// + public ushort StartProtocol { get; set; } +} diff --git a/src/TouchSocket.Dmtp/Common/DmtpOption.cs b/src/TouchSocket.Dmtp/Options/DmtpOption.cs similarity index 90% rename from src/TouchSocket.Dmtp/Common/DmtpOption.cs rename to src/TouchSocket.Dmtp/Options/DmtpOption.cs index 82e3ebd7f..4418f32d0 100644 --- a/src/TouchSocket.Dmtp/Common/DmtpOption.cs +++ b/src/TouchSocket.Dmtp/Options/DmtpOption.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using TouchSocket.Core; - namespace TouchSocket.Dmtp; /// @@ -28,7 +25,7 @@ public class DmtpOption /// /// 连接时指定Id。 /// - /// 使用该功能时,仅在服务器的Handshaking之后生效。且如果id重复,则会连接失败。 + /// 使用该功能时,仅在服务器的Connecting之后生效。且如果id重复,则会连接失败。 /// /// public string Id { get; set; } diff --git a/src/TouchSocket.Dmtp/Plugins/DmtpHeartbeatPlugin.cs b/src/TouchSocket.Dmtp/Plugins/DmtpHeartbeatPlugin.cs deleted file mode 100644 index 337d62221..000000000 --- a/src/TouchSocket.Dmtp/Plugins/DmtpHeartbeatPlugin.cs +++ /dev/null @@ -1,65 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; -using System.Threading.Tasks; -using TouchSocket.Core; -using TouchSocket.Sockets; - -namespace TouchSocket.Dmtp; - -/// -/// 基于Dmtp的心跳插件。服务器和客户端均适用 -/// -[PluginOption(Singleton = true)] -public class DmtpHeartbeatPlugin : HeartbeatPlugin, IDmtpHandshakedPlugin -{ - /// - public async Task OnDmtpHandshaked(IDmtpActorObject client, DmtpVerifyEventArgs e) - { - _ = EasyTask.Run(async () => - { - var failedCount = 0; - while (true) - { - if (this.DisposedValue) - { - return; - } - await Task.Delay(this.Tick); - if (client.DmtpActor == null || !client.DmtpActor.Online) - { - return; - } - if (DateTimeOffset.UtcNow - client.DmtpActor.LastActiveTime < this.Tick) - { - continue; - } - - if (await client.DmtpActor.PingAsync()) - { - failedCount = 0; - } - else - { - failedCount++; - if (failedCount > this.MaxFailCount) - { - await client.DmtpActor.CloseAsync("自动心跳失败次数达到最大,已断开连接。"); - } - } - } - }); - - await e.InvokeNext(); - } -} \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Plugins/DmtpReconnectionPlugin.cs b/src/TouchSocket.Dmtp/Plugins/DmtpReconnectionPlugin.cs deleted file mode 100644 index 816d2e59c..000000000 --- a/src/TouchSocket.Dmtp/Plugins/DmtpReconnectionPlugin.cs +++ /dev/null @@ -1,74 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; -using System.Threading.Tasks; -using TouchSocket.Core; -using TouchSocket.Sockets; - -namespace TouchSocket.Dmtp; - -internal class DmtpReconnectionPlugin : ReconnectionPlugin, IDmtpClosedPlugin where TClient : IDmtpClient -{ - public override Func> ActionForCheck { get; set; } - - public DmtpReconnectionPlugin() - { - this.ActionForCheck = this.OnActionForCheck; - } - - private async Task OnActionForCheck(TClient client, int i) - { - if (!client.Online) - { - return false; - } - - if (DateTimeOffset.UtcNow - client.GetLastActiveTime() < this.Tick) - { - return null; - } - - return await client.PingAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - - public async Task OnDmtpClosed(IDmtpActorObject client, ClosedEventArgs e) - { - await e.InvokeNext().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - - if (client is not TClient tClient) - { - return; - } - - _ = Task.Run(async () => - { - if (e.Manual) - { - return; - } - - while (true) - { - if (this.DisposedValue) - { - return; - } - - if (await this.ActionForConnect.Invoke(tClient).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) - { - return; - } - } - }); - } -} \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Plugins/IDmtpClosedPlugin.cs b/src/TouchSocket.Dmtp/Plugins/IDmtpClosedPlugin.cs index 32be03e0b..1b89c3895 100644 --- a/src/TouchSocket.Dmtp/Plugins/IDmtpClosedPlugin.cs +++ b/src/TouchSocket.Dmtp/Plugins/IDmtpClosedPlugin.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Sockets; namespace TouchSocket.Dmtp; diff --git a/src/TouchSocket.Dmtp/Plugins/IDmtpClosingPlugin.cs b/src/TouchSocket.Dmtp/Plugins/IDmtpClosingPlugin.cs index c2d9a9cea..5f4260130 100644 --- a/src/TouchSocket.Dmtp/Plugins/IDmtpClosingPlugin.cs +++ b/src/TouchSocket.Dmtp/Plugins/IDmtpClosingPlugin.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Sockets; namespace TouchSocket.Dmtp; diff --git a/src/TouchSocket.Dmtp/Plugins/IDmtpHandshakedPlugin.cs b/src/TouchSocket.Dmtp/Plugins/IDmtpConnectedPlugin.cs similarity index 87% rename from src/TouchSocket.Dmtp/Plugins/IDmtpHandshakedPlugin.cs rename to src/TouchSocket.Dmtp/Plugins/IDmtpConnectedPlugin.cs index 718a1c818..65f81e8a9 100644 --- a/src/TouchSocket.Dmtp/Plugins/IDmtpHandshakedPlugin.cs +++ b/src/TouchSocket.Dmtp/Plugins/IDmtpConnectedPlugin.cs @@ -10,16 +10,13 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Dmtp; /// /// 定义了一个插件接口,该插件在完成与Dmtp的握手连接后需要被调用。 /// [DynamicMethod] -public interface IDmtpHandshakedPlugin : IPlugin +public interface IDmtpConnectedPlugin : IPlugin { /// /// 在完成握手连接时被调用的方法。 @@ -27,5 +24,5 @@ public interface IDmtpHandshakedPlugin : IPlugin /// 参与握手的Dmtp客户端对象。 /// 握手验证事件参数。 /// 一个Task对象,表示异步操作的结果。 - Task OnDmtpHandshaked(IDmtpActorObject client, DmtpVerifyEventArgs e); + Task OnDmtpConnected(IDmtpActorObject client, DmtpVerifyEventArgs e); } \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Plugins/IDmtpHandshakingPlugin.cs b/src/TouchSocket.Dmtp/Plugins/IDmtpConnectingPlugin.cs similarity index 82% rename from src/TouchSocket.Dmtp/Plugins/IDmtpHandshakingPlugin.cs rename to src/TouchSocket.Dmtp/Plugins/IDmtpConnectingPlugin.cs index 969eee030..e1affdd4c 100644 --- a/src/TouchSocket.Dmtp/Plugins/IDmtpHandshakingPlugin.cs +++ b/src/TouchSocket.Dmtp/Plugins/IDmtpConnectingPlugin.cs @@ -10,18 +10,15 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Dmtp; /// -/// IDmtpHandshakingPlugin接口定义了插件在Dmtp握手过程中需要实现的方法。 +/// IDmtpConnectingPlugin接口定义了插件在Dmtp握手过程中需要实现的方法。 /// 它继承自IPlugin接口。 /// [DynamicMethod] -public interface IDmtpHandshakingPlugin : IPlugin +public interface IDmtpConnectingPlugin : IPlugin { /// /// 在Dmtp建立握手连接之前执行的操作。 @@ -30,5 +27,5 @@ public interface IDmtpHandshakingPlugin : IPlugin /// 正在与之建立握手连接的客户端对象。 /// 包含验证过程中需要的信息的事件参数。 /// 一个Task对象,表示异步操作的结果。 - Task OnDmtpHandshaking(IDmtpActorObject client, DmtpVerifyEventArgs e); + Task OnDmtpConnecting(IDmtpActorObject client, DmtpVerifyEventArgs e); } \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Plugins/IDmtpCreatedChannelPlugin.cs b/src/TouchSocket.Dmtp/Plugins/IDmtpCreatedChannelPlugin.cs index 1566015fe..3cab47d08 100644 --- a/src/TouchSocket.Dmtp/Plugins/IDmtpCreatedChannelPlugin.cs +++ b/src/TouchSocket.Dmtp/Plugins/IDmtpCreatedChannelPlugin.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Dmtp; /// diff --git a/src/TouchSocket.Dmtp/Plugins/IDmtpReceivedPlugin.cs b/src/TouchSocket.Dmtp/Plugins/IDmtpReceivedPlugin.cs index 78d7760dc..bfff3f581 100644 --- a/src/TouchSocket.Dmtp/Plugins/IDmtpReceivedPlugin.cs +++ b/src/TouchSocket.Dmtp/Plugins/IDmtpReceivedPlugin.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Dmtp; /// diff --git a/src/TouchSocket.Dmtp/Plugins/IDmtpRoutingPlugin.cs b/src/TouchSocket.Dmtp/Plugins/IDmtpRoutingPlugin.cs index dd939af70..dac9fae3c 100644 --- a/src/TouchSocket.Dmtp/Plugins/IDmtpRoutingPlugin.cs +++ b/src/TouchSocket.Dmtp/Plugins/IDmtpRoutingPlugin.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Dmtp; /// diff --git a/src/TouchSocket.Dmtp/ProtocolSecure/ProtocolSecureService.cs b/src/TouchSocket.Dmtp/ProtocolSecure/ProtocolSecureService.cs index d61b8d025..dadbcd305 100644 --- a/src/TouchSocket.Dmtp/ProtocolSecure/ProtocolSecureService.cs +++ b/src/TouchSocket.Dmtp/ProtocolSecure/ProtocolSecureService.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Collections.Generic; - namespace TouchSocket.Dmtp; internal class ProtocolSecureService : IProtocolSecureService diff --git a/src/TouchSocket.Dmtp/Readme.md b/src/TouchSocket.Dmtp/Readme.md index 06cefb2bd..555f76dc5 100644 --- a/src/TouchSocket.Dmtp/Readme.md +++ b/src/TouchSocket.Dmtp/Readme.md @@ -15,7 +15,7 @@ DMTP(Duplex Message Transport Protocol 双工消息传输协议)是一个简 - netstandard2.0 - netstandard2.1 - net6.0 -- net9.0 +- net10.0 - net8.0 ## 使用方法 diff --git a/src/TouchSocket.Dmtp/Services/Route/DmtpRouteService.cs b/src/TouchSocket.Dmtp/Services/Route/DmtpRouteService.cs index 79401f94e..2aec831a3 100644 --- a/src/TouchSocket.Dmtp/Services/Route/DmtpRouteService.cs +++ b/src/TouchSocket.Dmtp/Services/Route/DmtpRouteService.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; - namespace TouchSocket.Dmtp; /// diff --git a/src/TouchSocket.Dmtp/Services/Route/DmtpRouteServiceExtension.cs b/src/TouchSocket.Dmtp/Services/Route/DmtpRouteServiceExtension.cs index 8c4cab1d7..ca66eaf91 100644 --- a/src/TouchSocket.Dmtp/Services/Route/DmtpRouteServiceExtension.cs +++ b/src/TouchSocket.Dmtp/Services/Route/DmtpRouteServiceExtension.cs @@ -10,9 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; -using TouchSocket.Core; +using System.Diagnostics.CodeAnalysis; namespace TouchSocket.Dmtp; @@ -35,7 +33,7 @@ public static class DmtpRouteServiceExtension /// /// DMTP路由服务的具体类型。 /// 服务注册器接口,用于在服务容器中注册服务。 - public static void AddDmtpRouteService(this IRegistrator registrator) + public static void AddDmtpRouteService<[DynamicallyAccessedMembers(AOT.Container)] TDmtpRouteService>(this IRegistrator registrator) where TDmtpRouteService : class, IDmtpRouteService { // 使用单例模式注册DMTP路由服务,确保在整个应用生命周期中只创建一个实例。 @@ -49,7 +47,7 @@ public static class DmtpRouteServiceExtension /// public static void AddDmtpRouteService(this IRegistrator registrator, Func> func) { - registrator.RegisterSingleton(new DmtpRouteService() + registrator.RegisterSingleton(new DmtpRouteService() { FindDmtpActor = func }); diff --git a/src/TouchSocket.Dmtp/Services/Route/IDmtpRouteService.cs b/src/TouchSocket.Dmtp/Services/Route/IDmtpRouteService.cs index 7e18c42b5..146aa3296 100644 --- a/src/TouchSocket.Dmtp/Services/Route/IDmtpRouteService.cs +++ b/src/TouchSocket.Dmtp/Services/Route/IDmtpRouteService.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; - namespace TouchSocket.Dmtp; /// diff --git a/src/TouchSocket.Dmtp/TouchSocket.Dmtp.csproj b/src/TouchSocket.Dmtp/TouchSocket.Dmtp.csproj index ea9b891f2..5b305ab75 100644 --- a/src/TouchSocket.Dmtp/TouchSocket.Dmtp.csproj +++ b/src/TouchSocket.Dmtp/TouchSocket.Dmtp.csproj @@ -1,6 +1,6 @@ - net481;net45;net462;net472;netstandard2.0;netstandard2.1;net6.0;net9.0;net8.0 + net462;netstandard2.0;netstandard2.1;net6.0;net10.0;net8.0 Duplex;Rpc;FileTransfer;Redis;TouchSocket DMTP(Duplex Message Transport Protocol双工消息传输协议)是一个简单易用,便捷高效,且易于扩展的二进制数据协议。目前基于该协议,已实现的功能包括:连接验证、同步Id、Rpc(包括客户端请求服务器,服务器请求客户端、客户端请求客户端)、文件传输(包括客户端向服务器请求文件、客户端向服务器推送文件、服务器向客户端请求文件、服务器向客户端推送文件、 客户端之间请求、推送文件)、Redis等。 @@ -10,6 +10,7 @@ + diff --git a/src/TouchSocket.Hosting/Extensions/ServiceCollectionExtensions.cs b/src/TouchSocket.Hosting/Extensions/ServiceCollectionExtensions.cs index 6909ad4ed..b6d9b376d 100644 --- a/src/TouchSocket.Hosting/Extensions/ServiceCollectionExtensions.cs +++ b/src/TouchSocket.Hosting/Extensions/ServiceCollectionExtensions.cs @@ -1,18 +1,16 @@ //------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Diagnostics.CodeAnalysis; -using TouchSocket.Core; using TouchSocket.Core.AspNetCore; using TouchSocket.Hosting; using TouchSocket.Hosting.Sockets.HostService; @@ -21,26 +19,21 @@ using TouchSocket.Sockets; namespace Microsoft.Extensions.DependencyInjection; /// -/// ServiceCollectionExtensions +/// 服务集合扩展类 /// public static class ServiceCollectionExtensions { - /// - /// DynamicallyAccessed - /// - public const DynamicallyAccessedMemberTypes DynamicallyAccessed = DynamicallyAccessedMemberTypes.PublicConstructors; - #region SetupConfig /// - /// 添加SingletonSetupConfigObject服务。 + /// 添加单例生命周期的配置对象服务 /// - /// - /// - /// - /// - /// - public static IServiceCollection AddSingletonSetupConfigObject(this IServiceCollection services, Action actionConfig) + /// 服务接口类型 + /// 服务实现类型 + /// 服务集合 + /// 配置操作委托 + /// 服务集合 + public static IServiceCollection AddSingletonSetupConfigObject(this IServiceCollection services, Action actionConfig) where TObjectService : class, ISetupConfigObject where TObjectImpService : class, TObjectService { @@ -66,14 +59,14 @@ public static class ServiceCollectionExtensions } /// - /// 添加TransientSetupConfigObject服务。 + /// 添加瞬态生命周期的配置对象服务 /// - /// - /// - /// - /// - /// - public static IServiceCollection AddTransientSetupConfigObject(this IServiceCollection services, Action actionConfig) + /// 服务接口类型 + /// 服务实现类型 + /// 服务集合 + /// 配置操作委托 + /// 服务集合 + public static IServiceCollection AddTransientSetupConfigObject(this IServiceCollection services, Action actionConfig) where TObjectService : class, ISetupConfigObject where TObjectImpService : class, TObjectService { @@ -99,14 +92,14 @@ public static class ServiceCollectionExtensions } /// - /// 添加ScopedSetupConfigObject服务。 + /// 添加作用域生命周期的配置对象服务 /// - /// - /// - /// - /// - /// - public static IServiceCollection AddScopedSetupConfigObject(this IServiceCollection services, Action actionConfig) + /// 服务接口类型 + /// 服务实现类型 + /// 服务集合 + /// 配置操作委托 + /// 服务集合 + public static IServiceCollection AddScopedSetupConfigObject(this IServiceCollection services, Action actionConfig) where TObjectService : class, ISetupConfigObject where TObjectImpService : class, TObjectService { @@ -136,14 +129,14 @@ public static class ServiceCollectionExtensions #region HostedService /// - /// 添加Service类型的HostedService服务。这类服务必须实现 + /// 添加托管服务,该服务必须实现接口 /// - /// - /// - /// - /// - /// - public static IServiceCollection AddServiceHostedService(this IServiceCollection services, Action actionConfig) + /// 服务接口类型 + /// 服务实现类型 + /// 服务集合 + /// 配置操作委托 + /// 服务集合 + public static IServiceCollection AddServiceHostedService(this IServiceCollection services, Action actionConfig) where TObjectService : class, ISetupConfigObject, IServiceBase where TObjectImpService : class, TObjectService { @@ -151,15 +144,15 @@ public static class ServiceCollectionExtensions } /// - /// 添加SetupConfigObjectHostedService服务。 + /// 添加配置对象托管服务 /// - /// - /// - /// - /// - /// - /// - public static IServiceCollection AddSetupConfigObjectHostedService(this IServiceCollection services, Action actionConfig) + /// 托管服务类型 + /// 服务接口类型 + /// 服务实现类型 + /// 服务集合 + /// 配置操作委托 + /// 服务集合 + public static IServiceCollection AddSetupConfigObjectHostedService(this IServiceCollection services, Action actionConfig) where THostService : SetupConfigObjectHostedService, new() where TObjectService : class, ISetupConfigObject where TObjectImpService : class, TObjectService @@ -190,14 +183,14 @@ public static class ServiceCollectionExtensions #region TcpService /// - /// 添加TcpService服务。 + /// 添加TCP服务 /// - /// - /// - /// - /// - /// - public static IServiceCollection AddTcpService(this IServiceCollection services, Action actionConfig) + /// 服务接口类型 + /// 服务实现类型 + /// 服务集合 + /// 配置操作委托 + /// 服务集合 + public static IServiceCollection AddTcpService(this IServiceCollection services, Action actionConfig) where TService : class, ITcpServiceBase where TImpService : class, TService { @@ -205,11 +198,11 @@ public static class ServiceCollectionExtensions } /// - /// 添加TcpService服务。并使用注册服务。 + /// 添加TCP服务,使用作为服务接口注册 /// - /// - /// - /// + /// 服务集合 + /// 配置操作委托 + /// 服务集合 public static IServiceCollection AddTcpService(this IServiceCollection services, Action actionConfig) { return services.AddTcpService(actionConfig); @@ -220,14 +213,14 @@ public static class ServiceCollectionExtensions #region UdpSession /// - /// 添加UdpSession服务。 + /// 添加UDP会话服务 /// - /// - /// - /// - /// - /// - public static IServiceCollection AddUdpSession(this IServiceCollection services, Action actionConfig) + /// 服务接口类型 + /// 服务实现类型 + /// 服务集合 + /// 配置操作委托 + /// 服务集合 + public static IServiceCollection AddUdpSession(this IServiceCollection services, Action actionConfig) where TService : class, IUdpSession where TImpService : class, TService { @@ -235,11 +228,11 @@ public static class ServiceCollectionExtensions } /// - /// 添加Udp服务。并使用注册服务。 + /// 添加UDP会话服务,使用作为服务接口注册 /// - /// - /// - /// + /// 服务集合 + /// 配置操作委托 + /// 服务集合 public static IServiceCollection AddUdpSession(this IServiceCollection services, Action actionConfig) { return services.AddUdpSession(actionConfig); @@ -250,14 +243,14 @@ public static class ServiceCollectionExtensions #region TcpClient /// - /// 添加单例TcpClient服务。 + /// 添加单例生命周期的TCP客户端服务 /// - /// - /// - /// - /// - /// - public static IServiceCollection AddSingletonTcpClient(this IServiceCollection services, Action actionConfig) + /// 客户端接口类型 + /// 客户端实现类型 + /// 服务集合 + /// 配置操作委托 + /// 服务集合 + public static IServiceCollection AddSingletonTcpClient(this IServiceCollection services, Action actionConfig) where TClient : class, ITcpClient where TImpClient : class, TClient { @@ -265,25 +258,25 @@ public static class ServiceCollectionExtensions } /// - /// 添加单例TcpClient服务。并使用注册服务。 + /// 添加单例生命周期的TCP客户端服务,使用作为服务接口注册 /// - /// - /// - /// + /// 服务集合 + /// 配置操作委托 + /// 服务集合 public static IServiceCollection AddSingletonTcpClient(this IServiceCollection services, Action actionConfig) { return services.AddSingletonTcpClient(actionConfig); } /// - /// 添加瞬态TcpClient服务。 + /// 添加瞬态生命周期的TCP客户端服务 /// - /// - /// - /// - /// - /// - public static IServiceCollection AddTransientTcpClient(this IServiceCollection services, Action actionConfig) + /// 客户端接口类型 + /// 客户端实现类型 + /// 服务集合 + /// 配置操作委托 + /// 服务集合 + public static IServiceCollection AddTransientTcpClient(this IServiceCollection services, Action actionConfig) where TClient : class, ITcpClient where TImpClient : class, TClient { @@ -291,36 +284,36 @@ public static class ServiceCollectionExtensions } /// - /// 添加瞬态TcpClient服务。并使用注册服务。 + /// 添加瞬态生命周期的TCP客户端服务,使用作为服务接口注册 /// - /// - /// - /// + /// 服务集合 + /// 配置操作委托 + /// 服务集合 public static IServiceCollection AddTransientTcpClient(this IServiceCollection services, Action actionConfig) { return services.AddTransientTcpClient(actionConfig); } /// - /// 添加Scoped TcpClient服务。 + /// 添加作用域生命周期的TCP客户端服务 /// - /// - /// - /// - /// - /// - public static IServiceCollection AddScopedTcpClient(this IServiceCollection services, Action actionConfig) where TClient : class, ITcpClient + /// 客户端接口类型 + /// 客户端实现类型 + /// 服务集合 + /// 配置操作委托 + /// 服务集合 + public static IServiceCollection AddScopedTcpClient(this IServiceCollection services, Action actionConfig) where TClient : class, ITcpClient where TImpClient : class, TClient { return AddScopedSetupConfigObject(services, actionConfig); } /// - /// 添加Scoped TcpClient服务。并使用注册服务。 + /// 添加作用域生命周期的TCP客户端服务,使用作为服务接口注册 /// - /// - /// - /// + /// 服务集合 + /// 配置操作委托 + /// 服务集合 public static IServiceCollection AddScopedTcpClient(this IServiceCollection services, Action actionConfig) { return services.AddScopedTcpClient(actionConfig); diff --git a/src/TouchSocket.Hosting/HostedServices/ServiceHost.cs b/src/TouchSocket.Hosting/HostedServices/ServiceHost.cs index 7909d5fdc..2212992d7 100644 --- a/src/TouchSocket.Hosting/HostedServices/ServiceHost.cs +++ b/src/TouchSocket.Hosting/HostedServices/ServiceHost.cs @@ -12,10 +12,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using System; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Resources; using TouchSocket.Sockets; diff --git a/src/TouchSocket.Hosting/Logger/AspNetCoreLogger.cs b/src/TouchSocket.Hosting/Logger/AspNetCoreLogger.cs index b8c4ce0fa..228c6301f 100644 --- a/src/TouchSocket.Hosting/Logger/AspNetCoreLogger.cs +++ b/src/TouchSocket.Hosting/Logger/AspNetCoreLogger.cs @@ -11,8 +11,6 @@ //------------------------------------------------------------------------------ using Microsoft.Extensions.Logging; -using System; -using TouchSocket.Core; using LogLevel = TouchSocket.Core.LogLevel; namespace TouchSocket.Hosting; diff --git a/src/TouchSocket.Hosting/Readme.md b/src/TouchSocket.Hosting/Readme.md index 8d9f877d5..a73a6602c 100644 --- a/src/TouchSocket.Hosting/Readme.md +++ b/src/TouchSocket.Hosting/Readme.md @@ -9,7 +9,7 @@ TouchSocket.Hosting 是 TouchSocket 基于通用主机的扩展项目,提供 - netstandard2.0 - netstandard2.1 - net6.0 -- net9.0 +- net10.0 - net8.0 ## 使用方法 diff --git a/src/TouchSocket.Hosting/SetupConfigObject/SetupConfigObjectHostedService.cs b/src/TouchSocket.Hosting/SetupConfigObject/SetupConfigObjectHostedService.cs index 41e933ec6..d2eab006c 100644 --- a/src/TouchSocket.Hosting/SetupConfigObject/SetupConfigObjectHostedService.cs +++ b/src/TouchSocket.Hosting/SetupConfigObject/SetupConfigObjectHostedService.cs @@ -11,10 +11,6 @@ //------------------------------------------------------------------------------ using Microsoft.Extensions.Hosting; -using System; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Core.AspNetCore; namespace TouchSocket.Hosting; diff --git a/src/TouchSocket.Hosting/TouchSocket.Hosting.csproj b/src/TouchSocket.Hosting/TouchSocket.Hosting.csproj index d9172a383..4444735c5 100644 --- a/src/TouchSocket.Hosting/TouchSocket.Hosting.csproj +++ b/src/TouchSocket.Hosting/TouchSocket.Hosting.csproj @@ -1,7 +1,7 @@  - net462;net472;netstandard2.0;netstandard2.1;net6.0;net9.0;net8.0 + net462;netstandard2.0;netstandard2.1;net6.0;net10.0;net8.0 Hosting;Socket;JsonRpc;NamedPipe;XmlRpc;Dmtp;TouchSocket 这是TouchSocket基于通用主机的扩展。目前包括Tcp、Udp、NamedPipe、Dmtp、SerialPort等服务。 @@ -39,7 +39,7 @@ - + diff --git a/src/TouchSocket.Http/BlockSegment/HttpBlockSegment.cs b/src/TouchSocket.Http/BlockSegment/HttpBlockSegment.cs deleted file mode 100644 index 4789724f6..000000000 --- a/src/TouchSocket.Http/BlockSegment/HttpBlockSegment.cs +++ /dev/null @@ -1,72 +0,0 @@ -// ------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -// ------------------------------------------------------------------------------ - -using System; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; - -namespace TouchSocket.Http; -class HttpBlockSegment : BlockSegment -{ - HttpReadOnlyMemoryBlockResult m_blockResult; - protected override IReadOnlyMemoryBlockResult CreateResult(Action actionForDispose) - { - this.m_blockResult = new HttpReadOnlyMemoryBlockResult(actionForDispose); - return this.m_blockResult; - } - - internal async Task InternalComplete(string msg) - { - try - { - this.m_blockResult.IsCompleted = true; - this.m_blockResult.Message = msg; - await this.TriggerAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - catch - { - } - } - - internal Task InternalInputAsync(in ReadOnlyMemory memory) - { - this.m_blockResult.Memory = memory; - return base.TriggerAsync(); - } - - protected override void CompleteRead() - { - // 清除结果中的内存数据 - this.m_blockResult.Memory = default; - // 清除结果中的消息 - this.m_blockResult.Message = default; - base.CompleteRead(); - } - - - internal void InternalReset() - { - // 将块结果标记为未完成 - this.m_blockResult.IsCompleted = false; - // 清除结果中的内存数据 - this.m_blockResult.Memory = default; - // 清除结果中的消息 - this.m_blockResult.Message = default; - } - - internal ValueTask InternalValueWaitAsync(CancellationToken cancellationToken) - { - return base.ProtectedReadAsync(cancellationToken); - } -} - diff --git a/src/TouchSocket.Http/BlockSegment/HttpReadOnlyMemoryBlockResult.cs b/src/TouchSocket.Http/BlockSegment/HttpReadOnlyMemoryBlockResult.cs deleted file mode 100644 index 23483b8e4..000000000 --- a/src/TouchSocket.Http/BlockSegment/HttpReadOnlyMemoryBlockResult.cs +++ /dev/null @@ -1,43 +0,0 @@ -// ------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -// ------------------------------------------------------------------------------ - -using System; -using TouchSocket.Core; - -namespace TouchSocket.Http; -class HttpReadOnlyMemoryBlockResult : IReadOnlyMemoryBlockResult -{ - - public static readonly IReadOnlyMemoryBlockResult Completed = new HttpReadOnlyMemoryBlockResult(() => { }) { IsCompleted = true }; - - public static IReadOnlyMemoryBlockResult FromResult(ReadOnlyMemory memory) - { - return new HttpReadOnlyMemoryBlockResult(() => { }) { IsCompleted = true, Memory = memory }; - } - - private readonly Action m_actionForDispose; - - public HttpReadOnlyMemoryBlockResult(Action actionForDispose) - { - this.m_actionForDispose = actionForDispose; - } - public ReadOnlyMemory Memory { get; set; } - - public bool IsCompleted { get; set; } - - public string Message { get; set; } - - public void Dispose() - { - this.m_actionForDispose.Invoke(); - } -} \ No newline at end of file diff --git a/src/TouchSocket.Http/Common/ClientHttpResponse.cs b/src/TouchSocket.Http/Common/ClientHttpResponse.cs new file mode 100644 index 000000000..256ed20e4 --- /dev/null +++ b/src/TouchSocket.Http/Common/ClientHttpResponse.cs @@ -0,0 +1,420 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.Buffers; +using System.IO.Pipelines; +using TouchSocket.Sockets; + +namespace TouchSocket.Http; + +internal sealed class ClientHttpResponse : HttpResponse +{ + private readonly HttpClientBase m_httpClientBase; + private long m_bytesRead = 0; + private ByteBlock m_contentByteBlock; + private ReadOnlyMemory m_contentMemory; + private bool m_isContentReadingStarted = false; + + internal ClientHttpResponse(HttpClientBase httpClientBase) : base(httpClientBase) + { + this.m_httpClientBase = httpClientBase; + } + + /// + public override async ValueTask> GetContentAsync(CancellationToken cancellationToken = default) + { + if (this.ContentStatus == ContentCompletionStatus.Unknown) + { + var contentLength = this.ContentLength; + if (!this.IsChunk && contentLength == 0) + { + this.m_contentMemory = ReadOnlyMemory.Empty; + return this.m_contentMemory; + } + + if (!this.IsChunk && contentLength > MaxCacheSize) + { + ThrowHelper.ThrowArgumentOutOfRangeException_MoreThan(nameof(contentLength), contentLength, MaxCacheSize); + } + + try + { + var byteBlock = new ByteBlock((int)contentLength); + + while (true) + { + using (var blockResult = await this.ReadAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) + { + byteBlock.Write(blockResult.Memory.Span); + if (blockResult.IsCompleted) + { + break; + } + } + + if (byteBlock.Length > MaxCacheSize) + { + ThrowHelper.ThrowArgumentOutOfRangeException_MoreThan(nameof(contentLength), contentLength, MaxCacheSize); + } + } + this.InternalSetContent(byteBlock); + return this.m_contentMemory; + } + catch + { + this.ContentStatus = ContentCompletionStatus.Incomplete; + this.m_contentMemory = null; + return this.m_contentMemory; + } + } + else + { + return this.ContentStatus == ContentCompletionStatus.ContentCompleted ? this.m_contentMemory : default; + } + } + + public override async ValueTask ReadAsync(CancellationToken cancellationToken = default) + { + if (this.ContentStatus == ContentCompletionStatus.ContentCompleted) + { + // 已经完成读取 + return new HttpReadOnlyMemoryBlockResult(default, this.m_contentMemory, true); + } + if (this.ContentStatus == ContentCompletionStatus.ReadCompleted) + { + ThrowHelper.ThrowInvalidOperationException("内容已读取完毕。"); + } + if (this.ContentLength == 0 && !this.IsChunk) + { + return HttpReadOnlyMemoryBlockResult.Completed; + } + + // 获取Transport和Reader + var transport = this.m_httpClientBase.InternalTransport; + var reader = transport.Reader; + + // 如果尚未开始读取内容,首先需要处理可能在头部解析过程中缓存的数据 + if (!this.m_isContentReadingStarted) + { + this.m_isContentReadingStarted = true; + } + + // 根据传输类型处理内容 + if (this.IsChunk) + { + return await this.ReadChunkedContentAsync(reader, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + else if (this.ContentLength > 0) + { + return await this.ReadFixedLengthContentAsync(reader, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + + return HttpReadOnlyMemoryBlockResult.Completed; + } + + public async ValueTask ReadHeader(CancellationToken cancellationToken) + { + var reader = this.m_httpClientBase.InternalTransport.Reader; + // 解析HTTP响应头 + while (true) + { + var readResult = await reader.ReadAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var buffer = readResult.Buffer; + var bytesReader = new BytesReader(buffer); + try + { + // 解析HTTP响应头 + if (this.ParsingHeader(ref bytesReader)) + { + // 头部解析完成,计算消费的数据量 + var consumedBytes = bytesReader.BytesRead; + + // 如果没有内容或内容长度为0,直接返回 + if (this.ContentLength == 0 && !this.IsChunk) + { + this.InternalSetContent(ReadOnlySequence.Empty); + reader.AdvanceTo(buffer.GetPosition(consumedBytes)); + return true; + } + + // 检查是否还有足够的数据来读取完整内容 + var remainingBuffer = buffer.Slice(buffer.GetPosition(consumedBytes)); + if (!this.IsChunk && remainingBuffer.Length >= this.ContentLength) + { + // 有足够数据读取完整内容 + var contentBuffer = remainingBuffer.Slice(0, (int)this.ContentLength); + this.InternalSetContent(contentBuffer); + + // 推进读取位置 + reader.AdvanceTo(buffer.GetPosition(consumedBytes + this.ContentLength)); + return true; + } + + // 需要继续读取内容,推进到头部结束位置 + reader.AdvanceTo(buffer.GetPosition(consumedBytes)); + break; + } + else + { + // 头部未完成,需要更多数据 + if (readResult.IsCompleted) + { + throw new InvalidOperationException("连接意外关闭,HTTP响应头不完整"); + } + reader.AdvanceTo(buffer.Start, buffer.End); + } + } + catch + { + reader.AdvanceTo(buffer.Start); + throw; + } + finally + { + bytesReader.Dispose(); + } + } + + return false; + } + + protected internal override void Reset() + { + this.m_contentByteBlock?.Dispose(); + this.m_contentByteBlock = null; + this.m_contentMemory = null; + this.m_bytesRead = 0; + this.m_isContentReadingStarted = false; + base.Reset(); + } + + /// + /// 解析分块传输的块大小 + /// + /// 十六进制字节数据 + /// 解析出的块大小 + /// 是否解析成功 + private static bool TryParseChunkSize(ReadOnlySpan hexBytes, out int chunkSize) + { + chunkSize = 0; + + if (hexBytes.Length == 0) + { + return false; + } + + for (var i = 0; i < hexBytes.Length; i++) + { + var b = hexBytes[i]; + int digit; + + if (b >= '0' && b <= '9') + { + digit = b - '0'; + } + else if (b >= 'A' && b <= 'F') + { + digit = b - 'A' + 10; + } + else if (b >= 'a' && b <= 'f') + { + digit = b - 'a' + 10; + } + else if (b == ';') + { + // 块扩展,忽略分号后的内容 + break; + } + else + { + return false; // 无效字符 + } + + // 检查溢出 + if (chunkSize > (int.MaxValue - digit) / 16) + { + return false; + } + + chunkSize = chunkSize * 16 + digit; + } + + return true; + } + + /// + /// 读取分块传输的内容 + /// + /// 管道读取器 + /// 取消令牌 + /// HTTP读取结果 + private async ValueTask ReadChunkedContentAsync(PipeReader reader, CancellationToken cancellationToken) + { + while (true) + { + var readResult = await reader.ReadAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var buffer = readResult.Buffer; + + try + { + var examined = buffer.Start; + var consumed = buffer.Start; + + // 查找块大小行结束符 + var crlfIndex = buffer.IndexOf(TouchSocketHttpUtility.CRLF); + if (crlfIndex < 0) + { + // 没有找到CRLF,需要更多数据 + if (readResult.IsCompleted) + { + ThrowHelper.ThrowInvalidOperationException("连接意外关闭,分块传输不完整"); + } + reader.AdvanceTo(consumed, buffer.End); + continue; + } + + // 解析块大小 + var chunkSizeSlice = buffer.Slice(0, crlfIndex); + + using var memoryBuffer = new ContiguousMemoryBuffer(chunkSizeSlice); + var chunkSizeSpan = memoryBuffer.Memory.Span; + + if (!TryParseChunkSize(chunkSizeSpan, out var chunkSize)) + { + ThrowHelper.ThrowInvalidOperationException("无效的块大小格式"); + } + + // 跳过块大小和第一个CRLF + var afterChunkSizePosition = buffer.GetPosition(crlfIndex + 2); + var remainingBuffer = buffer.Slice(afterChunkSizePosition); + + if (chunkSize == 0) + { + // 最后一个块,跳过可能的尾部CRLF + if (remainingBuffer.Length >= 2) + { + consumed = buffer.GetPosition(2, afterChunkSizePosition); + } + else + { + consumed = afterChunkSizePosition; + } + reader.AdvanceTo(consumed); + this.ContentStatus = ContentCompletionStatus.ReadCompleted; + return HttpReadOnlyMemoryBlockResult.Completed; + } + + // 检查是否有足够的数据读取完整块(块数据 + 尾部CRLF) + if (remainingBuffer.Length < chunkSize + 2) // +2 for trailing CRLF + { + // 数据不足,需要等待更多数据 + if (readResult.IsCompleted) + { + ThrowHelper.ThrowInvalidOperationException("连接意外关闭,分块传输不完整"); + } + reader.AdvanceTo(consumed, buffer.End); + continue; + } + + // 读取块数据 + var chunkDataSlice = remainingBuffer.Slice(0, chunkSize); + + var result = new HttpReadOnlyMemoryBlockResult(chunkDataSlice, false); + + // 计算消费位置(块大小行 + 第一个CRLF + 块数据 + 尾部CRLF) + consumed = buffer.GetPosition(chunkSize + 2, afterChunkSizePosition); + reader.AdvanceTo(consumed); + + return result; + } + catch + { + reader.AdvanceTo(buffer.Start); + throw; + } + } + } + + /// + /// 读取固定长度的内容 + /// + /// 管道读取器 + /// 取消令牌 + /// HTTP读取结果 + private async ValueTask ReadFixedLengthContentAsync(PipeReader reader, CancellationToken cancellationToken) + { + var totalBytesToRead = this.ContentLength; + + if (this.m_bytesRead >= totalBytesToRead) + { + this.ContentStatus = ContentCompletionStatus.ReadCompleted; + return HttpReadOnlyMemoryBlockResult.Completed; + } + + var readResult = await reader.ReadAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var buffer = readResult.Buffer; + + if (buffer.IsEmpty && readResult.IsCompleted) + { + ThrowHelper.ThrowInvalidOperationException("连接意外关闭,内容读取不完整"); + } + + var remainingBytes = totalBytesToRead - this.m_bytesRead; + var bytesToRead = Math.Min(Math.Min(remainingBytes, buffer.Length), TouchSocketHttpUtility.MaxReadSize); + + var contentSlice = buffer.Slice(0, bytesToRead); + + this.m_bytesRead += bytesToRead; + HttpReadOnlyMemoryBlockResult result; + // 检查是否已读取完所有内容 + if (this.m_bytesRead >= totalBytesToRead) + { + this.ContentStatus = ContentCompletionStatus.ReadCompleted; + result = new HttpReadOnlyMemoryBlockResult(contentSlice, true); + } + else + { + result = new HttpReadOnlyMemoryBlockResult(contentSlice, false); + } + reader.AdvanceTo(buffer.GetPosition(bytesToRead)); + + return result; + } + + internal void InternalSetContent(ReadOnlySequence content) + { + if (content.IsEmpty) + { + this.m_contentMemory = ReadOnlyMemory.Empty; + this.ContentLength = 0; + this.ContentStatus = ContentCompletionStatus.ContentCompleted; + return; + } + this.m_contentByteBlock = new ByteBlock((int)content.Length); + + foreach (var item in content) + { + this.m_contentByteBlock.Write(item.Span); + } + this.m_contentMemory = this.m_contentByteBlock.Memory; + this.ContentLength = content.Length; + this.ContentStatus = ContentCompletionStatus.ContentCompleted; + } + + internal void InternalSetContent(ByteBlock byteBlock) + { + this.m_contentByteBlock = byteBlock; + this.m_contentMemory = this.m_contentByteBlock.Memory; + this.ContentLength = byteBlock.Length; + this.ContentStatus = ContentCompletionStatus.ContentCompleted; + } +} \ No newline at end of file diff --git a/src/TouchSocket.Http/Common/WebProxy/AuthenticationType.cs b/src/TouchSocket.Http/Common/ContentCompletionStatus.cs similarity index 70% rename from src/TouchSocket.Http/Common/WebProxy/AuthenticationType.cs rename to src/TouchSocket.Http/Common/ContentCompletionStatus.cs index 7e454d0e3..a39c43fe0 100644 --- a/src/TouchSocket.Http/Common/WebProxy/AuthenticationType.cs +++ b/src/TouchSocket.Http/Common/ContentCompletionStatus.cs @@ -10,32 +10,30 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Http; /// -/// 代理身份认证类型 +/// 内容完成状态枚举 /// -[Obsolete("此配置已被弃用,不再支持代理", true)] -public enum AuthenticationType -{ /// - /// 不允许身份认证 - /// - None, +public enum ContentCompletionStatus +{ + /// + /// 未知状态,初始状态 + /// + Unknown = 0, /// - /// 指定摘要身份验证。 + /// 内容已完成,可以多次获取 /// - Digest = 1, + ContentCompleted = 1, /// - /// 指定基本身份验证。 + /// 持续读取已完成,只能获取一次 /// - Basic = 8, + ReadCompleted = 2, /// - /// 指定匿名身份验证。 + /// 内容未完成,仍在进行中 /// - Anonymous = 0x8000 + Incomplete = 3 } \ No newline at end of file diff --git a/src/TouchSocket.Http/Common/HttpBase.cs b/src/TouchSocket.Http/Common/HttpBase.cs index de0f89f6e..1c64dc2ed 100644 --- a/src/TouchSocket.Http/Common/HttpBase.cs +++ b/src/TouchSocket.Http/Common/HttpBase.cs @@ -6,17 +6,13 @@ // Gitee源代码仓库:https://gitee.com/RRQM_Home // Github源代码仓库:https://github.com/RRQM // API首页:https://touchsocket.net/ -// 交流QQ群:234762506 +//交流QQ群:234762506 // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.IO; +using System.Diagnostics; using System.Reflection; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; +using System.Runtime.CompilerServices; using TouchSocket.Sockets; namespace TouchSocket.Http; @@ -41,12 +37,10 @@ public abstract class HttpBase : IRequestInfo private readonly InternalHttpHeader m_headers = new InternalHttpHeader(); - private readonly HttpBlockSegment m_httpBlockSegment = new HttpBlockSegment(); - /// /// 可接受MIME类型 /// - public string Accept + public TextValues Accept { get => this.m_headers.Get(HttpHeaders.Accept); set => this.m_headers.Add(HttpHeaders.Accept, value); @@ -55,7 +49,7 @@ public abstract class HttpBase : IRequestInfo /// /// 允许编码 /// - public string AcceptEncoding + public TextValues AcceptEncoding { get => this.m_headers.Get(HttpHeaders.AcceptEncoding); set => this.m_headers.Add(HttpHeaders.AcceptEncoding, value); @@ -67,9 +61,32 @@ public abstract class HttpBase : IRequestInfo public virtual HttpContent Content { get; set; } /// - /// 内容填充完成 + /// 内容填充完成状态 /// - public bool? ContentCompleted { get; protected set; } = null; + public ContentCompletionStatus ContentStatus { get; protected set; } = ContentCompletionStatus.Unknown; + + /// + /// 是否分块 + /// + public bool IsChunk + { + get + { + var transferEncoding = this.Headers.Get(HttpHeaders.TransferEncoding); + return "chunked".Equals(transferEncoding, StringComparison.OrdinalIgnoreCase); + } + set + { + if (value) + { + this.Headers.Add(HttpHeaders.TransferEncoding, "chunked"); + } + else + { + this.Headers.Remove(HttpHeaders.TransferEncoding); + } + } + } /// /// 内容长度 @@ -79,7 +96,7 @@ public abstract class HttpBase : IRequestInfo get { var contentLength = this.m_headers.Get(HttpHeaders.ContentLength); - return contentLength.IsNullOrEmpty() ? 0 : long.TryParse(contentLength, out var value) ? value : 0; + return contentLength.IsEmpty ? 0 : long.TryParse(contentLength, out var value) ? value : 0; } set => this.m_headers.Add(HttpHeaders.ContentLength, value.ToString()); } @@ -87,7 +104,7 @@ public abstract class HttpBase : IRequestInfo /// /// 内容类型 /// - public string ContentType + public TextValues ContentType { get => this.m_headers.Get(HttpHeaders.ContentType); set => this.m_headers.Add(HttpHeaders.ContentType, value); @@ -113,46 +130,40 @@ public abstract class HttpBase : IRequestInfo /// public string ProtocolVersion { get; set; } = "1.1"; - ///// - ///// 请求行 - ///// - //public string RequestLine { get; private set; } - - internal Task CompleteInput() + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool ParsingHeader(ref TReader reader) where TReader : IBytesReader { - return this.m_httpBlockSegment.InternalComplete(string.Empty); - } + var index = ReaderExtension.IndexOf(ref reader, TouchSocketHttpUtility.CRLFCRLF); - internal Task InternalInputAsync(ReadOnlyMemory memory) - { - return this.m_httpBlockSegment.InternalInputAsync(memory); - } - - internal bool ParsingHeader(ref TByteBlock byteBlock) where TByteBlock : IByteBlock - { - var unreadSpan = byteBlock.Span.Slice(byteBlock.Position); - var index = unreadSpan.IndexOf(StringExtension.Default_RNRN_Utf8Span); - if (index > 0) - { - var headerLength = index + 2; - this.ReadHeaders(unreadSpan.Slice(0, headerLength)); - byteBlock.Position += headerLength; - byteBlock.Position += 2; - return true; - } - else + if (index < 0) { return false; } + + var headerLength = (int)index; + + // 提前检查数据完整性,避免后续无效操作 + var totalRequired = headerLength + 4; // +4 为 \r\n\r\n 的长度 + if (reader.BytesRemaining < totalRequired) + { + return false; + } + + // 一次性获取头部数据,减少多次调用GetSpan的开销 + var headerSpan = reader.GetSpan(headerLength); + + // 调用优化后的头部解析方法 + this.ReadHeadersOptimized(headerSpan); + + // 跳过头部数据和 \r\n\r\n 分隔符 + reader.Advance(totalRequired); + return true; } - internal virtual void ResetHttp() + protected internal virtual void Reset() { this.m_headers.Clear(); - this.ContentCompleted = null; - //this.RequestLine = default; - - this.m_httpBlockSegment.InternalReset(); + this.ContentStatus = ContentCompletionStatus.Unknown; } /// @@ -161,72 +172,111 @@ public abstract class HttpBase : IRequestInfo /// 包含请求行的只读字节跨度。 protected abstract void ReadRequestLine(ReadOnlySpan requestLineSpan); - private void ParseHeaderLine(ReadOnlySpan line) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ParseHeaderLineOptimized(ReadOnlySpan line) { - var colonIndex = line.IndexOf((byte)':'); + var colonIndex = line.IndexOf(TouchSocketHttpUtility.COLON); if (colonIndex <= 0) { - return; // 无效格式 + return; // 无效格式,冒号必须存在且不能在第一位 } - // 分割键值 - var keySpan = line.Slice(0, colonIndex).Trim(); - var valueSpan = line.Slice(colonIndex + 1).Trim(); + var keySpan = TouchSocketHttpUtility.TrimWhitespace(line.Slice(0, colonIndex)); + var valueSpan = TouchSocketHttpUtility.TrimWhitespace(line.Slice(colonIndex + 1)); - if (!keySpan.IsEmpty && !valueSpan.IsEmpty) + if (keySpan.IsEmpty || valueSpan.IsEmpty) + { + return; + } + + var key = keySpan.ToString(Encoding.UTF8).ToLowerInvariant(); + + // 检测是否包含英文逗号,按需分割 + if (valueSpan.IndexOf((byte)',') < 0) { - var key = keySpan.ToString(Encoding.UTF8).ToLower(); var value = valueSpan.ToString(Encoding.UTF8); - this.m_headers[key] = value; + this.m_headers.Add(key, value); + return; + } + + // 多值处理 + var values = new List(); + int start = 0; + while (start < valueSpan.Length) + { + int commaIndex = valueSpan.Slice(start).IndexOf((byte)','); + ReadOnlySpan segment; + if (commaIndex >= 0) + { + segment = valueSpan.Slice(start, commaIndex); + start += commaIndex + 1; + } + else + { + segment = valueSpan.Slice(start); + start = valueSpan.Length; + } + segment = TouchSocketHttpUtility.TrimWhitespace(segment); + if (!segment.IsEmpty) + { + values.Add(segment.ToString(Encoding.UTF8)); + } + } + if (values.Count == 1) + { + this.m_headers.Add(key, values[0]); + } + else if (values.Count > 1) + { + this.m_headers.Add(key, values.ToArray()); } } - private void ReadHeaders(ReadOnlySpan span) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadHeadersOptimized(ReadOnlySpan span) { - //string ss=span.ToString(Encoding.UTF8); - this.m_headers.Clear(); - // 解析请求行(首个有效行) - var lineEnd = span.IndexOf("\r\n"u8); - if (lineEnd == -1) // 没有完整请求行 + var lineEnd = span.IndexOf(TouchSocketHttpUtility.CRLF); + if (lineEnd == -1) { - throw new ArgumentException("Invalid HTTP header format."); + ThrowHelper.ThrowException("Invalid HTTP header format."); } - // 提取请求行 + // 提取并处理请求行 var requestLineSpan = span.Slice(0, lineEnd); this.ReadRequestLine(requestLineSpan); - //this.RequestLine = requestLineSpan.ToString(Encoding.UTF8); - // 跳过请求行及CRLF(+2) + // 跳过请求行及CRLF var remaining = span.Slice(lineEnd + 2); - // 解析headers - while (true) + // 优化的头部解析循环 + while (!remaining.IsEmpty) { - // 查找当前行结尾 - var headerEnd = remaining.IndexOf("\r\n"u8); + var headerEnd = remaining.IndexOf(TouchSocketHttpUtility.CRLF); + if (headerEnd == -1) { + // 处理最后一行没有CRLF的情况 + if (!remaining.IsEmpty) + { + this.ParseHeaderLineOptimized(remaining); + } break; } - // 空行表示headers结束 if (headerEnd == 0) { - remaining = remaining.Slice(2); // 跳过空行的CRLF + // 空行表示headers结束 break; } - // 提取单行header + // 提取并解析当前行 var lineSpan = remaining.Slice(0, headerEnd); - this.ParseHeaderLine(lineSpan); + this.ParseHeaderLineOptimized(lineSpan); // 移动到下一行 remaining = remaining.Slice(headerEnd + 2); } - - //this.LoadHeaderProperties(); } #region Content @@ -238,8 +288,6 @@ public abstract class HttpBase : IRequestInfo /// 用于取消异步操作的令牌。 public abstract ValueTask> GetContentAsync(CancellationToken cancellationToken = default); - internal abstract void InternalSetContent(in ReadOnlyMemory content); - #endregion Content #region Read @@ -249,11 +297,7 @@ public abstract class HttpBase : IRequestInfo /// /// 用于取消异步操作的令牌。 /// 返回一个,表示异步读取操作的结果。 - public virtual ValueTask ReadAsync(CancellationToken cancellationToken) - { - // 调用m_httpBlockSegment的InternalValueWaitAsync方法,等待HTTP块段的内部值。 - return this.m_httpBlockSegment.InternalValueWaitAsync(cancellationToken); - } + public abstract ValueTask ReadAsync(CancellationToken cancellationToken = default); /// /// 异步读取并复制流数据 @@ -261,24 +305,14 @@ public abstract class HttpBase : IRequestInfo /// 需要读取并复制的流 /// 异步操作的取消令牌 /// 一个异步任务,表示复制操作的完成 - public async Task ReadCopyToAsync(Stream stream, CancellationToken cancellationToken = default) + public async Task ReadCopyToAsync(Stream stream, CancellationToken cancellationToken = default) { - while (true) + var flowOperator = new HttpFlowOperator() { - using (var blockResult = await this.ReadAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) - { - cancellationToken.ThrowIfCancellationRequested(); - if (!blockResult.Memory.Equals(ReadOnlyMemory.Empty)) - { - var memory = blockResult.Memory; - await stream.WriteAsync(memory, cancellationToken); - } - if (blockResult.IsCompleted) - { - break; - } - } - } + Token = cancellationToken, + MaxSpeed = int.MaxValue + }; + return await this.ReadCopyToAsync(stream, flowOperator).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// @@ -298,11 +332,17 @@ public abstract class HttpBase : IRequestInfo { using (var blockResult = await this.ReadAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) { - cancellationToken.ThrowIfCancellationRequested(); - if (!blockResult.Memory.Equals(ReadOnlyMemory.Empty)) + if (cancellationToken.IsCancellationRequested) { - var memory = blockResult.Memory; - await stream.WriteAsync(memory, cancellationToken); + return flowOperator.SetResult(Result.Canceled); + } + + var memory = blockResult.Memory; + Debug.WriteLine($"读取块大小:{memory.Length},时间:{DateTime.Now:HH:mm:ss ffff}"); + if (!memory.IsEmpty) + { + + await stream.WriteAsync(memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); await flowOperator.AddFlowAsync(memory.Length).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } if (blockResult.IsCompleted) diff --git a/src/TouchSocket.Http/Common/HttpMethod.cs b/src/TouchSocket.Http/Common/HttpMethod.cs index 2450e66f1..1d92003fa 100644 --- a/src/TouchSocket.Http/Common/HttpMethod.cs +++ b/src/TouchSocket.Http/Common/HttpMethod.cs @@ -11,7 +11,6 @@ //------------------------------------------------------------------------------ using System.Diagnostics; -using TouchSocket.Core; namespace TouchSocket.Http; @@ -46,6 +45,11 @@ public readonly record struct HttpMethod /// public static readonly HttpMethod Delete = new HttpMethod("delete"); + /// + /// Connect + /// + public static readonly HttpMethod Connect = new HttpMethod("connect"); + /// /// 表示 /// diff --git a/src/TouchSocket.Http/Common/HttpRange.cs b/src/TouchSocket.Http/Common/HttpRange.cs index af1d9cc22..3113e94d2 100644 --- a/src/TouchSocket.Http/Common/HttpRange.cs +++ b/src/TouchSocket.Http/Common/HttpRange.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; - namespace TouchSocket.Http; /// diff --git a/src/TouchSocket.Http/Common/HttpReadOnlyMemoryBlockResult.cs b/src/TouchSocket.Http/Common/HttpReadOnlyMemoryBlockResult.cs new file mode 100644 index 000000000..8bee4f466 --- /dev/null +++ b/src/TouchSocket.Http/Common/HttpReadOnlyMemoryBlockResult.cs @@ -0,0 +1,74 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.Buffers; + +namespace TouchSocket.Http; + +/// +/// 表示只读内存块的结果,支持释放资源。 +/// +public readonly struct HttpReadOnlyMemoryBlockResult : IDisposable +{ + /// + /// 获取已完成的只读内存块结果。 + /// + public static readonly HttpReadOnlyMemoryBlockResult Completed = new HttpReadOnlyMemoryBlockResult(ReadOnlySequence.Empty, true); + + private readonly Action m_dispose; + private readonly IMemoryOwner m_memoryOwner; + + /// + /// 初始化 结构的新实例。 + /// + /// 只读字节序列。 + /// 指示是否已完成。 + public HttpReadOnlyMemoryBlockResult(ReadOnlySequence memories, bool isCompleted) + { + this.IsCompleted = isCompleted; + var length = (int)memories.Length; + this.m_memoryOwner = MemoryPool.Shared.Rent(length); + + memories.CopyTo(this.m_memoryOwner.Memory.Span); + this.Memory = this.m_memoryOwner.Memory.Slice(0, length); + } + + /// + /// 初始化 结构的新实例。 + /// + /// 释放资源的委托。 + /// 只读内存块。 + /// 指示是否已完成。 + public HttpReadOnlyMemoryBlockResult(Action dispose, ReadOnlyMemory memory, bool isCompleted) + { + this.m_dispose = dispose; + this.Memory = memory; + this.IsCompleted = isCompleted; + } + + /// + /// 获取一个值,指示操作是否已完成。 + /// + public bool IsCompleted { get; } + + /// + /// 获取只读内存块。 + /// + public ReadOnlyMemory Memory { get; } + + /// + public void Dispose() + { + this.m_memoryOwner?.Dispose(); + this.m_dispose?.Invoke(); + } +} \ No newline at end of file diff --git a/src/TouchSocket.Http/Common/HttpRequest.cs b/src/TouchSocket.Http/Common/HttpRequest.cs index d59529b46..818790425 100644 --- a/src/TouchSocket.Http/Common/HttpRequest.cs +++ b/src/TouchSocket.Http/Common/HttpRequest.cs @@ -10,12 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Sockets; namespace TouchSocket.Http; @@ -27,7 +21,6 @@ public class HttpRequest : HttpBase { private readonly bool m_isServer; private readonly InternalHttpParams m_query = new InternalHttpParams(); - private ReadOnlyMemory m_contentMemory; private string m_relativeURL = "/"; private string m_url = "/"; @@ -39,18 +32,7 @@ public class HttpRequest : HttpBase /// public HttpRequest() { - // 初始化时,设置m_isServer为false,表示当前请求不是由服务器发起的。 this.m_isServer = false; - // 初始化时,设置m_canRead为false,表示当前请求不能读取数据。 - } - - /// - /// 初始化 HttpRequest 实例。 - /// - /// 提供底层 HTTP 通信功能的 HttpClientBase 实例。 - [Obsolete("此构造函数已被弃用,请使用无参构造函数代替", true)] - public HttpRequest(HttpClientBase httpClientBase) - { } internal HttpRequest(HttpSessionClient httpSessionClient) @@ -59,20 +41,7 @@ public class HttpRequest : HttpBase } /// - public override HttpContent Content - { - get => base.Content; - set - { - if (value is ReadonlyMemoryHttpContent readonlyMemoryHttpContent) - { - this.ContentLength = readonlyMemoryHttpContent.Memory.Length; - this.ContentCompleted = true; - this.m_contentMemory = readonlyMemoryHttpContent.Memory; - } - base.Content = value; - } - } + public override bool IsServer => this.m_isServer; /// /// 保持连接。 @@ -86,8 +55,8 @@ public class HttpRequest : HttpBase { var keepAlive = this.Headers.Get(HttpHeaders.Connection); return this.ProtocolVersion == "1.0" - ? !keepAlive.IsNullOrEmpty() && keepAlive.Equals("keep-alive", StringComparison.OrdinalIgnoreCase) - : keepAlive.IsNullOrEmpty() || keepAlive.Equals("keep-alive", StringComparison.OrdinalIgnoreCase); + ? !keepAlive.IsEmpty && keepAlive.Equals("keep-alive", StringComparison.OrdinalIgnoreCase) + : keepAlive.IsEmpty || keepAlive.Equals("keep-alive", StringComparison.OrdinalIgnoreCase); } set { @@ -112,10 +81,6 @@ public class HttpRequest : HttpBase } } - - /// - public override bool IsServer => this.m_isServer; - /// /// HTTP请求方式。 /// @@ -129,7 +94,7 @@ public class HttpRequest : HttpBase /// /// 相对路径(不含参数) /// - public string RelativeURL { get => this.m_relativeURL; } + public string RelativeURL => this.m_relativeURL; /// /// Url全地址,包含参数 @@ -139,90 +104,21 @@ public class HttpRequest : HttpBase get => this.m_url; set { - // 确保URL以斜杠开始,如果不是,则添加斜杠 - this.m_url = value.StartsWith("/") ? value : $"/{value}"; - // 解析设置后的URL,以进行进一步的操作 + this.m_url = value; this.ParseUrl(this.m_url.AsSpan()); } } /// - public override async ValueTask> GetContentAsync(CancellationToken cancellationToken = default) + public override ValueTask> GetContentAsync(CancellationToken cancellationToken = default) { - if (!this.ContentCompleted.HasValue) - { - if (!this.IsServer) - { - //非Server模式下不允许获取 - return default; - } - if (this.ContentLength == 0) - { - this.m_contentMemory = ReadOnlyMemory.Empty; - this.ContentCompleted = true; - return this.m_contentMemory; - } - - if (this.ContentLength > MaxCacheSize) - { - ThrowHelper.ThrowArgumentOutOfRangeException_MoreThan(nameof(this.ContentLength), this.ContentLength, MaxCacheSize); - } - - try - { - using (var memoryStream = new MemoryStream((int)this.ContentLength)) - { - while (true) - { - using (var blockResult = await this.ReadAsync(cancellationToken)) - { - var segment = blockResult.Memory.GetArray(); - if (blockResult.IsCompleted) - { - break; - } - memoryStream.Write(segment.Array, segment.Offset, segment.Count); - } - } - this.ContentCompleted = true; - this.m_contentMemory = memoryStream.ToArray(); - return this.m_contentMemory; - } - } - catch - { - this.ContentCompleted = false; - return default; - } - finally - { - } - } - else - { - return this.ContentCompleted == true ? this.m_contentMemory : default; - } + throw new NotImplementedException(); } /// - public override async ValueTask ReadAsync(CancellationToken cancellationToken = default) + public override ValueTask ReadAsync(CancellationToken cancellationToken = default) { - if (this.ContentLength == 0) - { - return HttpReadOnlyMemoryBlockResult.Completed; - } - if (this.ContentCompleted.HasValue && this.ContentCompleted.Value) - { - return HttpReadOnlyMemoryBlockResult.FromResult(this.m_contentMemory); - } - - var blockResult = await base.ReadAsync(cancellationToken); - if (blockResult.IsCompleted) - { - this.ContentCompleted = true; - } - - return blockResult; + throw new NotImplementedException(); } /// @@ -238,66 +134,57 @@ public class HttpRequest : HttpBase return this; } - internal void BuildHeader(ref TByteBlock byteBlock) where TByteBlock : IByteBlock + internal void BuildHeader(ref TWriter writer) where TWriter : IBytesWriter { - byteBlock.WriteNormalString(this.Method.ToString(), Encoding.UTF8);//Get - TouchSocketHttpUtility.AppendSpace(ref byteBlock);//空格 - TouchSocketHttpUtility.AppendUtf8String(ref byteBlock, this.RelativeURL); + WriterExtension.WriteNormalString(ref writer, this.Method.ToString(), Encoding.UTF8);//Get + TouchSocketHttpUtility.AppendSpace(ref writer);//空格 + TouchSocketHttpUtility.AppendUtf8String(ref writer, this.RelativeURL); if (this.m_query.Count > 0) { - TouchSocketHttpUtility.AppendQuestionMark(ref byteBlock); + TouchSocketHttpUtility.AppendQuestionMark(ref writer); var i = 0; foreach (var item in this.m_query.Keys) { - byteBlock.WriteNormalString(item, Encoding.UTF8); - TouchSocketHttpUtility.AppendEqual(ref byteBlock); + WriterExtension.WriteNormalString(ref writer, item, Encoding.UTF8); + TouchSocketHttpUtility.AppendEqual(ref writer); var value = this.m_query[item]; - if (value.HasValue()) + if (!value.IsEmpty) { - byteBlock.WriteNormalString(Uri.EscapeDataString(value), Encoding.UTF8); + WriterExtension.WriteNormalString(ref writer, Uri.EscapeDataString(value), Encoding.UTF8); } if (++i < this.m_query.Count) { - TouchSocketHttpUtility.AppendAnd(ref byteBlock); + TouchSocketHttpUtility.AppendAnd(ref writer); } } } - TouchSocketHttpUtility.AppendSpace(ref byteBlock);//空格 - TouchSocketHttpUtility.AppendHTTP(ref byteBlock);//HTTP - TouchSocketHttpUtility.AppendSlash(ref byteBlock);//斜杠 - byteBlock.WriteNormalString(this.ProtocolVersion, Encoding.UTF8);//1.1 - TouchSocketHttpUtility.AppendRn(ref byteBlock);//换行 + TouchSocketHttpUtility.AppendSpace(ref writer);//空格 + TouchSocketHttpUtility.AppendHTTP(ref writer);//HTTP + TouchSocketHttpUtility.AppendSlash(ref writer);//斜杠 + WriterExtension.WriteNormalString(ref writer, this.ProtocolVersion, Encoding.UTF8);//1.1 + TouchSocketHttpUtility.AppendRn(ref writer);//换行 foreach (var headerKey in this.Headers.Keys) { - byteBlock.WriteNormalString(headerKey, Encoding.UTF8);//key - TouchSocketHttpUtility.AppendColon(ref byteBlock);//冒号 - TouchSocketHttpUtility.AppendSpace(ref byteBlock);//空格 - byteBlock.WriteNormalString(this.Headers[headerKey], Encoding.UTF8);//value - TouchSocketHttpUtility.AppendRn(ref byteBlock);//换行 + WriterExtension.WriteNormalString(ref writer, headerKey, Encoding.UTF8);//key + TouchSocketHttpUtility.AppendColon(ref writer);//冒号 + TouchSocketHttpUtility.AppendSpace(ref writer);//空格 + WriterExtension.WriteNormalString(ref writer, this.Headers[headerKey], Encoding.UTF8);//value + TouchSocketHttpUtility.AppendRn(ref writer);//换行 } - TouchSocketHttpUtility.AppendRn(ref byteBlock); + TouchSocketHttpUtility.AppendRn(ref writer); } /// - internal override void InternalSetContent(in ReadOnlyMemory content) + protected internal override void Reset() { - this.m_contentMemory = content; - this.ContentLength = content.Length; - this.ContentCompleted = true; - } + base.Reset(); - /// - internal override void ResetHttp() - { - base.ResetHttp(); - this.m_contentMemory = null; - //this.m_sentHeader = false; this.m_relativeURL = "/"; this.m_url = "/"; - //this.m_sentLength = 0; + this.m_query.Clear(); } diff --git a/src/TouchSocket.Http/Common/HttpResponse.cs b/src/TouchSocket.Http/Common/HttpResponse.cs index 33260a1a0..39910560f 100644 --- a/src/TouchSocket.Http/Common/HttpResponse.cs +++ b/src/TouchSocket.Http/Common/HttpResponse.cs @@ -10,12 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Sockets; namespace TouchSocket.Http; @@ -23,7 +17,7 @@ namespace TouchSocket.Http; /// /// Http响应 /// -public class HttpResponse : HttpBase +public abstract class HttpResponse : HttpBase { #region 字段 @@ -31,7 +25,7 @@ public class HttpResponse : HttpBase private readonly HttpSessionClient m_httpSessionClient; private readonly bool m_isServer; private bool m_canWrite; - private ReadOnlyMemory m_contentMemory; + private bool m_sentHeader; private long m_sentLength; @@ -59,29 +53,6 @@ public class HttpResponse : HttpBase #region 属性 - /// - /// 是否分块 - /// - public bool IsChunk - { - get - { - var transferEncoding = this.Headers.Get(HttpHeaders.TransferEncoding); - return "chunked".Equals(transferEncoding, StringComparison.OrdinalIgnoreCase); - } - set - { - if (value) - { - this.Headers.Add(HttpHeaders.TransferEncoding, "chunked"); - } - else - { - this.Headers.Remove(HttpHeaders.TransferEncoding); - } - } - } - /// /// 是否代理权限验证。 /// @@ -116,60 +87,47 @@ public class HttpResponse : HttpBase /// 构建数据并回应。 /// 该方法仅在具有Client实例时有效。 /// - public async Task AnswerAsync(CancellationToken token = default) + public async Task AnswerAsync(CancellationToken cancellationToken = default) { this.ThrowIfResponsed(); + var transport = this.GetITransport(); var content = this.Content; if (content == null) { - var byteBlock = new ValueByteBlock(1024); - try - { - //没有内容时,需要设置内容长度为0 - this.ContentLength = 0; - this.BuildHeader(ref byteBlock); - - // 异步发送请求 - await this.InternalSendAsync(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - finally - { - byteBlock.Dispose(); - } + var writer = new PipeBytesWriter(transport.Writer); + this.BuildHeader(ref writer); + await writer.FlushAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } else { content.InternalTryComputeLength(out var contentLength); - var byteBlock = new ValueByteBlock((int)Math.Min(contentLength + 1024, 1024 * 64)); - try + var writer = new PipeBytesWriter(transport.Writer); + content.InternalBuildingHeader(this.Headers); + this.BuildHeader(ref writer); + + var result = content.InternalBuildingContent(ref writer); + + await writer.FlushAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + if (!result) { - content.InternalBuildingHeader(this.Headers); - this.BuildHeader(ref byteBlock); - - var result = content.InternalBuildingContent(ref byteBlock); - - // 异步发送请求 - await this.InternalSendAsync(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - - if (!result) - { - await content.InternalWriteContent(this.InternalSendAsync, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - } - finally - { - byteBlock.Dispose(); + await content.InternalWriteContent(transport.Writer, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } - this.Responsed = true; + // 100状态码不表示响应结束。后续可以继续发送响应内容。 + if (this.StatusCode != 100) + { + this.Responsed = true; + } + } /// /// 当传输模式是Chunk时,用于结束传输。 /// - public async Task CompleteChunkAsync() + public async Task CompleteChunkAsync(CancellationToken cancellationToken = default) { if (!this.m_canWrite) { @@ -180,134 +138,35 @@ public class HttpResponse : HttpBase this.m_canWrite = false; if (this.IsChunk) { - var byteBlock = new ValueByteBlock(1024); - try - { - TouchSocketHttpUtility.AppendHex(ref byteBlock, 0); - TouchSocketHttpUtility.AppendRn(ref byteBlock); - TouchSocketHttpUtility.AppendRn(ref byteBlock); - - await this.InternalSendAsync(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.Responsed = true; - } - finally - { - byteBlock.Dispose(); - } + var transport = this.GetITransport(); + var writer = new PipeBytesWriter(transport.Writer); + TouchSocketHttpUtility.AppendHex(ref writer, 0); + TouchSocketHttpUtility.AppendRn(ref writer); + TouchSocketHttpUtility.AppendRn(ref writer); + await writer.FlushAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + this.Responsed = true; } } - /// - /// - public override async ValueTask> GetContentAsync(CancellationToken cancellationToken = default) - { - if (!this.ContentCompleted.HasValue) - { - var contentLength= this.ContentLength; - if (!this.IsChunk && contentLength == 0) - { - this.m_contentMemory = ReadOnlyMemory.Empty; - return this.m_contentMemory; - } - - if (!this.IsChunk && contentLength > MaxCacheSize) - { - ThrowHelper.ThrowArgumentOutOfRangeException_MoreThan(nameof(contentLength), contentLength, MaxCacheSize); - } - - try - { - using (var memoryStream = new MemoryStream((int)contentLength)) - { - while (true) - { - using (var blockResult = await this.ReadAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) - { - var segment = blockResult.Memory.GetArray(); - if (blockResult.IsCompleted) - { - break; - } - memoryStream.Write(segment.Array, segment.Offset, segment.Count); - } - - if (memoryStream.Length > MaxCacheSize) - { - ThrowHelper.ThrowArgumentOutOfRangeException_MoreThan(nameof(contentLength), contentLength, MaxCacheSize); - } - } - this.ContentCompleted = true; - this.m_contentMemory = memoryStream.ToArray(); - return this.m_contentMemory; - } - } - catch - { - this.ContentCompleted = false; - this.m_contentMemory = null; - return this.m_contentMemory; - } - finally - { - } - } - else - { - return this.ContentCompleted == true ? this.m_contentMemory : default; - } - } - - /// - public override async ValueTask ReadAsync(CancellationToken cancellationToken = default) - { - if (this.ContentLength == 0 && !this.IsChunk) - { - return HttpReadOnlyMemoryBlockResult.Completed; - } - - if (this.ContentCompleted.HasValue && this.ContentCompleted.Value) - { - return HttpReadOnlyMemoryBlockResult.FromResult(this.m_contentMemory); - } - var blockResult = await base.ReadAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - if (blockResult.IsCompleted) - { - this.ContentCompleted = true; - } - return blockResult; - } - - /// - internal override void InternalSetContent(in ReadOnlyMemory content) - { - this.m_contentMemory = content; - this.ContentLength = content.Length; - this.ContentCompleted = true; - } - #region Write /// /// 异步写入指定的只读内存数据。 /// /// 要写入的只读内存数据。 + /// 可取消令箭 /// 一个任务,表示异步写入操作。 - public async Task WriteAsync(ReadOnlyMemory memory) + public async Task WriteAsync(ReadOnlyMemory memory, CancellationToken cancellationToken = default) { this.ThrowIfResponsed(); + var transport = this.GetITransport(); + var writer = new PipeBytesWriter(transport.Writer); + if (!this.m_sentHeader) { - var byteBlock = new ValueByteBlock(1024); - try - { - this.BuildHeader(ref byteBlock); - await this.InternalSendAsync(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - finally - { - byteBlock.Dispose(); - } + this.BuildHeader(ref writer); + await writer.FlushAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); this.m_sentHeader = true; } @@ -315,34 +174,19 @@ public class HttpResponse : HttpBase if (this.IsChunk) { - var byteBlock = new ValueByteBlock(count + 1024); - try - { - TouchSocketHttpUtility.AppendHex(ref byteBlock, count); - TouchSocketHttpUtility.AppendRn(ref byteBlock); - byteBlock.Write(memory.Span); - TouchSocketHttpUtility.AppendRn(ref byteBlock); - await this.InternalSendAsync(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.m_sentLength += count; - } - finally - { - byteBlock.Dispose(); - } - //using () - //{ - // byteBlock.Write(Encoding.UTF8.GetBytes($"{count:X}\r\n")); - // byteBlock.Write(memory.Span); - // byteBlock.Write(Encoding.UTF8.GetBytes("\r\n")); - // await this.InternalSendAsync(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - // this.m_sentLength += count; - //} + TouchSocketHttpUtility.AppendHex(ref writer, count); + TouchSocketHttpUtility.AppendRn(ref writer); + writer.Write(memory.Span); + TouchSocketHttpUtility.AppendRn(ref writer); + await writer.FlushAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + this.m_sentLength += count; } else { if (this.m_sentLength + count <= this.ContentLength) { - await this.InternalSendAsync(memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + writer.Write(memory.Span); + await writer.FlushAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); this.m_sentLength += count; if (this.m_sentLength == this.ContentLength) { @@ -355,16 +199,8 @@ public class HttpResponse : HttpBase #endregion Write - internal override void ResetHttp() + protected internal override void Reset() { - base.ResetHttp(); - this.m_sentHeader = false; - this.m_sentLength = 0; - this.Responsed = false; - this.IsChunk = false; - this.StatusCode = 200; - this.StatusMessage = "Success"; - this.Content = default; if (this.m_isServer) { this.m_canWrite = true; @@ -373,6 +209,14 @@ public class HttpResponse : HttpBase { this.m_canWrite = false; } + base.Reset(); + this.m_sentHeader = false; + this.m_sentLength = 0; + this.Responsed = false; + this.IsChunk = false; + this.StatusCode = 200; + this.StatusMessage = "Success"; + this.Content = default; } /// @@ -408,26 +252,6 @@ public class HttpResponse : HttpBase } } - //private static bool TryParseStatusCode(ReadOnlySpan span, out int code) - //{ - // code = 0; - // if (span.Length != 3) - // { - // return false; - // } - - // for (var i = 0; i < 3; i++) - // { - // if (span[i] < (byte)'0' || span[i] > (byte)'9') - // { - // return false; - // } - // } - - // code = 100 * (span[0] - '0') + 10 * (span[1] - '0') + (span[2] - '0'); - // return true; - //} - private static bool TryParseStatusCode(ReadOnlySpan span, out int code) { code = 0; @@ -480,42 +304,34 @@ public class HttpResponse : HttpBase return true; } - private void BuildHeader(ref TByteBlock byteBlock) where TByteBlock : IByteBlock + private void BuildHeader(ref TWriter writer) where TWriter : IBytesWriter { - TouchSocketHttpUtility.AppendHTTP(ref byteBlock); - TouchSocketHttpUtility.AppendSlash(ref byteBlock); - TouchSocketHttpUtility.AppendUtf8String(ref byteBlock, this.ProtocolVersion); - TouchSocketHttpUtility.AppendSpace(ref byteBlock); - TouchSocketHttpUtility.AppendUtf8String(ref byteBlock, this.StatusCode.ToString()); - TouchSocketHttpUtility.AppendSpace(ref byteBlock); - TouchSocketHttpUtility.AppendUtf8String(ref byteBlock, this.StatusMessage); - TouchSocketHttpUtility.AppendRn(ref byteBlock); - //stringBuilder.Append($"HTTP/{this.ProtocolVersion} {this.StatusCode} {this.StatusMessage}\r\n"); - - //if (this.IsChunk) - //{ - // this.Headers.Add(HttpHeaders.TransferEncoding, "chunked"); - //} + TouchSocketHttpUtility.AppendHTTP(ref writer); + TouchSocketHttpUtility.AppendSlash(ref writer); + TouchSocketHttpUtility.AppendUtf8String(ref writer, this.ProtocolVersion); + TouchSocketHttpUtility.AppendSpace(ref writer); + TouchSocketHttpUtility.AppendUtf8String(ref writer, this.StatusCode.ToString()); + TouchSocketHttpUtility.AppendSpace(ref writer); + TouchSocketHttpUtility.AppendUtf8String(ref writer, this.StatusMessage); + TouchSocketHttpUtility.AppendRn(ref writer); foreach (var header in this.Headers) { - TouchSocketHttpUtility.AppendUtf8String(ref byteBlock, header.Key); - TouchSocketHttpUtility.AppendColon(ref byteBlock); - TouchSocketHttpUtility.AppendSpace(ref byteBlock); - TouchSocketHttpUtility.AppendUtf8String(ref byteBlock, header.Value); - TouchSocketHttpUtility.AppendRn(ref byteBlock); - //stringBuilder.Append($"{header}: "); - //stringBuilder.Append(this.Headers[header] + "\r\n"); + TouchSocketHttpUtility.AppendUtf8String(ref writer, header.Key); + TouchSocketHttpUtility.AppendColon(ref writer); + TouchSocketHttpUtility.AppendSpace(ref writer); + TouchSocketHttpUtility.AppendUtf8String(ref writer, header.Value); + TouchSocketHttpUtility.AppendRn(ref writer); + } - TouchSocketHttpUtility.AppendRn(ref byteBlock); - //stringBuilder.Append("\r\n"); - //byteBlock.Write(Encoding.UTF8.GetBytes(stringBuilder.ToString())); + TouchSocketHttpUtility.AppendRn(ref writer); + } - private Task InternalSendAsync(ReadOnlyMemory memory) + private ITransport GetITransport() { - return this.m_isServer ? this.m_httpSessionClient.InternalSendAsync(memory) : this.m_httpClientBase.InternalSendAsync(memory); + return this.m_isServer ? this.m_httpSessionClient.InternalTransport : this.m_httpClientBase.InternalTransport; } private void ParseProtocol(ReadOnlySpan protocolSpan) diff --git a/src/TouchSocket.Http/Common/HttpResponseResult.cs b/src/TouchSocket.Http/Common/HttpResponseResult.cs index 12ed6100b..dd5d1089b 100644 --- a/src/TouchSocket.Http/Common/HttpResponseResult.cs +++ b/src/TouchSocket.Http/Common/HttpResponseResult.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Http; /// @@ -19,32 +17,22 @@ namespace TouchSocket.Http; /// public readonly struct HttpResponseResult : IDisposable { - /// - /// 一个操作委托,用于在Dispose时执行特定操作以释放资源。 - /// - private readonly Action m_action; + private readonly ClientHttpResponse m_response; - /// - /// 初始化HttpResponseResult结构体。 - /// - /// HTTP响应对象,用于处理HTTP请求的响应。 - /// 一个Action委托,将在Dispose方法中调用,用于执行资源释放操作。 - public HttpResponseResult(HttpResponse response, Action action) + internal HttpResponseResult(ClientHttpResponse response) { - this.Response = response; - this.m_action = action; + this.m_response = response; } /// /// 获取HTTP响应对象。 /// - public HttpResponse Response { get; } + public HttpResponse Response => this.m_response; - /// - /// 执行资源释放操作。调用构造函数中传入的Action委托以执行具体释放逻辑。 - /// + + /// public void Dispose() { - this.m_action.Invoke(); + this.m_response.Reset(); } } \ No newline at end of file diff --git a/src/TouchSocket.Http/Common/Internal/InternalHttpCollection.cs b/src/TouchSocket.Http/Common/Internal/InternalHttpCollection.cs new file mode 100644 index 000000000..8dd9a0811 --- /dev/null +++ b/src/TouchSocket.Http/Common/Internal/InternalHttpCollection.cs @@ -0,0 +1,110 @@ +//------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +//------------------------------------------------------------------------------ + +namespace TouchSocket.Http; + +internal abstract class InternalHttpCollection : IDictionary +{ + private readonly Dictionary m_dictionary; + + protected InternalHttpCollection(IEqualityComparer comparer = null) + { + m_dictionary = new Dictionary(comparer ?? StringComparer.OrdinalIgnoreCase); + } + + public TextValues this[string key] + { + get + { + ThrowHelper.ThrowIfNull(key, nameof(key)); + return m_dictionary.TryGetValue(key, out var value) ? value : TextValues.Empty; + } + set + { + ThrowHelper.ThrowIfNull(key, nameof(key)); + m_dictionary[key] = value; + } + } + + public ICollection Keys => m_dictionary.Keys; + + public ICollection Values => m_dictionary.Values; + + public int Count => m_dictionary.Count; + + public bool IsReadOnly => false; + + public void Add(string key, TextValues value) + { + ThrowHelper.ThrowIfNull(key, nameof(key)); + if (m_dictionary.TryGetValue(key, out var old)) + { + m_dictionary[key] = Merge(old, value); + } + else + { + m_dictionary.Add(key, value); + } + } + + public bool ContainsKey(string key) + { + ThrowHelper.ThrowIfNull(key, nameof(key)); + return m_dictionary.ContainsKey(key); + } + + public bool Remove(string key) + { + ThrowHelper.ThrowIfNull(key, nameof(key)); + return m_dictionary.Remove(key); + } + + public bool TryGetValue(string key, out TextValues value) + { + ThrowHelper.ThrowIfNull(key, nameof(key)); + return m_dictionary.TryGetValue(key, out value); + } + + public void Add(KeyValuePair item) => Add(item.Key, item.Value); + + public void Clear() => m_dictionary.Clear(); + + public bool Contains(KeyValuePair item) => ((IDictionary)m_dictionary).Contains(item); + + public void CopyTo(KeyValuePair[] array, int arrayIndex) => ((IDictionary)m_dictionary).CopyTo(array, arrayIndex); + + public bool Remove(KeyValuePair item) => ((IDictionary)m_dictionary).Remove(item); + + public IEnumerator> GetEnumerator() => m_dictionary.GetEnumerator(); + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); + + protected static TextValues Merge(TextValues a, TextValues b) + { + if (a.IsEmpty) return b; + if (b.IsEmpty) return a; + var ac = a.Count; + var bc = b.Count; + var arrA = a.ToArray(); + var arrB = b.ToArray(); + var newArr = new string[ac + bc]; + Array.Copy(arrA, 0, newArr, 0, ac); + Array.Copy(arrB, 0, newArr, ac, bc); + return new TextValues(newArr); + } + + public TextValues Get(string key) + { + ThrowHelper.ThrowIfNull(key, nameof(key)); + return m_dictionary.TryGetValue(key, out var value) ? value : TextValues.Empty; + } +} diff --git a/src/TouchSocket.Http/Common/Internal/InternalHttpHeader.cs b/src/TouchSocket.Http/Common/Internal/InternalHttpHeader.cs index edff0b930..666cf3576 100644 --- a/src/TouchSocket.Http/Common/Internal/InternalHttpHeader.cs +++ b/src/TouchSocket.Http/Common/Internal/InternalHttpHeader.cs @@ -10,41 +10,29 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using TouchSocket.Core; - namespace TouchSocket.Http; -internal sealed class InternalHttpHeader : Dictionary, IHttpHeader +internal sealed class InternalHttpHeader : InternalHttpCollection, IHttpHeader { public InternalHttpHeader() : base(StringComparer.OrdinalIgnoreCase) { } - public new string this[string key] + public bool Contains(string key, TextValues value, bool ignoreCase = true) { - get - { - ThrowHelper.ThrowArgumentNullExceptionIf(key, nameof(key)); - return this.TryGetValue(key, out var value) ? value : null; - } - set - { - ThrowHelper.ThrowArgumentNullExceptionIf(key, nameof(key)); - base[key] = value; - } - } + ThrowHelper.ThrowIfNull(key, nameof(key)); - public new void Add(string key, string value) - { - ThrowHelper.ThrowArgumentNullExceptionIf(key, nameof(key)); - base[key] = value; // 直接覆盖,避免二次查找 - } + if (!TryGetValue(key, out var headerValue)) + { + return false; + } - public string Get(string key) - { - ThrowHelper.ThrowArgumentNullExceptionIf(key, nameof(key)); - return this.TryGetValue(key, out var value) ? value : null; + if (value == TextValues.Empty) + { + return headerValue == TextValues.Empty; + } + + var comparison = ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; + return string.Equals(headerValue, value, comparison); } } \ No newline at end of file diff --git a/src/TouchSocket.Http/Common/Internal/InternalHttpParams.cs b/src/TouchSocket.Http/Common/Internal/InternalHttpParams.cs index 4755e08b6..3f123a19e 100644 --- a/src/TouchSocket.Http/Common/Internal/InternalHttpParams.cs +++ b/src/TouchSocket.Http/Common/Internal/InternalHttpParams.cs @@ -10,36 +10,11 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Collections.Generic; -using TouchSocket.Core; - namespace TouchSocket.Http; -internal sealed class InternalHttpParams : Dictionary, IHttpParams +internal sealed class InternalHttpParams : InternalHttpCollection, IHttpParams { - public new string this[string key] + public InternalHttpParams() : base(StringComparer.Ordinal) { - get - { - ThrowHelper.ThrowArgumentNullExceptionIf(key, nameof(key)); - return this.TryGetValue(key, out var value) ? value : null; - } - set - { - ThrowHelper.ThrowArgumentNullExceptionIf(key, nameof(key)); - base[key] = value; - } - } - - public new void Add(string key, string value) - { - ThrowHelper.ThrowArgumentNullExceptionIf(key, nameof(key)); - base[key] = value; // 直接覆盖,避免二次查找 - } - - public string Get(string key) - { - ThrowHelper.ThrowArgumentNullExceptionIf(key, nameof(key)); - return this.TryGetValue(key, out var value) ? value : null; } } \ No newline at end of file diff --git a/src/TouchSocket.Http/Common/InternalFormCollection.cs b/src/TouchSocket.Http/Common/InternalFormCollection.cs index 39831f400..4f4163f3b 100644 --- a/src/TouchSocket.Http/Common/InternalFormCollection.cs +++ b/src/TouchSocket.Http/Common/InternalFormCollection.cs @@ -10,19 +10,18 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; using System.Collections.Specialized; -using System.Text; -using TouchSocket.Core; namespace TouchSocket.Http; +/// +/// 性能还没有优化,后续会优化2025.7.20 +/// internal class InternalFormCollection : Dictionary, IFormCollection { private readonly InternalMultifileCollection m_files = new InternalMultifileCollection(); - public InternalFormCollection(ReadOnlyMemory context, Span boundary) : this() + public InternalFormCollection(ReadOnlyMemory context, ReadOnlySpan boundary) : this() { if (context.IsEmpty) { diff --git a/src/TouchSocket.Http/Common/InternalMultifileCollection.cs b/src/TouchSocket.Http/Common/InternalMultifileCollection.cs index 6227d4ce6..e78c7cfdf 100644 --- a/src/TouchSocket.Http/Common/InternalMultifileCollection.cs +++ b/src/TouchSocket.Http/Common/InternalMultifileCollection.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Collections.Generic; - namespace TouchSocket.Http; internal class InternalMultifileCollection : List, IMultifileCollection diff --git a/src/TouchSocket.Http/Common/ServerHttpRequest.cs b/src/TouchSocket.Http/Common/ServerHttpRequest.cs new file mode 100644 index 000000000..4253377c6 --- /dev/null +++ b/src/TouchSocket.Http/Common/ServerHttpRequest.cs @@ -0,0 +1,144 @@ +//------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +//------------------------------------------------------------------------------ + +namespace TouchSocket.Http; + +internal sealed class ServerHttpRequest : HttpRequest +{ + private readonly AsyncExchange> m_asyncExchange; + + private ReadOnlyMemory m_contentMemory; + + public ServerHttpRequest(HttpSessionClient httpSessionClient) : base(httpSessionClient) + { + this.m_asyncExchange = new AsyncExchange>(); + } + + protected internal override void Reset() + { + if (this.m_asyncExchange.IsCompleted) + { + this.m_asyncExchange.Reset(); + } + base.Reset(); + } + + /// + public override async ValueTask> GetContentAsync(CancellationToken cancellationToken = default) + { + if (this.ContentStatus == ContentCompletionStatus.Unknown) + { + if (!this.IsChunk && this.ContentLength == 0) + { + this.m_contentMemory = ReadOnlyMemory.Empty; + this.ContentStatus = ContentCompletionStatus.ContentCompleted; + return this.m_contentMemory; + } + + if (!this.IsChunk && this.ContentLength > MaxCacheSize) + { + ThrowHelper.ThrowArgumentOutOfRangeException_MoreThan(nameof(this.ContentLength), this.ContentLength, MaxCacheSize); + } + + try + { + using (var memoryStream = new MemoryStream((int)this.ContentLength)) + { + while (true) + { + using (var blockResult = await this.ReadAsync(cancellationToken)) + { + var segment = blockResult.Memory.GetArray(); + if (blockResult.IsCompleted) + { + break; + } + memoryStream.Write(segment.Array, segment.Offset, segment.Count); + } + } + this.ContentStatus = ContentCompletionStatus.ContentCompleted; + this.m_contentMemory = memoryStream.ToArray(); + return this.m_contentMemory; + } + } + catch + { + this.ContentStatus = ContentCompletionStatus.Incomplete; + return default; + } + finally + { + } + } + else + { + return this.ContentStatus == ContentCompletionStatus.ContentCompleted ? this.m_contentMemory : default; + } + } + + /// + public override async ValueTask ReadAsync(CancellationToken cancellationToken = default) + { + if (this.ContentStatus == ContentCompletionStatus.ContentCompleted) + { + // 已经完成读取 + return new HttpReadOnlyMemoryBlockResult(default, this.m_contentMemory, true); + } + if (this.ContentStatus == ContentCompletionStatus.ReadCompleted) + { + ThrowHelper.ThrowInvalidOperationException("内容已读取完毕。"); + } + if (this.ContentLength == 0 && !this.IsChunk) + { + return HttpReadOnlyMemoryBlockResult.Completed; + } + + + var readLeaseTask = this.m_asyncExchange.ReadAsync(cancellationToken); + + ReadLease> readLease; + if (readLeaseTask.IsCompleted) + { + readLease = readLeaseTask.Result; + } + else + { + readLease = await readLeaseTask.ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + + var memory = readLease.Value; + + if (readLease.IsCompleted) + { + this.ContentStatus = ContentCompletionStatus.ReadCompleted; + } + + return new HttpReadOnlyMemoryBlockResult(readLease.Dispose, memory, readLease.IsCompleted); + } + + internal void CompleteInput() + { + this.m_asyncExchange.Complete(); + } + + internal ValueTask InternalInputAsync(in ReadOnlyMemory memory) + { + return this.m_asyncExchange.WriteAsync(memory, CancellationToken.None); + } + + /// + internal void InternalSetContent(ReadOnlyMemory content) + { + this.m_contentMemory = content; + this.ContentStatus = ContentCompletionStatus.ContentCompleted; + } +} \ No newline at end of file diff --git a/src/TouchSocket.Core/Serialization/FastBinary/Converts/DataTableFastBinaryConverter.cs b/src/TouchSocket.Http/Common/ServerHttpResponse.cs similarity index 57% rename from src/TouchSocket.Core/Serialization/FastBinary/Converts/DataTableFastBinaryConverter.cs rename to src/TouchSocket.Http/Common/ServerHttpResponse.cs index 446aa18ec..bffdd63bf 100644 --- a/src/TouchSocket.Core/Serialization/FastBinary/Converts/DataTableFastBinaryConverter.cs +++ b/src/TouchSocket.Http/Common/ServerHttpResponse.cs @@ -10,22 +10,21 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Data; +namespace TouchSocket.Http; -namespace TouchSocket.Core; - -internal class DataTableFastBinaryConverter : FastBinaryConverter +internal sealed class ServerHttpResponse : HttpResponse { - protected override DataTable Read(ref TByteBlock byteBlock, Type type) + public ServerHttpResponse(ServerHttpRequest request, HttpSessionClient httpSessionClient) : base(request, httpSessionClient) { - var bytes = byteBlock.ReadBytesPackage(); - return SerializeConvert.BinaryDeserialize(bytes); } - protected override void Write(ref TByteBlock byteBlock, in DataTable obj) + public override ValueTask> GetContentAsync(CancellationToken cancellationToken = default) { - var bytes = SerializeConvert.BinarySerialize(obj); - byteBlock.WriteBytesPackage(bytes); + throw new NotImplementedException(); + } + + public override ValueTask ReadAsync(CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); } } \ No newline at end of file diff --git a/src/TouchSocket.Http/Common/TouchSocketHttpUtility.cs b/src/TouchSocket.Http/Common/TouchSocketHttpUtility.cs index 2cd6f3e43..a39b16a42 100644 --- a/src/TouchSocket.Http/Common/TouchSocketHttpUtility.cs +++ b/src/TouchSocket.Http/Common/TouchSocketHttpUtility.cs @@ -10,9 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Text; -using TouchSocket.Core; +using System.Runtime.CompilerServices; namespace TouchSocket.Http; @@ -21,10 +19,23 @@ namespace TouchSocket.Http; /// public static class TouchSocketHttpUtility { + public const int MaxReadSize = 1024 * 1024; + + // HTTP头部解析相关常量 /// - /// 非缓存上限 + /// 冒号字节值 /// - public const int NoCacheMaxSize = 1024 * 1024; + public const byte COLON = (byte)':'; + + /// + /// 空格字节值 + /// + public const byte SPACE = (byte)' '; + + /// + /// 制表符字节值 + /// + public const byte TAB = (byte)'\t'; /// /// 获取一个只读的字节序列,表示回车换行(CRLF)。 @@ -32,108 +43,144 @@ public static class TouchSocketHttpUtility /// /// 一个包含回车和换行字节的只读字节序列。 /// - public static ReadOnlySpan CRLF => new byte[] { (byte)'\r', (byte)'\n' }; + public static ReadOnlySpan CRLF => "\r\n"u8; + + /// + /// 获取一个只读的字节序列,表示双回车换行(CRLFCRLF)。 + /// + /// + /// 一个包含双回车和换行字节的只读字节序列。 + /// + public static ReadOnlySpan CRLFCRLF => "\r\n\r\n"u8; /// /// 在 中追加 "&" 符号。 /// - /// 实现了 的类型。 - /// 字节块实例。 - public static void AppendAnd(ref TByteBlock byteBlock) where TByteBlock : IByteBlock + /// 实现了 的类型。 + /// 字节块实例。 + public static void AppendAnd(ref TWriter writer) where TWriter : IBytesWriter { - byteBlock.Write("&"u8); + writer.Write("&"u8); } /// /// 在 中追加 ":" 符号。 /// - /// 实现了 的类型。 - /// 字节块实例。 - public static void AppendColon(ref TByteBlock byteBlock) where TByteBlock : IByteBlock + /// 实现了 的类型。 + /// 字节块实例。 + public static void AppendColon(ref TWriter writer) where TWriter : IBytesWriter { - byteBlock.Write(":"u8); + writer.Write(":"u8); } /// /// 在 中追加 "=" 符号。 /// - /// 实现了 的类型。 - /// 字节块实例。 - public static void AppendEqual(ref TByteBlock byteBlock) where TByteBlock : IByteBlock + /// 实现了 的类型。 + /// 字节块实例。 + public static void AppendEqual(ref TWriter writer) where TWriter : IBytesWriter { - byteBlock.Write("="u8); + writer.Write("="u8); } /// /// 在 中追加 "HTTP" 字符串。 /// - /// 实现了 的类型。 - /// 字节块实例。 - public static void AppendHTTP(ref TByteBlock byteBlock) where TByteBlock : IByteBlock + /// 实现了 的类型。 + /// 字节块实例。 + public static void AppendHTTP(ref TWriter writer) where TWriter : IBytesWriter { - byteBlock.Write("HTTP"u8); + writer.Write("HTTP"u8); } /// /// 在 中追加 "?" 符号。 /// - /// 实现了 的类型。 - /// 字节块实例。 - public static void AppendQuestionMark(ref TByteBlock byteBlock) where TByteBlock : IByteBlock + /// 实现了 的类型。 + /// 字节块实例。 + public static void AppendQuestionMark(ref TWriter writer) where TWriter : IBytesWriter { - byteBlock.Write("?"u8); + writer.Write("?"u8); } /// /// 在 中追加回车换行符 "\r\n"。 /// - /// 实现了 的类型。 - /// 字节块实例。 - public static void AppendRn(ref TByteBlock byteBlock) where TByteBlock : IByteBlock + /// 实现了 的类型。 + /// 字节块实例。 + public static void AppendRn(ref TWriter writer) where TWriter : IBytesWriter { - byteBlock.Write("\r\n"u8); + writer.Write(CRLF); } /// /// 在 中追加 "/" 符号。 /// - /// 实现了 的类型。 - /// 字节块实例。 - public static void AppendSlash(ref TByteBlock byteBlock) where TByteBlock : IByteBlock + /// 实现了 的类型。 + /// 字节块实例。 + public static void AppendSlash(ref TWriter writer) where TWriter : IBytesWriter { - byteBlock.Write("/"u8); + writer.Write("/"u8); } /// /// 在 中追加空格符。 /// - /// 实现了 的类型。 - /// 字节块实例。 - public static void AppendSpace(ref TByteBlock byteBlock) where TByteBlock : IByteBlock + /// 实现了 的类型。 + /// 字节块实例。 + public static void AppendSpace(ref TWriter writer) where TWriter : IBytesWriter { - byteBlock.Write(StringExtension.DefaultSpaceUtf8Span); + writer.Write(StringExtension.DefaultSpaceUtf8Span); } /// /// 在 中追加指定的 UTF-8 编码字符串。 /// - /// 实现了 的类型。 - /// 字节块实例。 + /// 实现了 的类型。 + /// 字节块实例。 /// 要追加的字符串。 - public static void AppendUtf8String(ref TByteBlock byteBlock, string value) where TByteBlock : IByteBlock + public static void AppendUtf8String(ref TWriter writer, string value) where TWriter : IBytesWriter { - byteBlock.WriteNormalString(value, Encoding.UTF8); + WriterExtension.WriteNormalString(ref writer, value, Encoding.UTF8); } /// /// 在 中追加指定整数的十六进制表示。 /// - /// 实现了 的类型。 - /// 字节块实例。 + /// 实现了 的类型。 + /// 字节块实例。 /// 要追加的整数值。 - public static void AppendHex(ref TByteBlock byteBlock, int value) where TByteBlock : IByteBlock + public static void AppendHex(ref TWriter writer, int value) where TWriter : IBytesWriter { - AppendUtf8String(ref byteBlock, $"{value:X}"); + AppendUtf8String(ref writer, $"{value:X}"); + } + + /// + /// 检查字节是否为HTTP规范允许的空白字符(空格或制表符) + /// + /// 要检查的字节 + /// 如果是空白字符返回true,否则返回false + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool IsWhitespace(byte b) => b == SPACE || b == TAB; + + /// + /// 高效的空白字符去除,避免使用通用的Trim方法 + /// + /// 要处理的字节跨度 + /// 去除前后空白字符后的字节跨度 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ReadOnlySpan TrimWhitespace(ReadOnlySpan span) + { + var start = 0; + var end = span.Length - 1; + + while (start <= end && IsWhitespace(span[start])) + start++; + + while (end >= start && IsWhitespace(span[end])) + end--; + + return start > end ? ReadOnlySpan.Empty : span.Slice(start, end - start + 1); } internal static string UnescapeDataString(ReadOnlySpan urlSpan) @@ -152,16 +199,13 @@ public static class TouchSocketHttpUtility internal static string UnescapeDataString(ReadOnlySpan urlSpan) { #if NET9_0_OR_GREATER - return Uri.UnescapeDataString(urlSpan); + return Uri.UnescapeDataString(urlSpan); #else return Uri.UnescapeDataString(urlSpan.ToString()); #endif } - internal static bool IsWhitespace(byte b) => b == ' ' || b == '\t'; - - internal static int FindNextWhitespace(ReadOnlySpan span, int start) { for (var i = start; i < span.Length; i++) @@ -205,7 +249,7 @@ public static class TouchSocketHttpUtility { var key = TouchSocketHttpUtility.UnescapeDataString(keySpan); var value = TouchSocketHttpUtility.UnescapeDataString(valueSpan); - parameters.AddOrUpdate(key, value); + parameters.Add(key, value); } } } \ No newline at end of file diff --git a/src/TouchSocket.Http/Common/WebProxy/AuthenticationChallenge.cs b/src/TouchSocket.Http/Common/WebProxy/AuthenticationChallenge.cs deleted file mode 100644 index cfdf0eb50..000000000 --- a/src/TouchSocket.Http/Common/WebProxy/AuthenticationChallenge.cs +++ /dev/null @@ -1,153 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; -using System.Collections.Generic; -using System.Text; - -namespace TouchSocket.Http; - -/// -/// 处理代理认证凭证 -/// -[Obsolete("此配置已被弃用,不再支持代理", true)] -internal class AuthenticationChallenge -{ - /// - /// 构造 - /// - /// 服务器返回的凭证认证类型 - /// 基本凭证用户名密码 - /// 暂时不知道是什么 - public AuthenticationChallenge(string value, NetworkCredential credential, uint nonceCount = 0) - { - this.Parse(value, credential); - this.NonceCount = nonceCount; - } - - /// - /// 暂时不知 - /// - public uint NonceCount { get; set; } - - /// - /// 其实用不用他都一样 - /// - public Dictionary Parameters { get; set; } - - /// - /// 凭证类型 - /// - public AuthenticationType Type { get; set; } - - /// - /// 转换成凭证本文 - /// - /// - /// - public override string ToString() - { - return this.Type == AuthenticationType.Basic ? this.ToBasicString() : throw new Exception("该凭证类型不支持"); - } - - private void Parse(string value, NetworkCredential credential) - { - var chal = value.Split(new[] { ' ' }, 2); - if (chal.Length != 2) - throw new Exception("该凭证类型不支持"); - - var schm = chal[0].ToLower(); - this.Parameters = this.ParseParameters(chal[1]); - - if (this.Parameters.ContainsKey("username") == false) - this.Parameters.Add("username", credential.Username); - if (this.Parameters.ContainsKey("password") == false) - this.Parameters.Add("password", credential.Password); - - /* - * Basic基本类型貌似只需要用户名密码即可 - * if (this.parameters.ContainsKey("uri") == false) - this.parameters.Add("uri", credential.Domain);*/ - - this.Type = schm == "basic" ? AuthenticationType.Basic : throw new Exception("该凭证类型不支持"); - } - - private Dictionary ParseParameters(string value) - { - var res = new Dictionary(); - var values = this.SplitHeaderValue(value, ','); - foreach (var param in values) - { - var i = param.IndexOf('='); - var name = i > 0 ? param.Substring(0, i).Trim() : null; - var val = i < 0 - ? param.Trim().Trim('"') - : i < param.Length - 1 - ? param.Substring(i + 1).Trim().Trim('"') - : string.Empty; - - res.Add(name, val); - } - return res; - } - - private IEnumerable SplitHeaderValue(string value, params char[] separators) - { - var len = value.Length; - var end = len - 1; - - var buff = new StringBuilder(32); - var escaped = false; - var quoted = false; - - for (var i = 0; i <= end; i++) - { - var c = value[i]; - buff.Append(c); - if (c == '"') - { - if (escaped) - { - escaped = false; - continue; - } - quoted = !quoted; - continue; - } - if (c == '\\') - { - if (i == end) - break; - if (value[i + 1] == '"') - escaped = true; - continue; - } - if (Array.IndexOf(separators, c) > -1) - { - if (quoted) - continue; - buff.Length -= 1; - yield return buff.ToString(); - buff.Length = 0; - continue; - } - } - yield return buff.ToString(); - } - - private string ToBasicString() - { - var userPass = $"{this.Parameters["username"]}:{this.Parameters["password"]}"; - var cred = Convert.ToBase64String(Encoding.UTF8.GetBytes(userPass)); - return "Basic " + cred; - } -} \ No newline at end of file diff --git a/src/TouchSocket.Http/Common/WebProxy/NetworkCredential.cs b/src/TouchSocket.Http/Common/WebProxy/NetworkCredential.cs deleted file mode 100644 index f22fb89bf..000000000 --- a/src/TouchSocket.Http/Common/WebProxy/NetworkCredential.cs +++ /dev/null @@ -1,65 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; - -namespace TouchSocket.Http; - -/// -/// 代理身份认证 -/// -[Obsolete("此配置已被弃用,不再支持代理", true)] -public class NetworkCredential -{ - /// - /// 构造 - /// - /// - /// - /// 基本认证应该不需要这个 - /// - /// - /// - public NetworkCredential(string username, string password, string domain, params string[] roles) - { - if (username == null) - throw new ArgumentNullException("username"); - - if (username.Length == 0) - throw new ArgumentException("An empty string.", "username"); - this.Username = username; - this.Password = password; - this.Domain = domain; - this.Roles = roles; - } - - /// - /// 凭证用户名 - /// - - public string Username { get; } - - /// - /// 凭证密码 - /// - public string Password { get; } - - /// - /// Domain - /// - public string Domain { get; } - - /// - /// Roles - /// - public string[] Roles { get; } -} \ No newline at end of file diff --git a/src/TouchSocket.Http/Components/HttpClient.cs b/src/TouchSocket.Http/Components/HttpClient.cs index 9ccb8e02a..bce09b9b7 100644 --- a/src/TouchSocket.Http/Components/HttpClient.cs +++ b/src/TouchSocket.Http/Components/HttpClient.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading; -using System.Threading.Tasks; - namespace TouchSocket.Http; /// @@ -20,22 +17,26 @@ namespace TouchSocket.Http; /// public class HttpClient : HttpClientBase, IHttpClient { - /// - public Task ConnectAsync(int millisecondsTimeout, CancellationToken token) - { - return this.TcpConnectAsync(millisecondsTimeout, token); - } - + private readonly SemaphoreSlim m_semaphoreSlim = new SemaphoreSlim(1, 1); /// - public Task RequestAsync(HttpRequest request, int millisecondsTimeout = 10000, CancellationToken token = default) + public async Task ConnectAsync(CancellationToken cancellationToken) { - return this.ProtectedRequestAsync(request, millisecondsTimeout, token); + await this.m_semaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + try + { + await base.HttpConnectAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + finally + { + this.m_semaphoreSlim.Release(); + } } /// - public Task RequestContentAsync(HttpRequest request, int millisecondsTimeout = 10000, CancellationToken token = default) + public ValueTask RequestAsync(HttpRequest request, CancellationToken cancellationToken = default) { - return this.ProtectedRequestContentAsync(request, millisecondsTimeout, token); + return this.ProtectedRequestAsync(request, cancellationToken); } } \ No newline at end of file diff --git a/src/TouchSocket.Http/Components/HttpClientBase.cs b/src/TouchSocket.Http/Components/HttpClientBase.cs index eab9fc76b..615e1b5fc 100644 --- a/src/TouchSocket.Http/Components/HttpClientBase.cs +++ b/src/TouchSocket.Http/Components/HttpClientBase.cs @@ -10,11 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; +using System.Net; using TouchSocket.Sockets; namespace TouchSocket.Http; @@ -24,220 +20,259 @@ namespace TouchSocket.Http; /// public abstract class HttpClientBase : TcpClientBase, IHttpSession { - #region 字段 - - private readonly SemaphoreSlim m_semaphoreForRequest = new SemaphoreSlim(1, 1); - //private readonly AsyncAutoResetEvent m_waitRelease = new AsyncAutoResetEvent(); - private readonly WaitDataAsync m_waitResponseDataAsync = new WaitDataAsync(); - private bool m_getContent; - private HttpClientDataHandlingAdapter m_dataHandlingAdapter; - #endregion 字段 - - internal Task InternalSendAsync(ReadOnlyMemory memory) + /// + /// 初始化 类的新实例。 + /// + public HttpClientBase() { - return this.ProtectedDefaultSendAsync(memory); - } - - /// - protected override void Dispose(bool disposing) - { - if (disposing) - { - this.m_semaphoreForRequest.Dispose(); - //this.m_waitRelease.Dispose(); - this.m_waitResponseDataAsync.Dispose(); - } - base.Dispose(disposing); + this.m_httpClientResponse = new ClientHttpResponse(this); } /// - /// 设置用于处理单流数据的转换适配器 + /// 获取内部传输层对象,用于HTTP响应内容读取 /// - /// 要设置的SingleStreamDataHandlingAdapter实例 - protected void SetWarpAdapter(SingleStreamDataHandlingAdapter adapter) + internal ITransport InternalTransport => this.Transport; + + /// + /// 异步连接HTTP服务器,支持代理连接 + /// + /// 用于取消操作的CancellationToken + /// 返回一个任务,表示异步连接操作 + protected virtual async Task HttpConnectAsync(CancellationToken cancellationToken) { - // 将提供的适配器设置为当前数据处理适配器的WarpAdapter - this.m_dataHandlingAdapter.WarpAdapter = adapter; + this.ThrowIfDisposed(); + + this.ThrowIfConfigIsNull(); + + var proxy = this.Config.Proxy; + if (proxy != null) + { + await this.ConnectThroughProxyAsync(proxy, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + else + { + await base.TcpConnectAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + } + + /// + /// 此方法会一直抛出异常,请使用进行连接 + /// + /// + /// + /// + [Obsolete("请使用HttpConnectAsync进行连接", true)] + protected sealed override Task TcpConnectAsync(CancellationToken cancellationToken) + { + throw new NotImplementedException("请使用HttpConnectAsync进行连接"); + //return base.TcpConnectAsync(cancellationToken); + } + + /// + protected override void SafetyDispose(bool disposing) + { + base.SafetyDispose(disposing); + } + + private async Task ConnectThroughProxyAsync(IWebProxy proxy, CancellationToken cancellationToken) + { + var targetHost = this.RemoteIPHost; + var proxyUri = proxy.GetProxy(targetHost); + + if (proxyUri == null || proxyUri.Equals(targetHost)) + { + // 代理返回原始URI或null,直接连接 + await base.TcpConnectAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return; + } + + // 先连接到代理服务器 + var originalRemoteIPHost = this.RemoteIPHost; + try + { + // 临时设置代理服务器作为连接目标 + var proxyIPHost = new IPHost(proxyUri.ToString()); + var config = this.Config; + + config.SetRemoteIPHost(proxyIPHost); + + // 连接到代理服务器 + await base.TcpConnectAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + // 发送 CONNECT 请求建立隧道 + await this.EstablishProxyTunnelAsync(proxy, targetHost, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + await this.TryAuthenticateAsync(originalRemoteIPHost).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + finally + { + // 恢复原始的远程主机配置 + this.Config.SetRemoteIPHost(originalRemoteIPHost); + } + } + + private async Task TryAuthenticateAsync(IPHost iPHost) + { + if (!this.Config.TryGetValue(TouchSocketConfigExtension.ClientSslOptionProperty, out var sslOption)) + { + if (!iPHost.IsSsl) + { + return; + } + + sslOption = new ClientSslOption() + { + TargetHost = iPHost.Host + }; + } + await this.AuthenticateAsync(sslOption).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + + private async Task EstablishProxyTunnelAsync(IWebProxy proxy, IPHost targetHost, CancellationToken cancellationToken) + { + // 构建 CONNECT 请求 + var connectRequest = new HttpRequest(); + connectRequest.Method = HttpMethod.Connect; + connectRequest.URL = $"{targetHost.Host}:{targetHost.Port}"; + connectRequest.Headers.Add(HttpHeaders.Host, $"{targetHost.Host}:{targetHost.Port}"); + + // 处理代理身份验证 + var credentials = proxy.Credentials; + if (credentials != null) + { + var networkCredential = credentials.GetCredential(targetHost, "Basic"); + if (networkCredential != null && !string.IsNullOrEmpty(networkCredential.UserName)) + { + var auth = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{networkCredential.UserName}:{networkCredential.Password}")); + connectRequest.Headers.Add("Proxy-Authorization", $"Basic {auth}"); + } + } + + // 发送 CONNECT 请求 + // 接收代理响应 + using (var responseResult = await this.ProtectedRequestAsync(connectRequest, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) + { + var response = responseResult.Response; + // 检查响应状态 + if (response.StatusCode == 200) + { + // 隧道建立成功,现在可以发送HTTPS流量 + return; + } + else if (response.StatusCode == 407) + { + // 代理身份验证失败 + throw new ProxyAuthenticationException("代理服务器要求身份验证"); + } + else + { + // 其他错误 + throw new ProxyConnectionException($"代理连接失败: {response.StatusCode} {response.StatusMessage}"); + } + } } #region Request - private void ReleaseLock() - { - this.m_semaphoreForRequest.Release(); - this.m_dataHandlingAdapter.SetCompleteLock(); - //this.m_waitRelease.Set(); - } + private readonly ClientHttpResponse m_httpClientResponse; /// /// 异步发送Http请求,并仅等待响应头 /// /// 要发送的HttpRequest对象 - /// 超时时间,单位为毫秒,默认为10秒 - /// 用于取消操作的CancellationToken + /// 用于取消操作的CancellationToken /// 返回HttpResponseResult对象,包含响应结果和释放锁的方法 /// 当操作超时时抛出 /// 当操作被取消时抛出 /// 当发生其他异常时抛出 - protected async Task ProtectedRequestAsync(HttpRequest request, int millisecondsTimeout = 10 * 1000, CancellationToken token = default) + protected async ValueTask ProtectedRequestAsync(HttpRequest request, CancellationToken cancellationToken) { - // 等待信号量,以控制并发请求的数量,超时和取消策略 - await this.m_semaphoreForRequest.WaitTimeAsync(millisecondsTimeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - try - { - // 标记不获取响应内容 - this.m_getContent = false; - // 重置状态,为发送请求做准备 - this.Reset(token); - request.Headers.TryAdd(HttpHeaders.Host, this.RemoteIPHost.Authority); - await this.BuildAndSend(request, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + // 设置Host头部 + request.Headers.TryAdd(HttpHeaders.Host, this.RemoteIPHost.Authority); - // 等待响应状态,超时设定 - var status = await this.m_waitResponseDataAsync.WaitAsync(millisecondsTimeout).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + // 发送请求 + await this.BuildAndSendAsync(request, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - // 如果响应状态不是运行中,抛出异常 - status.ThrowIfNotRunning(); + // 直接从Transport的Reader读取响应 + var response = await this.ReadHttpResponseAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - // 返回响应结果对象 - return new HttpResponseResult(this.m_waitResponseDataAsync.WaitResult, this.ReleaseLock); - - } - catch - { - // 发生异常时,释放信号量 - this.m_semaphoreForRequest.Release(); - throw; - } + return new HttpResponseResult(response); } - private async Task BuildAndSend(HttpRequest request, CancellationToken token) + private async Task BuildAndSendAsync(HttpRequest request, CancellationToken cancellationToken) { var content = request.Content; + var writer = new PipeBytesWriter(this.Transport.Writer); if (content == null) { - var byteBlock = new ValueByteBlock(1024); - try - { - request.BuildHeader(ref byteBlock); - // 异步发送请求 - await this.ProtectedDefaultSendAsync(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - finally - { - byteBlock.Dispose(); - } + request.BuildHeader(ref writer); + await writer.FlushAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return; } - else + + content.InternalTryComputeLength(out var contentLength); + content.InternalBuildingHeader(request.Headers); + request.BuildHeader(ref writer); + + var result = content.InternalBuildingContent(ref writer); + + await writer.FlushAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + if (!result) { - content.InternalTryComputeLength(out var contentLength); - var byteBlock = new ValueByteBlock((int)Math.Min(contentLength + 1024, 1024 * 64)); - content.InternalBuildingHeader(request.Headers); - try - { - request.BuildHeader(ref byteBlock); - - var result = content.InternalBuildingContent(ref byteBlock); - - // 异步发送请求 - await this.ProtectedDefaultSendAsync(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - - if (!result) - { - await content.InternalWriteContent(this.ProtectedDefaultSendAsync, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - } - finally - { - byteBlock.Dispose(); - } + await content.InternalWriteContent(this.Transport.Writer, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } - /// - /// 异步发送Http请求,并等待全部响应 + /// 直接从Transport读取HTTP响应 /// - /// Http请求对象 - /// 超时时间,单位为毫秒,默认为10秒 - /// 取消令牌 - /// 返回Http响应结果 - /// 当操作超时时抛出 - /// 当操作被取消时抛出 - /// 当发生其他异常时抛出 - protected async Task ProtectedRequestContentAsync(HttpRequest request, int millisecondsTimeout = 10 * 1000, CancellationToken token = default) + /// 取消令牌 + /// HTTP响应对象 + private async Task ReadHttpResponseAsync(CancellationToken cancellationToken) { - // 使用信号量控制并发,确保系统稳定性 - await this.m_semaphoreForRequest.WaitTimeAsync(millisecondsTimeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - try - { - // 标记为获取内容状态 - this.m_getContent = true; - - // 重置状态,为发送请求做准备 - this.Reset(token); - - request.Headers.TryAdd(HttpHeaders.Host, this.RemoteIPHost.Authority); - - await this.BuildAndSend(request, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - - // 等待响应状态,超时设定 - var status = await this.m_waitResponseDataAsync.WaitAsync(millisecondsTimeout).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - - // 如果响应状态不是运行中,抛出异常 - status.ThrowIfNotRunning(); - - // 返回响应结果对象 - return new HttpResponseResult(this.m_waitResponseDataAsync.WaitResult, this.ReleaseLock); - - } - catch - { - // 发生异常时,释放信号量 - this.m_semaphoreForRequest.Release(); - throw; - } + this.m_httpClientResponse.Reset(); + await this.m_httpClientResponse.ReadHeader(cancellationToken); + return this.m_httpClientResponse; } #endregion Request #region override - /// - protected override async Task OnTcpConnecting(ConnectingEventArgs e) - { - this.Protocol = Protocol.Http; - this.m_dataHandlingAdapter = new HttpClientDataHandlingAdapter(); - this.SetAdapter(this.m_dataHandlingAdapter); - await base.OnTcpConnecting(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - /// protected override async Task OnTcpClosed(ClosedEventArgs e) { - this.m_waitResponseDataAsync.Cancel(); await base.OnTcpClosed(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// - protected override async Task OnTcpReceived(ReceivedDataEventArgs e) + protected override async Task OnTcpConnecting(ConnectingEventArgs e) { - if (e.RequestInfo is HttpResponse response) + this.Protocol = Protocol.Http; + + await base.OnTcpConnecting(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + + /// + /// Http客户端中,不再允许子类在中自动接收数据 + protected sealed override async Task ReceiveLoopAsync(ITransport transport) + { + if (transport.ClosedToken.IsCancellationRequested) { - if (this.m_getContent) - { - await response.GetContentAsync(CancellationToken.None).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - this.m_waitResponseDataAsync.Set(response); - //await this.SetAsync(response).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return; + } + try + { + //重写接收循环,阻止基类自动接收。 + //http的接收由适配器自行处理。 + await Task.Delay(-1, transport.ClosedToken); + } + catch + { + //忽略异常 } } #endregion override - - private void Reset(in CancellationToken token) - { - //this.m_waitRelease.Reset(); - this.m_waitResponseDataAsync.Reset(); - this.m_waitResponseDataAsync.SetCancellationToken(token); - } } \ No newline at end of file diff --git a/src/TouchSocket.Http/Components/HttpClientSlim.cs b/src/TouchSocket.Http/Components/HttpClientSlim.cs index 0302921e9..c905d6a44 100644 --- a/src/TouchSocket.Http/Components/HttpClientSlim.cs +++ b/src/TouchSocket.Http/Components/HttpClientSlim.cs @@ -10,13 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -#if SystemNetHttp -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Sockets; namespace TouchSocket.Http; @@ -50,4 +43,3 @@ public class HttpClientSlim : SetupConfigObject base.LoadConfig(config); } } -#endif \ No newline at end of file diff --git a/src/TouchSocket.Http/Components/HttpSessionClient.WebSocket.cs b/src/TouchSocket.Http/Components/HttpSessionClient.WebSocket.cs index e3d98da6a..a74786969 100644 --- a/src/TouchSocket.Http/Components/HttpSessionClient.WebSocket.cs +++ b/src/TouchSocket.Http/Components/HttpSessionClient.WebSocket.cs @@ -11,8 +11,6 @@ //------------------------------------------------------------------------------ using System.Net.WebSockets; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Http.WebSockets; using TouchSocket.Resources; using TouchSocket.Sockets; @@ -31,29 +29,29 @@ public partial class HttpSessionClient : TcpSessionClientBase, IHttpSessionClien #region 事件 - private Task PrivateWebSocketHandshaking(IWebSocket webSocket, HttpContextEventArgs e) + private Task PrivateWebSocketConnecting(IWebSocket webSocket, HttpContextEventArgs e) { - return this.OnWebSocketHandshaking(webSocket, e); + return this.OnWebSocketConnecting(webSocket, e); } - private Task PrivateWebSocketHandshaked(IWebSocket webSocket, HttpContextEventArgs e) + private Task PrivateWebSocketConnected(IWebSocket webSocket, HttpContextEventArgs e) { - return this.OnWebSocketHandshaked(webSocket, e); + return this.OnWebSocketConnected(webSocket, e); } private async Task PrivateWebSocketReceived(WSDataFrame dataFrame) { if (dataFrame.IsClose && this.GetValue(WebSocketFeature.AutoCloseProperty)) { - var bytes = dataFrame.PayloadData; - bytes.SeekToStart(); - if (bytes.Length >= 2) + var payloadMemory = dataFrame.PayloadData; + var payloadSpan = payloadMemory.Span; + if (payloadSpan.Length >= 2) { - var closeStatus = (WebSocketCloseStatus)bytes.ReadUInt16(EndianType.Big); + var closeStatus = (WebSocketCloseStatus)payloadSpan.ReadValue(EndianType.Big); this.m_webSocket.CloseStatus = closeStatus; } - var msg = bytes.ReadToSpan(bytes.CanReadLength).ToString(System.Text.Encoding.UTF8); + var msg = payloadSpan.ToString(System.Text.Encoding.UTF8); await this.PrivateWebSocketClosing(new ClosingEventArgs(msg)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); await this.m_webSocket.CloseAsync(msg ?? "Auto closed successful").ConfigureAwait(EasyTask.ContinueOnCapturedContext); @@ -67,7 +65,7 @@ public partial class HttpSessionClient : TcpSessionClientBase, IHttpSessionClien if (this.m_webSocket.AllowAsyncRead) { - await this.m_webSocket.InputReceiveAsync(dataFrame).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.m_webSocket.InputReceiveAsync(dataFrame, CancellationToken.None).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return; } @@ -84,7 +82,7 @@ public partial class HttpSessionClient : TcpSessionClientBase, IHttpSessionClien this.m_webSocket.Online = false; if (this.m_webSocket.AllowAsyncRead) { - await this.m_webSocket.Complete(e.Message).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + this.m_webSocket.Complete(e.Message); } await this.OnWebSocketClosed(this.m_webSocket, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } @@ -95,10 +93,10 @@ public partial class HttpSessionClient : TcpSessionClientBase, IHttpSessionClien /// WebSocket对象,用于进行WebSocket通信 /// HTTP上下文参数,提供关于HTTP请求和响应的信息 /// 返回一个任务,该任务在插件处理完成后结束 - protected virtual async Task OnWebSocketHandshaking(IWebSocket webSocket, HttpContextEventArgs e) + protected virtual async Task OnWebSocketConnecting(IWebSocket webSocket, HttpContextEventArgs e) { // 提前WebSocket握手过程中的插件执行 - await this.PluginManager.RaiseAsync(typeof(IWebSocketHandshakingPlugin), this.Resolver, webSocket, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.PluginManager.RaiseIWebSocketConnectingPluginAsync(this.Resolver, webSocket, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// @@ -107,10 +105,10 @@ public partial class HttpSessionClient : TcpSessionClientBase, IHttpSessionClien /// WebSocket对象,表示握手成功的WebSocket连接。 /// HttpContextEventArgs对象,包含HTTP上下文信息。 /// 一个表示事件处理完成的Task对象。 - protected virtual Task OnWebSocketHandshaked(IWebSocket webSocket, HttpContextEventArgs e) + protected virtual async Task OnWebSocketConnected(IWebSocket webSocket, HttpContextEventArgs e) { - // 在一个任务中异步调用插件管理器的RaiseAsync方法,传递WebSocket和HTTP上下文参数 - return Task.Run(() => this.PluginManager.RaiseAsync(typeof(IWebSocketHandshakedPlugin), this.Resolver, webSocket, e)); + await this.PluginManager.RaiseIWebSocketConnectedPluginAsync(this.Resolver, webSocket, e) + .ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// @@ -124,7 +122,7 @@ public partial class HttpSessionClient : TcpSessionClientBase, IHttpSessionClien /// protected virtual async Task OnWebSocketReceived(IWebSocket webSocket, WSDataFrameEventArgs e) { - await this.PluginManager.RaiseAsync(typeof(IWebSocketReceivedPlugin), this.Resolver, webSocket, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.PluginManager.RaiseIWebSocketReceivedPluginAsync(this.Resolver, webSocket, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// @@ -136,7 +134,7 @@ public partial class HttpSessionClient : TcpSessionClientBase, IHttpSessionClien protected virtual async Task OnWebSocketClosing(IWebSocket webSocket, ClosingEventArgs e) { // 提前通知所有IWebSocketClosingPlugin插件,WebSocket即将关闭 - await this.PluginManager.RaiseAsync(typeof(IWebSocketClosingPlugin), this.Resolver, webSocket, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.PluginManager.RaiseIWebSocketClosingPluginAsync(this.Resolver, webSocket, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// @@ -149,7 +147,7 @@ public partial class HttpSessionClient : TcpSessionClientBase, IHttpSessionClien /// protected virtual async Task OnWebSocketClosed(IWebSocket webSocket, ClosedEventArgs e) { - await this.PluginManager.RaiseAsync(typeof(IWebSocketClosedPlugin), this.Resolver, webSocket, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.PluginManager.RaiseIWebSocketClosedPluginAsync(this.Resolver, webSocket, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } #endregion 事件 @@ -178,7 +176,7 @@ public partial class HttpSessionClient : TcpSessionClientBase, IHttpSessionClien var webSocket = new InternalWebSocket(this); - await this.PrivateWebSocketHandshaking(webSocket, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.PrivateWebSocketConnecting(webSocket, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); if (response.Responsed) { @@ -191,7 +189,7 @@ public partial class HttpSessionClient : TcpSessionClientBase, IHttpSessionClien await response.AnswerAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - _ = EasyTask.SafeRun(this.PrivateWebSocketHandshaked, webSocket, new HttpContextEventArgs(httpContext)); + _ = EasyTask.SafeRun(this.PrivateWebSocketConnected, webSocket, new HttpContextEventArgs(httpContext)); return Result.Success; } @@ -220,7 +218,7 @@ public partial class HttpSessionClient : TcpSessionClientBase, IHttpSessionClien private void InitWebSocket(InternalWebSocket webSocket) { - this.SetAdapter(new WebSocketDataHandlingAdapter()); + this.SetAdapter(this.m_webSocketAdapter); this.Protocol = Protocol.WebSocket; this.m_webSocket = webSocket; webSocket.Online = true; diff --git a/src/TouchSocket.Http/Components/HttpSessionClient.cs b/src/TouchSocket.Http/Components/HttpSessionClient.cs index 7d4c114b7..a1dcc3e61 100644 --- a/src/TouchSocket.Http/Components/HttpSessionClient.cs +++ b/src/TouchSocket.Http/Components/HttpSessionClient.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Http.WebSockets; using TouchSocket.Sockets; @@ -25,38 +22,21 @@ public abstract partial class HttpSessionClient : TcpSessionClientBase, IHttpSes { private HttpContext m_httpContext; + private ServerHttpResponse m_serverHttpResponse; + private readonly HttpServerDataHandlingAdapter m_httpAdapter; + private WebSocketDataHandlingAdapter m_webSocketAdapter; /// /// 构造函数 /// protected HttpSessionClient() { this.Protocol = Protocol.Http; + this.m_httpAdapter = new HttpServerDataHandlingAdapter(this.OnReceivingHttpRequest); + this.m_webSocketAdapter = new WebSocketDataHandlingAdapter(); } - #region Send - internal Task InternalSendAsync(in ReadOnlyMemory memory) - { - return this.ProtectedDefaultSendAsync(memory); - } - - #endregion Send - - /// - protected override void Dispose(bool disposing) - { - if (this.DisposedValue) - { - return; - } - - if (disposing && this.m_webSocket != null) - { - this.m_webSocket.Dispose(); - } - - base.Dispose(disposing); - } + internal ITransport InternalTransport => this.Transport; /// /// 当收到到Http请求时。覆盖父类方法将不会触发插件。 @@ -65,7 +45,7 @@ public abstract partial class HttpSessionClient : TcpSessionClientBase, IHttpSes { var e = new HttpContextEventArgs(httpContext); - await this.PluginManager.RaiseAsync(typeof(IHttpPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.PluginManager.RaiseIHttpPluginAsync(this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// @@ -81,24 +61,62 @@ public abstract partial class HttpSessionClient : TcpSessionClientBase, IHttpSes /// protected override Task OnTcpConnecting(ConnectingEventArgs e) { - this.SetAdapter(new HttpServerDataHandlingAdapter()); + this.SetAdapter(this.m_httpAdapter); return base.OnTcpConnecting(e); } + /// + protected sealed override async ValueTask OnTcpReceiving(IBytesReader reader) + { + var webSocket = this.m_webSocket; + if (webSocket is null) + { + await this.m_httpAdapter.ReceivedInputAsync(reader).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + else + { + await this.m_webSocketAdapter.ReceivedInputAsync(reader).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + + return true; + } + + private async Task OnReceivingHttpRequest(ServerHttpRequest request) + { + if (this.m_httpContext == null) + { + this.m_serverHttpResponse = new ServerHttpResponse(request, this); + this.m_httpContext = new HttpContext(request, this.m_serverHttpResponse); + } + + await this.OnReceivedHttpRequest(this.m_httpContext).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + this.m_serverHttpResponse.Reset(); + } + /// protected override async Task OnTcpReceived(ReceivedDataEventArgs e) { - if (e.RequestInfo is HttpRequest request) - { - this.m_httpContext ??= new HttpContext(request, new HttpResponse(request, this)); - await this.OnReceivedHttpRequest(this.m_httpContext).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.m_httpContext.Response.ResetHttp(); - } - else if (this.m_webSocket != null && e.RequestInfo is WSDataFrame dataFrame) + if (this.m_webSocket != null && e.RequestInfo is WSDataFrame dataFrame) { e.Handled = true; await this.PrivateWebSocketReceived(dataFrame).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return; } } + + /// + protected override void SafetyDispose(bool disposing) + { + if (this.DisposedValue) + { + return; + } + + if (disposing && this.m_webSocket != null) + { + this.m_webSocket.Dispose(); + } + + base.SafetyDispose(disposing); + } } \ No newline at end of file diff --git a/src/TouchSocket.Http/Components/Interfaces/IHttpClient.cs b/src/TouchSocket.Http/Components/Interfaces/IHttpClient.cs index 3695db511..cbde88c66 100644 --- a/src/TouchSocket.Http/Components/Interfaces/IHttpClient.cs +++ b/src/TouchSocket.Http/Components/Interfaces/IHttpClient.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Sockets; namespace TouchSocket.Http; @@ -26,17 +23,7 @@ public interface IHttpClient : IHttpSession, ISetupConfigObject, IOnlineClient, /// 发起请求 /// /// 请求体 - /// 等待超时时间 - /// 结束等待令箭 + /// 结束等待令箭 /// - Task RequestAsync(HttpRequest request, int millisecondsTimeout = 10 * 1000, CancellationToken token = default); - - /// - /// 发起请求,并获取数据体 - /// - /// 请求体 - /// 等待超时时间 - /// 结束等待令箭 - /// - Task RequestContentAsync(HttpRequest request, int millisecondsTimeout = 10 * 1000, CancellationToken token = default); + ValueTask RequestAsync(HttpRequest request, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/TouchSocket.Http/Components/Interfaces/IHttpSessionClient.cs b/src/TouchSocket.Http/Components/Interfaces/IHttpSessionClient.cs index 8918524ed..a6af38e93 100644 --- a/src/TouchSocket.Http/Components/Interfaces/IHttpSessionClient.cs +++ b/src/TouchSocket.Http/Components/Interfaces/IHttpSessionClient.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Http.WebSockets; using TouchSocket.Sockets; diff --git a/src/TouchSocket.Http/Config/HttpConfigExtensions.cs b/src/TouchSocket.Http/Config/HttpConfigExtensions.cs index 3f920a607..295eb297b 100644 --- a/src/TouchSocket.Http/Config/HttpConfigExtensions.cs +++ b/src/TouchSocket.Http/Config/HttpConfigExtensions.cs @@ -10,86 +10,89 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using TouchSocket.Core; -using TouchSocket.Http; +using System.Net; namespace TouchSocket.Sockets; /// -/// HttpConfigExtensions +/// 的Http扩展配置。 /// public static class HttpConfigExtensions { - //#region 创建 - - ///// - ///// 构建Http类客户端,并连接 - ///// - ///// - ///// - ///// - //public static TClient BuildWithHttpClient(this TouchSocketConfig config) where TClient : IHttpClient - //{ - // var client = Activator.CreateInstance(); - // client.Setup(config); - // client.Connect(); - // return client; - //} - - ///// - ///// 构建Http类客户端,并连接 - ///// - ///// - ///// - //public static HttpClient BuildWithHttpClient(this TouchSocketConfig config) - //{ - // return BuildWithHttpClient(config); - //} - - ///// - ///// 构建Http类服务器,并启动。 - ///// - ///// - ///// - ///// - //public static TService BuildWithHttpService(this TouchSocketConfig config) where TService : IHttpServiceBase - //{ - // var service = Activator.CreateInstance(); - // service.Setup(config); - // service.Start(); - // return service; - //} - - ///// - ///// 构建Http类服务器,并启动。 - ///// - ///// - ///// - //public static HttpService BuildWithHttpService(this TouchSocketConfig config) - //{ - // return BuildWithHttpService(config); - //} - - //#endregion 创建 + /// + /// 代理属性。 + /// + [GeneratorProperty(TargetType = typeof(TouchSocketConfig))] + public static readonly DependencyProperty ProxyProperty = new DependencyProperty("Proxy", default); /// - /// Http代理 + /// 设置代理。 /// - [Obsolete("此配置已被弃用,不再支持代理", true)] - public static readonly DependencyProperty HttpProxyProperty = - DependencyProperty.Register("HttpProxy", null); - - /// - ///设置Http代理 - /// - /// - /// - /// - [Obsolete("此配置已被弃用,不再支持代理", true)] - public static TouchSocketConfig SetHttpProxy(this TouchSocketConfig config, HttpProxy value) + /// 配置对象 + /// 代理Uri + /// 配置对象 + public static TouchSocketConfig SetProxy(this TouchSocketConfig config, Uri proxyUri) { - config.SetValue(HttpProxyProperty, value); + config.SetProxy(new WebProxy(proxyUri)); + return config; + } + + /// + /// 设置代理。 + /// + /// 配置对象 + /// 主机 + /// 端口 + /// 配置对象 + public static TouchSocketConfig SetProxy(this TouchSocketConfig config, string host, int port) + { + config.SetProxy(new WebProxy(host, port)); + return config; + } + + /// + /// 设置带凭证的代理。 + /// + /// 配置对象 + /// 代理Uri + /// 用户名 + /// 密码 + /// 配置对象 + public static TouchSocketConfig SetProxy(this TouchSocketConfig config, Uri proxyUri, string username, string password) + { + config.SetProxy(new WebProxy(proxyUri) + { + Credentials = new NetworkCredential(username, password) + }); + return config; + } + + /// + /// 设置带凭证的代理。 + /// + /// 配置对象 + /// 主机 + /// 端口 + /// 用户名 + /// 密码 + /// 配置对象 + public static TouchSocketConfig SetProxy(this TouchSocketConfig config, string host, int port, string username, string password) + { + config.SetProxy(new WebProxy(host, port) + { + Credentials = new NetworkCredential(username, password) + }); + return config; + } + + /// + /// 设置系统代理。 + /// + /// 配置对象 + /// 配置对象 + public static TouchSocketConfig SetSystemProxy(this TouchSocketConfig config) + { + config.SetProxy(WebRequest.GetSystemWebProxy()); return config; } } \ No newline at end of file diff --git a/src/TouchSocket.Http/Cors/CorsOptions.cs b/src/TouchSocket.Http/Cors/CorsOptions.cs index 4f87a1487..8fcf65c99 100644 --- a/src/TouchSocket.Http/Cors/CorsOptions.cs +++ b/src/TouchSocket.Http/Cors/CorsOptions.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; - namespace TouchSocket.Http; /// diff --git a/src/TouchSocket.Http/Cors/CorsPlugin.cs b/src/TouchSocket.Http/Cors/CorsPlugin.cs index 51ce4c77a..6ab159fab 100644 --- a/src/TouchSocket.Http/Cors/CorsPlugin.cs +++ b/src/TouchSocket.Http/Cors/CorsPlugin.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Http; /// diff --git a/src/TouchSocket.Http/Cors/CorsPolicy.cs b/src/TouchSocket.Http/Cors/CorsPolicy.cs index 109e8c3a5..d6513ffc7 100644 --- a/src/TouchSocket.Http/Cors/CorsPolicy.cs +++ b/src/TouchSocket.Http/Cors/CorsPolicy.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Collections.Generic; -using TouchSocket.Core; - namespace TouchSocket.Http; diff --git a/src/TouchSocket.Http/Cors/Services/CorsService.cs b/src/TouchSocket.Http/Cors/Services/CorsService.cs index c7ad4ff7f..1df95f0c4 100644 --- a/src/TouchSocket.Http/Cors/Services/CorsService.cs +++ b/src/TouchSocket.Http/Cors/Services/CorsService.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Collections.Generic; - namespace TouchSocket.Http; internal class CorsService : ICorsService diff --git a/src/TouchSocket.Http/DataAdapter/HttpClientDataHandlingAdapter.cs b/src/TouchSocket.Http/DataAdapter/HttpClientDataHandlingAdapter.cs index 1627f1b79..2b78813c3 100644 --- a/src/TouchSocket.Http/DataAdapter/HttpClientDataHandlingAdapter.cs +++ b/src/TouchSocket.Http/DataAdapter/HttpClientDataHandlingAdapter.cs @@ -1,253 +1,336 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ -using System; -using System.Text; -using System.Threading.Tasks; -using TouchSocket.Core; +//using System; +//using System.Threading.Tasks; +//using TouchSocket.Core; -namespace TouchSocket.Http; +//namespace TouchSocket.Http; -/// -/// Http客户端数据处理适配器 -/// -internal sealed class HttpClientDataHandlingAdapter : SingleStreamDataHandlingAdapter -{ - private readonly AsyncAutoResetEvent m_autoResetEvent = new AsyncAutoResetEvent(false); - private HttpResponse m_httpResponse; - private HttpResponse m_httpResponseRoot; - private long m_surLen; - private Task m_task; - private ByteBlock m_tempByteBlock; - private string s; +///// +///// Http客户端数据处理适配器 +///// +//internal sealed class HttpClientDataHandlingAdapter : SingleStreamDataHandlingAdapter +//{ +// private readonly AsyncManualResetEvent m_resetEvent = new AsyncManualResetEvent(); +// private HttpResponse m_httpResponse; +// private HttpResponse m_httpResponseRoot; +// private long m_surLen; +// private Task m_task; - /// - public override bool CanSplicingSend => false; +// // 优化:预分配的状态变量,避免重复计算 +// private bool m_isProcessingChunk; - public SingleStreamDataHandlingAdapter WarpAdapter { get; set; } +// private bool m_isWaitingForContent; - /// - public override void OnLoaded(object owner) - { - if (owner is not HttpClientBase clientBase) - { - throw new Exception($"此适配器必须适用于{nameof(HttpClientBase)}"); - } - this.m_httpResponseRoot = new HttpResponse(clientBase); - base.OnLoaded(owner); - } +// /// +// public SingleStreamDataHandlingAdapter WarpAdapter { get; set; } - public void SetCompleteLock() - { - this.m_autoResetEvent.Set(); - } +// /// +// public override void OnLoaded(object owner) +// { +// if (owner is not HttpClientBase clientBase) +// { +// throw new Exception($"此适配器必须适用于{nameof(HttpClientBase)}"); +// } +// this.m_httpResponseRoot = new HttpClientResponse(clientBase); +// base.OnLoaded(owner); +// } - protected override void Dispose(bool disposing) - { - if (disposing) - { - this.m_autoResetEvent.Set(); - this.m_autoResetEvent.Dispose(); - } - base.Dispose(disposing); - } +// public void SetCompleteLock() +// { +// this.m_resetEvent.Set(); +// } - /// - /// - protected override async Task PreviewReceivedAsync(ByteBlock byteBlock) - { - this.s = byteBlock.ToString(); +// /// +// protected override async Task PreviewReceivedAsync(TReader reader) +// { +// while (reader.BytesRemaining > 0) +// { +// // 优化:提前检查包装适配器,减少重复访问 +// var adapter = this.WarpAdapter; +// if (adapter != null) +// { +// await adapter.ReceivedInputAsync(reader).ConfigureAwait(EasyTask.ContinueOnCapturedContext); +// return; +// } - if (this.m_tempByteBlock == null) - { - byteBlock.Position = 0; - await this.Single(byteBlock).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - else - { - this.m_tempByteBlock.Write(byteBlock.Span); - var block = this.m_tempByteBlock; - this.m_tempByteBlock = null; - block.Position = 0; - using (block) - { - await this.Single(block).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - } - } +// // 优化:使用状态标记减少条件判断 +// if (this.m_httpResponse == null) +// { +// if (!await this.ProcessNewResponseAsync(reader).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) +// { +// return; +// } +// } +// else +// { +// if (!await this.ProcessExistingResponseAsync(reader).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) +// { +// return; +// } +// } +// } +// } - private void Cache(ByteBlock byteBlock) - { - if (byteBlock.CanReadLength > 0) - { - this.m_tempByteBlock = new ByteBlock(1024 * 64); - this.m_tempByteBlock.Write(byteBlock.Span.Slice(byteBlock.Position, byteBlock.CanReadLength)); - if (this.m_tempByteBlock.Length > this.MaxPackageSize) - { - this.OnError(default, "缓存的数据长度大于设定值的情况下未收到解析信号", true, true); - } - } - } +// protected override void SafetyDispose(bool disposing) +// { +// if (disposing) +// { +// this.m_resetEvent.Set(); +// // 优化:重置状态标记 +// this.m_isProcessingChunk = false; +// this.m_isWaitingForContent = false; +// } +// base.SafetyDispose(disposing); +// } - private async Task ReadChunk(ByteBlock byteBlock) - { - var position = byteBlock.Position; - var index = byteBlock.Span.Slice(byteBlock.Position, byteBlock.CanReadLength).IndexOf(TouchSocketHttpUtility.CRLF); - if (index > 0) - { - //var headerLength = index - byteBlock.Position; - var headerLength = index; - var hex = byteBlock.Span.Slice(byteBlock.Position, headerLength).ToString(Encoding.UTF8); - var count = hex.ByHexStringToInt32(); - //byteBlock.Position += headerLength + 1; - byteBlock.Position += headerLength; - byteBlock.Position += 2; +// /// +// /// 处理新的HTTP响应 +// /// +// private async ValueTask ProcessNewResponseAsync(TReader reader) +// where TReader : class, IBytesReader +// { +// this.m_httpResponseRoot.Reset(); +// this.m_httpResponse = this.m_httpResponseRoot; - if (count > 0) - { - if (count > byteBlock.CanReadLength) - { - byteBlock.Position = position; - return FilterResult.Cache; - } +// if (!this.m_httpResponse.ParsingHeader(ref reader)) +// { +// this.m_httpResponse = null; - await this.m_httpResponse.InternalInputAsync(byteBlock.Memory.Slice(byteBlock.Position, count)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - byteBlock.Position += count; - byteBlock.Position += 2; - return FilterResult.GoOn; - } - else - { - byteBlock.Position += 2; - return FilterResult.Success; - } - } - else - { - return FilterResult.Cache; - } - } +// // 优化:等待之前的任务完成 +// if (this.m_task != null) +// { +// await this.m_task.ConfigureAwait(EasyTask.ContinueOnCapturedContext); +// await this.m_resetEvent.WaitOneAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); +// this.m_task = null; +// } +// return false; +// } - private Task RunGoReceived(HttpResponse response) - { - return Task.Run(() => this.GoReceivedAsync(null, response)); - } +// // 优化:预先计算状态,减少后续判断 +// var contentLength = this.m_httpResponse.ContentLength; +// this.m_isProcessingChunk = this.m_httpResponse.IsChunk; +// this.m_isWaitingForContent = this.m_isProcessingChunk || contentLength > reader.BytesRemaining; - private async Task Single(ByteBlock byteBlock) - { - while (byteBlock.CanReadLength > 0) - { - var adapter = this.WarpAdapter; - if (adapter != null) - { - await adapter.ReceivedInputAsync(byteBlock).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - return; - } - if (this.m_httpResponse == null) - { - this.m_httpResponseRoot.ResetHttp(); - this.m_httpResponse = this.m_httpResponseRoot; - if (this.m_httpResponse.ParsingHeader(ref byteBlock)) - { - if (this.m_httpResponse.IsChunk || this.m_httpResponse.ContentLength > byteBlock.CanReadLength) - { - this.m_surLen = this.m_httpResponse.ContentLength; - this.m_task = this.RunGoReceived(this.m_httpResponse); - } - else - { - this.m_httpResponse.InternalSetContent(byteBlock.ReadToSpan((int)this.m_httpResponse.ContentLength).ToArray()); - await this.GoReceivedAsync(null, this.m_httpResponse).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - await this.m_autoResetEvent.WaitOneAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.m_httpResponse = null; - } - } - else - { - this.Cache(byteBlock); - this.m_httpResponse = null; +// if (this.m_isWaitingForContent) +// { +// this.m_surLen = contentLength; +// this.m_task = this.GoReceivedAsync(ReadOnlyMemory.Empty, this.m_httpResponse); +// } +// else +// { +// // 优化:直接读取内容,避免数组分配 +// await this.ProcessCompleteContentAsync(reader, contentLength).ConfigureAwait(EasyTask.ContinueOnCapturedContext); +// } - if (this.m_task != null) - { - await this.m_task.ConfigureAwait(EasyTask.ContinueOnCapturedContext); - await this.m_autoResetEvent.WaitOneAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.m_task = null; - } - return; - } - } - else - { - if (this.m_httpResponse.IsChunk) - { - switch (await this.ReadChunk(byteBlock).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) - { - case FilterResult.Cache: - this.Cache(byteBlock); - return; +// return true; +// } - case FilterResult.Success: +// /// +// /// 处理现有的HTTP响应 +// /// +// private async ValueTask ProcessExistingResponseAsync(TReader reader) +// where TReader : class, IBytesReader +// { +// if (this.m_isProcessingChunk) +// { +// return await this.ProcessChunkedContentAsync(reader).ConfigureAwait(EasyTask.ContinueOnCapturedContext); +// } +// else if (this.m_surLen > 0) +// { +// return await this.ProcessRemainingContentAsync(reader).ConfigureAwait(EasyTask.ContinueOnCapturedContext); +// } +// else +// { +// await this.CompleteResponseAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); +// return true; +// } +// } - await this.m_httpResponse.CompleteInput().ConfigureAwait(EasyTask.ContinueOnCapturedContext); +// /// +// /// 处理完整内容(内容长度已知且数据充足) +// /// +// private async Task ProcessCompleteContentAsync(TReader reader, long contentLength) +// where TReader : class, IBytesReader +// { +// var len = (int)contentLength; +// var content = reader.GetMemory(len); +// reader.Advance(len); +// this.m_httpResponse.InternalSetContent(content); - this.m_httpResponse = null; - if (this.m_task != null) - { - await this.m_task.ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.m_task = null; - } +// this.m_httpResponse.CompleteInput(); +// await this.GoReceivedAsync(ReadOnlyMemory.Empty, this.m_httpResponse).ConfigureAwait(EasyTask.ContinueOnCapturedContext); +// await this.m_resetEvent.WaitOneAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); +// this.m_httpResponse = null; +// this.m_isProcessingChunk = false; +// this.m_isWaitingForContent = false; +// } - await this.m_autoResetEvent.WaitOneAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - break; +// /// +// /// 处理分块传输内容 +// /// +// private async ValueTask ProcessChunkedContentAsync(TReader reader) +// where TReader : class, IBytesReader +// { +// var result = await this.ReadChunk(reader).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - case FilterResult.GoOn: - default: - break; - } - } - else if (this.m_surLen > 0) - { - if (byteBlock.CanRead) - { - var len = (int)Math.Min(this.m_surLen, byteBlock.CanReadLength); - await this.m_httpResponse.InternalInputAsync(byteBlock.Memory.Slice(byteBlock.Position, len)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.m_surLen -= len; - byteBlock.Position += len; - if (this.m_surLen == 0) - { - await this.m_httpResponse.CompleteInput().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.m_httpResponse = null; - if (this.m_task != null) - { - await this.m_task.ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.m_task = null; - } +// switch (result) +// { +// case FilterResult.Cache: +// return false; - await this.m_autoResetEvent.WaitOneAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - } - } - else - { - this.m_httpResponse = null; - if (this.m_task != null) - { - await this.m_task.ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.m_task = null; +// case FilterResult.Success: +// this.m_httpResponse.CompleteInput(); +// await this.CompleteResponseAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); +// break; - await this.m_autoResetEvent.WaitOneAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - } - } - } - } -} \ No newline at end of file +// case FilterResult.GoOn: +// default: +// break; +// } + +// return true; +// } + +// /// +// /// 处理剩余内容 +// /// +// private async ValueTask ProcessRemainingContentAsync(TReader reader) +// where TReader : class, IBytesReader +// { +// if (reader.BytesRemaining <= 0) +// { +// return true; +// } + +// var len = (int)Math.Min(this.m_surLen, reader.BytesRemaining); +// await this.m_httpResponse.InternalInputAsync(reader.GetMemory(len)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); +// this.m_surLen -= len; +// reader.Advance(len); + +// if (this.m_surLen == 0) +// { +// this.m_httpResponse.CompleteInput(); +// await this.CompleteResponseAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); +// } + +// return true; +// } + +// /// +// /// 完成响应处理 +// /// +// private async Task CompleteResponseAsync() +// { +// this.m_httpResponse = null; +// this.m_isProcessingChunk = false; +// this.m_isWaitingForContent = false; + +// if (this.m_task != null) +// { +// await this.m_task.ConfigureAwait(EasyTask.ContinueOnCapturedContext); +// this.m_task = null; +// } + +// await this.m_resetEvent.WaitOneAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); +// } + +// private async ValueTask ReadChunk(TReader reader) +// where TReader : class, IBytesReader +// { +// var position = reader.BytesRead; + +// // 优化:使用ReadOnlySpan直接查找,避免重复的IndexOf调用 +// var crlfIndex = (int)reader.Sequence.IndexOf(TouchSocketHttpUtility.CRLF); +// if (crlfIndex <= 0) +// { +// return FilterResult.Cache; +// } + +// var headerLength = crlfIndex; + +// // 优化:直接使用Span进行十六进制解析,避免字符串分配 +// var hexSpan = reader.GetSpan(headerLength); +// if (!TryParseHexLength(hexSpan, out var count)) +// { +// reader.Advance(1); // 跳过无效字符 +// return FilterResult.GoOn; +// } + +// reader.Advance(headerLength + 2); // 跳过长度和CRLF + +// if (count > 0) +// { +// if (count > reader.BytesRemaining) +// { +// reader.BytesRead = position; +// return FilterResult.Cache; +// } + +// await this.m_httpResponse.InternalInputAsync(reader.GetMemory(count)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); +// reader.Advance(count + 2); // 跳过内容和结尾CRLF +// return FilterResult.GoOn; +// } +// else +// { +// reader.Advance(2); // 跳过结尾CRLF +// return FilterResult.Success; +// } +// } + +// /// +// /// 优化:直接从字节解析十六进制,避免字符串分配 +// /// +// private static bool TryParseHexLength(ReadOnlySpan hexBytes, out int result) +// { +// result = 0; + +// if (hexBytes.Length == 0) +// { +// return false; +// } + +// for (var i = 0; i < hexBytes.Length; i++) +// { +// var b = hexBytes[i]; +// int digit; + +// if (b >= '0' && b <= '9') +// { +// digit = b - '0'; +// } +// else if (b >= 'A' && b <= 'F') +// { +// digit = b - 'A' + 10; +// } +// else if (b >= 'a' && b <= 'f') +// { +// digit = b - 'a' + 10; +// } +// else +// { +// return false; // 无效字符 +// } + +// // 检查溢出 +// if (result > (int.MaxValue - digit) / 16) +// { +// return false; +// } + +// result = result * 16 + digit; +// } + +// return true; +// } +//} \ No newline at end of file diff --git a/src/TouchSocket.Http/DataAdapter/HttpClientDataHandlingAdapter2.cs b/src/TouchSocket.Http/DataAdapter/HttpClientDataHandlingAdapter2.cs deleted file mode 100644 index b5766dc43..000000000 --- a/src/TouchSocket.Http/DataAdapter/HttpClientDataHandlingAdapter2.cs +++ /dev/null @@ -1,81 +0,0 @@ -// ------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -// ------------------------------------------------------------------------------ - -//using System; -//using TouchSocket.Core; - -//namespace TouchSocket.Http; - -///// -///// Http客户端数据处理适配器 -///// -//internal sealed class HttpClientDataHandlingAdapter2 : CustomUnfixedHeaderDataHandlingAdapter -//{ -// private UnfixedHeaderHttpResponse m_httpResponseRoot; - -// /// -// public override bool CanSplicingSend => false; - -// /// -// public override void OnLoaded(object owner) -// { -// if (owner is not HttpClientBase clientBase) -// { -// throw new Exception($"此适配器必须适用于{nameof(HttpClientBase)}"); -// } -// this.m_httpResponseRoot = new UnfixedHeaderHttpResponse(clientBase); -// base.OnLoaded(owner); -// } - -// protected override UnfixedHeaderHttpResponse GetInstance() -// { -// return this.m_httpResponseRoot; -// } - -// protected override void OnReceivedSuccess(UnfixedHeaderHttpResponse request) -// { -// base.OnReceivedSuccess(request); -// request.ResetHttp(); -// } -//} - -//internal class UnfixedHeaderHttpResponse : HttpResponse, IUnfixedHeaderRequestInfo -//{ -// private int m_headerLength; - -// internal UnfixedHeaderHttpResponse(HttpClientBase httpClientBase) : base(httpClientBase) -// { -// } - -// public int BodyLength => (int)this.ContentLength; - -// public int HeaderLength => this.m_headerLength; - -// public bool OnParsingBody(ReadOnlySpan body) -// { -// this.InternalSetContent(body.ToArray()); -// return true; -// } - -// public bool OnParsingHeader(ref TByteBlock byteBlock) where TByteBlock : IByteBlock -// { -// var startPos = byteBlock.Position; - -// if (this.ParsingHeader(ref byteBlock)) -// { -// this.m_headerLength = byteBlock.Position - startPos; -// return true; -// } - -// return false; -// } -//} \ No newline at end of file diff --git a/src/TouchSocket.Http/DataAdapter/HttpServerDataHandlingAdapter.cs b/src/TouchSocket.Http/DataAdapter/HttpServerDataHandlingAdapter.cs index d18351d93..d939653b9 100644 --- a/src/TouchSocket.Http/DataAdapter/HttpServerDataHandlingAdapter.cs +++ b/src/TouchSocket.Http/DataAdapter/HttpServerDataHandlingAdapter.cs @@ -1,34 +1,58 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; -using System.Threading.Tasks; -using TouchSocket.Core; +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ namespace TouchSocket.Http; -/// -/// Http服务器数据处理适配器 -/// -public sealed class HttpServerDataHandlingAdapter : SingleStreamDataHandlingAdapter +internal sealed class HttpServerDataHandlingAdapter : SingleStreamDataHandlingAdapter { - private HttpRequest m_currentRequest; - private HttpRequest m_requestRoot; + public HttpServerDataHandlingAdapter(Func func) + { + this.m_func = func; + } + private ServerHttpRequest m_currentRequest; + private ServerHttpRequest m_requestRoot; private long m_surLen; private Task m_task; - private ByteBlock m_tempByteBlock; - /// - public override bool CanSplicingSend => false; + // chunked 解析相关状态 + private bool m_isChunked; + private ChunkedState m_chunkedState; + + /// + /// Chunked 传输状态枚举 + /// + private enum ChunkedState + { + /// + /// 等待块大小 + /// + WaitingChunkSize, + /// + /// 等待块数据 + /// + WaitingChunkData, + /// + /// 等待块结束标记 + /// + WaitingChunkEnd, + /// + /// 等待尾部标记 + /// + WaitingTrailer, + /// + /// 完成 + /// + Completed + } /// public override void OnLoaded(object owner) @@ -38,147 +62,391 @@ public sealed class HttpServerDataHandlingAdapter : SingleStreamDataHandlingAdap throw new Exception($"此适配器必须适用于{nameof(IHttpService)}"); } - this.m_requestRoot = new HttpRequest(httpSessionClient); + this.m_requestRoot = new ServerHttpRequest(httpSessionClient); base.OnLoaded(owner); } /// - protected override void Dispose(bool disposing) + protected override async Task PreviewReceivedAsync(TReader reader) { - if (disposing) + while (reader.BytesRemaining > 0) { - //this.m_requestRoot.SafeDispose(); - this.m_tempByteBlock.SafeDispose(); - } - base.Dispose(disposing); - } - - /// - /// - protected override async Task PreviewReceivedAsync(ByteBlock byteBlock) - { - if (this.m_tempByteBlock == null) - { - byteBlock.Position = 0; - await this.Single(byteBlock, false).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - else - { - this.m_tempByteBlock.Write(byteBlock.Span); - var block = this.m_tempByteBlock; - this.m_tempByteBlock = null; - block.Position = 0; - await this.Single(block, true).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - } - - private void Cache(ByteBlock byteBlock) - { - if (byteBlock.CanReadLength > 0) - { - this.m_tempByteBlock = new ByteBlock(1024 * 64); - this.m_tempByteBlock.Write(byteBlock.Span.Slice(byteBlock.Position, byteBlock.CanReadLength)); - if (this.m_tempByteBlock.Length > this.MaxPackageSize) + if (this.DisposedValue) { - this.OnError(default, "缓存的数据长度大于设定值的情况下未收到解析信号", true, true); + return; } - } - } - //private void DestroyRequest() - //{ - // this.m_currentRequest = null; - // //if (this.m_task != null) - // //{ - // // await this.m_task.ConfigureAwait(EasyTask.ContinueOnCapturedContext); - // // this.m_task = null; - // //} - //} - - private async Task Single(ByteBlock byteBlock, bool dis) - { - try - { - while (byteBlock.CanReadLength > 0) + if (this.m_currentRequest == null) { - if (this.DisposedValue) + if (this.m_task != null) { - return; + await this.m_task.ConfigureAwait(EasyTask.ContinueOnCapturedContext); + this.m_task = null; } - if (this.m_currentRequest == null) + + this.m_requestRoot.Reset(); + this.m_currentRequest = this.m_requestRoot; + if (this.m_currentRequest.ParsingHeader(ref reader)) { - if (this.m_task != null) + + var contentLength = this.m_currentRequest.ContentLength; + if (contentLength == 0) { - await this.m_task.ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.m_task = null; + // 没有具体长度时,才检查是否为 chunked 传输 + this.m_isChunked = this.m_currentRequest.IsChunk; } - this.m_requestRoot.ResetHttp(); - this.m_currentRequest = this.m_requestRoot; - if (this.m_currentRequest.ParsingHeader(ref byteBlock)) + if (this.m_isChunked) { - //byteBlock.Position++; - if (this.m_currentRequest.ContentLength > byteBlock.CanReadLength) - { - this.m_surLen = this.m_currentRequest.ContentLength; - - this.m_task = this.TaskRunGoReceived(this.m_currentRequest); - } - else - { - var contentLength = (int)this.m_currentRequest.ContentLength; - var content = byteBlock.Memory.Slice(byteBlock.Position, contentLength); - byteBlock.Position += contentLength; - - this.m_currentRequest.InternalSetContent(content); - - this.m_task = this.TaskRunGoReceived(this.m_currentRequest); - - this.m_currentRequest = null; - } + // 初始化 chunked 状态 + this.m_chunkedState = ChunkedState.WaitingChunkSize; + this.m_task = this.GoReceivedAsync(this.m_currentRequest); + } + else if (contentLength > reader.BytesRemaining) + { + this.m_surLen = contentLength; + this.m_task = this.GoReceivedAsync(this.m_currentRequest); } else { - this.Cache(byteBlock); - this.m_currentRequest = null; - return; - } - } - - if (this.m_surLen > 0) - { - if (byteBlock.CanRead) - { - var len = (int)Math.Min(this.m_surLen, byteBlock.CanReadLength); - - await this.m_currentRequest.InternalInputAsync(byteBlock.Memory.Slice(byteBlock.Position, len)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.m_surLen -= len; - byteBlock.Position += len; - if (this.m_surLen == 0) + if (contentLength > 0) { - await this.m_currentRequest.CompleteInput().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.m_currentRequest = null; + var content = reader.GetMemory((int)contentLength); + reader.Advance((int)contentLength); + this.m_currentRequest.InternalSetContent(content); } + else + { + this.m_currentRequest.InternalSetContent(ReadOnlyMemory.Empty); + } + await this.GoReceivedAsync(this.m_currentRequest); + this.m_currentRequest = null; } } else { this.m_currentRequest = null; + return; } } - } - finally - { - if (dis) + else if (this.m_isChunked) { - byteBlock.Dispose(); + // 处理 chunked 数据 + var result = await this.ProcessChunkedDataAsync(reader).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + if (result == ChunkedProcessResult.NeedMoreData) + { + return; // 需要更多数据 + } + else if (result == ChunkedProcessResult.Completed) + { + // chunked 传输完成 + this.m_currentRequest.CompleteInput(); + this.ResetChunkedState(); + } + // Continue 表示继续处理 + } + else if (this.m_surLen > 0) + { + if (reader.BytesRemaining > 0) + { + var len = (int)Math.Min(this.m_surLen, reader.BytesRemaining); + + await this.m_currentRequest.InternalInputAsync(reader.GetMemory(len)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + this.m_surLen -= len; + reader.Advance(len); + if (this.m_surLen == 0) + { + this.m_currentRequest.CompleteInput(); + this.m_currentRequest = null; + } + } + } + else + { + this.m_currentRequest = null; } } } - private Task TaskRunGoReceived(HttpRequest request) + private Task GoReceivedAsync(ServerHttpRequest request) { - var task = EasyTask.SafeRun(this.GoReceivedAsync, null, request); - return task; + return this.m_func.Invoke(request); + } + + /// + /// Chunked 处理结果枚举 + /// + private enum ChunkedProcessResult + { + /// + /// 需要更多数据 + /// + NeedMoreData, + /// + /// 继续处理 + /// + Continue, + /// + /// 完成 + /// + Completed + } + + /// + /// 处理 chunked 传输数据 + /// + /// 字节读取器类型 + /// 字节读取器 + /// 处理结果 + private async ValueTask ProcessChunkedDataAsync(TReader reader) where TReader : class, IBytesReader + { + switch (this.m_chunkedState) + { + case ChunkedState.WaitingChunkSize: + return await this.ProcessChunkSizeAsync(reader).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + case ChunkedState.WaitingChunkData: + return await this.ProcessChunkDataAsync(reader).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + case ChunkedState.WaitingChunkEnd: + return this.ProcessChunkEnd(reader); + + case ChunkedState.WaitingTrailer: + return this.ProcessTrailer(reader); + + case ChunkedState.Completed: + return ChunkedProcessResult.Completed; + + default: + throw new InvalidOperationException($"未知的 chunked 状态: {this.m_chunkedState}"); + } + } + + private long m_expectedChunkSize; + private readonly Func m_func; + + /// + /// 处理块大小 + /// + private async ValueTask ProcessChunkSizeAsync(TReader reader) where TReader : class, IBytesReader + { + // 查找 CRLF + var crlfIndex = FindCRLF(reader); + if (crlfIndex < 0) + { + return ChunkedProcessResult.NeedMoreData; + } + + // 解析块大小 + var chunkSizeSpan = reader.GetSpan(crlfIndex); + if (!TryParseChunkSize(chunkSizeSpan, out var chunkSize)) + { + throw new InvalidOperationException("无效的 chunk 大小格式"); + } + + this.m_expectedChunkSize = chunkSize; + + // 跳过块大小行和 CRLF + reader.Advance(crlfIndex + 2); + + if (chunkSize == 0) + { + // 最后一个块 + this.m_chunkedState = ChunkedState.WaitingTrailer; + } + else + { + // 有数据的块 + this.m_chunkedState = ChunkedState.WaitingChunkData; + } + + return ChunkedProcessResult.Continue; + } + + /// + /// 处理块数据 + /// + private async ValueTask ProcessChunkDataAsync(TReader reader) where TReader : class, IBytesReader + { + if (reader.BytesRemaining < this.m_expectedChunkSize) + { + return ChunkedProcessResult.NeedMoreData; + } + + // 读取块数据 + var chunkData = reader.GetMemory((int)this.m_expectedChunkSize); + await this.m_currentRequest.InternalInputAsync(chunkData).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + reader.Advance((int)this.m_expectedChunkSize); + + this.m_chunkedState = ChunkedState.WaitingChunkEnd; + return ChunkedProcessResult.Continue; + } + + /// + /// 处理块结束标记 + /// + private ChunkedProcessResult ProcessChunkEnd(TReader reader) where TReader : IBytesReader + { + if (!TrySkipCRLF(reader)) + { + return ChunkedProcessResult.NeedMoreData; + } + + // 准备处理下一个块 + this.m_chunkedState = ChunkedState.WaitingChunkSize; + return ChunkedProcessResult.Continue; + } + + /// + /// 处理尾部标记 + /// + private ChunkedProcessResult ProcessTrailer(TReader reader) where TReader : IBytesReader + { + // 检查是否有足够的数据处理 trailer + if (reader.BytesRemaining < 2) + { + return ChunkedProcessResult.NeedMoreData; + } + + // 检查是否直接是最终的 CRLF + var span = reader.GetSpan(2); + if (span[0] == '\r' && span[1] == '\n') + { + reader.Advance(2); + this.m_chunkedState = ChunkedState.Completed; + return ChunkedProcessResult.Completed; + } + + // 处理可能的 trailer headers + while (reader.BytesRemaining > 0) + { + var crlfIndex = FindCRLF(reader); + if (crlfIndex < 0) + { + return ChunkedProcessResult.NeedMoreData; + } + + if (crlfIndex == 0) + { + // 空行,表示 trailer 结束 + reader.Advance(2); + this.m_chunkedState = ChunkedState.Completed; + return ChunkedProcessResult.Completed; + } + + // 跳过 trailer header 行 + reader.Advance(crlfIndex + 2); + } + + return ChunkedProcessResult.NeedMoreData; + } + + /// + /// 在读取器中查找 CRLF 的位置 + /// + /// 字节读取器类型 + /// 字节读取器 + /// CRLF 的位置,如果未找到则返回 -1 + private static int FindCRLF(TReader reader) where TReader : IBytesReader + { + const int maxSearchLength = 256; // 限制搜索长度,防止恶意数据 + var searchLength = (int)Math.Min(reader.BytesRemaining, maxSearchLength); + + if (searchLength < 2) + { + return -1; + } + + var sequence = reader.Sequence.Slice(0, searchLength); + + // 使用 ReadOnlySequence 的 IndexOf 方法查找 CRLF + var crlfIndex = sequence.IndexOf(TouchSocketHttpUtility.CRLF); + return crlfIndex >= 0 ? (int)crlfIndex : -1; + } + + /// + /// 尝试解析十六进制块大小 + /// + /// 十六进制字节跨度 + /// 解析出的块大小 + /// true 如果解析成功 + private static bool TryParseChunkSize(ReadOnlySpan hexSpan, out long chunkSize) + { + chunkSize = 0; + + if (hexSpan.Length == 0) + { + return false; + } + + for (var i = 0; i < hexSpan.Length; i++) + { + var b = hexSpan[i]; + + // 检查是否遇到了块扩展分隔符或空格 + if (b == ';' || b == ' ' || b == '\t') + { + break; // 忽略块扩展和空格 + } + + int digit; + if (b >= '0' && b <= '9') + { + digit = b - '0'; + } + else if (b >= 'A' && b <= 'F') + { + digit = b - 'A' + 10; + } + else if (b >= 'a' && b <= 'f') + { + digit = b - 'a' + 10; + } + else + { + return false; // 无效字符 + } + + // 检查溢出 + if (chunkSize > (long.MaxValue - digit) / 16) + { + return false; + } + + chunkSize = chunkSize * 16 + digit; + } + + return true; + } + + /// + /// 尝试跳过 CRLF + /// + /// 字节读取器类型 + /// 字节读取器 + /// true 如果成功跳过 + private static bool TrySkipCRLF(TReader reader) where TReader : IBytesReader + { + if (reader.BytesRemaining >= 2) + { + var span = reader.GetSpan(2); + if (span[0] == '\r' && span[1] == '\n') + { + reader.Advance(2); + return true; + } + } + return false; + } + + /// + /// 重置 chunked 状态 + /// + private void ResetChunkedState() + { + this.m_isChunked = false; + this.m_chunkedState = ChunkedState.WaitingChunkSize; + this.m_expectedChunkSize = 0; + this.m_currentRequest = null; } } \ No newline at end of file diff --git a/src/TouchSocket.Http/DelegateCollection.cs b/src/TouchSocket.Http/DelegateCollection.cs index 8a0855add..2ace24f02 100644 --- a/src/TouchSocket.Http/DelegateCollection.cs +++ b/src/TouchSocket.Http/DelegateCollection.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; - namespace TouchSocket.Http; /// diff --git a/src/TouchSocket.Http/EventArgs/HttpContextEventArgs.cs b/src/TouchSocket.Http/EventArgs/HttpContextEventArgs.cs index b35b355fa..18856a03f 100644 --- a/src/TouchSocket.Http/EventArgs/HttpContextEventArgs.cs +++ b/src/TouchSocket.Http/EventArgs/HttpContextEventArgs.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Http; /// diff --git a/src/TouchSocket.Http/Exceptions/ProxyAuthenticationException.cs b/src/TouchSocket.Http/Exceptions/ProxyAuthenticationException.cs new file mode 100644 index 000000000..30cc4cc41 --- /dev/null +++ b/src/TouchSocket.Http/Exceptions/ProxyAuthenticationException.cs @@ -0,0 +1,32 @@ +//------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +//------------------------------------------------------------------------------ + +namespace TouchSocket.Http; + +/// +/// 代理身份验证异常 +/// +public class ProxyAuthenticationException : Exception +{ + /// + /// 构造函数 + /// + /// 异常消息 + public ProxyAuthenticationException(string message) : base(message) { } + + /// + /// 构造函数 + /// + /// 异常消息 + /// 内部异常 + public ProxyAuthenticationException(string message, Exception innerException) : base(message, innerException) { } +} diff --git a/src/TouchSocket.Http/Exceptions/ProxyConnectionException.cs b/src/TouchSocket.Http/Exceptions/ProxyConnectionException.cs new file mode 100644 index 000000000..c14276ed7 --- /dev/null +++ b/src/TouchSocket.Http/Exceptions/ProxyConnectionException.cs @@ -0,0 +1,32 @@ +//------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +//------------------------------------------------------------------------------ + +namespace TouchSocket.Http; + +/// +/// 代理连接异常 +/// +public class ProxyConnectionException : Exception +{ + /// + /// 构造函数 + /// + /// 异常消息 + public ProxyConnectionException(string message) : base(message) { } + + /// + /// 构造函数 + /// + /// 异常消息 + /// 内部异常 + public ProxyConnectionException(string message, Exception innerException) : base(message, innerException) { } +} \ No newline at end of file diff --git a/src/TouchSocket.Http/Extensions/HttpClientExtension.cs b/src/TouchSocket.Http/Extensions/HttpClientExtension.cs index 6a55ddd50..452e8aba7 100644 --- a/src/TouchSocket.Http/Extensions/HttpClientExtension.cs +++ b/src/TouchSocket.Http/Extensions/HttpClientExtension.cs @@ -10,12 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Http; /// @@ -28,11 +22,10 @@ public static class HttpClientExtension /// /// 发起HTTP请求的客户端。 /// 要请求的URL。 - /// 请求超时时间,以毫秒为单位,默认为10秒。 - /// 用于取消操作的取消令牌。 + /// 用于取消操作的取消令牌。 /// 包含从URL获取的字节的数组。 /// 如果HTTP请求失败,将抛出异常。 - public static async Task GetByteArrayAsync(this IHttpClient httpClient, string url, int millisecondsTimeout = 10 * 1000, CancellationToken token = default) + public static async Task GetByteArrayAsync(this IHttpClient httpClient, string url, CancellationToken cancellationToken) { // 创建HTTP请求对象 var request = new HttpRequest(); @@ -44,7 +37,7 @@ public static class HttpClientExtension request.URL = (url); // 使用指定的超时时间和取消令牌发起HTTP请求 - using (var responseResult = await httpClient.RequestAsync(request, millisecondsTimeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) + using (var responseResult = await httpClient.RequestAsync(request, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) { // 获取HTTP响应 var response = responseResult.Response; @@ -56,7 +49,7 @@ public static class HttpClientExtension } // 读取响应内容并将其转换为字节数组返回 - return (await response.GetContentAsync(token)).ToArray(); + return (await response.GetContentAsync(cancellationToken)).ToArray(); } } @@ -65,13 +58,12 @@ public static class HttpClientExtension /// /// 用于发送HTTP请求的客户端。 /// 要请求的URL。 - /// 请求超时时间,以毫秒为单位,默认为10秒。 - /// 用于取消操作的取消令牌。 + /// 用于取消操作的取消令牌。 /// 返回从指定URL获取的字符串。 - public static async Task GetStringAsync(this IHttpClient httpClient, string url, int millisecondsTimeout = 10 * 1000, CancellationToken token = default) + public static async Task GetStringAsync(this IHttpClient httpClient, string url, CancellationToken cancellationToken = default) { // 将获取到的字节数组转换为UTF-8编码的字符串 - return (await GetByteArrayAsync(httpClient, url, millisecondsTimeout, token)).ToUtf8String(); + return (await GetByteArrayAsync(httpClient, url, cancellationToken)).ToUtf8String(); } #region Download @@ -82,19 +74,18 @@ public static class HttpClientExtension /// HTTP客户端接口 /// HTTP请求对象 /// 用于存储文件内容的目标流 - /// 请求超时时间,以毫秒为单位,默认为10秒 - /// 用于取消操作的取消令牌 + /// 用于取消操作的取消令牌 /// 返回一个异步任务 - public static async Task GetFileAsync(this IHttpClient httpClient, HttpRequest request, Stream stream, int millisecondsTimeout = 10 * 1000, CancellationToken token = default) + public static async Task GetFileAsync(this IHttpClient httpClient, HttpRequest request, Stream stream, CancellationToken cancellationToken) { // 使用using语句确保响应对象正确地被释放 - using (var responseResult = await httpClient.RequestAsync(request, millisecondsTimeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) + using (var responseResult = await httpClient.RequestAsync(request, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) { // 提取HTTP响应 var response = responseResult.Response; // 将响应内容异步读取并复制到指定的流中 - await response.ReadCopyToAsync(stream, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await response.ReadCopyToAsync(stream, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } @@ -108,10 +99,10 @@ public static class HttpClientExtension /// 返回一个Result对象,表示下载结果。 public static async Task GetFileAsync(this IHttpClient httpClient, HttpRequest request, Stream stream, HttpFlowOperator flowOperator) { - var token = flowOperator.Token; - var timeout = (int)flowOperator.Timeout.TotalMilliseconds; + var cancellationToken = flowOperator.Token; + // 使用using语句确保响应对象正确地被释放 - using (var responseResult = await httpClient.RequestAsync(request, timeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) + using (var responseResult = await httpClient.RequestAsync(request, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) { // 提取HTTP响应 var response = responseResult.Response; @@ -127,10 +118,9 @@ public static class HttpClientExtension /// 用于发送HTTP请求的客户端。 /// 要获取的文件的URL。 /// 将文件内容写入的流。 - /// 操作超时时间,以毫秒为单位,默认为10秒。 - /// 用于取消异步操作的取消令牌。 + /// 用于取消异步操作的取消令牌。 /// 返回一个Task对象,表示异步操作。 - public static Task GetFileAsync(this IHttpClient httpClient, string url, Stream stream, int millisecondsTimeout = 10 * 1000, CancellationToken token = default) + public static Task GetFileAsync(this IHttpClient httpClient, string url, Stream stream, CancellationToken cancellationToken) { // 创建并初始化HttpRequest对象,用于封装HTTP请求的相关信息和操作 var request = new HttpRequest(); @@ -138,7 +128,7 @@ public static class HttpClientExtension request.URL = (url); // 设置请求的URL request.SetHost(httpClient.RemoteIPHost.Host); // 调用重载的GetFileAsync方法,传入封装好的请求对象 - return GetFileAsync(httpClient, request, stream, millisecondsTimeout, token); + return GetFileAsync(httpClient, request, stream, cancellationToken); } /// @@ -170,10 +160,9 @@ public static class HttpClientExtension /// HttpClient实例,用于发送HTTP请求。 /// 文件上传的URL地址。 /// 包含文件信息的FileInfo对象,用于获取文件内容和属性。 - /// 请求的超时时间,默认为10秒。如果在此时间内未完成上传,请求将被取消。 - /// 用于取消操作的取消令牌。 + /// 用于取消操作的取消令牌。 /// 客户端类型,必须继承自HttpClientBase并实现IHttpClient接口。 - public static async Task UploadFileAsync(this TClient client, string url, FileInfo fileInfo, int millisecondsTimeout = 10 * 1000, CancellationToken token = default) + public static async Task UploadFileAsync(this TClient client, string url, FileInfo fileInfo, CancellationToken cancellationToken) where TClient : HttpClientBase, IHttpClient { using (var stream = fileInfo.OpenRead()) @@ -187,7 +176,7 @@ public static class HttpClientExtension request.SetHost(client.RemoteIPHost.Host); request.AsPost(); - using (var responseResult = await client.RequestAsync(request, millisecondsTimeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) + using (var responseResult = await client.RequestAsync(request, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) { var response = responseResult.Response; if (response.IsSuccess()) @@ -213,13 +202,12 @@ public static class HttpClientExtension { try { - var token = flowOperator.Token; - var timeout = (int)flowOperator.Timeout.TotalMilliseconds; + var cancellationToken = flowOperator.Token; //创建一个请求 request.SetContent(new StreamHttpContent(stream, flowOperator)); - using (var responseResult = await client.RequestAsync(request, timeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) + using (var responseResult = await client.RequestAsync(request, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) { var response = responseResult.Response; if (response.IsSuccess()) diff --git a/src/TouchSocket.Http/Extensions/HttpContainerExtension.cs b/src/TouchSocket.Http/Extensions/HttpContainerExtension.cs index 9b3074874..9b27fb4dd 100644 --- a/src/TouchSocket.Http/Extensions/HttpContainerExtension.cs +++ b/src/TouchSocket.Http/Extensions/HttpContainerExtension.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using TouchSocket.Core; - namespace TouchSocket.Http; /// @@ -21,15 +18,14 @@ namespace TouchSocket.Http; public static class HttpContainerExtension { /// - /// 向注册器中添加跨域服务。 + /// 向注册器中添加跨域服务 /// - /// - /// - /// - public static IRegistrator AddCors(this IRegistrator registrator, Action action) + /// 容器注册器 + /// 跨域选项配置委托 + public static void AddCors(this IRegistrator registrator, Action action) { var corsOptions = new CorsOptions(); action.Invoke(corsOptions); - return registrator.RegisterSingleton(new CorsService(corsOptions)); + registrator.RegisterSingleton(new CorsService(corsOptions)); } } \ No newline at end of file diff --git a/src/TouchSocket.Http/Extensions/HttpExtensions.cs b/src/TouchSocket.Http/Extensions/HttpExtensions.cs index d7f28418f..62367d489 100644 --- a/src/TouchSocket.Http/Extensions/HttpExtensions.cs +++ b/src/TouchSocket.Http/Extensions/HttpExtensions.cs @@ -10,14 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Http; /// @@ -25,6 +17,14 @@ namespace TouchSocket.Http; /// public static partial class HttpExtensions { + static HttpExtensions() + { + s_dateString = DateTime.UtcNow.ToGMTString(); + s_timer = new Timer((state) => + { + s_dateString = DateTime.UtcNow.ToGMTString(); + }, null, 0, 1000); + } #region HttpBase /// @@ -32,6 +32,18 @@ public static partial class HttpExtensions /// public const string MultipartFormData = "multipart/form-data"; + /// + /// 向请求头添加当前GMT时间的Date字段。 + /// + /// 请求类型,必须继承自 + /// 要添加Date头的请求对象。 + /// 返回添加了Date头的请求对象。 + public static TRequest AddCurrentDateHeader(this TRequest request) where TRequest : HttpBase + { + request.Headers.Add(HttpHeaders.Date, CurrentHttpDate);// 添加GMT时间到Header + return request; + } + /// /// 添加Header参数 /// @@ -44,29 +56,17 @@ public static partial class HttpExtensions request.Headers.Add(key, value); return request; } - - /// - /// 获取Body的字符串 - /// - /// - /// - [Obsolete("该方法已被弃用,请使用GetBodyAsync异步方法代替")] - public static string GetBody(this HttpBase httpBase) - { - return GetBodyAsync(httpBase).GetFalseAwaitResult(); - } - /// /// 异步获取 HTTP 请求的主体内容。 /// /// HttpBase 实例,用于发起 HTTP 请求。 /// 编码格式 - /// 可取消令箭 + /// 可取消令箭 /// 返回主体内容的字符串表示,如果内容为空则返回 null。 - public static async Task GetBodyAsync(this HttpBase httpBase, Encoding encoding, CancellationToken token = default) + public static async Task GetBodyAsync(this HttpBase httpBase, Encoding encoding, CancellationToken cancellationToken = default) { // 异步获取 HTTP 响应的内容作为字节数组 - var bytes = await httpBase.GetContentAsync(token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var bytes = await httpBase.GetContentAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); // 如果字节数组为空,则返回 null,否则使用 UTF-8 编码将字节数组转换为字符串并返回 return bytes.IsEmpty ? null : bytes.Span.ToString(encoding); } @@ -75,11 +75,11 @@ public static partial class HttpExtensions /// 异步获取utf8编码的 HTTP 请求的主体内容。 /// /// HttpBase 实例,用于发起 HTTP 请求。 - /// 可取消令箭 + /// 可取消令箭 /// 返回主体内容的字符串表示,如果内容为空则返回 null。 - public static Task GetBodyAsync(this HttpBase httpBase, CancellationToken token = default) + public static Task GetBodyAsync(this HttpBase httpBase, CancellationToken cancellationToken = default) { - return GetBodyAsync(httpBase, Encoding.UTF8, token); + return GetBodyAsync(httpBase, Encoding.UTF8, cancellationToken); } /// @@ -94,12 +94,12 @@ public static partial class HttpExtensions var contentType = httpBase.ContentType; - if (contentType.IsNullOrEmpty()) + if (contentType.IsEmpty) { return string.Empty; } // 分割ContentType字符串,以";"为分隔符 - var strs = contentType.Split(';'); + var strs = contentType.First.Split(';'); if (strs.Length != 2) { @@ -124,20 +124,6 @@ public static partial class HttpExtensions return string.Empty; } - /// - /// 同步获取一次性内容。 - /// - /// 返回一个只读内存块,该内存块包含具体的字节内容。 - /// - /// 一个CancellationToken对象,用于取消异步操作。 - [Obsolete("该方法已被弃用,请使用GetContentAsync异步方法")] - public static ReadOnlyMemory GetContent(this HttpBase httpBase, CancellationToken cancellationToken = default) - { - // 使用Task.Run来启动一个新的任务,该任务将异步地获取内容。 - // 这里使用GetFalseAwaitResult()方法来处理任务的结果,确保即使在同步上下文中也能正确处理异常。 - return Task.Run(async () => await httpBase.GetContentAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext), cancellationToken).GetFalseAwaitResult(); - } - #region 设置内容 /// @@ -253,19 +239,6 @@ public static partial class HttpExtensions #region HttpRequest - /// - /// 设置Url,可带参数 - /// - /// - /// 要设置的URL地址 - /// 返回当前HttpRequest实例,支持链式调用 - public static TRequest SetUrl(this TRequest request, string url) - where TRequest : HttpRequest - { - request.URL = url; - return request; - } - /// /// 添加Query参数 /// @@ -307,9 +280,19 @@ public static partial class HttpExtensions } else { - var boundary = $"--{boundaryString}".ToUtf8Bytes(); + boundaryString = $"--{boundaryString}"; - return new InternalFormCollection(await request.GetContentAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext), boundary); + var valueByteBlock = new ValueByteBlock(Encoding.UTF8.GetMaxByteCount(boundaryString.Length)); + + try + { + WriterExtension.WriteNormalString(ref valueByteBlock, boundaryString, Encoding.UTF8); + return new InternalFormCollection(await request.GetContentAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext), valueByteBlock.Span); + } + finally + { + valueByteBlock.Dispose(); + } } } @@ -378,6 +361,18 @@ public static partial class HttpExtensions return request; } + /// + /// 设置Url,可带参数 + /// + /// + /// 要设置的URL地址 + /// 返回当前HttpRequest实例,支持链式调用 + public static TRequest SetUrl(this TRequest request, string url) + where TRequest : HttpRequest + { + request.URL = url; + return request; + } /// /// 对比不包含参数的Url。其中有任意一方为,则均返回False。 /// @@ -572,7 +567,7 @@ public static partial class HttpExtensions var acceptEncoding = request.AcceptEncoding; // 检查接受编码是否包含Gzip - return !acceptEncoding.IsNullOrEmpty() && acceptEncoding.Contains("gzip"); + return !acceptEncoding.IsEmpty && acceptEncoding.Contains("gzip"); } /// @@ -592,6 +587,18 @@ public static partial class HttpExtensions #region HttpResponse + #region DateString + + private static readonly Timer s_timer; + + private static volatile string s_dateString; + + /// + /// 获取当前的GMT格式日期字符串,通常用于HTTP响应头中的Date字段。 + /// + public static string CurrentHttpDate => s_dateString; + #endregion + /// /// 表示 HTTP 头部的服务器信息。 /// @@ -694,21 +701,8 @@ public static partial class HttpExtensions response.StatusCode = status; // 设置HTTP状态码 response.StatusMessage = msg; // 设置状态描述信息 response.Headers.TryAdd(HttpHeaders.Server, HttpHeadersServer); // 添加服务器版本信息到Header - response.Headers.TryAdd(HttpHeaders.Date, DateTimeOffset.UtcNow.ToGMTString()); // 添加GMT时间到Header - return response; // 返回修改后的HttpResponse对象 - } - - /// - /// 设置默认Success状态,并且附带时间戳。 - /// - /// 泛型参数,表示HttpResponse的类型。 - /// 要设置状态的HttpResponse对象。 - /// 返回设置后的HttpResponse对象。 - [Obsolete("此方法由于方法名称不能清楚表达http状态,已被弃用,请使用SetStatusWithSuccess直接代替")] - public static TResponse SetStatus(this TResponse response) where TResponse : HttpResponse - { - // 调用重载的SetStatus方法,设置状态码为200,状态信息为"Success"。 - return SetStatus(response, 200, "Success"); + response.Headers.TryAdd(HttpHeaders.Date, CurrentHttpDate); + return response; } /// @@ -719,7 +713,6 @@ public static partial class HttpExtensions /// 返回设置后的HttpResponse对象。 public static TResponse SetStatusWithSuccess(this TResponse response) where TResponse : HttpResponse { - // 调用重载的SetStatus方法,设置状态码为200,状态信息为"Success"。 return SetStatus(response, 200, "Success"); } diff --git a/src/TouchSocket.Http/Extensions/HttpExtensions_File.cs b/src/TouchSocket.Http/Extensions/HttpExtensions_File.cs index e8c1eec58..05666b20d 100644 --- a/src/TouchSocket.Http/Extensions/HttpExtensions_File.cs +++ b/src/TouchSocket.Http/Extensions/HttpExtensions_File.cs @@ -10,14 +10,8 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Buffers; -using System.Collections.Generic; -using System.IO; using System.IO.Compression; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; namespace TouchSocket.Http; diff --git a/src/TouchSocket.Http/Extensions/HttpExtensions_Stream.cs b/src/TouchSocket.Http/Extensions/HttpExtensions_Stream.cs index 383252b3c..a4ddade98 100644 --- a/src/TouchSocket.Http/Extensions/HttpExtensions_Stream.cs +++ b/src/TouchSocket.Http/Extensions/HttpExtensions_Stream.cs @@ -10,12 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Http; public static partial class HttpExtensions diff --git a/src/TouchSocket.Http/Extensions/HttpPluginRaiseExtension.cs b/src/TouchSocket.Http/Extensions/HttpPluginRaiseExtension.cs new file mode 100644 index 000000000..4f03460c6 --- /dev/null +++ b/src/TouchSocket.Http/Extensions/HttpPluginRaiseExtension.cs @@ -0,0 +1,20 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using TouchSocket.Http; + +namespace TouchSocket.Core; + +[PluginRaise(typeof(IHttpPlugin))] +internal static partial class HttpPluginRaiseExtension +{ +} diff --git a/src/TouchSocket.Http/Extensions/HttpPluginsManagerExtension.cs b/src/TouchSocket.Http/Extensions/HttpPluginsManagerExtension.cs index fa6733cd5..f3042799d 100644 --- a/src/TouchSocket.Http/Extensions/HttpPluginsManagerExtension.cs +++ b/src/TouchSocket.Http/Extensions/HttpPluginsManagerExtension.cs @@ -10,8 +10,8 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using TouchSocket.Http; +using TouchSocket.Sockets; namespace TouchSocket.Core; @@ -79,4 +79,16 @@ public static class HttpPluginManagerExtension // 为插件管理器添加一个CorsPlugin,使用给定的跨域策略名称 return pluginManager.Add(resolver => new CorsPlugin(resolver.Resolve(), policyName)); } + + + /// + /// 启用HttpSession的定期检查与清理插件。 + /// 该插件用于定期检查会话并进行清理操作,防止资源泄漏。 + /// + /// 插件管理器实例,用于添加和管理插件。 + /// 返回创建并添加到插件管理器的实例。 + public static CheckClearPlugin UseHttpSessionCheckClear(this IPluginManager pluginManager, Action> options = null) + { + return pluginManager.UseCheckClear(options); + } } \ No newline at end of file diff --git a/src/TouchSocket.Http/FileTransfer/HttpFlowOperator.cs b/src/TouchSocket.Http/FileTransfer/HttpFlowOperator.cs index a94e7cc04..04c15db5b 100644 --- a/src/TouchSocket.Http/FileTransfer/HttpFlowOperator.cs +++ b/src/TouchSocket.Http/FileTransfer/HttpFlowOperator.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Http; /// diff --git a/src/TouchSocket.Http/HttpContent/HttpContent.cs b/src/TouchSocket.Http/HttpContent/HttpContent.cs index a69a7d9ec..43a51bf61 100644 --- a/src/TouchSocket.Http/HttpContent/HttpContent.cs +++ b/src/TouchSocket.Http/HttpContent/HttpContent.cs @@ -10,12 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; +using System.IO.Pipelines; namespace TouchSocket.Http; @@ -27,12 +22,12 @@ public abstract class HttpContent /// /// 内部方法,用于构建HTTP响应的内容 /// - /// 实现IByteBlock接口的类型 - /// 字节块的引用 + /// 实现IByteBlock接口的类型 + /// 字节块的引用 /// 返回一个布尔值,表示构建内容是否成功 - internal bool InternalBuildingContent(ref TByteBlock byteBlock) where TByteBlock : IByteBlock + internal bool InternalBuildingContent(ref TWriter writer) where TWriter : IBytesWriter { - return this.OnBuildingContent(ref byteBlock); + return this.OnBuildingContent(ref writer); } /// @@ -53,24 +48,18 @@ public abstract class HttpContent return this.TryComputeLength(out length); } - /// - /// 内部方法,用于写入HTTP响应内容 - /// - /// 一个函数,用于处理字节块的写入操作 - /// 用于取消操作的令牌 - /// 返回一个任务对象,代表异步写入操作 - internal Task InternalWriteContent(Func, Task> func, CancellationToken token) + internal Task InternalWriteContent(PipeWriter writer, CancellationToken cancellationToken) { - return this.WriteContent(func, token); + return this.WriteContent(writer, cancellationToken); } /// /// 抽象方法,由子类实现,用于构建HTTP响应的内容 /// - /// 实现IByteBlock接口的类型 - /// 字节块的引用 + /// 实现IByteBlock接口的类型 + /// 字节块的引用 /// 返回一个布尔值,表示构建内容是否成功 - protected abstract bool OnBuildingContent(ref TByteBlock byteBlock) where TByteBlock : IByteBlock; + protected abstract bool OnBuildingContent(ref TWriter writer) where TWriter : IBytesWriter; /// /// 抽象方法,由子类实现,用于构建HTTP头 @@ -85,13 +74,7 @@ public abstract class HttpContent /// 如果成功计算长度,则返回 true;否则返回 false。 protected abstract bool TryComputeLength(out long length); - /// - /// 抽象方法,由子类实现,用于写入HTTP响应内容 - /// - /// 一个函数,用于处理字节块的写入操作 - /// 用于取消操作的令牌 - /// 返回一个任务对象,代表异步写入操作 - protected abstract Task WriteContent(Func, Task> writeFunc, CancellationToken token); + protected abstract Task WriteContent(PipeWriter writer, CancellationToken cancellationToken); #region implicit diff --git a/src/TouchSocket.Http/HttpContent/ReadonlyMemoryHttpContent.cs b/src/TouchSocket.Http/HttpContent/ReadonlyMemoryHttpContent.cs index df6b636cd..89277f9c7 100644 --- a/src/TouchSocket.Http/HttpContent/ReadonlyMemoryHttpContent.cs +++ b/src/TouchSocket.Http/HttpContent/ReadonlyMemoryHttpContent.cs @@ -10,11 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; +using System.IO.Pipelines; namespace TouchSocket.Http; @@ -50,7 +46,7 @@ public class ReadonlyMemoryHttpContent : HttpContent { return true;//直接构建成功,也不用调用后续的WriteContent } - if (byteBlock.FreeLength > this.m_memory.Length) + if (this.m_memory.Length < 1024 * 1024) { //如果空闲空间足够,构建成功,也不用调用后续的WriteContent byteBlock.Write(this.m_memory.Span); @@ -76,9 +72,9 @@ public class ReadonlyMemoryHttpContent : HttpContent } /// - protected override async Task WriteContent(Func, Task> writeFunc, CancellationToken token) + protected override async Task WriteContent(PipeWriter writer, CancellationToken cancellationToken) { - await writeFunc(this.m_memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await writer.WriteAsync(this.m_memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// diff --git a/src/TouchSocket.Http/HttpContent/StreamHttpContent.cs b/src/TouchSocket.Http/HttpContent/StreamHttpContent.cs index 844dd7f90..03edefdc3 100644 --- a/src/TouchSocket.Http/HttpContent/StreamHttpContent.cs +++ b/src/TouchSocket.Http/HttpContent/StreamHttpContent.cs @@ -10,12 +10,8 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Buffers; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; +using System.IO.Pipelines; namespace TouchSocket.Http; @@ -97,7 +93,7 @@ public class StreamHttpContent : HttpContent } /// - protected override async Task WriteContent(Func, Task> writeFunc, CancellationToken token) + protected override async Task WriteContent(PipeWriter writer, CancellationToken cancellationToken) { // 创建一个缓冲区,用于存储读取的数据 @@ -121,7 +117,7 @@ public class StreamHttpContent : HttpContent { while (true) { - var r = await this.m_stream.ReadAsync(memory, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var r = await this.m_stream.ReadAsync(memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); if (r == 0) { break; @@ -134,7 +130,7 @@ public class StreamHttpContent : HttpContent TouchSocketHttpUtility.AppendRn(ref byteBlock); byteBlock.Write(target.Span); TouchSocketHttpUtility.AppendRn(ref byteBlock); - await writeFunc.Invoke(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await writer.WriteAsync(byteBlock.Memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); await this.m_flowOperator.AddFlowAsync(r).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } @@ -143,7 +139,7 @@ public class StreamHttpContent : HttpContent TouchSocketHttpUtility.AppendHex(ref byteBlock, 0); TouchSocketHttpUtility.AppendRn(ref byteBlock); TouchSocketHttpUtility.AppendRn(ref byteBlock); - await writeFunc.Invoke(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await writer.WriteAsync(byteBlock.Memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } finally { @@ -154,12 +150,12 @@ public class StreamHttpContent : HttpContent { while (true) { - var r = await this.m_stream.ReadAsync(memory, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var r = await this.m_stream.ReadAsync(memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); if (r == 0) { break; } - await writeFunc.Invoke(memory.Slice(0, r)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await writer.WriteAsync(memory.Slice(0, r), cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); await this.m_flowOperator.AddFlowAsync(r).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } diff --git a/src/TouchSocket.Http/HttpContent/StringHttpContent.cs b/src/TouchSocket.Http/HttpContent/StringHttpContent.cs index c71c593e3..e9d4d3478 100644 --- a/src/TouchSocket.Http/HttpContent/StringHttpContent.cs +++ b/src/TouchSocket.Http/HttpContent/StringHttpContent.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Text; - namespace TouchSocket.Http; /// diff --git a/src/TouchSocket.Http/Interface/IFormCollection.cs b/src/TouchSocket.Http/Interface/IFormCollection.cs index 129d222a5..078e09f09 100644 --- a/src/TouchSocket.Http/Interface/IFormCollection.cs +++ b/src/TouchSocket.Http/Interface/IFormCollection.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Collections.Generic; - namespace TouchSocket.Http; /// diff --git a/src/TouchSocket.Http/Interface/IFormFile.cs b/src/TouchSocket.Http/Interface/IFormFile.cs index a4225e688..c328e23b8 100644 --- a/src/TouchSocket.Http/Interface/IFormFile.cs +++ b/src/TouchSocket.Http/Interface/IFormFile.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Collections.Specialized; namespace TouchSocket.Http; diff --git a/src/TouchSocket.Http/Interface/IHttpHeader.cs b/src/TouchSocket.Http/Interface/IHttpHeader.cs index 795353fc9..a701bc470 100644 --- a/src/TouchSocket.Http/Interface/IHttpHeader.cs +++ b/src/TouchSocket.Http/Interface/IHttpHeader.cs @@ -10,19 +10,27 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Collections.Generic; - namespace TouchSocket.Http; + /// -/// 表示http的headers +/// 表示HTTP头部的接口,继承自,用于管理HTTP头部键值对。 /// -public interface IHttpHeader : IDictionary +public interface IHttpHeader : IDictionary { /// - /// 获取Header + /// 获取指定键的Header值。 /// - /// - /// - string Get(string key); + /// 要获取的Header键。 + /// 返回指定键的Header值,如果不存在则返回 + TextValues Get(string key); + + /// + /// 判断指定键的值是否等于指定值。 + /// + /// 要检查的键。 + /// 要匹配的值。 + /// 是否忽略大小写,默认为。 + /// 如果存在指定键且值匹配则返回,否则返回 + bool Contains(string key, TextValues value, bool ignoreCase = true); } \ No newline at end of file diff --git a/src/TouchSocket.Http/Interface/IHttpParams.cs b/src/TouchSocket.Http/Interface/IHttpParams.cs index 889784cfb..b5772676d 100644 --- a/src/TouchSocket.Http/Interface/IHttpParams.cs +++ b/src/TouchSocket.Http/Interface/IHttpParams.cs @@ -10,19 +10,17 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Collections.Generic; - namespace TouchSocket.Http; /// /// Http参数 /// -public interface IHttpParams : IDictionary +public interface IHttpParams : IDictionary { /// /// 获取参数 /// /// /// - string Get(string key); + TextValues Get(string key); } \ No newline at end of file diff --git a/src/TouchSocket.Http/Interface/IMultifileCollection.cs b/src/TouchSocket.Http/Interface/IMultifileCollection.cs index f26176be1..e7774476a 100644 --- a/src/TouchSocket.Http/Interface/IMultifileCollection.cs +++ b/src/TouchSocket.Http/Interface/IMultifileCollection.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Collections.Generic; - namespace TouchSocket.Http; /// diff --git a/src/TouchSocket.Http/ObsoleteClass.cs b/src/TouchSocket.Http/ObsoleteClass.cs index 3937e4d69..3ee5f89aa 100644 --- a/src/TouchSocket.Http/ObsoleteClass.cs +++ b/src/TouchSocket.Http/ObsoleteClass.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Http; /// diff --git a/src/TouchSocket.Http/Plugins/AuthenticationPlugin.cs b/src/TouchSocket.Http/Plugins/AuthenticationPlugin.cs new file mode 100644 index 000000000..8a672c0bb --- /dev/null +++ b/src/TouchSocket.Http/Plugins/AuthenticationPlugin.cs @@ -0,0 +1,91 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +namespace TouchSocket.Http; + +/// +/// Basic auth 认证插件 +/// +/// +/// PR https://github.com/RRQM/TouchSocket/pull/79 by Diego2098 +/// +public sealed class AuthenticationPlugin : PluginBase, IHttpPlugin +{ + public string Password { get; set; } = "111111"; + public string Realm { get; set; } = "Server"; + public string UserName { get; set; } = "admin"; + + public Task OnHttpRequest(IHttpSessionClient client, HttpContextEventArgs e) + { + var authorizationHeader = e.Context.Request.Headers["Authorization"]; + + if (string.IsNullOrEmpty(authorizationHeader)) + { + return this.Challenge(e, "Empty Authorization Header"); + } + + if (!authorizationHeader.ToString().StartsWith("Basic ", StringComparison.OrdinalIgnoreCase)) + { + return this.Challenge(e, "Invalid Authorization Header"); + } + + var authBase64 = authorizationHeader.ToString().Substring("Basic ".Length).Trim(); + + string authString; + try + { + authString = Encoding.UTF8.GetString(Convert.FromBase64String(authBase64)); + } + catch + { + return this.Challenge(e, "Invalid Base64 Authorization Header"); + } + + var credentials = authString.Split(':'); + if (credentials.Length != 2) + { + return this.Challenge(e, "Invalid Authorization Header"); + } + + var username = credentials[0]; + var password = credentials[1]; + + if (username != this.UserName || password != this.Password) + { + return this.Challenge(e, "Invalid Username or Password"); + } + + // 验证通过,继续下一个中间件或处理器 + return e.InvokeNext(); + } + + public AuthenticationPlugin SetCredentials(string userName, string password) + { + this.UserName = userName; + this.Password = password; + return this; + } + + public AuthenticationPlugin SetRealm(string realm = "Server") + { + this.Realm = realm; + return this; + } + + private Task Challenge(HttpContextEventArgs e, string message) + { + e.Context.Response.Headers.Add("WWW-Authenticate", $"Basic realm=\"{this.Realm}\""); + return e.Context.Response + .SetStatus(401, message) + .AnswerAsync(); + } +} \ No newline at end of file diff --git a/src/TouchSocket.Http/Plugins/DefaultHttpServicePlugin.cs b/src/TouchSocket.Http/Plugins/DefaultHttpServicePlugin.cs index 45edaab5d..148426f26 100644 --- a/src/TouchSocket.Http/Plugins/DefaultHttpServicePlugin.cs +++ b/src/TouchSocket.Http/Plugins/DefaultHttpServicePlugin.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Collections.Generic; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Http; /// diff --git a/src/TouchSocket.Http/Plugins/Interfaces/IHttpPlugin.cs b/src/TouchSocket.Http/Plugins/Interfaces/IHttpPlugin.cs index d955072a3..d45bd2826 100644 --- a/src/TouchSocket.Http/Plugins/Interfaces/IHttpPlugin.cs +++ b/src/TouchSocket.Http/Plugins/Interfaces/IHttpPlugin.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Http; /// diff --git a/src/TouchSocket.Http/Readme.md b/src/TouchSocket.Http/Readme.md index b1d24792d..19043e32a 100644 --- a/src/TouchSocket.Http/Readme.md +++ b/src/TouchSocket.Http/Readme.md @@ -19,14 +19,14 @@ TouchSocket.Http 是一个基于 Http1.1 协议的组件库,为开发者提供 ## 支持的目标框架 + - net481 -- net45 - net462 - net472 - netstandard2.0 - netstandard2.1 - net6.0 -- net9.0 +- net10.0 - net8.0 ## 使用方法 diff --git a/src/TouchSocket.Http/StaticPage/FileExtensionContentTypeProvider.cs b/src/TouchSocket.Http/StaticPage/FileExtensionContentTypeProvider.cs index 51e54f4e6..700685941 100644 --- a/src/TouchSocket.Http/StaticPage/FileExtensionContentTypeProvider.cs +++ b/src/TouchSocket.Http/StaticPage/FileExtensionContentTypeProvider.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; - namespace TouchSocket.Http; /// diff --git a/src/TouchSocket.Http/StaticPage/FolderEntry.cs b/src/TouchSocket.Http/StaticPage/FolderEntry.cs index de2e41fd2..206392ac4 100644 --- a/src/TouchSocket.Http/StaticPage/FolderEntry.cs +++ b/src/TouchSocket.Http/StaticPage/FolderEntry.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.IO; - namespace TouchSocket.Http; internal class FolderEntry : HashSet diff --git a/src/TouchSocket.Http/StaticPage/HttpStaticPagePlugin.cs b/src/TouchSocket.Http/StaticPage/HttpStaticPagePlugin.cs index 7f65b8041..04db719f8 100644 --- a/src/TouchSocket.Http/StaticPage/HttpStaticPagePlugin.cs +++ b/src/TouchSocket.Http/StaticPage/HttpStaticPagePlugin.cs @@ -10,10 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.IO.Compression; -using System.Threading.Tasks; -using TouchSocket.Core; namespace TouchSocket.Http; @@ -21,7 +18,7 @@ namespace TouchSocket.Http; /// Http静态内容插件 /// [PluginOption(Singleton = false)] -public class HttpStaticPagePlugin : PluginBase, IHttpPlugin +public sealed class HttpStaticPagePlugin : PluginBase, IHttpPlugin { private readonly StaticFilesPool m_filesPool; @@ -39,34 +36,23 @@ public class HttpStaticPagePlugin : PluginBase, IHttpPlugin /// /// 提供文件扩展名和MIME类型之间的映射。 /// - public IContentTypeProvider ContentTypeProvider { get; set; } + public IContentTypeProvider ContentTypeProvider { get; } + + /// + /// 重新导航 + /// + public Func> NavigateAction { get; } + + /// + /// 在响应之前调用。 + /// + public Func ResponseAction { get; } /// /// 静态文件池 /// public StaticFilesPool StaticFilesPool => this.m_filesPool; - /// - /// 重新导航 - /// - public Func> NavigateAction { get; set; } - - /// - /// 在响应之前调用。 - /// - public Func ResponseAction { get; set; } - - /// - /// 配置静态文件池 - /// - /// - /// - public HttpStaticPagePlugin ConfigureStaticFilesPool(Action action) - { - action?.Invoke(this.m_filesPool); - return this; - } - /// /// 添加静态文件目录 /// @@ -79,81 +65,6 @@ public class HttpStaticPagePlugin : PluginBase, IHttpPlugin this.m_filesPool.AddFolder(path, prefix, filter, timeout); } - /// - /// 设置提供文件扩展名和MIME类型之间的映射。 - /// - /// - /// - public HttpStaticPagePlugin SetContentTypeProvider(IContentTypeProvider provider) - { - this.ContentTypeProvider = provider ?? throw new ArgumentNullException(nameof(provider)); - return this; - } - - /// - /// 设置提供文件扩展名和MIME类型之间的映射。 - /// - /// 提供文件扩展名和MIME类型之间的映射的操作 - /// 返回当前的HttpStaticPagePlugin实例 - public HttpStaticPagePlugin SetContentTypeProvider(Action provider) - { - provider.Invoke(this.ContentTypeProvider); - return this; - } - - /// - /// 设定重新导航 - /// - /// - /// - public HttpStaticPagePlugin SetNavigateAction(Func> func) - { - this.NavigateAction = func; - return this; - } - - /// - /// 设定重新导航 - /// - /// - /// - public HttpStaticPagePlugin SetNavigateAction(Func func) - { - this.NavigateAction = (request) => - { - return Task.FromResult(func(request)); - }; - return this; - } - - /// - /// 在响应之前调用。 - /// - /// - /// - public HttpStaticPagePlugin SetResponseAction(Func func) - { - this.ResponseAction = func; - return this; - } - - /// - /// 在响应之前调用。 - /// - /// - /// - public HttpStaticPagePlugin SetResponseAction(Action action) - { - this.ResponseAction = (response) => - { - action.Invoke(response); - return EasyTask.CompletedTask; - }; - return this; - } - - #region Remove - /// /// 移除所有静态页面 /// @@ -162,20 +73,6 @@ public class HttpStaticPagePlugin : PluginBase, IHttpPlugin this.m_filesPool.Clear(); } - /// - /// 移除指定路径的静态文件 - /// - /// Static content path - public void RemoveFolder(string path) - { - path = FileUtility.PathFormat(path); - path = path.EndsWith("/") ? path : path + "/"; - - this.m_filesPool.RemoveFolder(path); - } - - #endregion Remove - /// public async Task OnHttpRequest(IHttpSessionClient client, HttpContextEventArgs e) { @@ -234,4 +131,16 @@ public class HttpStaticPagePlugin : PluginBase, IHttpPlugin await e.InvokeNext().ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } + + /// + /// 移除指定路径的静态文件 + /// + /// Static content path + public void RemoveFolder(string path) + { + path = FileUtility.PathFormat(path); + path = path.EndsWith("/") ? path : path + "/"; + + this.m_filesPool.RemoveFolder(path); + } } \ No newline at end of file diff --git a/src/TouchSocket.Http/StaticPage/MemCacheEntry.cs b/src/TouchSocket.Http/StaticPage/MemCacheEntry.cs index 50fbd845b..3ffc1076c 100644 --- a/src/TouchSocket.Http/StaticPage/MemCacheEntry.cs +++ b/src/TouchSocket.Http/StaticPage/MemCacheEntry.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Text; - namespace TouchSocket.Http; internal class MemCacheEntry diff --git a/src/TouchSocket.Http/StaticPage/StaticEntry.cs b/src/TouchSocket.Http/StaticPage/StaticEntry.cs index 713f97ebb..d71ae9f58 100644 --- a/src/TouchSocket.Http/StaticPage/StaticEntry.cs +++ b/src/TouchSocket.Http/StaticPage/StaticEntry.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.IO; - namespace TouchSocket.Http; /// @@ -45,7 +42,7 @@ public class StaticEntry /// /// 获取一个值,指示当前条目是否为字节缓存。 /// - /// 如果条目的值非空,则为true;否则为false。 + /// 如果条目的值非空,则为;否则为 public bool IsCacheBytes => this.Value != null; /// diff --git a/src/TouchSocket.Http/StaticPage/StaticFilesPool.cs b/src/TouchSocket.Http/StaticPage/StaticFilesPool.cs index 769ff3291..15e215be9 100644 --- a/src/TouchSocket.Http/StaticPage/StaticFilesPool.cs +++ b/src/TouchSocket.Http/StaticPage/StaticFilesPool.cs @@ -10,13 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; using System.Web; -using TouchSocket.Core; namespace TouchSocket.Http; diff --git a/src/TouchSocket.Http/StaticPage/StaticPageOptions.cs b/src/TouchSocket.Http/StaticPage/StaticPageOptions.cs index 8cdb90a79..f72773c1d 100644 --- a/src/TouchSocket.Http/StaticPage/StaticPageOptions.cs +++ b/src/TouchSocket.Http/StaticPage/StaticPageOptions.cs @@ -10,16 +10,12 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Http; /// /// 静态页面配置 /// -public class StaticPageOptions +public sealed class StaticPageOptions { private readonly StaticFilesPool m_filesPool = new StaticFilesPool(); @@ -95,11 +91,20 @@ public class StaticPageOptions /// 一个实现了IContentTypeProvider接口的对象,用于提供文件扩展名与MIME类型的映射。 public void SetContentTypeProvider(IContentTypeProvider provider) { - // 校验provider参数是否为空,如果为空则抛出ArgumentNullException异常。 - // 这里是确保ContentTypeProvider的设置必须是有效的,非空对象。 this.ContentTypeProvider = provider ?? throw new ArgumentNullException(nameof(provider)); } + /// + /// 设置提供文件扩展名和MIME类型之间的映射。 + /// + /// 用于配置的委托。 + public void SetContentTypeProvider(Action provider) + { + var contentTypeProvider = this.ContentTypeProvider ?? new FileExtensionContentTypeProvider(); + provider.Invoke(contentTypeProvider); + this.ContentTypeProvider = contentTypeProvider; + } + /// /// 设定重新导航 /// diff --git a/src/TouchSocket.Http/TouchSocket.Http.csproj b/src/TouchSocket.Http/TouchSocket.Http.csproj index 912e079bd..f9c0e00aa 100644 --- a/src/TouchSocket.Http/TouchSocket.Http.csproj +++ b/src/TouchSocket.Http/TouchSocket.Http.csproj @@ -1,33 +1,20 @@ - net481;net45;net462;net472;netstandard2.0;netstandard2.1;net6.0;net9.0;net8.0 + net462;netstandard2.0;netstandard2.1;net6.0;net10.0;net8.0 Http;Https;HttpServer;HttpClient;WebSocket;WebSocketServer;WebSocketClient;TouchSocket 这是一个基于Http1.1协议的组件库。它能提供Http服务器、客户端、以及WebSocket组件。功能上支持大文件下载、上传、以及多线程下载和断点续传,小文件form上传,WebApi声明和执行。所提供的Http客户端是基于连接的,可以捕获连接和断开连接等消息。 说明文档:https://touchsocket.net/ TouchSocket.Http - - - - - - - - - - - - - + - diff --git a/src/TouchSocket.Http/WebSockets/Common/InternalWebSocket.Read.cs b/src/TouchSocket.Http/WebSockets/Common/InternalWebSocket.Read.cs index af8e01c80..e01d999bb 100644 --- a/src/TouchSocket.Http/WebSockets/Common/InternalWebSocket.Read.cs +++ b/src/TouchSocket.Http/WebSockets/Common/InternalWebSocket.Read.cs @@ -10,47 +10,46 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Resources; namespace TouchSocket.Http.WebSockets; -internal sealed partial class InternalWebSocket : BlockSegment, IWebSocket +internal sealed partial class InternalWebSocket : SafetyDisposableObject, IWebSocket { - WebSocketReceiveBlockResult m_blockResult; + private readonly AsyncExchange m_asyncExchange = new(); - public async Task Complete(string msg) + private string msg; + + public void Complete(string msg) { try { - this.m_blockResult.IsCompleted = true; - this.m_blockResult.Message = msg; - await this.TriggerAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); + this.m_asyncExchange.Complete(); + this.msg = msg; } catch { } } - public ValueTask ReadAsync(CancellationToken token) + public async ValueTask ReadAsync(CancellationToken cancellationToken) { this.ThrowIfNotAllowAsyncRead(); - return base.ProtectedReadAsync(token); - } - internal async Task InputReceiveAsync(WSDataFrame dataFrame) - { - this.m_blockResult.DataFrame = dataFrame; - await base.TriggerAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var readLease = await this.m_asyncExchange.ReadAsync(cancellationToken); + var frame = readLease.Value; + return new WebSocketReceiveBlockResult(readLease.Dispose) + { + IsCompleted = readLease.IsCompleted, + DataFrame = frame, + Message = this.msg + }; } - protected override IWebSocketReceiveResult CreateResult(Action actionForDispose) + internal ValueTask InputReceiveAsync(WSDataFrame dataFrame, CancellationToken cancellationToken) { - this.m_blockResult = new WebSocketReceiveBlockResult(actionForDispose); - return this.m_blockResult; + return this.m_asyncExchange.WriteAsync(dataFrame, cancellationToken); } + private void ThrowIfNotAllowAsyncRead() { if (!this.m_allowAsyncRead) @@ -58,8 +57,10 @@ internal sealed partial class InternalWebSocket : BlockSegment @@ -84,6 +86,6 @@ internal sealed partial class InternalWebSocket : BlockSegment this.m_allowAsyncRead; set => this.m_allowAsyncRead = value; } public IHttpSession Client => this.m_isServer ? this.m_httpSocketClient : this.m_httpClientBase; + public CancellationToken ClosedToken => this.m_isServer ? this.m_httpSocketClient.ClosedToken : this.m_httpClientBase.ClosedToken; public WebSocketCloseStatus CloseStatus { get; set; } public bool IsClient => !this.m_isServer; public DateTimeOffset LastReceivedTime => this.Client.LastReceivedTime; @@ -52,11 +48,11 @@ internal sealed partial class InternalWebSocket : IWebSocket public IResolver Resolver => this.m_isServer ? this.m_httpSocketClient.Resolver : this.m_httpClientBase.Resolver; public string Version { get; set; } - public async Task CloseAsync(string msg, CancellationToken token = default) + public async Task CloseAsync(string msg, CancellationToken cancellationToken = default) { try { - return await this.CloseAsync(WebSocketCloseStatus.NormalClosure, msg, token); + return await this.CloseAsync(WebSocketCloseStatus.NormalClosure, msg, cancellationToken); } catch (Exception ex) { @@ -64,7 +60,7 @@ internal sealed partial class InternalWebSocket : IWebSocket } } - public async Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken token = default) + public async Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken = default) { if (!this.Online) { @@ -75,28 +71,29 @@ internal sealed partial class InternalWebSocket : IWebSocket try { - using (var frame = new WSDataFrame() { FIN = true, Opcode = WSDataType.Close }) + var byteBlock = new ByteBlock(1024); + try { - using (var byteBlock = new ByteBlock(1024)) + WriterExtension.WriteValue(ref byteBlock, (ushort)closeStatus, EndianType.Big); + if (statusDescription.HasValue()) { - byteBlock.WriteUInt16((ushort)closeStatus, EndianType.Big); - if (statusDescription.HasValue()) - { - byteBlock.WriteNormalString(statusDescription, Encoding.UTF8); - } - frame.PayloadData = byteBlock; - await this.SendAsync(frame).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + WriterExtension.WriteNormalString(ref byteBlock, statusDescription, Encoding.UTF8); } + var frame = new WSDataFrame(byteBlock.Memory) { FIN = true, Opcode = WSDataType.Close }; + await this.SendAsync(frame, true, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } + finally + { + byteBlock.Dispose(); + } + if (this.m_isServer) { - await this.m_httpSocketClient.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both); - await this.m_httpSocketClient.CloseAsync(statusDescription, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.m_httpSocketClient.CloseAsync(statusDescription, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } else { - await this.m_httpClientBase.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both); - await this.m_httpClientBase.CloseAsync(statusDescription, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.m_httpClientBase.CloseAsync(statusDescription, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } return Result.Success; } @@ -106,11 +103,11 @@ internal sealed partial class InternalWebSocket : IWebSocket } } - public async Task PingAsync() + public async Task PingAsync(CancellationToken cancellationToken = default) { try { - await this.SendAsync(new WSDataFrame() { FIN = true, Opcode = WSDataType.Ping }).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.SendAsync(new WSDataFrame() { FIN = true, Opcode = WSDataType.Ping }, cancellationToken: cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return Result.Success; } catch (Exception ex) @@ -119,74 +116,30 @@ internal sealed partial class InternalWebSocket : IWebSocket } } - public async Task PongAsync() + public async Task PongAsync(CancellationToken cancellationToken = default) { try { - await this.SendAsync(new WSDataFrame() { FIN = true, Opcode = WSDataType.Pong }).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.SendAsync(new WSDataFrame() { FIN = true, Opcode = WSDataType.Pong }, cancellationToken: cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return Result.Success; } catch (Exception ex) { - return Result.FromException(ex); } - } #region 发送 - public async Task SendAsync(string text, bool endOfMessage = true) + public async Task SendAsync(string text, bool endOfMessage = true, CancellationToken cancellationToken = default) { - using (var frame = new WSDataFrame() { FIN = endOfMessage, Opcode = WSDataType.Text }.AppendText(text)) - { - await this.SendAsync(frame, endOfMessage).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - } - - public async Task SendAsync(ReadOnlyMemory memory, bool endOfMessage = true) - { - using (var frame = new WSDataFrame() { FIN = endOfMessage, Opcode = WSDataType.Binary }) - { - frame.AppendBinary(memory.Span); - await this.SendAsync(frame, endOfMessage).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - } - - public async Task SendAsync(WSDataFrame dataFrame, bool endOfMessage = true) - { - WSDataType dataType; - if (this.m_isCont) - { - dataType = WSDataType.Cont; - if (endOfMessage) - { - this.m_isCont = false; - } - } - else - { - dataType = dataFrame.Opcode; - if (!endOfMessage) - { - this.m_isCont = true; - } - } - dataFrame.Opcode = dataType; - - var byteBlock = new ByteBlock(dataFrame.MaxLength); + ThrowHelper.ThrowArgumentNullExceptionIfStringIsNullOrEmpty(text, nameof(text)); + var byteBlock = new ByteBlock(Encoding.UTF8.GetMaxByteCount(text.Length)); try { - if (this.m_isServer) - { - dataFrame.BuildResponse(ref byteBlock); - await this.m_httpSocketClient.InternalSendAsync(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - else - { - dataFrame.BuildRequest(ref byteBlock); - await this.m_httpClientBase.InternalSendAsync(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } + WriterExtension.WriteNormalString(ref byteBlock, text, Encoding.UTF8); + var frame = new WSDataFrame(byteBlock.Memory) { FIN = endOfMessage, Opcode = WSDataType.Text }; + await this.SendAsync(frame, endOfMessage, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } finally { @@ -194,27 +147,74 @@ internal sealed partial class InternalWebSocket : IWebSocket } } - #endregion 发送 - - protected override void Dispose(bool disposing) + public async Task SendAsync(ReadOnlyMemory memory, bool endOfMessage = true, CancellationToken cancellationToken = default) { - if (this.DisposedValue) + var frame = new WSDataFrame(memory) { FIN = endOfMessage, Opcode = WSDataType.Binary }; + await this.SendAsync(frame, endOfMessage, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + + public async Task SendAsync(WSDataFrame dataFrame, bool endOfMessage = true, CancellationToken cancellationToken = default) + { + var opcode = dataFrame.Opcode; + var isControl = opcode == WSDataType.Close || opcode == WSDataType.Ping || opcode == WSDataType.Pong; + + if (isControl) { - return; + // 控制帧不能分片,必须FIN=true,且不影响当前分片状态 + dataFrame.FIN = true; + dataFrame.Opcode = opcode; } - - base.Dispose(disposing); - - if (disposing) + else { - if (this.m_isServer) + // 文本/二进制数据帧按分片状态机处理 + dataFrame.FIN = endOfMessage; + if (this.m_isCont) { - this.m_httpSocketClient.Dispose(); + dataFrame.Opcode = WSDataType.Cont; + if (endOfMessage) + { + this.m_isCont = false; + } } else { - this.m_httpClientBase.Dispose(); + dataFrame.Opcode = opcode; + if (!endOfMessage) + { + this.m_isCont = true; + } } } + + dataFrame.Mask = !this.m_isServer; + + var transport = this.m_isServer ? this.m_httpSocketClient.InternalTransport : this.m_httpClientBase.InternalTransport; + + await transport.WriteLocker.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + try + { + var writer = new PipeBytesWriter(transport.Writer); + + dataFrame.Build(ref writer); + await writer.FlushAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + finally + { + transport.WriteLocker.Release(); + } + } + + #endregion 发送 + + protected override void SafetyDispose(bool disposing) + { + if (this.m_isServer) + { + this.m_httpSocketClient.SafeDispose(); + } + else + { + this.m_httpClientBase.SafeDispose(); + } } } \ No newline at end of file diff --git a/src/TouchSocket.Http/WebSockets/Common/WSDataFrame.cs b/src/TouchSocket.Http/WebSockets/Common/WSDataFrame.cs index 0d880d29f..9159f5d70 100644 --- a/src/TouchSocket.Http/WebSockets/Common/WSDataFrame.cs +++ b/src/TouchSocket.Http/WebSockets/Common/WSDataFrame.cs @@ -10,20 +10,27 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Text; -using TouchSocket.Core; - namespace TouchSocket.Http.WebSockets; /// /// WebSocket数据帧 /// -public class WSDataFrame : DisposableObject, IRequestInfo, IRequestInfoBuilder, IBigUnfixedHeaderRequestInfo +public sealed class WSDataFrame : IRequestInfo, IRequestInfoBuilder, IBigUnfixedHeaderRequestInfo { + private readonly ReadOnlyMemory m_payloadData; private int m_headerLength; + private ByteBlock m_payloadDataBlock; private int m_payloadLength; + public WSDataFrame(ReadOnlyMemory payloadData) + { + this.m_payloadData = payloadData; + } + + internal WSDataFrame() + { + } + /// /// 是否为最后数据帧。 /// @@ -62,10 +69,10 @@ public class WSDataFrame : DisposableObject, IRequestInfo, IRequestInfoBuilder, /// /// 掩码值 /// - public byte[] MaskingKey { get; set; } + public ReadOnlyMemory MaskingKey { get; set; } /// - public int MaxLength => this.PayloadLength + 100; + public int MaxLength => this.PayloadData.Length + 14; /// /// 数据类型 @@ -75,17 +82,7 @@ public class WSDataFrame : DisposableObject, IRequestInfo, IRequestInfoBuilder, /// /// 有效数据 /// - public ByteBlock PayloadData { get; set; } - - /// - /// 有效载荷数据长度 - /// - public int PayloadLength - { - get => this.m_payloadLength != 0 ? this.m_payloadLength : this.PayloadData != null ? this.PayloadData.Length : 0; - - set => this.m_payloadLength = value; - } + public ReadOnlyMemory PayloadData => this.m_payloadDataBlock?.Memory ?? this.m_payloadData; /// /// 标识RSV-1。 @@ -103,9 +100,14 @@ public class WSDataFrame : DisposableObject, IRequestInfo, IRequestInfoBuilder, public bool RSV3 { get; set; } /// - public void Build(ref TByteBlock byteBlock) where TByteBlock : IByteBlock + public void Build(ref TWriter writer) where TWriter : IBytesWriter { - var memory = this.PayloadData == null ? ReadOnlyMemory.Empty : this.PayloadData.Memory; + var memory = this.PayloadData; + + var rawMemory = writer.GetMemory(memory.Length + 14); + + var rawWriter = new BytesWriter(rawMemory); + int payloadLength; Span extLen = stackalloc byte[8]; @@ -115,7 +117,7 @@ public class WSDataFrame : DisposableObject, IRequestInfo, IRequestInfoBuilder, if (length < 126) { payloadLength = length; - extLen = Span.Empty; + extLen = []; } else if (length < 65536) { @@ -140,34 +142,53 @@ public class WSDataFrame : DisposableObject, IRequestInfo, IRequestInfoBuilder, header = (header << 7) + payloadLength; - byteBlock.Write(TouchSocketBitConverter.BigEndian.GetBytes((ushort)header)); + WriterExtension.WriteValue(ref rawWriter, (ushort)header, EndianType.Big); if (payloadLength > 125) { - byteBlock.Write(extLen); + rawWriter.Write(extLen); } + Span maskingKey = stackalloc byte[4]; + if (this.Mask) { - byteBlock.Write(new ReadOnlySpan(this.MaskingKey, 0, 4)); + if (this.MaskingKey.IsEmpty) + { + TouchSocketBitConverter.BigEndian.WriteBytes(maskingKey, (int)DateTime.UtcNow.Ticks); + } + else + { + this.MaskingKey.Span.Slice(0, 4).CopyTo(maskingKey); + } + + rawWriter.Write(maskingKey); } if (payloadLength > 0) { if (this.Mask) { - if (byteBlock.Capacity < byteBlock.Position + length) - { - byteBlock.SetCapacity(byteBlock.Position + length, true); - } - WSTools.DoMask(byteBlock.TotalMemory.Span.Slice(byteBlock.Position), memory.Span, this.MaskingKey); - byteBlock.SetLength(byteBlock.Position + length); + WSTools.DoMask(rawMemory.Span.Slice((int)rawWriter.WrittenCount), memory.Span, maskingKey); + rawWriter.Advance(length); } else { - byteBlock.Write(memory.Span); + rawWriter.Write(memory.Span); } } + + writer.Advance((int)rawWriter.WrittenCount); + } + + public void SetMask(ReadOnlyMemory mask) + { + if (mask.Length != 4) + { + throw new OverlengthException("Mask只能为四位。"); + } + this.MaskingKey = mask; + this.Mask = true; } /// @@ -187,13 +208,10 @@ public class WSDataFrame : DisposableObject, IRequestInfo, IRequestInfoBuilder, } /// - protected override void Dispose(bool disposing) + internal void Dispose() { - if (disposing) - { - this.PayloadData.SafeDispose(); - } - base.Dispose(disposing); + this.m_payloadDataBlock.SafeDispose(); + this.m_payloadDataBlock = default; } #region UnfixedHeader @@ -203,7 +221,7 @@ public class WSDataFrame : DisposableObject, IRequestInfo, IRequestInfoBuilder, void IBigUnfixedHeaderRequestInfo.OnAppendBody(ReadOnlySpan buffer) { - this.PayloadData.Write(buffer); + this.m_payloadDataBlock.Write(buffer); } bool IBigUnfixedHeaderRequestInfo.OnFinished() @@ -217,18 +235,17 @@ public class WSDataFrame : DisposableObject, IRequestInfo, IRequestInfoBuilder, if (this.Mask) { - WSTools.DoMask(this.PayloadData.TotalMemory.Span, this.PayloadData.Memory.Span, this.MaskingKey); + WSTools.DoMask(this.m_payloadDataBlock.TotalMemory.Span, this.m_payloadDataBlock.Memory.Span, this.MaskingKey.Span); } } return true; } - bool IBigUnfixedHeaderRequestInfo.OnParsingHeader(ref TByteBlock byteBlock) + bool IBigUnfixedHeaderRequestInfo.OnParsingHeader(ref TReader reader) { - var offset = byteBlock.Position; - var index = offset; - var dataBuffer = byteBlock.Span.Slice(offset); + var offset = 0; + var dataBuffer = reader.GetSpan((int)reader.BytesRemaining); var length = dataBuffer.Length; if (length < 2) { @@ -251,8 +268,6 @@ public class WSDataFrame : DisposableObject, IRequestInfo, IRequestInfoBuilder, { if (length < 4) { - //offset = index; - //cache return false; } payloadLength = TouchSocketBitConverter.BigEndian.To(dataBuffer.Slice(++offset)); @@ -272,7 +287,7 @@ public class WSDataFrame : DisposableObject, IRequestInfo, IRequestInfoBuilder, if (this.Mask) { - if (length < (offset - index) + 4) + if (length < (offset) + 4) { return false; } @@ -284,30 +299,15 @@ public class WSDataFrame : DisposableObject, IRequestInfo, IRequestInfoBuilder, this.MaskingKey = maskingKey; } - this.m_headerLength = offset - index; - byteBlock.Position += this.m_headerLength; + this.m_headerLength = offset; + reader.Advance(this.m_headerLength); if (payloadLength > 0) { - this.PayloadData = new ByteBlock(payloadLength); + this.m_payloadDataBlock = new ByteBlock(payloadLength); } return true; - - //var block = new ByteBlock(payloadLength); - //dataFrame.PayloadData = block; - - //var surlen = length - (offset - index); - //if (payloadLength <= surlen) - //{ - // block.Write(new System.ReadOnlySpan(dataBuffer, offset, payloadLength)); - // offset += payloadLength; - //} - //else - //{ - // block.Write(new System.ReadOnlySpan(dataBuffer, offset, surlen)); - // offset += surlen; - //} } #endregion UnfixedHeader diff --git a/src/TouchSocket.Http/WebSockets/Common/WSTools.cs b/src/TouchSocket.Http/WebSockets/Common/WSTools.cs index 1407f80e0..62c7d42f9 100644 --- a/src/TouchSocket.Http/WebSockets/Common/WSTools.cs +++ b/src/TouchSocket.Http/WebSockets/Common/WSTools.cs @@ -10,11 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Text; -using TouchSocket.Core; - namespace TouchSocket.Http.WebSockets; /// @@ -22,90 +17,16 @@ namespace TouchSocket.Http.WebSockets; /// internal static class WSTools { - /// - /// 应答。 - /// + public const string AcceptMask = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - public static bool Build(ByteBlock byteBlock, WSDataFrame dataFrame, ReadOnlyMemory memory) - { - int payloadLength; - byte[] extLen; - - var length = memory.Length; - - if (length < 126) - { - payloadLength = length; - extLen = new byte[0]; - } - else if (length < 65536) - { - payloadLength = 126; - extLen = TouchSocketBitConverter.BigEndian.GetBytes((ushort)length); - } - else - { - payloadLength = 127; - extLen = TouchSocketBitConverter.BigEndian.GetBytes((ulong)length); - } - - var header = dataFrame.FIN ? 1 : 0; - header = (header << 1) + (dataFrame.RSV1 ? 1 : 0); - header = (header << 1) + (dataFrame.RSV2 ? 1 : 0); - header = (header << 1) + (dataFrame.RSV3 ? 1 : 0); - header = (header << 4) + (ushort)dataFrame.Opcode; - - header = dataFrame.Mask ? (header << 1) + 1 : (header << 1) + 0; - - header = (header << 7) + payloadLength; - - byteBlock.Write(TouchSocketBitConverter.BigEndian.GetBytes((ushort)header)); - - if (payloadLength > 125) - { - byteBlock.Write(new ReadOnlySpan(extLen, 0, extLen.Length)); - } - - if (dataFrame.Mask) - { - byteBlock.Write(new ReadOnlySpan(dataFrame.MaskingKey, 0, 4)); - } - - if (payloadLength > 0) - { - if (dataFrame.Mask) - { - if (byteBlock.Capacity < byteBlock.Position + length) - { - byteBlock.SetCapacity(byteBlock.Position + length, true); - } - WSTools.DoMask(byteBlock.TotalMemory.Span.Slice(byteBlock.Position), memory.Span, dataFrame.MaskingKey); - byteBlock.SetLength(byteBlock.Position + length); - } - else - { - byteBlock.Write(memory.Span); - } - } - return true; - } - - /// - /// 计算Base64值 - /// - /// - /// public static string CalculateBase64Key(string str) { return (str + AcceptMask).ToSha1(Encoding.UTF8).ToBase64(); } - /// - /// 获取Base64随即字符串。 - /// - /// + public static string CreateBase64Key() { var src = new byte[16]; @@ -114,7 +35,7 @@ internal static class WSTools } - public static void DoMask(Span span, ReadOnlySpan memorySpan, byte[] masks) + public static void DoMask(Span span, ReadOnlySpan memorySpan, ReadOnlySpan masks) { for (var i = 0; i < memorySpan.Length; i++) { @@ -122,13 +43,7 @@ internal static class WSTools } } - /// - /// 获取WS的请求头 - /// - /// - /// - /// - /// + public static HttpRequest GetWSRequest(HttpClientBase httpClientBase, string version, out string base64Key) { var request = new HttpRequest(); @@ -143,12 +58,7 @@ internal static class WSTools return request; } - /// - /// 获取响应 - /// - /// - /// - /// + public static bool TryGetResponse(HttpRequest request, HttpResponse response) { var upgrade = request.Headers.Get(HttpHeaders.Upgrade); @@ -168,10 +78,10 @@ internal static class WSTools } response.StatusCode = 101; - response.StatusMessage = "switching protocols"; - response.Headers.TryAdd(HttpHeaders.Connection, "upgrade"); + response.StatusMessage = "Switching Protocols"; + response.Headers.TryAdd(HttpHeaders.Connection, "Upgrade"); response.Headers.TryAdd(HttpHeaders.Upgrade, "websocket"); - response.Headers.TryAdd("sec-websocket-accept", CalculateBase64Key(secWebSocketKey)); + response.Headers.TryAdd("Sec-WebSocket-Accept", CalculateBase64Key(secWebSocketKey)); return true; } } \ No newline at end of file diff --git a/src/TouchSocket.Http/WebSockets/Common/WebSocketMessageCombinator.cs b/src/TouchSocket.Http/WebSockets/Common/WebSocketMessageCombinator.cs index dadab6868..e76fe1b59 100644 --- a/src/TouchSocket.Http/WebSockets/Common/WebSocketMessageCombinator.cs +++ b/src/TouchSocket.Http/WebSockets/Common/WebSocketMessageCombinator.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using TouchSocket.Core; - namespace TouchSocket.Http.WebSockets; /// @@ -29,7 +26,7 @@ public readonly struct WebSocketMessage : IDisposable /// 消息的数据类型,使用WSDataType枚举表示。 /// 消息的负载数据,使用ByteBlock结构表示。 /// 在消息释放时需要调用的动作。 - public WebSocketMessage(WSDataType opcode, ByteBlock payloadData, Action disposeAction) + public WebSocketMessage(WSDataType opcode, ReadOnlyMemory payloadData, Action disposeAction) { this.Opcode = opcode; this.PayloadData = payloadData; @@ -44,7 +41,7 @@ public readonly struct WebSocketMessage : IDisposable /// /// 获取消息的负载数据。 /// - public ByteBlock PayloadData { get; } + public ReadOnlyMemory PayloadData { get; } /// /// 释放消息资源。 @@ -84,7 +81,7 @@ public sealed class WebSocketMessageCombinator //判断中继包 if (dataFrame.FIN)//判断是否为最终包 { - webSocketMessage = new WebSocketMessage(this.m_wSDataType, this.m_byteBlock, this.PrivateClear); + webSocketMessage = new WebSocketMessage(this.m_wSDataType, this.m_byteBlock.Memory, this.PrivateClear); return true; } else diff --git a/src/TouchSocket.Http/WebSockets/Common/WebSocketOption.cs b/src/TouchSocket.Http/WebSockets/Common/WebSocketOption.cs index f2501b3f7..1779cf8e8 100644 --- a/src/TouchSocket.Http/WebSockets/Common/WebSocketOption.cs +++ b/src/TouchSocket.Http/WebSockets/Common/WebSocketOption.cs @@ -10,6 +10,8 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ +using System; + namespace TouchSocket.Http.WebSockets; /// @@ -21,4 +23,9 @@ public class WebSocketOption /// 版本 /// public string Version { get; set; } = "13"; + + /// + /// 客户端保活时间间隔。设置为大于0的值以启用底层WebSocket Ping保活;设置为则禁用。 + /// + public TimeSpan KeepAliveInterval { get; set; } = TimeSpan.Zero; } \ No newline at end of file diff --git a/src/TouchSocket.Http/WebSockets/Components/SetupClientWebSocket.cs b/src/TouchSocket.Http/WebSockets/Components/SetupClientWebSocket.cs index cdae74250..fea01664d 100644 --- a/src/TouchSocket.Http/WebSockets/Components/SetupClientWebSocket.cs +++ b/src/TouchSocket.Http/WebSockets/Components/SetupClientWebSocket.cs @@ -10,11 +10,8 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; +using System.Buffers; using System.Net.WebSockets; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Resources; using TouchSocket.Sockets; @@ -30,40 +27,32 @@ public abstract class SetupClientWebSocket : SetupConfigObject, IClosableClient, /// public SetupClientWebSocket() { - this.m_receiveCounter = new ValueCounter - { - Period = TimeSpan.FromSeconds(1), - OnPeriod = this.OnReceivePeriod - }; - this.m_sendCounter = new ValueCounter - { - Period = TimeSpan.FromSeconds(1), - OnPeriod = this.OnSendPeriod - }; + this.m_receiveCounter = new ValueCounter(TimeSpan.FromSeconds(1), this.OnReceivePeriod); + this.m_sendCounter = new ValueCounter(TimeSpan.FromSeconds(1), this.OnSendPeriod); } #region 字段 private readonly SemaphoreSlim m_semaphoreForConnect = new SemaphoreSlim(1, 1); private ClientWebSocket m_client; - private Task m_receiveTask; - private bool m_isOnline; - private int m_receiveBufferSize = 1024 * 10; + private Task m_runTask; + private bool m_online; private ValueCounter m_receiveCounter; private ValueCounter m_sendCounter; - private CancellationTokenSource m_tokenSourceForReceive; - + private CancellationTokenSource m_tokenSourceForOnline; + private ClosedEventArgs m_closedEventArgs; #endregion 字段 #region 连接 /// - public virtual async Task ConnectAsync(int millisecondsTimeout, CancellationToken token) + public virtual async Task ConnectAsync(CancellationToken cancellationToken) { - await this.m_semaphoreForConnect.WaitTimeAsync(millisecondsTimeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + this.ThrowIfDisposed(); + await this.m_semaphoreForConnect.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); try { - if (this.m_isOnline) + if (this.m_online) { return; } @@ -72,21 +61,22 @@ public abstract class SetupClientWebSocket : SetupConfigObject, IClosableClient, { this.m_client.SafeDispose(); this.m_client = new ClientWebSocket(); + + // 应用可配置的WebSocket保活间隔 + var wsOption = this.Config.GetValue(WebSocketConfigExtension.WebSocketOptionProperty); + if (wsOption != null) + { + this.m_client.Options.KeepAliveInterval = wsOption.KeepAliveInterval; + } } - await this.m_client.ConnectAsync(this.RemoteIPHost, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.m_client.ConnectAsync(this.RemoteIPHost, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.m_tokenSourceForReceive = new CancellationTokenSource(); + this.m_tokenSourceForOnline = new CancellationTokenSource(); - //// 确保上次接收任务已经结束 - //var receiveTask = this.m_receiveTask; - //if (receiveTask != null) - //{ - // await receiveTask.ConfigureAwait(EasyTask.ContinueOnCapturedContext); - //} - this.m_receiveTask = EasyTask.Run(this.BeginReceive, this.m_tokenSourceForReceive.Token); + this.m_runTask = EasyTask.SafeRun(this.PrivateOnConnected, this.m_tokenSourceForOnline.Token); - this.m_isOnline = true; + this.m_online = true; } finally { @@ -94,6 +84,24 @@ public abstract class SetupClientWebSocket : SetupConfigObject, IClosableClient, } } + private async Task PrivateOnConnected(CancellationToken cancellationToken) + { + var receiveTask = EasyTask.SafeRun(this.ReceiveLoopAsync, cancellationToken); + + await receiveTask.ConfigureAwait(EasyTask.ContinueOnCapturedContext); + this.m_online = false; + await this.OnWebSocketClosed(this.m_closedEventArgs).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + + private async Task WaitClearConnect() + { + // 确保上次接收任务已经结束 + var runTask = this.m_runTask; + if (runTask != null) + { + await runTask.ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + } #endregion 连接 /// @@ -114,20 +122,25 @@ public abstract class SetupClientWebSocket : SetupConfigObject, IClosableClient, protected ClientWebSocket ClientWebSocket => this.m_client; /// - public virtual bool Online => this.m_client != null && this.m_client.State == WebSocketState.Open && this.m_client.CloseStatus == null && this.m_isOnline; + public virtual bool Online => this.m_client != null && this.m_client.State == WebSocketState.Open && this.m_client.CloseStatus == null && this.m_online; /// - public virtual async Task CloseAsync(string msg, CancellationToken token = default) + public CancellationToken ClosedToken => this.m_tokenSourceForOnline.GetTokenOrCanceled(); + + /// + public virtual async Task CloseAsync(string msg, CancellationToken cancellationToken = default) { try { - var client = this.m_client; - if (client != null && client.State == WebSocketState.Open) + if (!this.m_online) { - await client.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, msg, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return Result.Success; } - this.Abort(true, msg); + this.m_closedEventArgs ??= new ClosedEventArgs(true, msg); + await this.m_client.SafeCloseClientAsync(msg, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + this.CancelReceive(); + await this.WaitClearConnect().ConfigureAwait(EasyTask.ContinueOnCapturedContext); return Result.Success; } catch (Exception ex) @@ -136,57 +149,28 @@ public abstract class SetupClientWebSocket : SetupConfigObject, IClosableClient, } } - /// - /// 中断连接 - /// - /// - /// - protected void Abort(bool manual, string msg) - { - lock (this.m_semaphoreForConnect) - { - if (this.m_isOnline) - { - this.m_isOnline = false; - this.m_client.Abort(); - this.m_client.SafeDispose(); - this.CancelReceive(); - _ = EasyTask.SafeRun(this.PrivateOnClosed, this.m_receiveTask, new ClosedEventArgs(manual, msg)); - } - } - } - - private async Task PrivateOnClosed(Task receiveTask, ClosedEventArgs e) - { - //await receiveTask.ConfigureAwait(EasyTask.ContinueOnCapturedContext); - await this.OnWebSocketClosed(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } private void CancelReceive() { - var tokenSourceForReceive = this.m_tokenSourceForReceive; + var tokenSourceForReceive = this.m_tokenSourceForOnline; if (tokenSourceForReceive != null) { - tokenSourceForReceive.Cancel(); - tokenSourceForReceive.Dispose(); + tokenSourceForReceive.SafeCancel(); + tokenSourceForReceive.SafeDispose(); } - this.m_tokenSourceForReceive = null; + this.m_tokenSourceForOnline = null; } /// - protected override void Dispose(bool disposing) + protected override void SafetyDispose(bool disposing) { - if (this.DisposedValue) - { - return; - } if (disposing) { - this.Abort(true, $"调用{nameof(Dispose)}"); + _ = EasyTask.SafeRun(async () => await this.CloseAsync(TouchSocketResource.DisposeClose).ConfigureAwait(EasyTask.ContinueOnCapturedContext)); } - base.Dispose(disposing); + base.SafetyDispose(disposing); } /// @@ -198,40 +182,34 @@ public abstract class SetupClientWebSocket : SetupConfigObject, IClosableClient, return EasyTask.CompletedTask; } - /// - /// 收到数据 - /// - /// - /// - /// - protected abstract Task OnReceived(WebSocketReceiveResult result, ByteBlock byteBlock); + protected abstract Task OnWebSocketReceived(WebSocketMessageType messageType, ReadOnlySequence sequence); /// /// 发送数据 /// - /// + /// 数据 /// /// - /// + /// 可取消令箭 /// - protected async Task ProtectedSendAsync(ReadOnlyMemory memory, WebSocketMessageType messageType, bool endOfMessage, CancellationToken token) + protected async Task ProtectedSendAsync(ReadOnlyMemory memory, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken) { #if NET6_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER - await this.m_client.SendAsync(memory, messageType, endOfMessage, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.m_client.SendAsync(memory, messageType, endOfMessage, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); #else var array = memory.GetArray(); - await this.m_client.SendAsync(array, messageType, endOfMessage, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.m_client.SendAsync(array, messageType, endOfMessage, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); #endif this.m_sendCounter.Increment(memory.Length); } - private async Task BeginReceive(CancellationToken token) + private async Task ReceiveLoopAsync(CancellationToken cancellationToken) { - var byteBlock = new ByteBlock(this.m_receiveBufferSize); try { - while (true) + using var writer = new SegmentedBytesWriter(1024); + while (!cancellationToken.IsCancellationRequested) { try { @@ -241,17 +219,21 @@ public abstract class SetupClientWebSocket : SetupConfigObject, IClosableClient, { break; } + var segment = writer.GetMemory(1024); +#if NET6_0_OR_GREATER + var result = await client.ReceiveAsync(segment, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); +#else + var result = await client.ReceiveAsync(segment.GetArray(), cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); +#endif - var segment = byteBlock.TotalMemory.GetArray(); - var result = await client.ReceiveAsync(segment, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); if (result.MessageType == WebSocketMessageType.Close) { try { - if (!token.IsCancellationRequested) + if (!cancellationToken.IsCancellationRequested) { - await client.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await client.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } catch @@ -264,50 +246,33 @@ public abstract class SetupClientWebSocket : SetupConfigObject, IClosableClient, { break; } - byteBlock.SetLength(result.Count); this.m_receiveCounter.Increment(result.Count); - - //处理数据 - await this.OnReceived(result, byteBlock).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + writer.Advance(result.Count); + if (result.EndOfMessage) + { + //处理数据 + await this.OnWebSocketReceived(result.MessageType, writer.Sequence).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + writer.Clear(); + } } catch (Exception ex) { this.Logger?.Debug(this, ex); break; } - finally - { - if (byteBlock.Holding || byteBlock.DisposedValue) - { - byteBlock.Dispose();//释放上个内存 - byteBlock = new ByteBlock(this.m_receiveBufferSize); - } - else - { - byteBlock.Reset(); - if (this.m_receiveBufferSize > byteBlock.Capacity) - { - byteBlock.SetCapacity(this.m_receiveBufferSize); - } - } - } } - - this.Abort(false, TouchSocketResource.RemoteDisconnects); + this.m_closedEventArgs ??= new ClosedEventArgs(false, TouchSocketResource.RemoteDisconnects); } catch (Exception ex) { - this.Abort(false, ex.Message); - } - finally - { - byteBlock.Dispose(); + this.m_closedEventArgs = new ClosedEventArgs(false, ex.Message); + this.Logger?.Debug(this, ex); } } private void OnReceivePeriod(long value) { - this.m_receiveBufferSize = TouchSocketCoreUtility.HitBufferLength(value); + //this.m_receiveBufferSize = TouchSocketCoreUtility.HitBufferLength(value); } private void OnSendPeriod(long value) diff --git a/src/TouchSocket.Http/WebSockets/Components/WebSocketClient.cs b/src/TouchSocket.Http/WebSockets/Components/WebSocketClient.cs index 0c6831415..3c9624d38 100644 --- a/src/TouchSocket.Http/WebSockets/Components/WebSocketClient.cs +++ b/src/TouchSocket.Http/WebSockets/Components/WebSocketClient.cs @@ -10,11 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Net.WebSockets; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Sockets; namespace TouchSocket.Http.WebSockets; @@ -24,12 +19,6 @@ namespace TouchSocket.Http.WebSockets; /// public partial class WebSocketClient : WebSocketClientBase, IWebSocketClient { - /// - public bool AllowAsyncRead { get => this.WebSocket.AllowAsyncRead; set => this.WebSocket.AllowAsyncRead = value; } - - /// - public IHttpSession Client => this; - #region 事件 /// @@ -39,10 +28,10 @@ public partial class WebSocketClient : WebSocketClientBase, IWebSocketClient public ClosingEventHandler Closing { get; set; } /// - public HttpContextEventHandler Handshaked { get; set; } + public HttpContextEventHandler Connected { get; set; } /// - public HttpContextEventHandler Handshaking { get; set; } + public HttpContextEventHandler Connecting { get; set; } /// public WSDataFrameEventHandler Received { get; set; } @@ -90,33 +79,33 @@ public partial class WebSocketClient : WebSocketClientBase, IWebSocketClient } /// - protected override async Task OnWebSocketHandshaked(HttpContextEventArgs e) + protected override async Task OnWebSocketConnected(HttpContextEventArgs e) { - if (this.Handshaked != null) + if (this.Connected != null) { - await this.Handshaked.Invoke(this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.Connected.Invoke(this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); if (e.Handled) { return; } } - await base.OnWebSocketHandshaked(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await base.OnWebSocketConnected(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// - protected override async Task OnWebSocketHandshaking(HttpContextEventArgs e) + protected override async Task OnWebSocketConnecting(HttpContextEventArgs e) { - if (this.Handshaking != null) + if (this.Connecting != null) { - await this.Handshaking.Invoke(this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.Connecting.Invoke(this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); if (e.Handled) { return; } } - await base.OnWebSocketHandshaking(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await base.OnWebSocketConnecting(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// @@ -135,51 +124,19 @@ public partial class WebSocketClient : WebSocketClientBase, IWebSocketClient #endregion 事件 - /// - public string Version => this.WebSocket.Version; + private readonly SemaphoreSlim m_semaphoreSlim = new SemaphoreSlim(1, 1); /// - public WebSocketCloseStatus CloseStatus => this.WebSocket.CloseStatus; - - /// - public Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken token = default) + public async Task ConnectAsync(CancellationToken cancellationToken) { - return this.WebSocket.CloseAsync(closeStatus, statusDescription, token); - } - - /// - public Task PingAsync() - { - return this.WebSocket.PingAsync(); - } - - /// - public Task PongAsync() - { - return this.WebSocket.PongAsync(); - } - - /// - public ValueTask ReadAsync(CancellationToken token) - { - return this.WebSocket.ReadAsync(token); - } - - /// - public Task SendAsync(WSDataFrame dataFrame, bool endOfMessage = true) - { - return this.WebSocket.SendAsync(dataFrame, endOfMessage); - } - - /// - public Task SendAsync(string text, bool endOfMessage = true) - { - return this.WebSocket.SendAsync(text, endOfMessage); - } - - /// - public Task SendAsync(ReadOnlyMemory memory, bool endOfMessage = true) - { - return this.WebSocket.SendAsync(memory, endOfMessage); + await this.m_semaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + try + { + await this.ProtectedWebSocketConnectAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + finally + { + this.m_semaphoreSlim.Release(); + } } } \ No newline at end of file diff --git a/src/TouchSocket.Http/WebSockets/Components/WebSocketClientBase.cs b/src/TouchSocket.Http/WebSockets/Components/WebSocketClientBase.cs index 5156cb3ab..9361ee24f 100644 --- a/src/TouchSocket.Http/WebSockets/Components/WebSocketClientBase.cs +++ b/src/TouchSocket.Http/WebSockets/Components/WebSocketClientBase.cs @@ -10,11 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Net.WebSockets; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Sockets; namespace TouchSocket.Http.WebSockets; @@ -22,7 +18,7 @@ namespace TouchSocket.Http.WebSockets; /// /// WebSocket用户终端。 /// -public abstract class WebSocketClientBase : HttpClientBase +public abstract class WebSocketClientBase : HttpClientBase, IWebSocket { /// /// WebSocket用户终端 @@ -32,61 +28,110 @@ public abstract class WebSocketClientBase : HttpClientBase this.m_webSocket = new InternalWebSocket(this); } + /// + public bool AllowAsyncRead { get => this.m_webSocket.AllowAsyncRead; set => this.m_webSocket.AllowAsyncRead = value; } + + /// + public IHttpSession Client => this; + + /// + public WebSocketCloseStatus CloseStatus => this.WebSocket.CloseStatus; + /// public override bool Online => this.m_webSocket.Online; + /// + public string Version => this.m_webSocket.Version; + + /// + public Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken = default) + { + return this.m_webSocket.CloseAsync(closeStatus, statusDescription, cancellationToken); + } + + /// + public Task PingAsync(CancellationToken cancellationToken = default) + { + return this.m_webSocket.PingAsync(cancellationToken); + } + + /// + public Task PongAsync(CancellationToken cancellationToken = default) + { + return this.m_webSocket.PongAsync(cancellationToken); + } + + /// + public ValueTask ReadAsync(CancellationToken cancellationToken) + { + return this.m_webSocket.ReadAsync(cancellationToken); + } + + /// + public Task SendAsync(WSDataFrame dataFrame, bool endOfMessage = true, CancellationToken cancellationToken = default) + { + return this.m_webSocket.SendAsync(dataFrame, endOfMessage, cancellationToken); + } + + /// + public Task SendAsync(string text, bool endOfMessage = true, CancellationToken cancellationToken = default) + { + return this.m_webSocket.SendAsync(text, endOfMessage, cancellationToken); + } + + /// + public Task SendAsync(ReadOnlyMemory memory, bool endOfMessage = true, CancellationToken cancellationToken = default) + { + return this.m_webSocket.SendAsync(memory, endOfMessage, cancellationToken); + } + #region Connect - /// - public virtual async Task ConnectAsync(int millisecondsTimeout, CancellationToken token) + /// + /// 异步建立 WebSocket 连接。 + /// + /// 用于取消操作的 。 + /// 表示异步操作的 + protected virtual async Task ProtectedWebSocketConnectAsync(CancellationToken cancellationToken) { - await this.m_semaphoreSlim.WaitTimeAsync(millisecondsTimeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - - try + if (!base.Online) { - if (!base.Online) - { - await this.TcpConnectAsync(millisecondsTimeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - - var option = this.Config.GetValue(WebSocketConfigExtension.WebSocketOptionProperty); - - var request = WSTools.GetWSRequest(this, option.Version, out var base64Key); - - await this.OnWebSocketHandshaking(new HttpContextEventArgs(new HttpContext(request, default))).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - - using (var responseResult = await this.ProtectedRequestAsync(request, millisecondsTimeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) - { - var response = responseResult.Response; - if (response.StatusCode != 101) - { - throw new WebSocketConnectException($"协议升级失败,信息:{response.StatusMessage},更多信息请捕获WebSocketConnectException异常,获得HttpContext得知。", new HttpContext(request, response)); - } - var accept = response.Headers.Get("sec-websocket-accept").Trim(); - if (accept.IsNullOrEmpty() || !accept.Equals(WSTools.CalculateBase64Key(base64Key).Trim(), StringComparison.OrdinalIgnoreCase)) - { - this.MainSocket.SafeDispose(); - throw new WebSocketConnectException($"WS服务器返回的应答码不正确,更多信息请捕获WebSocketConnectException异常,获得HttpContext得知。", new HttpContext(request, response)); - } - - this.InitWebSocket(); - - _ = EasyTask.Run(this.PrivateOnHandshaked, new HttpContextEventArgs(new HttpContext(request, response))); - } + await this.HttpConnectAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - finally + + var option = this.Config.WebSocketOption; + + var request = WSTools.GetWSRequest(this, option.Version, out var base64Key); + + await this.OnWebSocketConnecting(new HttpContextEventArgs(new HttpContext(request, default))) + .ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + //这里不要释放responseResult,主要是这是http最后一次请求,后续不会再用到http了。 + //如果释放了,会导致响应数据在PrivateOnConnected中失效。 + var responseResult = await this.ProtectedRequestAsync(request, cancellationToken) + .ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var response = responseResult.Response; + if (response.StatusCode != 101) { - this.m_semaphoreSlim.Release(); + throw new WebSocketConnectException($"协议升级失败,信息:{response.StatusMessage},更多信息请捕获WebSocketConnectException异常,获得HttpContext得知。", new HttpContext(request, response)); } + var accept = response.Headers.Get("sec-websocket-accept"); + if (accept.IsEmpty || !accept.Equals(WSTools.CalculateBase64Key(base64Key).Trim(), StringComparison.OrdinalIgnoreCase)) + { + await base.CloseAsync("WS服务器返回的应答3码不正确", cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + throw new WebSocketConnectException($"WS服务器返回的应答码不正确,更多信息请捕获WebSocketConnectException异常,获得HttpContext得知。", new HttpContext(request, response)); + } + this.InitWebSocket(); + + _ = EasyTask.SafeRun(this.PrivateOnConnected, new HttpContextEventArgs(new HttpContext(request, response))); } #endregion Connect #region 字段 - private readonly SemaphoreSlim m_semaphoreSlim = new SemaphoreSlim(1, 1); private readonly InternalWebSocket m_webSocket; - + private WebSocketDataHandlingAdapter m_dataHandlingAdapter; #endregion 字段 #region 事件 @@ -98,8 +143,7 @@ public abstract class WebSocketClientBase : HttpClientBase /// 一个 对象,表示异步操作的完成。 protected virtual async Task OnWebSocketClosed(ClosedEventArgs e) { - // 通知所有实现IWebSocketClosedPlugin接口的插件,WebSocket已关闭 - await this.PluginManager.RaiseAsync(typeof(IWebSocketClosedPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.PluginManager.RaiseIWebSocketClosedPluginAsync(this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// @@ -109,8 +153,7 @@ public abstract class WebSocketClientBase : HttpClientBase /// A 表示事件处理的异步操作。 protected virtual async Task OnWebSocketClosing(ClosingEventArgs e) { - // 通知所有实现了IWebSocketClosingPlugin接口的插件,WebSocket即将关闭 - await this.PluginManager.RaiseAsync(typeof(IWebSocketClosingPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.PluginManager.RaiseIWebSocketClosingPluginAsync(this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// @@ -118,9 +161,9 @@ public abstract class WebSocketClientBase : HttpClientBase /// /// 包含HTTP上下文信息的参数对象。 /// 一个表示任务已完成的Task对象。 - protected virtual async Task OnWebSocketHandshaked(HttpContextEventArgs e) + protected virtual async Task OnWebSocketConnected(HttpContextEventArgs e) { - await this.PluginManager.RaiseAsync(typeof(IWebSocketHandshakedPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.PluginManager.RaiseIWebSocketConnectedPluginAsync(this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// @@ -128,20 +171,14 @@ public abstract class WebSocketClientBase : HttpClientBase /// /// 包含HTTP上下文信息的参数对象 /// 一个表示异步操作完成的任务 - protected virtual async Task OnWebSocketHandshaking(HttpContextEventArgs e) + protected virtual async Task OnWebSocketConnecting(HttpContextEventArgs e) { - await this.PluginManager.RaiseAsync(typeof(IWebSocketHandshakingPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.PluginManager.RaiseIWebSocketConnectingPluginAsync(this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - private async Task PrivateOnHandshaked(object obj) + private Task PrivateOnConnected(HttpContextEventArgs e) { - try - { - await this.OnWebSocketHandshaked((HttpContextEventArgs)obj); - } - catch - { - } + return this.OnWebSocketConnected(e); } private async Task PrivateWebSocketClosed(ClosedEventArgs e) @@ -149,12 +186,12 @@ public abstract class WebSocketClientBase : HttpClientBase this.m_webSocket.Online = false; if (this.m_webSocket.AllowAsyncRead) { - await this.m_webSocket.Complete(e.Message).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + this.m_webSocket.Complete(e.Message); } await this.OnWebSocketClosed(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - private Task PrivateWebSocketClosing(ClosedEventArgs e) + private Task PrivateWebSocketClosing(ClosingEventArgs e) { return this.OnWebSocketClosing(e); } @@ -163,23 +200,24 @@ public abstract class WebSocketClientBase : HttpClientBase { if (dataFrame.IsClose) { - var bytes = dataFrame.PayloadData; - bytes.SeekToStart(); - if (bytes.Length >= 2) + var payloadMemory = dataFrame.PayloadData; + var payloadSpan = payloadMemory.Span; + if (payloadSpan.Length >= 2) { - var closeStatus = (WebSocketCloseStatus)bytes.ReadUInt16(EndianType.Big); + var closeStatus = (WebSocketCloseStatus)payloadSpan.ReadValue(EndianType.Big); this.m_webSocket.CloseStatus = closeStatus; } - var msg = bytes.ReadToSpan(bytes.CanReadLength).ToString(System.Text.Encoding.UTF8); + var msg = payloadSpan.ToString(System.Text.Encoding.UTF8); - await this.PrivateWebSocketClosing(new ClosedEventArgs(false, msg)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - await this.m_webSocket.CloseAsync(msg).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.PrivateWebSocketClosing(new ClosingEventArgs(msg)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + await this.m_webSocket.CloseAsync(msg ?? "Auto closed successful").ConfigureAwait(EasyTask.ContinueOnCapturedContext); return; } if (this.m_webSocket.AllowAsyncRead) { - await this.m_webSocket.InputReceiveAsync(dataFrame).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.m_webSocket.InputReceiveAsync(dataFrame, CancellationToken.None).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return; } @@ -200,11 +238,15 @@ public abstract class WebSocketClientBase : HttpClientBase private void InitWebSocket() { - var adapter = new WebSocketDataHandlingAdapter(); - base.SetWarpAdapter(adapter); - this.SetAdapter(adapter); + var webSocketDataHandlingAdapter = new WebSocketDataHandlingAdapter(); + this.SetAdapter(webSocketDataHandlingAdapter); + this.m_dataHandlingAdapter = webSocketDataHandlingAdapter; + this.Protocol = Protocol.WebSocket; this.m_webSocket.Online = true; + + var transport = base.Transport; + _ = EasyTask.SafeRun(this.WebSocketReceiveLoopAsync, transport); } #region Properties @@ -216,6 +258,61 @@ public abstract class WebSocketClientBase : HttpClientBase #endregion Properties + private async Task WebSocketReceiveLoopAsync(ITransport transport) + { + var cancellationToken = transport.ClosedToken; + using var reader = new PooledBytesReader(); + await transport.ReadLocker.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + try + { + while (true) + { + if (this.DisposedValue || cancellationToken.IsCancellationRequested) + { + return; + } + var result = await transport.Reader.ReadAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + if (result.Buffer.Length == 0) + { + break; + } + + try + { + reader.Reset(result.Buffer); + + if (!await this.OnTcpReceiving(reader).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) + { + await this.m_dataHandlingAdapter.ReceivedInputAsync(reader).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + var position = result.Buffer.GetPosition(reader.BytesRead); + transport.Reader.AdvanceTo(position, result.Buffer.End); + + if (result.IsCompleted || result.IsCanceled) + { + return; + } + reader.Clear(); + } + catch (Exception ex) + { + this.Logger?.Exception(this, ex); + await transport.CloseAsync(ex.Message).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + } + } + catch (Exception ex) + { + // 如果发生异常,记录日志并退出接收循环 + this.Logger?.Debug(this, ex); + } + finally + { + transport.ReadLocker.Release(); + } + } + + #region Override /// diff --git a/src/TouchSocket.Http/WebSockets/DataAdapter/WebSocketDataHandlingAdapter.cs b/src/TouchSocket.Http/WebSockets/DataAdapter/WebSocketDataHandlingAdapter.cs index 591c9a7d5..6f84bab0a 100644 --- a/src/TouchSocket.Http/WebSockets/DataAdapter/WebSocketDataHandlingAdapter.cs +++ b/src/TouchSocket.Http/WebSockets/DataAdapter/WebSocketDataHandlingAdapter.cs @@ -10,228 +10,23 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Http.WebSockets; -/// -/// WebSocket适配器 -/// -public sealed class WebSocketDataHandlingAdapter : SingleStreamDataHandlingAdapter + +internal sealed class WebSocketDataHandlingAdapter : CustomBigUnfixedHeaderDataHandlingAdapter { - private WSDataFrame m_dataFrameTemp; - - /// - /// 数据包剩余长度 - /// - private int m_surPlusLength = 0; - - /// - /// 临时包 - /// - private ByteBlock m_tempByteBlock; - - /// - /// 解码 - /// - /// - /// - /// - /// - /// - public FilterResult DecodingFromBytes(byte[] dataBuffer, ref int offset, int length, out WSDataFrame dataFrame) - { - var index = offset; - dataFrame = new WSDataFrame - { - RSV1 = dataBuffer[offset].GetBit(6), - RSV2 = dataBuffer[offset].GetBit(5), - RSV3 = dataBuffer[offset].GetBit(4), - FIN = (dataBuffer[offset] >> 7) == 1, - Opcode = (WSDataType)(dataBuffer[offset] & 0xf), - Mask = (dataBuffer[++offset] >> 7) == 1 - }; - - var payloadLength = dataBuffer[offset] & 0x7f; - if (payloadLength < 126) - { - offset++; - } - else if (payloadLength == 126) - { - if (length < 4) - { - offset = index; - return FilterResult.Cache; - } - payloadLength = TouchSocketBitConverter.BigEndian.ToUInt16(dataBuffer, ++offset); - offset += 2; - } - else if (payloadLength == 127) - { - if (length < 12) - { - this.m_tempByteBlock ??= new ByteBlock(1024 * 64); - this.m_tempByteBlock.Write(new System.ReadOnlySpan(dataBuffer, index, length)); - offset = index; - return FilterResult.GoOn; - } - payloadLength = (int)TouchSocketBitConverter.BigEndian.ToUInt64(dataBuffer, ++offset); - offset += 8; - } - - dataFrame.PayloadLength = payloadLength; - - if (dataFrame.Mask) - { - if (length < (offset - index) + 4) - { - this.m_tempByteBlock ??= new ByteBlock(1024 * 64); - this.m_tempByteBlock.Write(new System.ReadOnlySpan(dataBuffer, index, length)); - offset = index; - return FilterResult.GoOn; - } - dataFrame.MaskingKey = new byte[4]; - dataFrame.MaskingKey[0] = dataBuffer[offset++]; - dataFrame.MaskingKey[1] = dataBuffer[offset++]; - dataFrame.MaskingKey[2] = dataBuffer[offset++]; - dataFrame.MaskingKey[3] = dataBuffer[offset++]; - } - - var byteBlock = new ByteBlock(payloadLength); - dataFrame.PayloadData = byteBlock; - - var surLen = length - (offset - index); - if (payloadLength <= surLen) - { - byteBlock.Write(new System.ReadOnlySpan(dataBuffer, offset, payloadLength)); - offset += payloadLength; - } - else - { - byteBlock.Write(new System.ReadOnlySpan(dataBuffer, offset, surLen)); - offset += surLen; - } - - return FilterResult.Success; - } - - /// - /// 当接收到数据时处理数据 - /// - /// 数据流 - protected override async Task PreviewReceivedAsync(ByteBlock byteBlock) - { - var buffer = byteBlock.Memory.GetArray().Array; - var r = byteBlock.Length; - - if (this.m_tempByteBlock != null) - { - this.m_tempByteBlock.Write(new System.ReadOnlySpan(buffer, 0, r)); - buffer = this.m_tempByteBlock.ToArray(); - r = this.m_tempByteBlock.Position; - this.m_tempByteBlock.Dispose(); - this.m_tempByteBlock = null; - } - - if (this.m_dataFrameTemp == null) - { - await this.SplitPackageAsync(buffer, 0, r).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - else - { - if (this.m_surPlusLength == r) - { - this.m_dataFrameTemp.PayloadData.Write(new System.ReadOnlySpan(buffer, 0, this.m_surPlusLength)); - await this.PreviewHandle(this.m_dataFrameTemp).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.m_dataFrameTemp = null; - this.m_surPlusLength = 0; - } - else if (this.m_surPlusLength < r) - { - this.m_dataFrameTemp.PayloadData.Write(new System.ReadOnlySpan(buffer, 0, this.m_surPlusLength)); - await this.PreviewHandle(this.m_dataFrameTemp).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.m_dataFrameTemp = null; - await this.SplitPackageAsync(buffer, this.m_surPlusLength, r).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - else - { - this.m_dataFrameTemp.PayloadData.Write(new System.ReadOnlySpan(buffer, 0, r)); - this.m_surPlusLength -= r; - } - } - } - /// - protected override void Reset() + protected override WSDataFrame GetInstance() { - this.m_tempByteBlock.SafeDispose(); - this.m_tempByteBlock = null; - this.m_dataFrameTemp = null; - this.m_surPlusLength = 0; - base.Reset(); + return new WSDataFrame(); } - private async Task PreviewHandle(WSDataFrame dataFrame) + protected override async Task GoReceivedAsync(ReadOnlyMemory memory, IRequestInfo requestInfo) { - try + await base.GoReceivedAsync(memory, requestInfo); + if (requestInfo is WSDataFrame wsDataFrame) { - if (dataFrame.Mask) - { - WSTools.DoMask(dataFrame.PayloadData.TotalMemory.Span, dataFrame.PayloadData.Memory.Span, dataFrame.MaskingKey); - } - await this.GoReceivedAsync(null, dataFrame).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - finally - { - dataFrame.Dispose(); - } - } - - /// - /// 分解包 - /// - /// - /// - /// - private async Task SplitPackageAsync(byte[] dataBuffer, int offset, int length) - { - while (offset < length) - { - if (length - offset < 2) - { - this.m_tempByteBlock ??= new ByteBlock(1024 * 64); - this.m_tempByteBlock.Write(new System.ReadOnlySpan(dataBuffer, offset, length - offset)); - return; - } - - switch (this.DecodingFromBytes(dataBuffer, ref offset, length - offset, out var dataFrame)) - { - case FilterResult.Cache: - { - this.m_tempByteBlock ??= new ByteBlock(1024 * 64); - this.m_tempByteBlock.Write(new System.ReadOnlySpan(dataBuffer, offset, length - offset)); - return; - } - case FilterResult.Success: - { - if (dataFrame.PayloadLength == dataFrame.PayloadData.Length) - { - await this.PreviewHandle(dataFrame).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - else - { - this.m_surPlusLength = dataFrame.PayloadLength - dataFrame.PayloadData.Length; - this.m_dataFrameTemp = dataFrame; - } - } - break; - - case FilterResult.GoOn: - default: - return; - } + wsDataFrame.Dispose(); } } } \ No newline at end of file diff --git a/src/TouchSocket.Http/WebSockets/DataAdapter/WebSocketDataHandlingAdapter2.cs b/src/TouchSocket.Http/WebSockets/DataAdapter/WebSocketDataHandlingAdapter2.cs deleted file mode 100644 index 132129ea9..000000000 --- a/src/TouchSocket.Http/WebSockets/DataAdapter/WebSocketDataHandlingAdapter2.cs +++ /dev/null @@ -1,27 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using TouchSocket.Core; - -namespace TouchSocket.Http.WebSockets; - -/// -/// WebSocket适配器 -/// -public sealed class WebSocketDataHandlingAdapter2 : CustomBigUnfixedHeaderDataHandlingAdapter -{ - /// - protected override WSDataFrame GetInstance() - { - return new WSDataFrame(); - } -} \ No newline at end of file diff --git a/src/TouchSocket.Http/WebSockets/DelegateCollection.cs b/src/TouchSocket.Http/WebSockets/DelegateCollection.cs index 3c46112d5..0f20b856c 100644 --- a/src/TouchSocket.Http/WebSockets/DelegateCollection.cs +++ b/src/TouchSocket.Http/WebSockets/DelegateCollection.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; - namespace TouchSocket.Http.WebSockets; /// diff --git a/src/TouchSocket.Http/WebSockets/EventArgs/WSDataFrameEventArgs.cs b/src/TouchSocket.Http/WebSockets/EventArgs/WSDataFrameEventArgs.cs index 034d868b7..71fcbad21 100644 --- a/src/TouchSocket.Http/WebSockets/EventArgs/WSDataFrameEventArgs.cs +++ b/src/TouchSocket.Http/WebSockets/EventArgs/WSDataFrameEventArgs.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Http.WebSockets; /// diff --git a/src/TouchSocket.Http/WebSockets/Exceptions/WebSocketConnectException.cs b/src/TouchSocket.Http/WebSockets/Exceptions/WebSocketConnectException.cs index 312a71151..c60fab013 100644 --- a/src/TouchSocket.Http/WebSockets/Exceptions/WebSocketConnectException.cs +++ b/src/TouchSocket.Http/WebSockets/Exceptions/WebSocketConnectException.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Http.WebSockets; /// diff --git a/src/TouchSocket.Http/WebSockets/Extensions/WebSocketClientExtension.cs b/src/TouchSocket.Http/WebSockets/Extensions/WebSocketClientExtension.cs index 529ec24d9..7999fdc79 100644 --- a/src/TouchSocket.Http/WebSockets/Extensions/WebSocketClientExtension.cs +++ b/src/TouchSocket.Http/WebSockets/Extensions/WebSocketClientExtension.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Http.WebSockets; /// @@ -28,13 +24,11 @@ public static class WebSocketClientExtension /// 要发送的二进制数据。 /// 数据类型。 /// 是否为消息的结束帧。 + /// 可取消令箭 /// 表示异步操作的任务。 - public static async Task SendAsync(this IWebSocket webSocket, ReadOnlyMemory memory, WSDataType dataType, bool endOfMessage = true) + public static async Task SendAsync(this IWebSocket webSocket, ReadOnlyMemory memory, WSDataType dataType, bool endOfMessage = true, CancellationToken cancellationToken = default) { - using (var frame = new WSDataFrame() { FIN = endOfMessage, Opcode = dataType }) - { - frame.AppendBinary(memory.Span); - await webSocket.SendAsync(frame, endOfMessage).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } + var frame = new WSDataFrame(memory) { FIN = endOfMessage, Opcode = dataType }; + await webSocket.SendAsync(frame, endOfMessage, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } diff --git a/src/TouchSocket.Http/WebSockets/Extensions/WebSocketConfigExtension.cs b/src/TouchSocket.Http/WebSockets/Extensions/WebSocketConfigExtension.cs index 03dd14843..3918b3a3f 100644 --- a/src/TouchSocket.Http/WebSockets/Extensions/WebSocketConfigExtension.cs +++ b/src/TouchSocket.Http/WebSockets/Extensions/WebSocketConfigExtension.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; using TouchSocket.Http.WebSockets; namespace TouchSocket.Sockets; @@ -20,45 +19,10 @@ namespace TouchSocket.Sockets; /// public static class WebSocketConfigExtension { - ///// - ///// 构建WebSocketClient类客户端,并连接 - ///// - ///// - ///// - ///// - //public static TClient BuildWithWebSocketClient(this TouchSocketConfig config) where TClient : IWebSocketClient - //{ - // var client = Activator.CreateInstance(); - // client.Setup(config); - // client.Connect(); - // return client; - //} - - ///// - ///// 构建WebSocketClient类客户端,并连接 - ///// - ///// - ///// - //public static WebSocketClient BuildWithWebSocketClient(this TouchSocketConfig config) - //{ - // return BuildWithWebSocketClient(config); - //} - /// /// WebSocket配置属性 /// + [GeneratorProperty(TargetType = typeof(TouchSocketConfig), ActionMode = true)] public static readonly DependencyProperty WebSocketOptionProperty = new("WebSocketOption", new WebSocketOption()); - - /// - /// 设置WebSocket的相关配置 - /// - /// - /// - /// - public static TouchSocketConfig SetWebSocketOption(this TouchSocketConfig config, WebSocketOption value) - { - config.SetValue(WebSocketOptionProperty, value); - return config; - } } \ No newline at end of file diff --git a/src/TouchSocket.Http/WebSockets/Extensions/WebSocketDataFrameExtension.cs b/src/TouchSocket.Http/WebSockets/Extensions/WebSocketDataFrameExtension.cs index 31747d18c..8ad9d89e7 100644 --- a/src/TouchSocket.Http/WebSockets/Extensions/WebSocketDataFrameExtension.cs +++ b/src/TouchSocket.Http/WebSockets/Extensions/WebSocketDataFrameExtension.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Text; -using TouchSocket.Core; - namespace TouchSocket.Http.WebSockets; /// @@ -21,141 +17,7 @@ namespace TouchSocket.Http.WebSockets; /// public static class WebSocketDataFrameExtension { - - /// - /// 将二进制数据追加到WSDataFrame对象的PayloadData属性中。 - /// - /// 要追加数据的WSDataFrame对象。 - /// 要追加的二进制数据,使用类型以提高性能。 - /// 返回修改后的WSDataFrame对象,以便支持链式调用。 - public static WSDataFrame AppendBinary(this WSDataFrame dataFrame, ReadOnlySpan span) - { - // 如果PayloadData属性尚未初始化,则创建一个新的ByteBlock对象,大小为span的长度。 - dataFrame.PayloadData ??= new ByteBlock(span.Length); - // 将span中的数据写入PayloadData属性的末尾。 - dataFrame.PayloadData.Write(span); - // 返回修改后的WSDataFrame对象。 - return dataFrame; - } - - - /// - /// 将文本数据追加到WSDataFrame对象的PayloadData中。 - /// - /// 要追加文本数据的WSDataFrame对象。 - /// 要追加的文本数据。 - /// 文本数据的编码方式。默认为UTF8。 - /// 追加文本数据后的WSDataFrame对象。 - public static WSDataFrame AppendText(this WSDataFrame dataFrame, string text, Encoding encoding = default) - { - // 根据提供的编码方式或默认UTF8编码,将文本转换为字节数据。 - var data = (encoding == default ? Encoding.UTF8 : encoding).GetBytes(text); - - // 如果WSDataFrame的PayloadData尚未初始化,则使用当前数据长度初始化一个新的ByteBlock。 - dataFrame.PayloadData ??= new ByteBlock(data.Length); - - // 将转换后的字节数据写入PayloadData中。 - dataFrame.PayloadData.Write(data); - - // 返回更新后的WSDataFrame对象。 - return dataFrame; - } - - /// - /// 构建请求数据(含Make) - /// - /// 数据帧对象,用于封装请求数据 - /// 字节块对象,用于存储构建的请求数据 - /// 泛型参数,指定字节块的类型,必须实现IByteBlock接口 - /// - /// 此方法通过设置数据帧的Mask属性为true,并确保数据帧具有MaskingKey, - /// 然后调用dataFrame的Build方法来构建请求数据,并将结果存储在byteBlock中。 - /// 如果MaskingKey未设置,则使用"RRQM"作为默认值。 - /// - public static void BuildRequest(this WSDataFrame dataFrame, ref TByteBlock byteBlock) where TByteBlock : IByteBlock - { - // 设置数据帧的Mask属性为true,表示数据在传输前会被掩码处理 - dataFrame.Mask = true; - // 检查MaskingKey是否已设置,如果没有设置,则使用默认值"RRQM" - if (dataFrame.MaskingKey == null) - { - dataFrame.SetMaskString("RRQM"); - } - // 调用Build方法构建请求数据,并将结果存储在byteBlock中 - dataFrame.Build(ref byteBlock); - } - - /// - /// 构建请求数据(含Make) - /// - /// 要构建的数据帧 - /// 构建完成的字节数组 - public static byte[] BuildRequestToBytes(this WSDataFrame dataFrame) - { - // 设置数据帧的Mask属性为true,确保数据帧被掩码处理 - dataFrame.Mask = true; - // 如果数据帧的MaskingKey属性为空,则设置MaskingKey - if (dataFrame.MaskingKey == null) - { - dataFrame.SetMaskString("RRQM"); - } - // 创建一个ValueByteBlock对象,用于存储构建过程中的字节数据 - var byteBlock = new ValueByteBlock(dataFrame.MaxLength); - try - { - // 调用数据帧的Build方法,将数据帧构建到byteBlock中 - dataFrame.Build(ref byteBlock); - // 将byteBlock转换为字节数组 - var data = byteBlock.ToArray(); - // 返回构建完成的字节数组 - return data; - } - finally - { - // 释放byteBlock占用的资源 - byteBlock.Dispose(); - } - } - - /// - /// 构建响应数据(无Make) - /// - /// 待构建的WS数据帧 - /// 字节块,用于存储构建后的数据 - /// - /// 该方法直接调用WS数据帧的Build方法来构建数据, - /// 并将构建结果存储在字节块中,而不是创建一个新的对象。 - /// 这样可以提高性能,减少内存分配。 - /// - public static void BuildResponse(this WSDataFrame dataFrame, ref TByteBlock byteBlock) where TByteBlock : IByteBlock - { - dataFrame.Build(ref byteBlock); - } - - /// - /// 构建响应数据(无Make) - /// - /// 要转换为字节数组的数据帧 - /// 转换后的字节数组 - public static byte[] BuildResponseToBytes(this WSDataFrame dataFrame) - { - // 创建一个值字块,大小为数据帧的最大长度,用于存储即将构建的字节数据 - var byteBlock = new ValueByteBlock(dataFrame.MaxLength); - try - { - // 调用数据帧的Build方法,将数据帧的内容构建到byteBlock中 - dataFrame.Build(ref byteBlock); - // 将值字块转换为字节数组 - var data = byteBlock.ToArray(); - // 返回构建好的字节数组 - return data; - } - finally - { - // 确保在离开方法前释放值字块的资源 - byteBlock.Dispose(); - } - } + public static readonly ReadOnlyMemory DefaultMaskingKey = "RRQM".ToUtf8Bytes(); /// /// 当数据类型为时,将数据帧转换为文本消息。 diff --git a/src/TouchSocket.Http/WebSockets/Extensions/WebSocketExtension.cs b/src/TouchSocket.Http/WebSockets/Extensions/WebSocketExtension.cs index 55102b0c6..3e6d43c61 100644 --- a/src/TouchSocket.Http/WebSockets/Extensions/WebSocketExtension.cs +++ b/src/TouchSocket.Http/WebSockets/Extensions/WebSocketExtension.cs @@ -10,11 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; +using System.Net.WebSockets; namespace TouchSocket.Http.WebSockets; @@ -23,6 +19,31 @@ namespace TouchSocket.Http.WebSockets; /// public static class WebSocketExtension { + #region WebSocket + public static async Task SafeCloseClientAsync(this WebSocket webSocket, string msg, CancellationToken cancellationToken) + { + if (webSocket is null) + { + return Result.Success; + } + + if (webSocket.State != WebSocketState.Open) + { + return Result.Success; + } + + try + { + await webSocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, msg, cancellationToken); + return Result.Success; + } + catch (Exception ex) + { + return Result.FromException(ex); + } + } + #endregion + #region string /// @@ -34,9 +55,9 @@ public static class WebSocketExtension /// /// 要读取数据的WebSocket实例 /// 用于存储接收到的数据的字节块 - /// 用于取消操作的取消令牌 + /// 用于取消操作的取消令牌 /// 返回一个任务,该任务在完成后将包含读取到的字符串 - public static async Task ReadStringAsync(this IWebSocket webSocket, ByteBlock byteBlock, CancellationToken token = default) + public static async Task ReadStringAsync(this IWebSocket webSocket, ByteBlock byteBlock, CancellationToken cancellationToken = default) { if (!webSocket.AllowAsyncRead) { @@ -44,7 +65,7 @@ public static class WebSocketExtension } while (true) { - using (var receiveResult = await webSocket.ReadAsync(token)) + using (var receiveResult = await webSocket.ReadAsync(cancellationToken)) { if (receiveResult.IsCompleted) { @@ -101,13 +122,13 @@ public static class WebSocketExtension /// /// /// 要读取数据的WebSocket实例 - /// 用于取消异步读取操作的取消令牌 + /// 用于取消异步读取操作的取消令牌 /// 返回异步读取到的字符串 - public static async Task ReadStringAsync(this IWebSocket webSocket, CancellationToken token = default) + public static async Task ReadStringAsync(this IWebSocket webSocket, CancellationToken cancellationToken = default) { using (var byteBlock = new ByteBlock(1024 * 64)) { - await ReadStringAsync(webSocket, byteBlock, token); + await ReadStringAsync(webSocket, byteBlock, cancellationToken); return byteBlock.Span.ToString(Encoding.UTF8); } @@ -126,9 +147,9 @@ public static class WebSocketExtension /// /// 要读取数据的WebSocket实例。 /// 用于存储读取的二进制数据的容器。 - /// 用于取消异步读取操作的取消令牌。 + /// 用于取消异步读取操作的取消令牌。 /// 返回一个Task对象,表示异步读取操作。 - public static async Task ReadBinaryAsync(this IWebSocket webSocket, ByteBlock byteBlock, CancellationToken token = default) + public static async Task ReadBinaryAsync(this IWebSocket webSocket, ByteBlock byteBlock, CancellationToken cancellationToken = default) { if (!webSocket.AllowAsyncRead) { @@ -136,7 +157,7 @@ public static class WebSocketExtension } while (true) { - using (var receiveResult = await webSocket.ReadAsync(token)) + using (var receiveResult = await webSocket.ReadAsync(cancellationToken)) { if (receiveResult.IsCompleted) { @@ -194,9 +215,9 @@ public static class WebSocketExtension /// /// 要读取数据的WebSocket实例。 /// 用于存储读取的二进制数据的流。 - /// 用于取消异步读取操作的取消令牌。默认值为。 + /// 用于取消异步读取操作的取消令牌。默认值为。 /// 返回一个对象,表示异步读取操作。 - public static async Task ReadBinaryAsync(this IWebSocket webSocket, Stream stream, CancellationToken token = default) + public static async Task ReadBinaryAsync(this IWebSocket webSocket, Stream stream, CancellationToken cancellationToken = default) { if (!webSocket.AllowAsyncRead) { @@ -204,7 +225,7 @@ public static class WebSocketExtension } while (true) { - using (var receiveResult = await webSocket.ReadAsync(token)) + using (var receiveResult = await webSocket.ReadAsync(cancellationToken)) { if (receiveResult.IsCompleted) { @@ -221,14 +242,12 @@ public static class WebSocketExtension //收到的是中继包 if (dataFrame.FIN)//判断是否为最终包 { - var segment = data.Memory.GetArray(); - await stream.WriteAsync(segment.Array, segment.Offset, segment.Count); + await stream.WriteAsync(data, CancellationToken.None); return; } else { - var segment = data.Memory.GetArray(); - await stream.WriteAsync(segment.Array, segment.Offset, segment.Count); + await stream.WriteAsync(data, CancellationToken.None); } } break; @@ -237,14 +256,12 @@ public static class WebSocketExtension { if (dataFrame.FIN)//判断是不是最后的包 { - var segment = data.Memory.GetArray(); - await stream.WriteAsync(segment.Array, segment.Offset, segment.Count); + await stream.WriteAsync(data, CancellationToken.None); return; } else { - var segment = data.Memory.GetArray(); - await stream.WriteAsync(segment.Array, segment.Offset, segment.Count); + await stream.WriteAsync(data, CancellationToken.None); } } break; @@ -264,7 +281,7 @@ public static class WebSocketExtension /// /// WebSocketMessageCombinatorProperty /// - public readonly static DependencyProperty WebSocketMessageCombinatorProperty = + public static readonly DependencyProperty WebSocketMessageCombinatorProperty = new DependencyProperty("WebSocketMessageCombinator", (obj) => { var combinator = new WebSocketMessageCombinator(); diff --git a/src/TouchSocket.Http/WebSockets/Extensions/WebSocketPluginRaiseExtension.cs b/src/TouchSocket.Http/WebSockets/Extensions/WebSocketPluginRaiseExtension.cs new file mode 100644 index 000000000..57d28336e --- /dev/null +++ b/src/TouchSocket.Http/WebSockets/Extensions/WebSocketPluginRaiseExtension.cs @@ -0,0 +1,24 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using TouchSocket.Http.WebSockets; + +namespace TouchSocket.Core; + +[PluginRaise(typeof(IWebSocketClosedPlugin))] +[PluginRaise(typeof(IWebSocketClosingPlugin))] +[PluginRaise(typeof(IWebSocketConnectedPlugin))] +[PluginRaise(typeof(IWebSocketConnectingPlugin))] +[PluginRaise(typeof(IWebSocketReceivedPlugin))] +internal static partial class WebSocketPluginRaiseExtension +{ +} \ No newline at end of file diff --git a/src/TouchSocket.Http/WebSockets/Extensions/WebSocketPluginsManagerExtension.cs b/src/TouchSocket.Http/WebSockets/Extensions/WebSocketPluginsManagerExtension.cs index 0eff1152d..efcb99205 100644 --- a/src/TouchSocket.Http/WebSockets/Extensions/WebSocketPluginsManagerExtension.cs +++ b/src/TouchSocket.Http/WebSockets/Extensions/WebSocketPluginsManagerExtension.cs @@ -11,7 +11,6 @@ //------------------------------------------------------------------------------ using TouchSocket.Http.WebSockets; -using TouchSocket.Sockets; namespace TouchSocket.Core; @@ -24,48 +23,27 @@ public static class WebSocketPluginManagerExtension /// 使用WebSocket插件。 /// /// 插件类型实例 - public static WebSocketFeature UseWebSocket(this IPluginManager pluginManager) + public static WebSocketFeature UseWebSocket(this IPluginManager pluginManager, Action configure) { - return pluginManager.Add(); + var options = new WebSocketFeatureOptions(); + configure?.Invoke(options); + var webSocketFeature = new WebSocketFeature(options); + pluginManager.Add(webSocketFeature); + return webSocketFeature; } + /// - /// 使用WebSocket心跳插件,客户端、服务器均有效。但是一般建议客户端使用即可。 + /// 使用WebSocket插件,指定WebSocket的Url。 /// - /// 插件类型实例 - public static WebSocketHeartbeatPlugin UseWebSocketHeartbeat(this IPluginManager pluginManager) + /// 插件管理器。 + /// WebSocket的Url,默认为"/ws"。 + /// 插件类型实例 + public static WebSocketFeature UseWebSocket(this IPluginManager pluginManager, string url = "/ws") { - var heartbeatPlugin = new WebSocketHeartbeatPlugin(); - pluginManager.Add(heartbeatPlugin); - return heartbeatPlugin; + return pluginManager.UseWebSocket(options => + { + options.SetUrl(url); + }); } - - #region WebSocketReconnection - - /// - /// 使用断线重连。 - /// - /// - /// - /// - public static ReconnectionPlugin UseWebSocketReconnection(this IPluginManager pluginManager) where TClient : IWebSocketClient - { - var reconnectionPlugin = new WebSocketReconnectionPlugin(); - pluginManager.Add(reconnectionPlugin); - return reconnectionPlugin; - } - - /// - /// 使用断线重连。 - /// - /// - /// - public static ReconnectionPlugin UseWebSocketReconnection(this IPluginManager pluginManager) - { - var reconnectionPlugin = new WebSocketReconnectionPlugin(); - pluginManager.Add(reconnectionPlugin); - return reconnectionPlugin; - } - - #endregion WebSocketReconnection } \ No newline at end of file diff --git a/src/TouchSocket.Http/WebSockets/Interface/IWebSocket.cs b/src/TouchSocket.Http/WebSockets/Interface/IWebSocket.cs index ee03db0a2..8ed235746 100644 --- a/src/TouchSocket.Http/WebSockets/Interface/IWebSocket.cs +++ b/src/TouchSocket.Http/WebSockets/Interface/IWebSocket.cs @@ -10,11 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Net.WebSockets; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Sockets; namespace TouchSocket.Http.WebSockets; @@ -49,52 +45,55 @@ public interface IWebSocket : IDisposable, IOnlineClient, IClosableClient, IReso /// /// 关闭状态。 /// 状态描述。 - /// 可取消令箭 + /// 可取消令箭 /// 返回一个任务对象,表示异步操作的结果。 - Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken token = default); + Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken = default); /// /// 异步发送Ping请求。 /// /// 任务完成时返回。 - Task PingAsync(); + Task PingAsync(CancellationToken cancellationToken = default); /// /// 异步执行Pong操作。 /// /// 一个任务对象,表示异步操作的完成。 - Task PongAsync(); + Task PongAsync(CancellationToken cancellationToken = default); /// /// 异步等待读取数据 /// - /// 用于取消异步读取操作的取消令牌 + /// 用于取消异步读取操作的取消令牌 /// 返回一个值任务,该任务完成后将包含WebSocket接收的结果 - ValueTask ReadAsync(CancellationToken token); + ValueTask ReadAsync(CancellationToken cancellationToken); /// /// 采用WebSocket协议,发送WS数据。发送结束后,请及时释放 /// /// 要发送的数据帧 - /// 是否是消息的结束标志,默认为true + /// 是否是消息的结束标志,默认为 + /// 可取消令箭 /// 返回一个异步任务,用于指示发送操作的完成状态 - Task SendAsync(WSDataFrame dataFrame, bool endOfMessage = true); + Task SendAsync(WSDataFrame dataFrame, bool endOfMessage = true, CancellationToken cancellationToken = default); /// /// 异步发送文本消息。 /// /// 要发送的文本内容。 - /// 指示是否是消息的结束。默认为true。 + /// 指示是否是消息的结束。默认为。 + /// 可取消令箭 /// 返回一个任务对象,表示异步操作的结果。 - Task SendAsync(string text, bool endOfMessage = true); + Task SendAsync(string text, bool endOfMessage = true, CancellationToken cancellationToken = default); /// /// 异步发送指定的字节内存数据。 /// /// 要发送的字节数据,作为只读内存块。 - /// 指示当前数据是否为消息的结束。默认为true。 + /// 指示当前数据是否为消息的结束。默认为。 + /// 可取消令箭 /// /// 此方法允许异步发送数据,通过指定是否为消息的结束来控制数据流。 /// - Task SendAsync(ReadOnlyMemory memory, bool endOfMessage = true); + Task SendAsync(ReadOnlyMemory memory, bool endOfMessage = true, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/TouchSocket.Http/WebSockets/Interface/IWebSocketClient.cs b/src/TouchSocket.Http/WebSockets/Interface/IWebSocketClient.cs index 5a2a33586..66271fd52 100644 --- a/src/TouchSocket.Http/WebSockets/Interface/IWebSocketClient.cs +++ b/src/TouchSocket.Http/WebSockets/Interface/IWebSocketClient.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; using TouchSocket.Sockets; namespace TouchSocket.Http.WebSockets; @@ -33,12 +32,12 @@ public interface IWebSocketClient : IDependencyClient, IClosableClient, ISetupCo /// /// 表示完成握手后。 /// - HttpContextEventHandler Handshaked { get; set; } + HttpContextEventHandler Connected { get; set; } /// /// 表示在即将握手连接时。 /// - HttpContextEventHandler Handshaking { get; set; } + HttpContextEventHandler Connecting { get; set; } /// /// 收到WebSocket数据 diff --git a/src/TouchSocket.Http/WebSockets/Interface/IWebSocketReceiveResult.cs b/src/TouchSocket.Http/WebSockets/Interface/IWebSocketReceiveResult.cs index 16315ce08..7cd65a33a 100644 --- a/src/TouchSocket.Http/WebSockets/Interface/IWebSocketReceiveResult.cs +++ b/src/TouchSocket.Http/WebSockets/Interface/IWebSocketReceiveResult.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Http.WebSockets; /// diff --git a/src/TouchSocket.Http/WebSockets/Plugins/Interfaces/IWebSocketClosedPlugin.cs b/src/TouchSocket.Http/WebSockets/Plugins/Interfaces/IWebSocketClosedPlugin.cs index 84b26e836..01da897ae 100644 --- a/src/TouchSocket.Http/WebSockets/Plugins/Interfaces/IWebSocketClosedPlugin.cs +++ b/src/TouchSocket.Http/WebSockets/Plugins/Interfaces/IWebSocketClosedPlugin.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Sockets; namespace TouchSocket.Http.WebSockets; diff --git a/src/TouchSocket.Http/WebSockets/Plugins/Interfaces/IWebSocketClosingPlugin.cs b/src/TouchSocket.Http/WebSockets/Plugins/Interfaces/IWebSocketClosingPlugin.cs index 4260ddf4c..109082b87 100644 --- a/src/TouchSocket.Http/WebSockets/Plugins/Interfaces/IWebSocketClosingPlugin.cs +++ b/src/TouchSocket.Http/WebSockets/Plugins/Interfaces/IWebSocketClosingPlugin.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Sockets; namespace TouchSocket.Http.WebSockets; diff --git a/src/TouchSocket.Http/WebSockets/Plugins/Interfaces/IWebSocketHandshakingPlugin.cs b/src/TouchSocket.Http/WebSockets/Plugins/Interfaces/IWebSocketConnectedPlugin.cs similarity index 76% rename from src/TouchSocket.Http/WebSockets/Plugins/Interfaces/IWebSocketHandshakingPlugin.cs rename to src/TouchSocket.Http/WebSockets/Plugins/Interfaces/IWebSocketConnectedPlugin.cs index 48eab6540..bb88a1ee3 100644 --- a/src/TouchSocket.Http/WebSockets/Plugins/Interfaces/IWebSocketHandshakingPlugin.cs +++ b/src/TouchSocket.Http/WebSockets/Plugins/Interfaces/IWebSocketConnectedPlugin.cs @@ -10,22 +10,18 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Http.WebSockets; /// -/// IWebSocketHandshakingPlugin +/// IWebSocketConnectedPlugin /// [DynamicMethod] -public interface IWebSocketHandshakingPlugin : IPlugin +public interface IWebSocketConnectedPlugin : IPlugin { /// - /// 表示在即将握手连接时。 + /// 表示WebSocket已经连接成功。 /// /// /// - /// - Task OnWebSocketHandshaking(IWebSocket webSocket, HttpContextEventArgs e); + Task OnWebSocketConnected(IWebSocket webSocket, HttpContextEventArgs e); } \ No newline at end of file diff --git a/src/TouchSocket.Http/WebSockets/Plugins/Interfaces/IWebSocketHandshakedPlugin.cs b/src/TouchSocket.Http/WebSockets/Plugins/Interfaces/IWebSocketConnectingPlugin.cs similarity index 77% rename from src/TouchSocket.Http/WebSockets/Plugins/Interfaces/IWebSocketHandshakedPlugin.cs rename to src/TouchSocket.Http/WebSockets/Plugins/Interfaces/IWebSocketConnectingPlugin.cs index 58e706bce..9de8edfe0 100644 --- a/src/TouchSocket.Http/WebSockets/Plugins/Interfaces/IWebSocketHandshakedPlugin.cs +++ b/src/TouchSocket.Http/WebSockets/Plugins/Interfaces/IWebSocketConnectingPlugin.cs @@ -10,22 +10,18 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Http.WebSockets; /// -/// IWebSocketHandshakedPlugin +/// IWebSocketConnectingPlugin /// [DynamicMethod] -public interface IWebSocketHandshakedPlugin : IPlugin +public interface IWebSocketConnectingPlugin : IPlugin { /// - /// 表示完成握手后。 + /// 表示WebSocket即将连接。 /// /// /// - /// - Task OnWebSocketHandshaked(IWebSocket webSocket, HttpContextEventArgs e); + Task OnWebSocketConnecting(IWebSocket webSocket, HttpContextEventArgs e); } \ No newline at end of file diff --git a/src/TouchSocket.Http/WebSockets/Plugins/Interfaces/IWebSocketReceivedPlugin.cs b/src/TouchSocket.Http/WebSockets/Plugins/Interfaces/IWebSocketReceivedPlugin.cs index 6b25be750..55e9b2c96 100644 --- a/src/TouchSocket.Http/WebSockets/Plugins/Interfaces/IWebSocketReceivedPlugin.cs +++ b/src/TouchSocket.Http/WebSockets/Plugins/Interfaces/IWebSocketReceivedPlugin.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Http.WebSockets; /// diff --git a/src/TouchSocket.Http/WebSockets/Plugins/WebSocketCommandLinePlugin.cs b/src/TouchSocket.Http/WebSockets/Plugins/WebSocketCommandLinePlugin.cs index 2ac288c26..00c19da0b 100644 --- a/src/TouchSocket.Http/WebSockets/Plugins/WebSocketCommandLinePlugin.cs +++ b/src/TouchSocket.Http/WebSockets/Plugins/WebSocketCommandLinePlugin.cs @@ -10,12 +10,8 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Diagnostics.CodeAnalysis; using System.Reflection; -using System.Threading.Tasks; -using TouchSocket.Core; namespace TouchSocket.Http.WebSockets; @@ -23,6 +19,7 @@ namespace TouchSocket.Http.WebSockets; /// WS命令行插件。 /// [DynamicMethod] +[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] public abstract class WebSocketCommandLinePlugin : PluginBase, IWebSocketReceivedPlugin { private readonly ILog m_logger; diff --git a/src/TouchSocket.Http/WebSockets/Plugins/WebSocketFeature.cs b/src/TouchSocket.Http/WebSockets/Plugins/WebSocketFeature.cs index cb4aa3a2f..fd30cd9b9 100644 --- a/src/TouchSocket.Http/WebSockets/Plugins/WebSocketFeature.cs +++ b/src/TouchSocket.Http/WebSockets/Plugins/WebSocketFeature.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Sockets; namespace TouchSocket.Http.WebSockets; @@ -28,113 +25,38 @@ public sealed class WebSocketFeature : PluginBase, IHttpPlugin /// 自动响应Close报文 /// public static readonly DependencyProperty AutoCloseProperty = - new(nameof(AutoClose), true); + new("AutoClose", true); /// /// 自动响应Ping报文 /// public static readonly DependencyProperty AutoPongProperty = - new(nameof(AutoPong), false); + new("AutoPong", false); - private string m_wSUrl = "/ws"; + private readonly WebSocketFeatureOptions m_options; /// - /// WebSocketFeature + /// 使用指定配置选项初始化WebSocketFeature /// - public WebSocketFeature() + /// WebSocket功能配置选项 + public WebSocketFeature(WebSocketFeatureOptions options) { - this.VerifyConnection = this.ThisVerifyConnection; + this.m_options = options ?? throw new ArgumentNullException(nameof(options)); } /// - /// 是否默认处理Close报文。 + /// 获取配置选项的只读副本 /// - public bool AutoClose { get; set; } = true; - - /// - /// 当收到ping报文时,是否自动回应pong。 - /// - public bool AutoPong { get; set; } - - /// - /// 验证连接 - /// - public Func> VerifyConnection { get; set; } - - /// - /// 用于WebSocket连接的路径,默认为“/ws” - /// 如果设置为或空,则意味着所有的连接都将解释为WS - /// - public string WSUrl - { - get => this.m_wSUrl; - set => this.m_wSUrl = string.IsNullOrEmpty(value) ? "/" : value; - } - - /// - /// 不处理Close报文。 - /// - /// - public WebSocketFeature NoAutoClose() - { - this.AutoClose = false; - return this; - } - - /// - /// 验证连接 - /// - /// - /// - public WebSocketFeature SetVerifyConnection(Func func) - { - this.VerifyConnection = async (client, context) => - { - await EasyTask.CompletedTask.ConfigureAwait(EasyTask.ContinueOnCapturedContext); - return func.Invoke(client, context); - }; - return this; - } - - /// - /// 验证连接 - /// - /// - /// - public WebSocketFeature SetVerifyConnection(Func> func) - { - this.VerifyConnection = func; - return this; - } - - /// - /// 用于WebSocket连接的路径,默认为“/ws” - /// 如果设置为或空,则意味着所有的连接都将解释为WS - /// - /// - /// - public WebSocketFeature SetWSUrl(string url) - { - this.WSUrl = url; - return this; - } - - /// - /// 当收到ping报文时,自动回应pong。 - /// - /// - public WebSocketFeature UseAutoPong() - { - this.AutoPong = true; - return this; - } + public WebSocketFeatureOptions Options => this.m_options; /// public async Task OnHttpRequest(IHttpSessionClient client, HttpContextEventArgs e) { if (client.Protocol == Protocol.Http) { - if (await this.VerifyConnection.Invoke(client, e.Context).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) + var verifyConnection = this.m_options.VerifyConnection; + + if (await verifyConnection.Invoke(client, e.Context).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) { e.Handled = true; var result = await client.SwitchProtocolToWebSocketAsync(e.Context).ConfigureAwait(EasyTask.ContinueOnCapturedContext); @@ -145,11 +67,11 @@ public sealed class WebSocketFeature : PluginBase, IHttpPlugin return; } - if (!this.AutoClose) + if (!this.m_options.AutoClose) { client.SetValue(AutoCloseProperty, false); } - if (this.AutoPong) + if (this.m_options.AutoPong) { client.SetValue(AutoPongProperty, true); } @@ -158,18 +80,4 @@ public sealed class WebSocketFeature : PluginBase, IHttpPlugin } await e.InvokeNext().ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - - private async Task ThisVerifyConnection(IHttpSessionClient client, HttpContext context) - { - await EasyTask.CompletedTask.ConfigureAwait(EasyTask.ContinueOnCapturedContext); - if (context.Request.Method == HttpMethod.Get) - { - if (this.WSUrl == "/" || context.Request.UrlEquals(this.WSUrl)) - { - return true; - } - } - - return false; - } } \ No newline at end of file diff --git a/src/TouchSocket.Http/WebSockets/Plugins/WebSocketFeatureOptions.cs b/src/TouchSocket.Http/WebSockets/Plugins/WebSocketFeatureOptions.cs new file mode 100644 index 000000000..a2f225f16 --- /dev/null +++ b/src/TouchSocket.Http/WebSockets/Plugins/WebSocketFeatureOptions.cs @@ -0,0 +1,112 @@ +//------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +//------------------------------------------------------------------------------ + +namespace TouchSocket.Http.WebSockets; + +/// +/// WebSocket功能配置选项 +/// +public sealed class WebSocketFeatureOptions +{ + /// + /// 是否自动处理Close报文,默认为true + /// + public bool AutoClose { get; set; } = true; + + /// + /// 当收到ping报文时,是否自动回应pong,默认为false + /// + public bool AutoPong { get; set; } = false; + + /// + /// 验证连接的委托方法 + /// + public Func> VerifyConnection { get; set; } + + /// + /// 设置是否自动处理Close报文 + /// + /// 是否自动处理Close报文 + /// 返回当前配置选项实例,支持链式调用 + public WebSocketFeatureOptions SetAutoClose(bool autoClose) + { + this.AutoClose = autoClose; + return this; + } + + /// + /// 设置是否自动回应Ping报文 + /// + /// 是否自动回应Ping报文 + /// 返回当前配置选项实例,支持链式调用 + public WebSocketFeatureOptions SetAutoPong(bool autoPong) + { + this.AutoPong = autoPong; + return this; + } + + /// + /// 设置WebSocket连接的URL路径 + /// + /// WebSocket连接路径,如果为null或空则表示所有连接都解释为WS + /// 返回当前配置选项实例,支持链式调用 + public WebSocketFeatureOptions SetUrl(string url = "/ws") + { + if (url.IsNullOrEmpty()) + { + url = "/"; + } + else if (!url.StartsWith("/")) + { + url = "/" + url; + } + + this.SetVerifyConnection((client, context) => + { + if (url == "/" || context.Request.UrlEquals(url)) + { + return true; + } + + return false; + }); + return this; + } + + /// + /// 设置验证连接的同步方法 + /// + /// 验证连接的同步委托 + /// 返回当前配置选项实例,支持链式调用 + public WebSocketFeatureOptions SetVerifyConnection(Func verifyConnection) + { + ThrowHelper.ThrowIfNull(verifyConnection, nameof(verifyConnection)); + + this.VerifyConnection = async (client, context) => + { + await EasyTask.CompletedTask.ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return verifyConnection.Invoke(client, context); + }; + return this; + } + + /// + /// 设置验证连接的异步方法 + /// + /// 验证连接的异步委托 + /// 返回当前配置选项实例,支持链式调用 + public WebSocketFeatureOptions SetVerifyConnection(Func> verifyConnection) + { + ThrowHelper.ThrowIfNull(verifyConnection, nameof(verifyConnection)); + return this; + } +} \ No newline at end of file diff --git a/src/TouchSocket.Http/WebSockets/Plugins/WebSocketHeartbeatPlugin.cs b/src/TouchSocket.Http/WebSockets/Plugins/WebSocketHeartbeatPlugin.cs deleted file mode 100644 index b0aa9d462..000000000 --- a/src/TouchSocket.Http/WebSockets/Plugins/WebSocketHeartbeatPlugin.cs +++ /dev/null @@ -1,64 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System.Threading.Tasks; -using TouchSocket.Core; -using TouchSocket.Sockets; - -namespace TouchSocket.Http.WebSockets; - -/// -/// 初始化一个适用于WebSocket的心跳插件 -/// -[PluginOption(Singleton = true)] -public class WebSocketHeartbeatPlugin : HeartbeatPlugin, IWebSocketHandshakedPlugin -{ - /// - public async Task OnWebSocketHandshaked(IWebSocket client, HttpContextEventArgs e) - { - _ = EasyTask.SafeRun(async () => - { - var failedCount = 0; - while (true) - { - await Task.Delay(this.Tick).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - if (!client.Online) - { - return; - } - - try - { - var result = await client.PingAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - if (result.IsSuccess) - { - failedCount = 0; - } - else - { - failedCount++; - } - } - catch - { - failedCount++; - } - if (failedCount > this.MaxFailCount) - { - await client.CloseAsync("自动心跳失败次数达到最大,已断开连接。").ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - } - }); - - await e.InvokeNext().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } -} \ No newline at end of file diff --git a/src/TouchSocket.Http/WebSockets/Plugins/WebSocketReconnectionPlugin.cs b/src/TouchSocket.Http/WebSockets/Plugins/WebSocketReconnectionPlugin.cs deleted file mode 100644 index 0fc6c9401..000000000 --- a/src/TouchSocket.Http/WebSockets/Plugins/WebSocketReconnectionPlugin.cs +++ /dev/null @@ -1,61 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; -using System.Threading.Tasks; -using TouchSocket.Core; -using TouchSocket.Sockets; - -namespace TouchSocket.Http.WebSockets; - -internal sealed class WebSocketReconnectionPlugin : ReconnectionPlugin, IWebSocketClosedPlugin where TClient : IWebSocketClient -{ - public override Func> ActionForCheck { get; set; } - - public WebSocketReconnectionPlugin() - { - this.ActionForCheck = (c, i) => - { - return Task.FromResult(c.Online); - }; - } - - public async Task OnWebSocketClosed(IWebSocket webSocket, ClosedEventArgs e) - { - await e.InvokeNext().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - - if (webSocket is not TClient tClient) - { - return; - } - - if (e.Manual) - { - return; - } - - _ = Task.Run(async () => - { - while (true) - { - if (this.DisposedValue) - { - return; - } - if (await this.ActionForConnect.Invoke(tClient).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) - { - return; - } - } - }); - } -} \ No newline at end of file diff --git a/src/TouchSocket.JsonRpc.SourceGenerator/JsonRpcClientSourceGenerator.cs b/src/TouchSocket.JsonRpc.SourceGenerator/JsonRpcClientSourceGenerator.cs index 4aae26239..014c7bb4c 100644 --- a/src/TouchSocket.JsonRpc.SourceGenerator/JsonRpcClientSourceGenerator.cs +++ b/src/TouchSocket.JsonRpc.SourceGenerator/JsonRpcClientSourceGenerator.cs @@ -12,36 +12,83 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Collections.Generic; using System.Linq; +using TouchSocket.Rpc; namespace TouchSocket; [Generator] -public class JsonRpcClientSourceGenerator : ISourceGenerator +public class JsonRpcClientSourceGenerator : IIncrementalGenerator { - public void Initialize(GeneratorInitializationContext context) + public void Initialize(IncrementalGeneratorInitializationContext context) { - context.RegisterForSyntaxNotifications(() => new JsonRpcClientSyntaxReceiver()); - } + // 第一步:收集所有接口声明语法节点 + var interfaceDeclarations = context.SyntaxProvider + .CreateSyntaxProvider( + predicate: static (s, _) => s is InterfaceDeclarationSyntax, + transform: static (ctx, _) => (InterfaceDeclarationSyntax)ctx.Node); - public void Execute(GeneratorExecutionContext context) - { - var s = context.Compilation.GetMetadataReference(context.Compilation.Assembly); - - if (context.SyntaxReceiver is JsonRpcClientSyntaxReceiver receiver) - { - var builders = receiver - .GetRpcApiTypes(context.Compilation) - .Select(i => new JsonRpcClientCodeBuilder(i)) - .Distinct(CodeBuilderEqualityComparer.Default); - //Debugger.Launch(); - foreach (var builder in builders) + // 第二步:将语法节点转换为符号并过滤有效接口 + var interfacesWithSymbol = context.CompilationProvider.Combine(interfaceDeclarations.Collect()) + .SelectMany(static (tuple, _) => { - var tree = CSharpSyntaxTree.ParseText(builder.ToSourceText()); - var root = tree.GetRoot().NormalizeWhitespace(); - var ret = root.ToFullString(); - context.AddSource($"{builder.GetFileName()}.g.cs", ret); - } - } + var (compilation, interfaces) = tuple; + var results = new List(); + var attributeSymbol = RpcUtils.GetGeneratorRpcProxyAttribute(compilation); + + foreach (var interfaceSyntax in interfaces) + { + var model = compilation.GetSemanticModel(interfaceSyntax.SyntaxTree); + var interfaceSymbol = model.GetDeclaredSymbol(interfaceSyntax); + + if (interfaceSymbol != null && + attributeSymbol != null && + RpcUtils.IsRpcApiInterface(interfaceSymbol)) + { + results.Add(interfaceSymbol); + } + } + return results.Distinct(SymbolEqualityComparer.Default); + }); + + // 第三步:生成源代码 + context.RegisterSourceOutput(interfacesWithSymbol, + static (productionContext, interfaceSymbol) => + { + var builder = new JsonRpcClientCodeBuilder((INamedTypeSymbol)interfaceSymbol); + productionContext.AddSource(builder); + }); } -} \ No newline at end of file +} + +//[Generator] +//public class JsonRpcClientSourceGenerator : ISourceGenerator +//{ +// public void Initialize(GeneratorInitializationContext context) +// { +// context.RegisterForSyntaxNotifications(() => new JsonRpcClientSyntaxReceiver()); +// } + +// public void Execute(GeneratorExecutionContext context) +// { +// var s = context.Compilation.GetMetadataReference(context.Compilation.Assembly); + +// if (context.SyntaxReceiver is JsonRpcClientSyntaxReceiver receiver) +// { +// var builders = receiver +// .GetRpcApiTypes(context.Compilation) +// .Select(i => new JsonRpcClientCodeBuilder(i)) +// .Distinct(CodeBuilderEqualityComparer.Default); +// //Debugger.Launch(); +// foreach (var builder in builders) +// { +// var tree = CSharpSyntaxTree.ParseText(builder.ToSourceText()); +// var root = tree.GetRoot().NormalizeWhitespace(); +// var ret = root.ToFullString(); +// context.AddSource($"{builder.GetFileName()}.g.cs", ret); +// } +// } +// } +//} \ No newline at end of file diff --git a/src/TouchSocket.JsonRpc.SourceGenerator/TouchSocket.JsonRpc.SourceGenerator.csproj b/src/TouchSocket.JsonRpc.SourceGenerator/TouchSocket.JsonRpc.SourceGenerator.csproj index 21cf0e4da..da0ff4299 100644 --- a/src/TouchSocket.JsonRpc.SourceGenerator/TouchSocket.JsonRpc.SourceGenerator.csproj +++ b/src/TouchSocket.JsonRpc.SourceGenerator/TouchSocket.JsonRpc.SourceGenerator.csproj @@ -18,6 +18,6 @@ - + diff --git a/src/TouchSocket.JsonRpc/Attribute/JsonRpcAttribute.cs b/src/TouchSocket.JsonRpc/Attribute/JsonRpcAttribute.cs index fa15073b0..28894d0f9 100644 --- a/src/TouchSocket.JsonRpc/Attribute/JsonRpcAttribute.cs +++ b/src/TouchSocket.JsonRpc/Attribute/JsonRpcAttribute.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using TouchSocket.Core; using TouchSocket.Rpc; namespace TouchSocket.JsonRpc; diff --git a/src/TouchSocket.JsonRpc/Common/JsonRpcCallContextBase.cs b/src/TouchSocket.JsonRpc/Common/JsonRpcCallContextBase.cs index a121c638d..21f06fc84 100644 --- a/src/TouchSocket.JsonRpc/Common/JsonRpcCallContextBase.cs +++ b/src/TouchSocket.JsonRpc/Common/JsonRpcCallContextBase.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; using TouchSocket.Rpc; namespace TouchSocket.JsonRpc; @@ -57,7 +56,7 @@ public abstract class JsonRpcCallContextBase : CallContext, IJsonRpcCallContext } /// - protected override void Dispose(bool disposing) + protected override void SafetyDispose(bool disposing) { if (this.DisposedValue) { @@ -67,6 +66,6 @@ public abstract class JsonRpcCallContextBase : CallContext, IJsonRpcCallContext { this.m_scopedResolver.SafeDispose(); } - base.Dispose(disposing); + base.SafetyDispose(disposing); } } \ No newline at end of file diff --git a/src/TouchSocket.JsonRpc/Common/JsonRpcRequestConverter.cs b/src/TouchSocket.JsonRpc/Common/JsonRpcRequestConverter.cs index bda122cda..7b03dc899 100644 --- a/src/TouchSocket.JsonRpc/Common/JsonRpcRequestConverter.cs +++ b/src/TouchSocket.JsonRpc/Common/JsonRpcRequestConverter.cs @@ -12,7 +12,6 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using System; namespace TouchSocket.JsonRpc; diff --git a/src/TouchSocket.JsonRpc/Common/JsonRpcWaitResult.cs b/src/TouchSocket.JsonRpc/Common/JsonRpcWaitResult.cs index 40397f9b4..e1bcee103 100644 --- a/src/TouchSocket.JsonRpc/Common/JsonRpcWaitResult.cs +++ b/src/TouchSocket.JsonRpc/Common/JsonRpcWaitResult.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.JsonRpc; /// diff --git a/src/TouchSocket.JsonRpc/Common/JsonRpcWaitResultConverter.cs b/src/TouchSocket.JsonRpc/Common/JsonRpcWaitResultConverter.cs index f906348d7..ed6b2a83e 100644 --- a/src/TouchSocket.JsonRpc/Common/JsonRpcWaitResultConverter.cs +++ b/src/TouchSocket.JsonRpc/Common/JsonRpcWaitResultConverter.cs @@ -12,7 +12,6 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using System; namespace TouchSocket.JsonRpc; diff --git a/src/TouchSocket.JsonRpc/Components/HttpJsonRpcClient.cs b/src/TouchSocket.JsonRpc/Components/HttpJsonRpcClient.cs index 97c4f7869..71f15a2a0 100644 --- a/src/TouchSocket.JsonRpc/Components/HttpJsonRpcClient.cs +++ b/src/TouchSocket.JsonRpc/Components/HttpJsonRpcClient.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Http; using TouchSocket.Rpc; @@ -26,55 +22,60 @@ public class HttpJsonRpcClient : HttpClientBase, IHttpJsonRpcClient { private readonly JsonRpcActor m_jsonRpcActor; + private readonly SemaphoreSlim m_semaphoreSlim = new SemaphoreSlim(1, 1); + /// /// 初始化 类的新实例。 /// public HttpJsonRpcClient() { - this.SerializerConverter.Add(new JsonStringToClassSerializerFormatter()); this.m_jsonRpcActor = new JsonRpcActor() { - SendAction = this.SendAction, - SerializerConverter = this.SerializerConverter + SendAction = this.SendAction }; } /// /// 获取序列化转换器。 /// - public TouchSocketSerializerConverter SerializerConverter { get; } = new TouchSocketSerializerConverter(); + public TouchSocketSerializerConverter SerializerConverter => this.m_jsonRpcActor.SerializerConverter; #region JsonRpcActor - private async Task SendAction(ReadOnlyMemory memory) + private async Task SendAction(ReadOnlyMemory memory, CancellationToken cancellationToken) { - var request = new HttpRequest(); - request.Method = HttpMethod.Post; - request.URL = (this.RemoteIPHost.PathAndQuery); + var request = new HttpRequest + { + Method = HttpMethod.Post, + URL = this.RemoteIPHost.PathAndQuery + }; request.SetContent(memory); - using (var responseResult = await base.ProtectedRequestAsync(request).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) + using (var responseResult = await base.ProtectedRequestAsync(request, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) { var response = responseResult.Response; if (response.IsSuccess()) { - await this.m_jsonRpcActor.InputReceiveAsync(await response.GetContentAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext), default); + await this.m_jsonRpcActor.InputReceiveAsync(await response.GetContentAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext), default); } } } #endregion JsonRpcActor - /// - /// 异步连接到服务器。 - /// - /// 超时时间(毫秒)。 - /// 取消令牌。 - /// 表示异步操作的任务。 - public Task ConnectAsync(int millisecondsTimeout, CancellationToken token) + /// + public async Task ConnectAsync(CancellationToken cancellationToken) { - return this.TcpConnectAsync(millisecondsTimeout, token); + await this.m_semaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + try + { + await base.HttpConnectAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + finally + { + this.m_semaphoreSlim.Release(); + } } /// @@ -85,21 +86,11 @@ public class HttpJsonRpcClient : HttpClientBase, IHttpJsonRpcClient /// 调用选项。 /// 参数。 /// 表示异步操作的任务,包含调用结果。 - public Task InvokeAsync(string invokeKey, Type returnType, IInvokeOption invokeOption, params object[] parameters) + public Task InvokeAsync(string invokeKey, Type returnType, InvokeOption invokeOption, params object[] parameters) { return this.m_jsonRpcActor.InvokeAsync(invokeKey, returnType, invokeOption, parameters); } - /// - protected override void Dispose(bool disposing) - { - if (disposing) - { - this.m_jsonRpcActor.SafeDispose(); - } - base.Dispose(disposing); - } - /// /// 加载配置。 /// @@ -108,5 +99,19 @@ public class HttpJsonRpcClient : HttpClientBase, IHttpJsonRpcClient { base.LoadConfig(config); this.m_jsonRpcActor.Logger = this.Logger; + + var jsonRpcOption = config.GetValue(JsonRpcConfigExtension.JsonRpcOptionProperty) ?? new JsonRpcOption(); + + this.m_jsonRpcActor.SerializerConverter = jsonRpcOption.SerializerConverter; + } + + /// + protected override void SafetyDispose(bool disposing) + { + if (disposing) + { + this.m_jsonRpcActor.SafeDispose(); + } + base.SafetyDispose(disposing); } } \ No newline at end of file diff --git a/src/TouchSocket.JsonRpc/Components/HttpJsonRpcClientSlim.cs b/src/TouchSocket.JsonRpc/Components/HttpJsonRpcClientSlim.cs deleted file mode 100644 index 89d9e2830..000000000 --- a/src/TouchSocket.JsonRpc/Components/HttpJsonRpcClientSlim.cs +++ /dev/null @@ -1,332 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -//#if SystemNetHttp -//using System; -//using System.Collections.Generic; -//using System.Linq; -//using System.Net.Http; -//using System.Text; -//using System.Threading.Tasks; -//using TouchSocket.Core; -//using TouchSocket.Rpc; - -//namespace TouchSocket.JsonRpc -//{ -// /// -// /// 基于通讯模型的JsonRpc客户端 -// /// -// public class HttpJsonRpcClientSlim : Http.HttpClientSlim, IHttpJsonRpcClientSlim -// { -// /// -// /// 基于通讯模型的JsonRpc客户端 -// /// -// /// -// public HttpJsonRpcClientSlim(System.Net.Http.HttpClient httpClient = default) : base(httpClient) -// { -// } - -// private readonly WaitHandlePool m_waitHandle = new WaitHandlePool(); - -// /// -// public object Invoke(Type returnType, string method, IInvokeOption invokeOption, ref object[] parameters, Type[] types) -// { -// var context = new JsonRpcWaitResult(); -// var waitData = this.m_waitHandle.GetWaitData(context); - -// using (var byteBlock = BytePool.Default.GetByteBlock(this.BufferLength)) -// { -// if (invokeOption == default) -// { -// invokeOption = InvokeOption.WaitInvoke; -// } - -// parameters ??= new object[0]; -// var jsonRpcRequest = new JsonRpcRequest -// { -// Method = method, -// Params = parameters, -// Id = invokeOption.FeedbackType == FeedbackType.WaitInvoke ? context.Sign.ToString() : null -// }; -// var request = new HttpRequestMessage(); -// request.Method = HttpMethod.Post; -// request.RequestUri = (this.RemoteIPHost.PathAndQuery); -// request.FromJson(jsonRpcRequest.ToJson()); -// request.Build(byteBlock); -// switch (invokeOption.FeedbackType) -// { -// case FeedbackType.OnlySend: -// { -// this.HttpClient.po(byteBlock); -// this.m_waitHandle.Destroy(waitData); -// return default; -// } -// case FeedbackType.WaitSend: -// { -// this.Send(byteBlock); -// this.m_waitHandle.Destroy(waitData); -// return default; -// } -// case FeedbackType.WaitInvoke: -// { -// this.Send(byteBlock); -// waitData.Wait(invokeOption.Timeout); -// var resultContext = (JsonRpcWaitResult)waitData.WaitResult; -// this.m_waitHandle.Destroy(waitData); - -// if (resultContext.Status == 0) -// { -// throw new TimeoutException("等待结果超时"); -// } -// if (resultContext.Error != null) -// { -// throw new RpcException(resultContext.Error.Message); -// } - -// if (resultContext.Return == null) -// { -// return default; -// } -// else -// { -// if (returnType.IsPrimitive || returnType == typeof(string)) -// { -// return resultContext.Return.ToString().ParseToType(returnType); -// } -// else -// { -// return resultContext.Return.ToJson().FromJson(returnType); -// } -// } -// } -// default: -// return default; -// } -// } -// } - -// /// -// public void Invoke(string method, IInvokeOption invokeOption, ref object[] parameters, Type[] types) -// { -// var context = new JsonRpcWaitResult(); -// var waitData = this.m_waitHandle.GetWaitData(context); - -// using (var byteBlock = BytePool.Default.GetByteBlock(this.BufferLength)) -// { -// if (invokeOption == default) -// { -// invokeOption = InvokeOption.WaitInvoke; -// } -// parameters ??= new object[0]; -// var jsonRpcRequest = new JsonRpcRequest() -// { -// Method = method, -// Params = parameters -// }; - -// jsonRpcRequest.Id = invokeOption.FeedbackType == FeedbackType.WaitInvoke ? context.Sign.ToString() : null; -// var request = new HttpRequest(); -// request.Method = HttpMethod.Post; -// request.SetUrl(this.RemoteIPHost.PathAndQuery); -// request.FromJson(jsonRpcRequest.ToJson()); -// request.Build(byteBlock); -// switch (invokeOption.FeedbackType) -// { -// case FeedbackType.OnlySend: -// { -// this.Send(byteBlock); -// this.m_waitHandle.Destroy(waitData); -// return; -// } -// case FeedbackType.WaitSend: -// { -// this.Send(byteBlock); -// this.m_waitHandle.Destroy(waitData); -// return; -// } -// case FeedbackType.WaitInvoke: -// { -// this.Send(byteBlock); -// waitData.Wait(invokeOption.Timeout); -// var resultContext = (JsonRpcWaitResult)waitData.WaitResult; -// this.m_waitHandle.Destroy(waitData); - -// if (resultContext.Status == 0) -// { -// throw new TimeoutException("等待结果超时"); -// } -// if (resultContext.Error != null) -// { -// throw new RpcException(resultContext.Error.Message); -// } -// break; -// } -// default: -// return; -// } -// } -// } - -// /// -// public void Invoke(string method, IInvokeOption invokeOption, params object[] parameters) -// { -// this.Invoke(method, invokeOption, ref parameters, null); -// } - -// /// -// public object Invoke(Type returnType, string method, IInvokeOption invokeOption, params object[] parameters) -// { -// return this.Invoke(returnType, method, invokeOption, ref parameters, null); -// } - -// /// -// public async Task InvokeAsync(string method, IInvokeOption invokeOption, params object[] parameters) -// { -// var context = new JsonRpcWaitResult(); -// var waitData = this.m_waitHandle.GetWaitDataAsync(context); - -// using (var byteBlock = BytePool.Default.GetByteBlock(this.BufferLength)) -// { -// if (invokeOption == default) -// { -// invokeOption = InvokeOption.WaitInvoke; -// } -// parameters ??= new object[0]; -// var jsonRpcRequest = new JsonRpcRequest() -// { -// Method = method, -// Params = parameters -// }; - -// jsonRpcRequest.Id = invokeOption.FeedbackType == FeedbackType.WaitInvoke ? context.Sign.ToString() : null; -// var request = new HttpRequest(); -// request.Method = HttpMethod.Post; -// request.SetUrl(this.RemoteIPHost.PathAndQuery); -// request.FromJson(jsonRpcRequest.ToJson()); -// request.Build(byteBlock); -// switch (invokeOption.FeedbackType) -// { -// case FeedbackType.OnlySend: -// { -// this.Send(byteBlock); -// this.m_waitHandle.Destroy(waitData); -// return; -// } -// case FeedbackType.WaitSend: -// { -// this.Send(byteBlock); -// this.m_waitHandle.Destroy(waitData); -// return; -// } -// case FeedbackType.WaitInvoke: -// { -// this.Send(byteBlock); -// await waitData.WaitAsync(invokeOption.Timeout); -// var resultContext = (JsonRpcWaitResult)waitData.WaitResult; -// this.m_waitHandle.Destroy(waitData); - -// if (resultContext.Status == 0) -// { -// throw new TimeoutException("等待结果超时"); -// } -// if (resultContext.Error != null) -// { -// throw new RpcException(resultContext.Error.Message); -// } -// break; -// } -// default: -// return; -// } -// } -// } - -// /// -// public async Task InvokeAsync(Type returnType, string method, IInvokeOption invokeOption, params object[] parameters) -// { -// var context = new JsonRpcWaitResult(); -// var waitData = this.m_waitHandle.GetWaitDataAsync(context); - -// using (var byteBlock = BytePool.Default.GetByteBlock(this.BufferLength)) -// { -// if (invokeOption == default) -// { -// invokeOption = InvokeOption.WaitInvoke; -// } -// parameters ??= new object[0]; -// var jsonRpcRequest = new JsonRpcRequest -// { -// Method = method, -// Params = parameters, -// Id = invokeOption.FeedbackType == FeedbackType.WaitInvoke ? context.Sign.ToString() : null -// }; -// var request = new HttpRequest(); -// request.Method = HttpMethod.Post; -// request.SetUrl(this.RemoteIPHost.PathAndQuery); -// request.FromJson(jsonRpcRequest.ToJson()); -// request.Build(byteBlock); -// switch (invokeOption.FeedbackType) -// { -// case FeedbackType.OnlySend: -// { -// this.Send(byteBlock); -// this.m_waitHandle.Destroy(waitData); -// return default; -// } -// case FeedbackType.WaitSend: -// { -// this.Send(byteBlock); -// this.m_waitHandle.Destroy(waitData); -// return default; -// } -// case FeedbackType.WaitInvoke: -// { -// this.Send(byteBlock); -// await waitData.WaitAsync(invokeOption.Timeout); -// var resultContext = (JsonRpcWaitResult)waitData.WaitResult; -// this.m_waitHandle.Destroy(waitData); - -// if (resultContext.Status == 0) -// { -// throw new TimeoutException("等待结果超时"); -// } -// if (resultContext.Error != null) -// { -// throw new RpcException(resultContext.Error.Message); -// } - -// if (resultContext.Return == null) -// { -// return default; -// } -// else -// { -// if (returnType.IsPrimitive || returnType == typeof(string)) -// { -// return resultContext.Return.ToString().ParseToType(returnType); -// } -// else -// { -// return resultContext.Return.ToJson().FromJson(returnType); -// } -// } -// } -// default: -// return default; -// } -// } -// } - -// } -//} - -//#endif \ No newline at end of file diff --git a/src/TouchSocket.JsonRpc/Components/JsonRpcActor.cs b/src/TouchSocket.JsonRpc/Components/JsonRpcActor.cs index 4962e8a5e..ad938c412 100644 --- a/src/TouchSocket.JsonRpc/Components/JsonRpcActor.cs +++ b/src/TouchSocket.JsonRpc/Components/JsonRpcActor.cs @@ -12,10 +12,6 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using System; -using System.Text; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Rpc; namespace TouchSocket.JsonRpc; @@ -27,7 +23,7 @@ public sealed class JsonRpcActor : DisposableObject, IJsonRpcClient { private readonly JsonRpcRequestConverter m_jsonRpcRequestConverter = new JsonRpcRequestConverter(); private readonly JsonRpcWaitResultConverter m_jsonRpcWaitResultConverter = new JsonRpcWaitResultConverter(); - private readonly WaitHandlePool m_waitHandle = new WaitHandlePool(); + private readonly WaitHandlePool m_waitHandle = new(); private IRpcServerProvider m_rpcServerProvider; /// @@ -58,7 +54,7 @@ public sealed class JsonRpcActor : DisposableObject, IJsonRpcClient /// /// 获取或设置发送动作。 /// - public Func, Task> SendAction { get; set; } + public Func, CancellationToken, Task> SendAction { get; set; } /// /// 获取或设置序列化转换器。 @@ -72,8 +68,8 @@ public sealed class JsonRpcActor : DisposableObject, IJsonRpcClient /// 动作映射。 public static void AddRpcToMap(IRpcServerProvider rpcServerProvider, ActionMap actionMap) { - ThrowHelper.ThrowArgumentNullExceptionIf(rpcServerProvider, nameof(rpcServerProvider)); - ThrowHelper.ThrowArgumentNullExceptionIf(actionMap, nameof(actionMap)); + ThrowHelper.ThrowIfNull(rpcServerProvider, nameof(rpcServerProvider)); + ThrowHelper.ThrowIfNull(actionMap, nameof(actionMap)); foreach (var rpcMethod in rpcServerProvider.GetMethods()) { @@ -116,7 +112,7 @@ public sealed class JsonRpcActor : DisposableObject, IJsonRpcClient else if (this.TryParseResponse(str, out var internalJsonRpcWaitResult)) { internalJsonRpcWaitResult.Status = 1; - this.m_waitHandle.SetRun(internalJsonRpcWaitResult); + this.m_waitHandle.Set(internalJsonRpcWaitResult); } else { @@ -138,12 +134,20 @@ public sealed class JsonRpcActor : DisposableObject, IJsonRpcClient /// 调用选项。 /// 参数。 /// 任务对象。 - public async Task InvokeAsync(string invokeKey, Type returnType, IInvokeOption invokeOption, params object[] parameters) + public async Task InvokeAsync(string invokeKey, Type returnType, InvokeOption invokeOption, params object[] parameters) { var waitData = this.m_waitHandle.GetWaitDataAsync(out var sign); invokeOption ??= InvokeOption.WaitInvoke; - parameters ??= new object[0]; + parameters ??= []; + + var cancellationToken = invokeOption.Token; + CancellationTokenSource cts = default; + if (!cancellationToken.CanBeCanceled) + { + cts = new CancellationTokenSource(invokeOption.Timeout); + cancellationToken = cts.Token; + } var strs = new string[parameters.Length]; for (var i = 0; i < parameters.Length; i++) @@ -160,11 +164,16 @@ public sealed class JsonRpcActor : DisposableObject, IJsonRpcClient try { - using (var byteBlock = new ByteBlock(1024 * 64)) + var byteBlock = new ByteBlock(1024 * 64); + try { var str = this.BuildJsonRpcRequest(jsonRpcRequest); - byteBlock.WriteNormalString(str, this.Encoding); - await this.SendAction(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + WriterExtension.WriteNormalString(ref byteBlock, str, this.Encoding); + await this.SendAction(byteBlock.Memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + finally + { + byteBlock.Dispose(); } switch (invokeOption.FeedbackType) @@ -177,16 +186,11 @@ public sealed class JsonRpcActor : DisposableObject, IJsonRpcClient case FeedbackType.WaitInvoke: default: { - if (invokeOption.Token.CanBeCanceled) + switch (await waitData.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) { - waitData.SetCancellationToken(invokeOption.Token); - } - - switch (await waitData.WaitAsync(invokeOption.Timeout).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) - { - case WaitDataStatus.SetRunning: + case WaitDataStatus.Success: { - var resultContext = waitData.WaitResult; + var resultContext = waitData.CompletedData; if (resultContext.ErrorCode != 0) { throw new RpcException(resultContext.ErrorMessage); @@ -216,7 +220,8 @@ public sealed class JsonRpcActor : DisposableObject, IJsonRpcClient } finally { - this.m_waitHandle.Destroy(sign); + waitData.Dispose(); + cts?.Dispose(); } } @@ -237,8 +242,8 @@ public sealed class JsonRpcActor : DisposableObject, IJsonRpcClient /// 动作映射。 public void SetRpcServerProvider(IRpcServerProvider rpcServerProvider, ActionMap actionMap) { - ThrowHelper.ThrowArgumentNullExceptionIf(rpcServerProvider, nameof(rpcServerProvider)); - ThrowHelper.ThrowArgumentNullExceptionIf(actionMap, nameof(actionMap)); + ThrowHelper.ThrowIfNull(rpcServerProvider, nameof(rpcServerProvider)); + ThrowHelper.ThrowIfNull(actionMap, nameof(actionMap)); this.m_rpcServerProvider = rpcServerProvider; this.ActionMap = actionMap; } @@ -408,10 +413,15 @@ public sealed class JsonRpcActor : DisposableObject, IJsonRpcClient }; var str = JsonConvert.SerializeObject(response, this.m_jsonRpcWaitResultConverter); - using (var byteBlock = new ByteBlock(1024 * 64)) + var byteBlock = new ByteBlock(1024 * 64); + try { - byteBlock.WriteNormalString(str, this.Encoding); - await this.SendAction(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + WriterExtension.WriteNormalString(ref byteBlock, str, this.Encoding); + await this.SendAction(byteBlock.Memory, CancellationToken.None).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + finally + { + byteBlock.Dispose(); } } catch (Exception ex) diff --git a/src/TouchSocket.JsonRpc/Components/TcpJsonRpcClient.cs b/src/TouchSocket.JsonRpc/Components/TcpJsonRpcClient.cs index bf1d89561..057880893 100644 --- a/src/TouchSocket.JsonRpc/Components/TcpJsonRpcClient.cs +++ b/src/TouchSocket.JsonRpc/Components/TcpJsonRpcClient.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Rpc; using TouchSocket.Sockets; @@ -31,19 +27,17 @@ public class TcpJsonRpcClient : TcpClientBase, ITcpJsonRpcClient /// public TcpJsonRpcClient() { - this.SerializerConverter.Add(new JsonStringToClassSerializerFormatter()); this.m_jsonRpcActor = new JsonRpcActor() { - SendAction = this.SendAction, - SerializerConverter = this.SerializerConverter + SendAction = this.SendAction }; } #region JsonRpcActor - private Task SendAction(ReadOnlyMemory memory) + private Task SendAction(ReadOnlyMemory memory, CancellationToken cancellationToken) { - return base.ProtectedSendAsync(memory); + return base.ProtectedSendAsync(memory, cancellationToken); } #endregion JsonRpcActor @@ -54,28 +48,28 @@ public class TcpJsonRpcClient : TcpClientBase, ITcpJsonRpcClient public ActionMap ActionMap => this.m_jsonRpcActor.ActionMap; /// - public TouchSocketSerializerConverter SerializerConverter { get; } = new TouchSocketSerializerConverter(); + public TouchSocketSerializerConverter SerializerConverter => this.m_jsonRpcActor.SerializerConverter; /// - public Task ConnectAsync(int millisecondsTimeout, CancellationToken token) + public Task ConnectAsync(CancellationToken cancellationToken) { - return this.TcpConnectAsync(millisecondsTimeout, token); + return this.TcpConnectAsync(cancellationToken); } /// - public Task InvokeAsync(string invokeKey, Type returnType, IInvokeOption invokeOption, params object[] parameters) + public Task InvokeAsync(string invokeKey, Type returnType, InvokeOption invokeOption, params object[] parameters) { return this.m_jsonRpcActor.InvokeAsync(invokeKey, returnType, invokeOption, parameters); } /// - protected override void Dispose(bool disposing) + protected override void SafetyDispose(bool disposing) { if (disposing) { this.m_jsonRpcActor.SafeDispose(); } - base.Dispose(disposing); + base.SafetyDispose(disposing); } /// @@ -89,6 +83,10 @@ public class TcpJsonRpcClient : TcpClientBase, ITcpJsonRpcClient { this.m_jsonRpcActor.SetRpcServerProvider(rpcServerProvider); } + + var jsonRpcOption = config.GetValue(JsonRpcConfigExtension.JsonRpcOptionProperty) ?? new JsonRpcOption(); + + this.m_jsonRpcActor.SerializerConverter = jsonRpcOption.SerializerConverter; } /// @@ -103,9 +101,9 @@ public class TcpJsonRpcClient : TcpClientBase, ITcpJsonRpcClient { jsonRpcMemory = jsonPackage.Data; } - else if (e.ByteBlock != null) + else if (!e.Memory.IsEmpty) { - jsonRpcMemory = e.ByteBlock.Memory; + jsonRpcMemory = e.Memory; } if (jsonRpcMemory.IsEmpty) diff --git a/src/TouchSocket.JsonRpc/Components/WebSocketJsonRpcClient.cs b/src/TouchSocket.JsonRpc/Components/WebSocketJsonRpcClient.cs index fc7c0620a..b88697875 100644 --- a/src/TouchSocket.JsonRpc/Components/WebSocketJsonRpcClient.cs +++ b/src/TouchSocket.JsonRpc/Components/WebSocketJsonRpcClient.cs @@ -10,11 +10,8 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; +using System.Buffers; using System.Net.WebSockets; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Http.WebSockets; using TouchSocket.Rpc; @@ -32,11 +29,9 @@ public class WebSocketJsonRpcClient : SetupClientWebSocket, IWebSocketJsonRpcCli /// public WebSocketJsonRpcClient() { - this.SerializerConverter.Add(new JsonStringToClassSerializerFormatter()); this.m_jsonRpcActor = new JsonRpcActor() { - SendAction = this.SendAction, - SerializerConverter = this.SerializerConverter + SendAction = this.SendAction }; } @@ -46,33 +41,23 @@ public class WebSocketJsonRpcClient : SetupClientWebSocket, IWebSocketJsonRpcCli public ActionMap ActionMap => this.m_jsonRpcActor.ActionMap; /// - public TouchSocketSerializerConverter SerializerConverter { get; } = new TouchSocketSerializerConverter(); + public TouchSocketSerializerConverter SerializerConverter => this.m_jsonRpcActor.SerializerConverter; #region JsonRpcActor - private Task SendAction(ReadOnlyMemory memory) + private Task SendAction(ReadOnlyMemory memory, CancellationToken cancellationToken) { - return base.ProtectedSendAsync(memory, WebSocketMessageType.Text, true, CancellationToken.None); + return base.ProtectedSendAsync(memory, WebSocketMessageType.Text, true, cancellationToken); } #endregion JsonRpcActor /// - public Task InvokeAsync(string invokeKey, Type returnType, IInvokeOption invokeOption, params object[] parameters) + public Task InvokeAsync(string invokeKey, Type returnType, InvokeOption invokeOption, params object[] parameters) { return this.m_jsonRpcActor.InvokeAsync(invokeKey, returnType, invokeOption, parameters); } - /// - protected override void Dispose(bool disposing) - { - if (disposing) - { - this.m_jsonRpcActor.SafeDispose(); - } - base.Dispose(disposing); - } - /// protected override void LoadConfig(TouchSocketConfig config) { @@ -85,22 +70,39 @@ public class WebSocketJsonRpcClient : SetupClientWebSocket, IWebSocketJsonRpcCli { this.m_jsonRpcActor.SetRpcServerProvider(rpcServerProvider); } + + var jsonRpcOption = config.GetValue(JsonRpcConfigExtension.JsonRpcOptionProperty) ?? new JsonRpcOption(); + + this.m_jsonRpcActor.SerializerConverter = jsonRpcOption.SerializerConverter; } /// - protected override async Task OnReceived(System.Net.WebSockets.WebSocketReceiveResult result, ByteBlock byteBlock) + protected override async Task OnWebSocketReceived(WebSocketMessageType messageType, ReadOnlySequence sequence) { - if (result.MessageType == WebSocketMessageType.Text) + if (messageType == WebSocketMessageType.Text) { - var jsonMemory = byteBlock.Memory; - - if (jsonMemory.IsEmpty) + using (var buffer = new ContiguousMemoryBuffer(sequence)) { - return; - } + var jsonMemory = buffer.Memory; - var callContext = new WebSocketJsonRpcCallContext(this); - await this.m_jsonRpcActor.InputReceiveAsync(jsonMemory, callContext).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + if (jsonMemory.IsEmpty) + { + return; + } + + var callContext = new WebSocketJsonRpcCallContext(this); + await this.m_jsonRpcActor.InputReceiveAsync(jsonMemory, callContext).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } } } + + /// + protected override void SafetyDispose(bool disposing) + { + if (disposing) + { + this.m_jsonRpcActor.SafeDispose(); + } + base.SafetyDispose(disposing); + } } \ No newline at end of file diff --git a/src/TouchSocket.JsonRpc/Extensions/JsonRpcClientExtension.cs b/src/TouchSocket.JsonRpc/Extensions/JsonRpcClientExtension.cs index 8afe772a8..0425d7f81 100644 --- a/src/TouchSocket.JsonRpc/Extensions/JsonRpcClientExtension.cs +++ b/src/TouchSocket.JsonRpc/Extensions/JsonRpcClientExtension.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using TouchSocket.Core; using TouchSocket.Sockets; namespace TouchSocket.JsonRpc; @@ -43,23 +41,4 @@ public static class JsonRpcClientExtension throw new System.Exception("SessionClient必须是Tcp协议,或者完成WebSocket连接"); } } - -#if SystemTextJson - - /// - /// 使用System.Text.Json进行序列化 - /// - /// - /// - public static TJsonRpcClient UseSystemTextJson(this TJsonRpcClient jsonRpcClient, Action options) - where TJsonRpcClient : IJsonRpcClient - { - var serializerOptions = new System.Text.Json.JsonSerializerOptions(); - options.Invoke(serializerOptions); - jsonRpcClient.SerializerConverter.Clear(); - jsonRpcClient.SerializerConverter.Add(new SystemTextJsonStringToClassSerializerFormatter() { JsonSettings = serializerOptions }); - - return jsonRpcClient; - } -#endif } \ No newline at end of file diff --git a/src/TouchSocket.JsonRpc/Extensions/JsonRpcConfigExtension.cs b/src/TouchSocket.JsonRpc/Extensions/JsonRpcConfigExtension.cs index 28bac95ff..6f0313481 100644 --- a/src/TouchSocket.JsonRpc/Extensions/JsonRpcConfigExtension.cs +++ b/src/TouchSocket.JsonRpc/Extensions/JsonRpcConfigExtension.cs @@ -1,82 +1,26 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ -namespace TouchSocket.Sockets; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TouchSocket.JsonRpc; -/// -/// JsonRpcConfigExtension -/// public static class JsonRpcConfigExtension { - ///// - ///// 构建WebSocketJsonRpc类客户端,并连接 - ///// - ///// - ///// - ///// - //public static TClient BuildWithWebSocketJsonRpcClient(this TouchSocketConfig config) where TClient : IWebSocketJsonRpcClient, new() - //{ - // return config.BuildClientAsync(); - //} - - ///// - ///// 构建WebSocketJsonRpc类客户端,并连接 - ///// - ///// - ///// - //public static WebSocketJsonRpcClient BuildWithWebSocketJsonRpcClient(this TouchSocketConfig config) - //{ - // return BuildWithWebSocketJsonRpcClient(config); - //} - - ///// - ///// 构建HttpJsonRpc类客户端,并连接 - ///// - ///// - ///// - ///// - //public static TClient BuildWithHttpJsonRpcClient(this TouchSocketConfig config) where TClient : IHttpJsonRpcClient, new() - //{ - // return config.BuildClientAsync(); - //} - - ///// - ///// 构建HttpJsonRpc类客户端,并连接 - ///// - ///// - ///// - //public static HttpJsonRpcClient BuildWithHttpJsonRpcClient(this TouchSocketConfig config) - //{ - // return BuildWithHttpJsonRpcClient(config); - //} - - ///// - ///// 构建TcpJsonRpc类客户端,并连接 - ///// - ///// - ///// - ///// - //public static TClient BuildWithTcpJsonRpcClient(this TouchSocketConfig config) where TClient : ITcpJsonRpcClient, new() - //{ - // return config.BuildClientAsync(); - //} - - ///// - ///// 构建TcpJsonRpc类客户端,并连接 - ///// - ///// - ///// - //public static TcpJsonRpcClient BuildWithTcpJsonRpcClient(this TouchSocketConfig config) - //{ - // return BuildWithTcpJsonRpcClient(config); - //} -} \ No newline at end of file + [GeneratorProperty(TargetType =typeof(TouchSocketConfig),ActionMode =true)] + public readonly static DependencyProperty JsonRpcOptionProperty = + new("JsonRpcOption", default); +} diff --git a/src/TouchSocket.JsonRpc/Extensions/JsonRpcParserPluginExtension.cs b/src/TouchSocket.JsonRpc/Extensions/JsonRpcParserPluginExtension.cs deleted file mode 100644 index 9793a9dd3..000000000 --- a/src/TouchSocket.JsonRpc/Extensions/JsonRpcParserPluginExtension.cs +++ /dev/null @@ -1,43 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; -using TouchSocket.Core; - -namespace TouchSocket.JsonRpc; - -/// -/// 提供JsonRpcParserPlugin的扩展方法。 -/// -public static class JsonRpcParserPluginExtension -{ -#if SystemTextJson - - /// - /// 使用System.Text.Json进行序列化 - /// - /// JsonRpcParserPlugin实例。 - /// 配置JsonSerializer的选项。 - /// JsonRpcParserPlugin的类型。 - /// 配置后的JsonRpcParserPlugin实例。 - public static TJsonRpcParserPlugin UseSystemTextJson(this TJsonRpcParserPlugin jsonRpcParserPlugin, Action options) - where TJsonRpcParserPlugin : JsonRpcParserPluginBase - { - var serializerOptions = new System.Text.Json.JsonSerializerOptions(); - options.Invoke(serializerOptions); - jsonRpcParserPlugin.SerializerConverter.Clear(); - jsonRpcParserPlugin.SerializerConverter.Add(new SystemTextJsonStringToClassSerializerFormatter() { JsonSettings = serializerOptions }); - - return jsonRpcParserPlugin; - } -#endif -} \ No newline at end of file diff --git a/src/TouchSocket.JsonRpc/Extensions/JsonRpcPluginsManagerExtension.cs b/src/TouchSocket.JsonRpc/Extensions/JsonRpcPluginsManagerExtension.cs index 289dd08e4..a9c04e2f4 100644 --- a/src/TouchSocket.JsonRpc/Extensions/JsonRpcPluginsManagerExtension.cs +++ b/src/TouchSocket.JsonRpc/Extensions/JsonRpcPluginsManagerExtension.cs @@ -11,41 +11,73 @@ //------------------------------------------------------------------------------ using TouchSocket.JsonRpc; +using TouchSocket.Rpc; namespace TouchSocket.Core; /// -/// JsonRpcPluginManagerExtension +/// JsonRpc插件管理器扩展 /// public static class JsonRpcPluginManagerExtension { /// /// 使用基于Tcp协议的JsonRpc的插件。仅服务器有用。 /// - /// - /// + /// 插件管理器 + /// Tcp JsonRpc配置选项 + /// 返回实例 + public static TcpJsonRpcParserPlugin UseTcpJsonRpc(this IPluginManager pluginManager, Action options) + { + var option = new TcpJsonRpcOption(); + options.Invoke(option); + + var plugin = new TcpJsonRpcParserPlugin(pluginManager.Resolver.Resolve(), option); + pluginManager.Add(plugin); + return plugin; + } + + /// + /// 使用基于Tcp协议的JsonRpc的插件。仅服务器有用。 + /// + /// 插件管理器 + /// 返回实例 public static TcpJsonRpcParserPlugin UseTcpJsonRpc(this IPluginManager pluginManager) { - return pluginManager.Add(); + return UseTcpJsonRpc(pluginManager, options => + { + options.SetAllowJsonRpc(client => true); + }); + } /// /// 使用基于Http协议的JsonRpc的插件。仅服务器有用。 /// - /// - /// - public static HttpJsonRpcParserPlugin UseHttpJsonRpc(this IPluginManager pluginManager) + /// 插件管理器 + /// Http JsonRpc配置选项 + /// 返回实例 + public static HttpJsonRpcParserPlugin UseHttpJsonRpc(this IPluginManager pluginManager, Action options) { - return pluginManager.Add(); + var option = new HttpJsonRpcOption(); + options.Invoke(option); + + var plugin = new HttpJsonRpcParserPlugin(pluginManager.Resolver.Resolve(), option); + pluginManager.Add(plugin); + return plugin; } /// /// 使用基于WebSocket协议的JsonRpc的插件。仅服务器有用。 /// - /// - /// - public static WebSocketJsonRpcParserPlugin UseWebSocketJsonRpc(this IPluginManager pluginManager) + /// 插件管理器 + /// WebSocket JsonRpc配置选项 + /// 返回实例 + public static WebSocketJsonRpcParserPlugin UseWebSocketJsonRpc(this IPluginManager pluginManager, Action options) { - return pluginManager.Add(); + var option = new WebSocketJsonRpcOption(); + options.Invoke(option); + var plugin = new WebSocketJsonRpcParserPlugin(pluginManager.Resolver.Resolve(), option); + pluginManager.Add(plugin); + return plugin; } } \ No newline at end of file diff --git a/src/TouchSocket.JsonRpc/Interface/IHttpJsonRpcClient.cs b/src/TouchSocket.JsonRpc/Interface/IHttpJsonRpcClient.cs index dea57a705..aabc7b8f9 100644 --- a/src/TouchSocket.JsonRpc/Interface/IHttpJsonRpcClient.cs +++ b/src/TouchSocket.JsonRpc/Interface/IHttpJsonRpcClient.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; using TouchSocket.Http; using TouchSocket.Sockets; diff --git a/src/TouchSocket.JsonRpc/Interface/IJsonRpcClient.cs b/src/TouchSocket.JsonRpc/Interface/IJsonRpcClient.cs index 9eaf79ee8..56aad2fce 100644 --- a/src/TouchSocket.JsonRpc/Interface/IJsonRpcClient.cs +++ b/src/TouchSocket.JsonRpc/Interface/IJsonRpcClient.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; using TouchSocket.Rpc; namespace TouchSocket.JsonRpc; diff --git a/src/TouchSocket.JsonRpc/Interface/IJsonRpcRequestInfo.cs b/src/TouchSocket.JsonRpc/Interface/IJsonRpcRequestInfo.cs index 6299a236c..f58e29413 100644 --- a/src/TouchSocket.JsonRpc/Interface/IJsonRpcRequestInfo.cs +++ b/src/TouchSocket.JsonRpc/Interface/IJsonRpcRequestInfo.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.JsonRpc; /// @@ -19,13 +17,6 @@ namespace TouchSocket.JsonRpc; /// public interface IJsonRpcRequestInfo { - /// - /// 获取JsonRpc数据源。 - /// - /// - [Obsolete("该方法由于性能问题已被弃用,请使用GetJsonRpcMemory代替", true)] - string GetJsonRpcString(); - /// /// 获取JsonRpc数据源的内存表示形式。 /// diff --git a/src/TouchSocket.JsonRpc/Interface/ITcpJsonRpcClient.cs b/src/TouchSocket.JsonRpc/Interface/ITcpJsonRpcClient.cs index fbaa6c0b0..ab23408dc 100644 --- a/src/TouchSocket.JsonRpc/Interface/ITcpJsonRpcClient.cs +++ b/src/TouchSocket.JsonRpc/Interface/ITcpJsonRpcClient.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; using TouchSocket.Rpc; using TouchSocket.Sockets; diff --git a/src/TouchSocket.JsonRpc/Interface/IWebSocketJsonRpcClient.cs b/src/TouchSocket.JsonRpc/Interface/IWebSocketJsonRpcClient.cs index c4506417d..cba7a7f54 100644 --- a/src/TouchSocket.JsonRpc/Interface/IWebSocketJsonRpcClient.cs +++ b/src/TouchSocket.JsonRpc/Interface/IWebSocketJsonRpcClient.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; using TouchSocket.Rpc; using TouchSocket.Sockets; diff --git a/src/TouchSocket.JsonRpc/Json/TouchSocketJsonRpcSourceGenerationContext.cs b/src/TouchSocket.JsonRpc/Json/TouchSocketJsonRpcSourceGenerationContext.cs index e7f6f67d9..8c8e14565 100644 --- a/src/TouchSocket.JsonRpc/Json/TouchSocketJsonRpcSourceGenerationContext.cs +++ b/src/TouchSocket.JsonRpc/Json/TouchSocketJsonRpcSourceGenerationContext.cs @@ -10,13 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -#if SystemTextJson -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Text.Json.Serialization; -using System.Threading.Tasks; namespace TouchSocket.JsonRpc; @@ -27,5 +21,4 @@ namespace TouchSocket.JsonRpc; [JsonSerializable(typeof(object))] internal partial class TouchSocketJsonRpcSourceGenerationContext : JsonSerializerContext { -} -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/src/TouchSocket.JsonRpc/Options/HttpJsonRpcOption.cs b/src/TouchSocket.JsonRpc/Options/HttpJsonRpcOption.cs new file mode 100644 index 000000000..506b484d7 --- /dev/null +++ b/src/TouchSocket.JsonRpc/Options/HttpJsonRpcOption.cs @@ -0,0 +1,54 @@ +//------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +//------------------------------------------------------------------------------ + +using TouchSocket.Http; +using TouchSocket.Sockets; + +namespace TouchSocket.JsonRpc; + +/// +/// 基于Http协议的JsonRpc配置选项 +/// +public class HttpJsonRpcOption : JsonRpcOption +{ + /// + /// 允许JsonRpc的委托。 + /// + public Func> AllowJsonRpc { get; set; } = (client, context) => Task.FromResult(false); + + /// + /// 设置允许JsonRpc的委托。 + /// + /// 允许JsonRpc的委托 + public void SetAllowJsonRpc(Func> allowJsonRpc) + { + this.AllowJsonRpc = allowJsonRpc; + } + + /// + /// 设置允许JsonRpc的Url。 + /// + /// 允许的Url,默认为"/jsonrpc"。 + public void SetAllowJsonRpc(string url = "/jsonrpc") + { + this.AllowJsonRpc = (client, context) => Task.FromResult(context.Request.UrlEquals(url)); + } + + /// + /// 设置允许JsonRpc的委托。 + /// + /// 允许JsonRpc的委托 + public void SetAllowJsonRpc(Func allowJsonRpc) + { + this.AllowJsonRpc = (client, context) => Task.FromResult(allowJsonRpc(client, context)); + } +} \ No newline at end of file diff --git a/src/TouchSocket.JsonRpc/Options/JsonRpcOption.cs b/src/TouchSocket.JsonRpc/Options/JsonRpcOption.cs new file mode 100644 index 000000000..c8dbf2f65 --- /dev/null +++ b/src/TouchSocket.JsonRpc/Options/JsonRpcOption.cs @@ -0,0 +1,112 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +namespace TouchSocket.JsonRpc; + +/// +/// JsonRpc配置选项 +/// +public class JsonRpcOption +{ + public JsonRpcOption() + { + this.m_serializerConverter.Add(new JsonStringToClassSerializerFormatter()); + } + private readonly TouchSocketSerializerConverter m_serializerConverter = new TouchSocketSerializerConverter(); + + /// + /// 获取序列化转换器 + /// + public TouchSocketSerializerConverter SerializerConverter => this.m_serializerConverter; + + /// + /// 清除所有序列化格式化器 + /// + public void ClearAllFormatters() + { + this.SerializerConverter.Clear(); + } + + /// + /// 配置序列化转换器的高级设置 + /// + /// 配置序列化转换器的操作 + public void ConfigureAdvanced(Action> configureAction) + { + configureAction?.Invoke(this.SerializerConverter); + } + + /// + /// 移除指定类型的序列化格式化器 + /// + /// 要移除的格式化器类型 + public void RemoveFormatter() where TFormatter : ISerializerFormatter + { + this.SerializerConverter.Remove(typeof(TFormatter)); + } + + /// + /// 使用自定义序列化格式化器 + /// + /// 格式化器类型 + public void UseCustomFormatter() where TFormatter : ISerializerFormatter, new() + { + this.SerializerConverter.Add(new TFormatter()); + } + + /// + /// 使用自定义序列化格式化器实例 + /// + /// 格式化器实例 + public void UseCustomFormatter(ISerializerFormatter formatter) + { + this.SerializerConverter.Add(formatter); + } + + /// + /// 使用默认的Json序列化格式化器 + /// + public void UseDefaultJsonFormatter() + { + this.SerializerConverter.Add(new JsonStringToClassSerializerFormatter()); + } + + /// + /// 使用Newtonsoft.Json序列化格式化器,并配置Json设置 + /// + /// 配置Json设置的操作 + public void UseNewtonsoftJsonFormatter(Action configureSettings = null) + { + var formatter = new JsonStringToClassSerializerFormatter(); + configureSettings?.Invoke(formatter.JsonSettings); + this.SerializerConverter.Add(formatter); + } + + /// + /// 使用System.Text.Json序列化格式化器 + /// + public void UseSystemTextJsonFormatter() + { + this.SerializerConverter.Add(new SystemTextJsonStringToClassSerializerFormatter()); + } + + /// + /// 使用System.Text.Json序列化格式化器,并配置Json选项 + /// + /// 配置Json选项的操作 + public void UseSystemTextJsonFormatter(Action configureOptions) + { + var formatter = new SystemTextJsonStringToClassSerializerFormatter(); + configureOptions?.Invoke(formatter.JsonSettings); + this.SerializerConverter.Add(formatter); + } +} \ No newline at end of file diff --git a/src/TouchSocket.JsonRpc/Options/TcpJsonRpcOption.cs b/src/TouchSocket.JsonRpc/Options/TcpJsonRpcOption.cs new file mode 100644 index 000000000..2f3100337 --- /dev/null +++ b/src/TouchSocket.JsonRpc/Options/TcpJsonRpcOption.cs @@ -0,0 +1,48 @@ +//------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +//------------------------------------------------------------------------------ + +using TouchSocket.Sockets; + +namespace TouchSocket.JsonRpc; + +/// +/// 基于Tcp协议的JsonRpc配置选项 +/// +public class TcpJsonRpcOption : JsonRpcOption +{ + /// + /// 经过判断是否标识当前的客户端为JsonRpc + /// + public Func> AllowJsonRpc { get; set; } = (client) => Task.FromResult(true); + + /// + /// 设置是否允许JsonRpc的判断逻辑 + /// + /// 判断逻辑 + /// 返回当前实例,支持链式调用 + public TcpJsonRpcOption SetAllowJsonRpc(Func> allowJsonRpc) + { + this.AllowJsonRpc = allowJsonRpc; + return this; + } + + /// + /// 设置是否允许JsonRpc的判断逻辑 + /// + /// 判断逻辑 + /// 返回当前实例,支持链式调用 + public TcpJsonRpcOption SetAllowJsonRpc(Func allowJsonRpc) + { + this.AllowJsonRpc = (client) => Task.FromResult(allowJsonRpc(client)); + return this; + } +} \ No newline at end of file diff --git a/src/TouchSocket.JsonRpc/Options/WebSocketJsonRpcOption.cs b/src/TouchSocket.JsonRpc/Options/WebSocketJsonRpcOption.cs new file mode 100644 index 000000000..67643fae4 --- /dev/null +++ b/src/TouchSocket.JsonRpc/Options/WebSocketJsonRpcOption.cs @@ -0,0 +1,49 @@ +//------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +//------------------------------------------------------------------------------ + +using TouchSocket.Http; +using TouchSocket.Http.WebSockets; + +namespace TouchSocket.JsonRpc; + +/// +/// 基于WebSocket协议的JsonRpc配置选项 +/// +public class WebSocketJsonRpcOption : JsonRpcOption +{ + /// + /// 经过判断是否标识当前的客户端为JsonRpc + /// + public Func> AllowJsonRpc { get; set; } + + /// + /// 设置是否允许JsonRpc的判断逻辑 + /// + /// 判断逻辑 + /// 返回当前实例,支持链式调用 + public WebSocketJsonRpcOption SetAllowJsonRpc(Func> allowJsonRpc) + { + this.AllowJsonRpc = allowJsonRpc; + return this; + } + + /// + /// 设置是否允许JsonRpc的判断逻辑 + /// + /// 判断逻辑 + /// 返回当前实例,支持链式调用 + public WebSocketJsonRpcOption SetAllowJsonRpc(Func allowJsonRpc) + { + this.AllowJsonRpc = (client, context) => Task.FromResult(allowJsonRpc(client, context)); + return this; + } +} \ No newline at end of file diff --git a/src/TouchSocket.JsonRpc/Plugins/HttpJsonRpcParserPlugin.cs b/src/TouchSocket.JsonRpc/Plugins/HttpJsonRpcParserPlugin.cs index 575bfb182..a4a6dca21 100644 --- a/src/TouchSocket.JsonRpc/Plugins/HttpJsonRpcParserPlugin.cs +++ b/src/TouchSocket.JsonRpc/Plugins/HttpJsonRpcParserPlugin.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Http; using TouchSocket.Rpc; @@ -23,27 +21,21 @@ namespace TouchSocket.JsonRpc; [PluginOption(Singleton = true)] public sealed class HttpJsonRpcParserPlugin : JsonRpcParserPluginBase, IHttpPlugin { - private string m_jsonRpcUrl = "/jsonrpc"; + private readonly Func> m_allowJsonRpc; /// /// 构造函数,用于初始化 类的新实例。 /// /// IRpcServerProvider 类型的参数,提供 RPC 服务器服务。 + /// Http JsonRpc配置选项 /// - /// 该构造函数调用基类的构造函数,传递 参数。 + /// 该构造函数调用基类的构造函数,传递 参数。 /// 这对于确保基类能够访问 RPC 服务器提供者和依赖项解析器至关重要。 /// - public HttpJsonRpcParserPlugin(IRpcServerProvider rpcServerProvider) : base(rpcServerProvider) + public HttpJsonRpcParserPlugin(IRpcServerProvider rpcServerProvider, HttpJsonRpcOption option) + : base(rpcServerProvider, option) { - } - - /// - /// 当挂载在时,匹配Url然后响应。当设置为或空时,会全部响应。 - /// - public string JsonRpcUrl - { - get => this.m_jsonRpcUrl; - set => this.m_jsonRpcUrl = string.IsNullOrEmpty(value) ? "/" : value; + this.m_allowJsonRpc = option.AllowJsonRpc; } /// @@ -51,7 +43,8 @@ public sealed class HttpJsonRpcParserPlugin : JsonRpcParserPluginBase, IHttpPlug { if (e.Context.Request.Method == HttpMethod.Post) { - if (this.m_jsonRpcUrl == "/" || e.Context.Request.UrlEquals(this.m_jsonRpcUrl)) + var allowJsonRpc = await this.m_allowJsonRpc.Invoke(client,e.Context); + if (allowJsonRpc) { e.Handled = true; @@ -60,12 +53,12 @@ public sealed class HttpJsonRpcParserPlugin : JsonRpcParserPluginBase, IHttpPlug jsonRpcActor = new JsonRpcActor() { Resolver = client.Resolver, - SendAction = async (data) => + SendAction = async (data, cancellationToken) => { var response = e.Context.Response; response.SetContent(data); response.SetStatusWithSuccess(); - await response.AnswerAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await response.AnswerAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); }, SerializerConverter = this.SerializerConverter, RpcDispatcher = new ImmediateRpcDispatcher() @@ -81,17 +74,4 @@ public sealed class HttpJsonRpcParserPlugin : JsonRpcParserPluginBase, IHttpPlug } await e.InvokeNext().ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - - /// - /// 设置JSON-RPC URL的匹配规则。 - /// 当挂载在时,根据指定的URL进行匹配并响应请求。 - /// 如果设置为或空,将对所有请求进行响应。 - /// - /// 要匹配的JSON-RPC URL。 - /// 返回当前的实例,支持链式调用。 - public HttpJsonRpcParserPlugin SetJsonRpcUrl(string jsonRpcUrl) - { - this.JsonRpcUrl = jsonRpcUrl; - return this; - } } \ No newline at end of file diff --git a/src/TouchSocket.JsonRpc/Plugins/JsonRpcParserPluginBase.cs b/src/TouchSocket.JsonRpc/Plugins/JsonRpcParserPluginBase.cs index 65509efda..fba347822 100644 --- a/src/TouchSocket.JsonRpc/Plugins/JsonRpcParserPluginBase.cs +++ b/src/TouchSocket.JsonRpc/Plugins/JsonRpcParserPluginBase.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; using TouchSocket.Rpc; namespace TouchSocket.JsonRpc; @@ -30,12 +29,19 @@ public abstract class JsonRpcParserPluginBase : PluginBase /// public ActionMap ActionMap { get; } = new ActionMap(true); + /// + /// 获取序列化转换器。 + /// + public TouchSocketSerializerConverter SerializerConverter { get; } + /// /// 初始化 类的新实例。 /// /// RPC服务器提供程序。 - public JsonRpcParserPluginBase(IRpcServerProvider rpcServerProvider) + /// JsonRpc配置选项 + public JsonRpcParserPluginBase(IRpcServerProvider rpcServerProvider, JsonRpcOption option) { + this.SerializerConverter = option.SerializerConverter; this.SerializerConverter.Add(new JsonStringToClassSerializerFormatter()); if (rpcServerProvider is not null) { @@ -43,9 +49,4 @@ public abstract class JsonRpcParserPluginBase : PluginBase JsonRpcActor.AddRpcToMap(rpcServerProvider, this.ActionMap); } } - - /// - /// 获取序列化转换器。 - /// - public TouchSocketSerializerConverter SerializerConverter { get; } = new TouchSocketSerializerConverter(); } \ No newline at end of file diff --git a/src/TouchSocket.JsonRpc/Plugins/TcpJsonRpcParserPlugin.cs b/src/TouchSocket.JsonRpc/Plugins/TcpJsonRpcParserPlugin.cs index a36f8a50a..704ccaa14 100644 --- a/src/TouchSocket.JsonRpc/Plugins/TcpJsonRpcParserPlugin.cs +++ b/src/TouchSocket.JsonRpc/Plugins/TcpJsonRpcParserPlugin.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Rpc; using TouchSocket.Sockets; @@ -24,66 +21,16 @@ namespace TouchSocket.JsonRpc; [PluginOption(Singleton = true)] public sealed class TcpJsonRpcParserPlugin : JsonRpcParserPluginBase, ITcpConnectedPlugin, ITcpReceivedPlugin { + private readonly TcpJsonRpcOption m_option; + /// /// 基于Tcp协议的JsonRpc功能插件 /// /// - public TcpJsonRpcParserPlugin(IRpcServerProvider rpcServerProvider) : base(rpcServerProvider) + /// 配置选项 + public TcpJsonRpcParserPlugin(IRpcServerProvider rpcServerProvider, TcpJsonRpcOption option) : base(rpcServerProvider, option) { - this.AllowJsonRpc = (client) => - { - return Task.FromResult(true); - }; - } - - /// - /// 经过判断是否标识当前的客户端为JsonRpc - /// - /// - /// - public TcpJsonRpcParserPlugin SetAllowJsonRpc(Func> allowJsonRpc) - { - this.AllowJsonRpc = allowJsonRpc; - return this; - } - - /// - /// 经过判断是否标识当前的客户端为JsonRpc - /// - /// - /// - public TcpJsonRpcParserPlugin SetAllowJsonRpc(Func allowJsonRpc) - { - this.AllowJsonRpc = (client) => - { - return Task.FromResult(allowJsonRpc(client)); - }; - return this; - } - - - - /// - /// 经过判断是否标识当前的客户端为JsonRpc - /// - public Func> AllowJsonRpc { get; set; } - - /// - /// 自动转换协议 - /// - [Obsolete("此配置已被弃用,如果需要筛选客户端,请使用SetAllowJsonRpc方法实现", true)] - public bool AutoSwitch { get; set; } = true; - - /// - /// 不需要自动转化协议。 - /// 仅当服务器是Tcp时生效。才会解释为jsonRpc。 - /// - /// - [Obsolete("此配置已被弃用,如果需要筛选客户端,请使用SetAllowJsonRpc方法实现", true)] - public TcpJsonRpcParserPlugin NoSwitchProtocol() - { - this.AutoSwitch = false; - return this; + this.m_option = option; } /// @@ -91,15 +38,16 @@ public sealed class TcpJsonRpcParserPlugin : JsonRpcParserPluginBase, ITcpConnec { if (client is IClientSender clientSender) { - if (this.AllowJsonRpc != null) + if (this.m_option.AllowJsonRpc != null) { - if (await this.AllowJsonRpc.Invoke(client).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) + if (await this.m_option.AllowJsonRpc.Invoke(client) + .ConfigureAwait(EasyTask.ContinueOnCapturedContext)) { var jsonRpcActor = new JsonRpcActor() { SerializerConverter = this.SerializerConverter, Resolver = client.Resolver, - SendAction = (data) => clientSender.SendAsync(data), + SendAction = clientSender.SendAsync, Logger = client.Logger }; @@ -126,9 +74,9 @@ public sealed class TcpJsonRpcParserPlugin : JsonRpcParserPluginBase, ITcpConnec { jsonRpcMemory = jsonPackage.Data; } - else if (e.ByteBlock != null) + else if (!e.Memory.IsEmpty) { - jsonRpcMemory = e.ByteBlock.Memory; + jsonRpcMemory = e.Memory; } if (jsonRpcMemory.IsEmpty) diff --git a/src/TouchSocket.JsonRpc/Plugins/WebSocketJsonRpcParserPlugin.cs b/src/TouchSocket.JsonRpc/Plugins/WebSocketJsonRpcParserPlugin.cs index 0bb072b35..672870d73 100644 --- a/src/TouchSocket.JsonRpc/Plugins/WebSocketJsonRpcParserPlugin.cs +++ b/src/TouchSocket.JsonRpc/Plugins/WebSocketJsonRpcParserPlugin.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Http; using TouchSocket.Http.WebSockets; using TouchSocket.Rpc; @@ -23,33 +20,32 @@ namespace TouchSocket.JsonRpc; /// WebSocketJsonRpcParserPlugin /// [PluginOption(Singleton = true)] -public sealed class WebSocketJsonRpcParserPlugin : JsonRpcParserPluginBase, IWebSocketHandshakedPlugin, IWebSocketReceivedPlugin +public sealed class WebSocketJsonRpcParserPlugin : JsonRpcParserPluginBase, IWebSocketConnectedPlugin, IWebSocketReceivedPlugin { + private readonly WebSocketJsonRpcOption m_option; + /// /// WebSocketJsonRpcParserPlugin /// /// - public WebSocketJsonRpcParserPlugin(IRpcServerProvider rpcServerProvider) : base(rpcServerProvider) + /// WebSocket JsonRpc配置选项 + public WebSocketJsonRpcParserPlugin(IRpcServerProvider rpcServerProvider, WebSocketJsonRpcOption option) : base(rpcServerProvider, option) { + this.m_option = option; } - /// - /// 经过判断是否标识当前的客户端为JsonRpc - /// - public Func> AllowJsonRpc { get; set; } - /// - public async Task OnWebSocketHandshaked(IWebSocket client, HttpContextEventArgs e) + public async Task OnWebSocketConnected(IWebSocket client, HttpContextEventArgs e) { - if (this.AllowJsonRpc != null) + if (this.m_option.AllowJsonRpc != null) { - if (await this.AllowJsonRpc.Invoke(client, e.Context).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) + if (await this.m_option.AllowJsonRpc.Invoke(client, e.Context).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) { var jsonRpcActor = new JsonRpcActor() { SerializerConverter = this.SerializerConverter, Resolver = client.Client.Resolver, - SendAction = (data) => client.SendAsync(data, WSDataType.Text) + SendAction = (data, cancellationToken) => client.SendAsync(data, WSDataType.Text, true, cancellationToken) }; jsonRpcActor.SetRpcServerProvider(this.RpcServerProvider, this.ActionMap); @@ -68,38 +64,11 @@ public sealed class WebSocketJsonRpcParserPlugin : JsonRpcParserPluginBase, IWeb { e.Handled = true; - await jsonRpcActor.InputReceiveAsync(dataFrame.PayloadData.Memory, new WebSocketJsonRpcCallContext(client.Client)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await jsonRpcActor.InputReceiveAsync(dataFrame.PayloadData, new WebSocketJsonRpcCallContext(client.Client)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } else { await e.InvokeNext().ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } - - /// - /// 经过判断是否标识当前的客户端为JsonRpc - /// - /// - /// - public WebSocketJsonRpcParserPlugin SetAllowJsonRpc(Func> allowJsonRpc) - { - this.AllowJsonRpc = allowJsonRpc; - return this; - } - - /// - /// 经过判断是否标识当前的客户端为JsonRpc - /// - /// - /// - public WebSocketJsonRpcParserPlugin SetAllowJsonRpc(Func allowJsonRpc) - { - this.AllowJsonRpc = (client, context) => - { - return Task.FromResult(allowJsonRpc(client, context)); - }; - return this; - } - - } \ No newline at end of file diff --git a/src/TouchSocket.JsonRpc/Proxy/JsonRpcDispatchProxy.cs b/src/TouchSocket.JsonRpc/Proxy/JsonRpcDispatchProxy.cs index b46ad36fb..f5998af47 100644 --- a/src/TouchSocket.JsonRpc/Proxy/JsonRpcDispatchProxy.cs +++ b/src/TouchSocket.JsonRpc/Proxy/JsonRpcDispatchProxy.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -#if NET6_0_OR_GREATER || NET481_OR_GREATER using TouchSocket.Rpc; namespace TouchSocket.JsonRpc; @@ -30,5 +29,4 @@ public abstract class JsonRpcDispatchProxy : RpcDispatchProxy { -} -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/src/TouchSocket.JsonRpc/Proxy/JsonRpcRealityProxy.cs b/src/TouchSocket.JsonRpc/Proxy/JsonRpcRealityProxy.cs deleted file mode 100644 index a65759782..000000000 --- a/src/TouchSocket.JsonRpc/Proxy/JsonRpcRealityProxy.cs +++ /dev/null @@ -1,42 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -#if NET45_OR_GREATER -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using TouchSocket.Rpc; - -namespace TouchSocket.JsonRpc; - -/// -/// JsonRpcRealityProxy -/// -/// -/// -public abstract class JsonRpcRealityProxy : RpcRealityProxy where TClient : IJsonRpcClient -{ - -} - -/// -/// JsonRpcRealityProxy -/// -/// -public abstract class JsonRpcRealityProxy : JsonRpcRealityProxy -{ - -} - -#endif \ No newline at end of file diff --git a/src/TouchSocket.JsonRpc/Readme.md b/src/TouchSocket.JsonRpc/Readme.md index d0f3567d3..f729ad636 100644 --- a/src/TouchSocket.JsonRpc/Readme.md +++ b/src/TouchSocket.JsonRpc/Readme.md @@ -13,13 +13,12 @@ TouchSocket.JsonRpc 是一个提供 JsonRpc 服务器和客户端的组件库。 ## 支持的目标框架 - net481 -- net45 - net462 - net472 - netstandard2.0 - netstandard2.1 - net6.0 -- net9.0 +- net10.0 - net8.0 ## 贡献与反馈 diff --git a/src/TouchSocket.JsonRpc/TouchSocket.JsonRpc.csproj b/src/TouchSocket.JsonRpc/TouchSocket.JsonRpc.csproj index 10d8224b3..c218f9219 100644 --- a/src/TouchSocket.JsonRpc/TouchSocket.JsonRpc.csproj +++ b/src/TouchSocket.JsonRpc/TouchSocket.JsonRpc.csproj @@ -1,6 +1,6 @@ - net481;net45;net462;net472;netstandard2.0;netstandard2.1;net6.0;net9.0;net8.0 + net462;netstandard2.0;netstandard2.1;net6.0;net10.0;net8.0 JsonRpc;TouchSocket 这是一个提供JsonRpc服务器和客户端的组件库。可以通过该组件创建基于Tcp、Http、WebSocket协议的JsonRpc服务器和客户端,支持JsonRpc全部功能,可与Web,Android等平台无缝对接。 @@ -9,7 +9,7 @@ - + @@ -18,10 +18,6 @@ - - - - diff --git a/src/TouchSocket.Modbus/Adapter/Rtu/ModbusRtuAdapter.cs b/src/TouchSocket.Modbus/Adapter/Rtu/ModbusRtuAdapter.cs index dab3f7328..79926ad13 100644 --- a/src/TouchSocket.Modbus/Adapter/Rtu/ModbusRtuAdapter.cs +++ b/src/TouchSocket.Modbus/Adapter/Rtu/ModbusRtuAdapter.cs @@ -10,25 +10,23 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Modbus; internal class ModbusRtuAdapter : CustomDataHandlingAdapter { - protected override FilterResult Filter(ref TByteBlock byteBlock, bool beCached, ref ModbusRtuResponse request, ref int tempCapacity) + protected override FilterResult Filter(ref TReader reader, bool beCached, ref ModbusRtuResponse request) { - if (byteBlock.CanReadLength < 3) + if (reader.BytesRemaining < 3) { return FilterResult.Cache; } - var pos = byteBlock.Position; + var pos = reader.BytesRead; - var slaveId = byteBlock.ReadByte(); + var slaveId = ReaderExtension.ReadValue(ref reader); FunctionCode functionCode; var isError = false; - var code = byteBlock.ReadByte(); + var code = ReaderExtension.ReadValue(ref reader); if ((code & 0x80) == 0) { functionCode = (FunctionCode)code; @@ -47,17 +45,17 @@ internal class ModbusRtuAdapter : CustomDataHandlingAdapter int bodyLength; if (isError) { - errorCode = (ModbusErrorCode)byteBlock.ReadByte(); + errorCode = (ModbusErrorCode)ReaderExtension.ReadValue(ref reader); bodyLength = 2; - if (byteBlock.CanReadLength < bodyLength) + if (reader.BytesRemaining < bodyLength) { - byteBlock.Position = pos; + reader.BytesRead = pos; return FilterResult.Cache; } - var newCrc = TouchSocketModbusUtility.ToModbusCrcValue(byteBlock.Span.Slice(pos, byteBlock.Position - pos)); - var crc = byteBlock.ReadUInt16(EndianType.Big); + var newCrc = TouchSocketModbusUtility.ToModbusCrcValue(reader.TotalSequence.Slice(pos, reader.BytesRead - pos)); + var crc = ReaderExtension.ReadValue(ref reader, EndianType.Big); //下面crc验证失败时,不再抛出错误,而是返回错误码。 //https://gitee.com/RRQM_Home/TouchSocket/issues/IBC1J2 @@ -89,18 +87,20 @@ internal class ModbusRtuAdapter : CustomDataHandlingAdapter { if ((byte)functionCode <= 4 || functionCode == FunctionCode.ReadWriteMultipleRegisters) { - bodyLength = byteBlock.ReadByte() + 2; + bodyLength = ReaderExtension.ReadValue(ref reader) + 2; - if (byteBlock.CanReadLength < bodyLength) + if (reader.BytesRemaining < bodyLength) { - byteBlock.Position = pos; + reader.BytesRead = pos; return FilterResult.Cache; } - data = byteBlock.ReadToSpan(bodyLength - 2).ToArray(); + var len = bodyLength - 2; + data = reader.GetSpan(len).Slice(0, len).ToArray(); + reader.Advance(len); - var newCrc = TouchSocketModbusUtility.ToModbusCrcValue(byteBlock.Span.Slice(pos, byteBlock.Position - pos)); - var crc = byteBlock.ReadUInt16(EndianType.Big); + var newCrc = TouchSocketModbusUtility.ToModbusCrcValue(reader.TotalSequence.Slice(pos, reader.BytesRead - pos)); + var crc = ReaderExtension.ReadValue(ref reader, EndianType.Big); if (crc == newCrc) { @@ -127,15 +127,18 @@ internal class ModbusRtuAdapter : CustomDataHandlingAdapter else if (functionCode == FunctionCode.WriteSingleCoil || functionCode == FunctionCode.WriteSingleRegister) { bodyLength = 6; - if (byteBlock.CanReadLength < bodyLength) + if (reader.BytesRemaining < bodyLength) { - byteBlock.Position = pos; + reader.BytesRead = pos; return FilterResult.Cache; } - startingAddress = byteBlock.ReadUInt16(EndianType.Big); - data = byteBlock.ReadToSpan(bodyLength - 4).ToArray(); - var newCrc = TouchSocketModbusUtility.ToModbusCrcValue(byteBlock.Span.Slice(pos, byteBlock.Position - pos)); - var crc = byteBlock.ReadUInt16(EndianType.Big); + startingAddress = ReaderExtension.ReadValue(ref reader, EndianType.Big); + + var len = bodyLength - 4; + data = reader.GetSpan(len).Slice(0, len).ToArray(); + reader.Advance(len); + var newCrc = TouchSocketModbusUtility.ToModbusCrcValue(reader.TotalSequence.Slice(pos, reader.BytesRead - pos)); + var crc = ReaderExtension.ReadValue(ref reader, EndianType.Big); if (crc == newCrc) { @@ -167,17 +170,17 @@ internal class ModbusRtuAdapter : CustomDataHandlingAdapter else if (functionCode == FunctionCode.WriteMultipleCoils || functionCode == FunctionCode.WriteMultipleRegisters) { bodyLength = 6; - if (byteBlock.CanReadLength < bodyLength) + if (reader.BytesRemaining < bodyLength) { - byteBlock.Position = pos; + reader.BytesRead = pos; return FilterResult.Cache; } - startingAddress = byteBlock.ReadUInt16(EndianType.Big); - var quantity = byteBlock.ReadUInt16(EndianType.Big); + startingAddress = ReaderExtension.ReadValue(ref reader, EndianType.Big); + var quantity = ReaderExtension.ReadValue(ref reader, EndianType.Big); data = new byte[0]; - var newCrc = TouchSocketModbusUtility.ToModbusCrcValue(byteBlock.Span.Slice(pos, byteBlock.Position - pos)); - var crc = byteBlock.ReadUInt16(EndianType.Big); + var newCrc = TouchSocketModbusUtility.ToModbusCrcValue(reader.TotalSequence.Slice(pos, reader.BytesRead - pos)); + var crc = ReaderExtension.ReadValue(ref reader, EndianType.Big); if (crc == newCrc) { diff --git a/src/TouchSocket.Modbus/Adapter/Rtu/ModbusRtuRequest.cs b/src/TouchSocket.Modbus/Adapter/Rtu/ModbusRtuRequest.cs index 67aaf1976..d1b5ac153 100644 --- a/src/TouchSocket.Modbus/Adapter/Rtu/ModbusRtuRequest.cs +++ b/src/TouchSocket.Modbus/Adapter/Rtu/ModbusRtuRequest.cs @@ -10,70 +10,78 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Modbus; internal sealed class ModbusRtuRequest : ModbusRtuBase, IRequestInfoBuilder, IRequestInfo { - public ModbusRtuRequest(ModbusRequest request) + public ModbusRtuRequest(IModbusRequest request) { this.SlaveId = request.SlaveId; this.FunctionCode = request.FunctionCode; this.Quantity = request.Quantity; this.StartingAddress = request.StartingAddress; this.Data = request.Data; - this.ReadStartAddress = request.ReadStartAddress; - this.ReadQuantity = request.ReadQuantity; + + // 如果是读写操作,获取读取相关的属性 + if (request is IModbusReadWriteRequest readWriteRequest) + { + this.ReadStartAddress = readWriteRequest.ReadStartAddress; + this.ReadQuantity = readWriteRequest.ReadQuantity; + } } /// public int MaxLength => 1024; /// - public void Build(ref TByteBlock byteBlock) where TByteBlock : IByteBlock + public void Build(ref TWriter writer) + where TWriter : IBytesWriter + { + var memory = writer.GetMemory(this.MaxLength); + var bytesWriter = new BytesWriter(memory); if ((byte)this.FunctionCode <= 4) { - byteBlock.WriteByte(this.SlaveId); - byteBlock.WriteByte((byte)this.FunctionCode); - byteBlock.WriteUInt16(this.StartingAddress, EndianType.Big); - byteBlock.WriteUInt16(this.Quantity, EndianType.Big); + WriterExtension.WriteValue(ref bytesWriter, this.SlaveId); + WriterExtension.WriteValue(ref bytesWriter, (byte)this.FunctionCode); + WriterExtension.WriteValue(ref bytesWriter, this.StartingAddress, EndianType.Big); + WriterExtension.WriteValue(ref bytesWriter, this.Quantity, EndianType.Big); } else if (this.FunctionCode == FunctionCode.WriteSingleCoil || this.FunctionCode == FunctionCode.WriteSingleRegister) { - byteBlock.WriteByte(this.SlaveId); - byteBlock.WriteByte((byte)this.FunctionCode); - byteBlock.WriteUInt16(this.StartingAddress, EndianType.Big); - byteBlock.Write(this.Data.Span); + WriterExtension.WriteValue(ref bytesWriter, this.SlaveId); + WriterExtension.WriteValue(ref bytesWriter, (byte)this.FunctionCode); + WriterExtension.WriteValue(ref bytesWriter, this.StartingAddress, EndianType.Big); + bytesWriter.Write(this.Data.Span); } else if (this.FunctionCode == FunctionCode.WriteMultipleCoils || this.FunctionCode == FunctionCode.WriteMultipleRegisters) { - byteBlock.WriteByte(this.SlaveId); - byteBlock.WriteByte((byte)this.FunctionCode); - byteBlock.WriteUInt16(this.StartingAddress, EndianType.Big); - byteBlock.WriteUInt16(this.Quantity, EndianType.Big); - byteBlock.WriteByte((byte)this.Data.Length); - byteBlock.Write(this.Data.Span); + WriterExtension.WriteValue(ref bytesWriter, this.SlaveId); + WriterExtension.WriteValue(ref bytesWriter, (byte)this.FunctionCode); + WriterExtension.WriteValue(ref bytesWriter, this.StartingAddress, EndianType.Big); + WriterExtension.WriteValue(ref bytesWriter, this.Quantity, EndianType.Big); + WriterExtension.WriteValue(ref bytesWriter, (byte)this.Data.Length); + bytesWriter.Write(this.Data.Span); } else if (this.FunctionCode == FunctionCode.ReadWriteMultipleRegisters) { - byteBlock.WriteByte(this.SlaveId); - byteBlock.WriteByte((byte)this.FunctionCode); - byteBlock.WriteUInt16(this.ReadStartAddress, EndianType.Big); - byteBlock.WriteUInt16(this.ReadQuantity, EndianType.Big); - byteBlock.WriteUInt16(this.StartingAddress, EndianType.Big); - byteBlock.WriteUInt16(this.Quantity, EndianType.Big); - byteBlock.WriteByte((byte)this.Data.Length); - byteBlock.Write(this.Data.Span); + WriterExtension.WriteValue(ref bytesWriter, this.SlaveId); + WriterExtension.WriteValue(ref bytesWriter, (byte)this.FunctionCode); + WriterExtension.WriteValue(ref bytesWriter, this.ReadStartAddress, EndianType.Big); + WriterExtension.WriteValue(ref bytesWriter, this.ReadQuantity, EndianType.Big); + WriterExtension.WriteValue(ref bytesWriter, this.StartingAddress, EndianType.Big); + WriterExtension.WriteValue(ref bytesWriter, this.Quantity, EndianType.Big); + WriterExtension.WriteValue(ref bytesWriter, (byte)this.Data.Length); + bytesWriter.Write(this.Data.Span); } else { throw new System.InvalidOperationException("无法识别的功能码"); } - this.Crc = TouchSocketModbusUtility.ToModbusCrcValue(byteBlock.Span); + this.Crc = TouchSocketModbusUtility.ToModbusCrcValue(bytesWriter.Span); - byteBlock.WriteUInt16(this.Crc, EndianType.Big); + WriterExtension.WriteValue(ref bytesWriter, this.Crc, EndianType.Big); + writer.Advance((int)bytesWriter.WrittenCount); } } \ No newline at end of file diff --git a/src/TouchSocket.Modbus/Adapter/Rtu/ModbusRtuResponse.cs b/src/TouchSocket.Modbus/Adapter/Rtu/ModbusRtuResponse.cs index f86cdf59a..ffb21b3d9 100644 --- a/src/TouchSocket.Modbus/Adapter/Rtu/ModbusRtuResponse.cs +++ b/src/TouchSocket.Modbus/Adapter/Rtu/ModbusRtuResponse.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Modbus; internal class ModbusRtuResponse : ModbusRtuBase, IModbusResponse, IRequestInfo diff --git a/src/TouchSocket.Modbus/Adapter/Tcp/ModbusTcpAdapterForPoll.cs b/src/TouchSocket.Modbus/Adapter/Tcp/ModbusTcpAdapterForPoll.cs index 80f147d47..bdaa124e7 100644 --- a/src/TouchSocket.Modbus/Adapter/Tcp/ModbusTcpAdapterForPoll.cs +++ b/src/TouchSocket.Modbus/Adapter/Tcp/ModbusTcpAdapterForPoll.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Modbus; /// diff --git a/src/TouchSocket.Modbus/Adapter/Tcp/ModbusTcpRequest.cs b/src/TouchSocket.Modbus/Adapter/Tcp/ModbusTcpRequest.cs index d75153e77..4da6133f2 100644 --- a/src/TouchSocket.Modbus/Adapter/Tcp/ModbusTcpRequest.cs +++ b/src/TouchSocket.Modbus/Adapter/Tcp/ModbusTcpRequest.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Modbus; /// @@ -24,7 +22,7 @@ internal sealed class ModbusTcpRequest : ModbusTcpBase, IRequestInfoBuilder, IRe /// /// /// - public ModbusTcpRequest(ushort transactionId, ModbusRequest request) + public ModbusTcpRequest(ushort transactionId, IModbusRequest request) { this.TransactionId = transactionId; this.ProtocolId = 0; @@ -33,55 +31,62 @@ internal sealed class ModbusTcpRequest : ModbusTcpBase, IRequestInfoBuilder, IRe this.StartingAddress = request.StartingAddress; this.Quantity = request.Quantity; this.Data = request.Data; - this.ReadStartAddress = request.ReadStartAddress; - this.ReadQuantity = request.ReadQuantity; + + // 如果是读写操作,获取读取相关的属性 + if (request is IModbusReadWriteRequest readWriteRequest) + { + this.ReadStartAddress = readWriteRequest.ReadStartAddress; + this.ReadQuantity = readWriteRequest.ReadQuantity; + } } /// public int MaxLength => 1024; /// - public void Build(ref TByteBlock byteBlock) where TByteBlock : IByteBlock + public void Build(ref TWriter writer) + where TWriter : IBytesWriter + { - byteBlock.WriteUInt16(this.TransactionId, EndianType.Big); - byteBlock.WriteUInt16(this.ProtocolId, EndianType.Big); + WriterExtension.WriteValue(ref writer, this.TransactionId, EndianType.Big); + WriterExtension.WriteValue(ref writer, this.ProtocolId, EndianType.Big); if ((byte)this.FunctionCode <= 4) { - byteBlock.WriteUInt16(6, EndianType.Big); - byteBlock.WriteByte(this.SlaveId); - byteBlock.WriteByte((byte)this.FunctionCode); - byteBlock.WriteUInt16(this.StartingAddress, EndianType.Big); - byteBlock.WriteUInt16(this.Quantity, EndianType.Big); + WriterExtension.WriteValue(ref writer, 6, EndianType.Big); + WriterExtension.WriteValue(ref writer, this.SlaveId); + WriterExtension.WriteValue(ref writer, (byte)this.FunctionCode); + WriterExtension.WriteValue(ref writer, this.StartingAddress, EndianType.Big); + WriterExtension.WriteValue(ref writer, this.Quantity, EndianType.Big); } else if (this.FunctionCode == FunctionCode.WriteSingleCoil || this.FunctionCode == FunctionCode.WriteSingleRegister) { - byteBlock.WriteUInt16(6, EndianType.Big); - byteBlock.WriteByte(this.SlaveId); - byteBlock.WriteByte((byte)this.FunctionCode); - byteBlock.WriteUInt16(this.StartingAddress, EndianType.Big); - byteBlock.Write(this.Data.Span); + WriterExtension.WriteValue(ref writer, 6, EndianType.Big); + WriterExtension.WriteValue(ref writer, this.SlaveId); + WriterExtension.WriteValue(ref writer, (byte)this.FunctionCode); + WriterExtension.WriteValue(ref writer, this.StartingAddress, EndianType.Big); + writer.Write(this.Data.Span); } else if (this.FunctionCode == FunctionCode.WriteMultipleCoils || this.FunctionCode == FunctionCode.WriteMultipleRegisters) { - byteBlock.WriteUInt16((ushort)(this.Data.Length + 7), EndianType.Big); - byteBlock.WriteByte(this.SlaveId); - byteBlock.WriteByte((byte)this.FunctionCode); - byteBlock.WriteUInt16(this.StartingAddress, EndianType.Big); - byteBlock.WriteUInt16(this.Quantity, EndianType.Big); - byteBlock.WriteByte((byte)this.Data.Length); - byteBlock.Write(this.Data.Span); + WriterExtension.WriteValue(ref writer, (ushort)(this.Data.Length + 7), EndianType.Big); + WriterExtension.WriteValue(ref writer, this.SlaveId); + WriterExtension.WriteValue(ref writer, (byte)this.FunctionCode); + WriterExtension.WriteValue(ref writer, this.StartingAddress, EndianType.Big); + WriterExtension.WriteValue(ref writer, this.Quantity, EndianType.Big); + WriterExtension.WriteValue(ref writer, (byte)this.Data.Length); + writer.Write(this.Data.Span); } else if (this.FunctionCode == FunctionCode.ReadWriteMultipleRegisters) { - byteBlock.WriteUInt16((ushort)(this.Data.Length + 11), EndianType.Big); - byteBlock.WriteByte(this.SlaveId); - byteBlock.WriteByte((byte)this.FunctionCode); - byteBlock.WriteUInt16(this.ReadStartAddress, EndianType.Big); - byteBlock.WriteUInt16(this.ReadQuantity, EndianType.Big); - byteBlock.WriteUInt16(this.StartingAddress, EndianType.Big); - byteBlock.WriteUInt16(this.Quantity, EndianType.Big); - byteBlock.WriteByte((byte)this.Data.Length); - byteBlock.Write(this.Data.Span); + WriterExtension.WriteValue(ref writer, (ushort)(this.Data.Length + 11), EndianType.Big); + WriterExtension.WriteValue(ref writer, this.SlaveId); + WriterExtension.WriteValue(ref writer, (byte)this.FunctionCode); + WriterExtension.WriteValue(ref writer, this.ReadStartAddress, EndianType.Big); + WriterExtension.WriteValue(ref writer, this.ReadQuantity, EndianType.Big); + WriterExtension.WriteValue(ref writer, this.StartingAddress, EndianType.Big); + WriterExtension.WriteValue(ref writer, this.Quantity, EndianType.Big); + WriterExtension.WriteValue(ref writer, (byte)this.Data.Length); + writer.Write(this.Data.Span); } else { diff --git a/src/TouchSocket.Modbus/Adapter/Tcp/ModbusTcpResponse.cs b/src/TouchSocket.Modbus/Adapter/Tcp/ModbusTcpResponse.cs index 59129b18f..3e16a3d42 100644 --- a/src/TouchSocket.Modbus/Adapter/Tcp/ModbusTcpResponse.cs +++ b/src/TouchSocket.Modbus/Adapter/Tcp/ModbusTcpResponse.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using TouchSocket.Core; - namespace TouchSocket.Modbus; internal sealed class ModbusTcpResponse : ModbusTcpBase, IFixedHeaderRequestInfo, IWaitHandle, IModbusResponse @@ -52,7 +49,7 @@ internal sealed class ModbusTcpResponse : ModbusTcpBase, IFixedHeaderRequestInfo { this.StartingAddress = TouchSocketBitConverter.BigEndian.To(body); this.Quantity = TouchSocketBitConverter.BigEndian.To(body.Slice(2)); - this.Data = new byte[0]; + this.Data = ReadOnlyMemory.Empty; return true; } return false; diff --git a/src/TouchSocket.Modbus/Adapter/Udp/ModbusUdpAdapter.cs b/src/TouchSocket.Modbus/Adapter/Udp/ModbusUdpAdapter.cs index 76c144b57..0b8ce17a3 100644 --- a/src/TouchSocket.Modbus/Adapter/Udp/ModbusUdpAdapter.cs +++ b/src/TouchSocket.Modbus/Adapter/Udp/ModbusUdpAdapter.cs @@ -11,9 +11,6 @@ //------------------------------------------------------------------------------ using System.Net; -using System.Threading.Tasks; -using TouchSocket.Core; -using TouchSocket.Sockets; namespace TouchSocket.Modbus; @@ -21,13 +18,14 @@ internal class ModbusUdpAdapter : UdpDataHandlingAdapter { public override bool CanSendRequestInfo => true; - protected override async Task PreviewReceived(EndPoint remoteEndPoint, ByteBlock byteBlock) + + protected override async Task PreviewReceivedAsync(EndPoint remoteEndPoint, ReadOnlyMemory memory) { var response = new ModbusTcpResponse(); - if (((IFixedHeaderRequestInfo)response).OnParsingHeader(byteBlock.ToArray(0, 8))) + if (((IFixedHeaderRequestInfo)response).OnParsingHeader(memory.Span.Slice(0, 8))) { - if (((IFixedHeaderRequestInfo)response).OnParsingBody(byteBlock.ToArray(8))) + if (((IFixedHeaderRequestInfo)response).OnParsingBody(memory.Span.Slice(8))) { await this.GoReceived(remoteEndPoint, default, response).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } diff --git a/src/TouchSocket.Modbus/Adapter/Udp/ModbusUdpRtuAdapter.cs b/src/TouchSocket.Modbus/Adapter/Udp/ModbusUdpRtuAdapter.cs index 58f73f462..afc0eef21 100644 --- a/src/TouchSocket.Modbus/Adapter/Udp/ModbusUdpRtuAdapter.cs +++ b/src/TouchSocket.Modbus/Adapter/Udp/ModbusUdpRtuAdapter.cs @@ -11,9 +11,6 @@ //------------------------------------------------------------------------------ using System.Net; -using System.Threading.Tasks; -using TouchSocket.Core; -using TouchSocket.Sockets; namespace TouchSocket.Modbus; @@ -21,36 +18,37 @@ internal class ModbusUdpRtuAdapter : UdpDataHandlingAdapter { public override bool CanSendRequestInfo => true; - protected override async Task PreviewReceived(EndPoint remoteEndPoint, ByteBlock byteBlock) + protected override async Task PreviewReceivedAsync(EndPoint remoteEndPoint, ReadOnlyMemory memory) { + var reader = new BytesReader(memory); var response = new ModbusRtuResponse(); - response.SlaveId = byteBlock.ReadByte(); - response.FunctionCode = (FunctionCode)byteBlock.ReadByte(); + response.SlaveId = ReaderExtension.ReadValue(ref reader); + response.FunctionCode = (FunctionCode)ReaderExtension.ReadValue(ref reader); var crcLen = 0; if ((byte)response.FunctionCode <= 4) { - var len = byteBlock.ReadByte(); - response.SetValue(byteBlock.ReadToSpan(len).ToArray()); - response.Crc = byteBlock.ReadUInt16(EndianType.Big); + var len = ReaderExtension.ReadValue(ref reader); + response.SetValue(ReaderExtension.ReadToSpan(ref reader, len).ToArray()); + response.Crc = ReaderExtension.ReadValue(ref reader, EndianType.Big); crcLen = 3 + len; } else if (response.FunctionCode == FunctionCode.WriteSingleCoil || response.FunctionCode == FunctionCode.WriteSingleRegister) { - response.StartingAddress = byteBlock.ReadUInt16(EndianType.Big); - response.SetValue(byteBlock.ReadToSpan(2).ToArray()); - response.Crc = byteBlock.ReadUInt16(EndianType.Big); + response.StartingAddress = ReaderExtension.ReadValue(ref reader, EndianType.Big); + response.SetValue(ReaderExtension.ReadToSpan(ref reader, 2).ToArray()); + response.Crc = ReaderExtension.ReadValue(ref reader, EndianType.Big); crcLen = 6; } else if (response.FunctionCode == FunctionCode.WriteMultipleCoils || response.FunctionCode == FunctionCode.WriteMultipleRegisters) { - response.StartingAddress = byteBlock.ReadUInt16(EndianType.Big); - response.Quantity = byteBlock.ReadUInt16(EndianType.Big); - response.Crc = byteBlock.ReadUInt16(EndianType.Big); + response.StartingAddress = ReaderExtension.ReadValue(ref reader, EndianType.Big); + response.Quantity = ReaderExtension.ReadValue(ref reader, EndianType.Big); + response.Crc = ReaderExtension.ReadValue(ref reader, EndianType.Big); crcLen = 6; } - var crc = TouchSocketModbusUtility.ToModbusCrcValue(byteBlock.Span.Slice(0, crcLen)); + var crc = TouchSocketModbusUtility.ToModbusCrcValue(memory.Span.Slice(0, crcLen)); if (crc == (response.Crc)) { await base.GoReceived(remoteEndPoint, null, response).ConfigureAwait(EasyTask.ContinueOnCapturedContext); diff --git a/src/TouchSocket.Modbus/Common/ModbusRequest.cs b/src/TouchSocket.Modbus/Common/ModbusRequest.cs index ad56529f6..cccbba657 100644 --- a/src/TouchSocket.Modbus/Common/ModbusRequest.cs +++ b/src/TouchSocket.Modbus/Common/ModbusRequest.cs @@ -10,15 +10,12 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using TouchSocket.Core; - namespace TouchSocket.Modbus; /// /// Modbus请求类 /// -public class ModbusRequest : IModbusRequest +public class ModbusRequest : IModbusReadWriteRequest { /// /// Modbus请求类 @@ -61,31 +58,31 @@ public class ModbusRequest : IModbusRequest this.FunctionCode = functionCode; } - + /// public ReadOnlyMemory Data { get; set; } - + /// public FunctionCode FunctionCode { get; set; } - + /// public ushort Quantity { get; set; } - + /// public ushort ReadQuantity { get; set; } - + /// public ushort ReadStartAddress { get; set; } - + /// public byte SlaveId { get; set; } - + /// public ushort StartingAddress { get; set; } @@ -135,10 +132,9 @@ public class ModbusRequest : IModbusRequest /// 设置的值为 bool数组,同时设置的数量(即数组长度)。 /// /// 要设置的 bool数组 - public void SetValue(bool[] values) + public void SetValue(ReadOnlySpan values) { - // 将 bool数组转换为字节,并赋值给Data属性 - this.Data = TouchSocketBitConverter.BigEndian.GetBytes(values); + this.Data = TouchSocketBitConverter.ConvertValues(values); // 设置Quantity属性为数组的长度,以记录 bool值的数量 this.Quantity = (ushort)values.Length; } diff --git a/src/TouchSocket.Modbus/Common/TouchSocketModbusUtility.cs b/src/TouchSocket.Modbus/Common/TouchSocketModbusUtility.cs index 128fb388d..603c855af 100644 --- a/src/TouchSocket.Modbus/Common/TouchSocketModbusUtility.cs +++ b/src/TouchSocket.Modbus/Common/TouchSocketModbusUtility.cs @@ -10,8 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using TouchSocket.Core; +using System.Buffers; using TouchSocket.Sockets; namespace TouchSocket.Modbus; @@ -83,7 +82,7 @@ public static class TouchSocketModbusUtility ushort wCrc = 0xFFFF; for (var i = 0; i < sourceSpan.Length; i++) { - wCrc ^= Convert.ToUInt16(sourceSpan[i]); + wCrc ^= sourceSpan[i]; for (var j = 0; j < 8; j++) { if ((wCrc & 0x0001) == 1) @@ -101,7 +100,15 @@ public static class TouchSocketModbusUtility crcSpan[1] = (byte)((wCrc & 0xFF00) >> 8);//高位在后 crcSpan[0] = (byte)(wCrc & 0x00FF); //低位在前 - return TouchSocketBitConverter.BigEndian.UnsafeTo(ref crcSpan[0]); + return TouchSocketBitConverter.BigEndian.To(crcSpan); + } + + public static ushort ToModbusCrcValue(ReadOnlySequence sourceSequence) + { + using (var buffer = new ContiguousMemoryBuffer(sourceSequence)) + { + return ToModbusCrcValue(buffer.Memory.Span); + } } /// @@ -109,7 +116,7 @@ public static class TouchSocketModbusUtility /// /// /// - public static byte[] BoolToBytes(bool value) + public static ReadOnlyMemory BoolToBytes(bool value) { return value ? new byte[] { 255, 0 } : new byte[] { 0, 0 }; } diff --git a/src/TouchSocket.Modbus/Components/ModbusRtuMaster.cs b/src/TouchSocket.Modbus/Components/ModbusRtuMaster.cs index c00b5907d..78232651b 100644 --- a/src/TouchSocket.Modbus/Components/ModbusRtuMaster.cs +++ b/src/TouchSocket.Modbus/Components/ModbusRtuMaster.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.SerialPorts; using TouchSocket.Sockets; @@ -23,7 +20,7 @@ namespace TouchSocket.Modbus; /// public class ModbusRtuMaster : SerialPortClientBase, IModbusRtuMaster { - private ModbusRequest m_modbusRequest; + private IModbusRequest m_modbusRequest; /// /// 基于串口的Modbus主站接口 @@ -34,15 +31,15 @@ public class ModbusRtuMaster : SerialPortClientBase, IModbusRtuMaster } /// - public Task ConnectAsync(int millisecondsTimeout, CancellationToken token) + public Task ConnectAsync(CancellationToken cancellationToken) { - return base.SerialPortConnectAsync(millisecondsTimeout, token); + return base.SerialPortConnectAsync(cancellationToken); } /// - public async Task SendModbusRequestAsync(ModbusRequest request, int millisecondsTimeout, CancellationToken token) + public async Task SendModbusRequestAsync(IModbusRequest request, CancellationToken cancellationToken) { - await this.m_semaphoreSlimForRequest.WaitTimeAsync(millisecondsTimeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.m_semaphoreSlimForRequest.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); try { @@ -52,18 +49,16 @@ public class ModbusRtuMaster : SerialPortClientBase, IModbusRtuMaster try { modbusRequest.Build(ref byteBlock); - await this.ProtectedDefaultSendAsync(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.ProtectedSendAsync(byteBlock.Memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } finally { byteBlock.Dispose(); } - this.m_waitDataAsync.SetCancellationToken(token); - var waitDataStatus = await this.m_waitDataAsync.WaitAsync(millisecondsTimeout).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - waitDataStatus.ThrowIfNotRunning(); + this.m_waitDataAsync = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var response = await this.m_waitDataAsync.Task.WithCancellation(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - var response = this.m_waitDataAsync.WaitResult; TouchSocketModbusThrowHelper.ThrowIfNotSuccess(response.ErrorCode); response.Request = request; @@ -86,7 +81,7 @@ public class ModbusRtuMaster : SerialPortClientBase, IModbusRtuMaster #region 字段 private readonly SemaphoreSlim m_semaphoreSlimForRequest = new SemaphoreSlim(1, 1); - private readonly WaitDataAsync m_waitDataAsync = new WaitDataAsync(); + private TaskCompletionSource m_waitDataAsync; #endregion 字段 @@ -98,7 +93,7 @@ public class ModbusRtuMaster : SerialPortClientBase, IModbusRtuMaster var result = this.SetRun(this.m_modbusRequest, response); if (result) { - this.m_waitDataAsync.Set(response); + this.m_waitDataAsync?.TrySetResult(response); } } await base.OnSerialReceived(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); diff --git a/src/TouchSocket.Modbus/Components/ModbusRtuOverTcpMaster.cs b/src/TouchSocket.Modbus/Components/ModbusRtuOverTcpMaster.cs index 1f6678b00..7e2d9d04b 100644 --- a/src/TouchSocket.Modbus/Components/ModbusRtuOverTcpMaster.cs +++ b/src/TouchSocket.Modbus/Components/ModbusRtuOverTcpMaster.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Sockets; namespace TouchSocket.Modbus; @@ -32,9 +29,9 @@ public class ModbusRtuOverTcpMaster : TcpClientBase, IModbusRtuOverTcpMaster /// - public async Task SendModbusRequestAsync(ModbusRequest request, int millisecondsTimeout, CancellationToken token) + public async Task SendModbusRequestAsync(IModbusRequest request, CancellationToken cancellationToken) { - await this.m_semaphoreSlimForRequest.WaitTimeAsync(millisecondsTimeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.m_semaphoreSlimForRequest.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); try { @@ -45,18 +42,15 @@ public class ModbusRtuOverTcpMaster : TcpClientBase, IModbusRtuOverTcpMaster { modbusRequest.Build(ref byteBlock); - await this.ProtectedSendAsync(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.ProtectedSendAsync(byteBlock.Memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } finally { byteBlock.Dispose(); } - this.m_waitDataAsync.SetCancellationToken(token); - var waitDataStatus = await this.m_waitDataAsync.WaitAsync(millisecondsTimeout).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - waitDataStatus.ThrowIfNotRunning(); - - var response = this.m_waitData.WaitResult; + this.m_waitDataAsync = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var response = await this.m_waitDataAsync.Task.WithCancellation(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); response.Request = request; TouchSocketModbusThrowHelper.ThrowIfNotSuccess(response.ErrorCode); return response; @@ -77,8 +71,7 @@ public class ModbusRtuOverTcpMaster : TcpClientBase, IModbusRtuOverTcpMaster #region 字段 private readonly SemaphoreSlim m_semaphoreSlimForRequest = new SemaphoreSlim(1, 1); - private readonly WaitData m_waitData = new WaitData(); - private readonly WaitDataAsync m_waitDataAsync = new WaitDataAsync(); + private TaskCompletionSource m_waitDataAsync; #endregion 字段 @@ -87,21 +80,15 @@ public class ModbusRtuOverTcpMaster : TcpClientBase, IModbusRtuOverTcpMaster { if (e.RequestInfo is ModbusRtuResponse response) { - this.SetRun(response); + this.m_waitDataAsync.TrySetResult(response); } return EasyTask.CompletedTask; } - private void SetRun(ModbusRtuResponse response) - { - this.m_waitData.Set(response); - this.m_waitDataAsync.Set(response); - } - /// - public Task ConnectAsync(int millisecondsTimeout, CancellationToken token) + public Task ConnectAsync(CancellationToken cancellationToken) { - return this.TcpConnectAsync(millisecondsTimeout, token); + return this.TcpConnectAsync(cancellationToken); } } \ No newline at end of file diff --git a/src/TouchSocket.Modbus/Components/ModbusRtuOverUdpMaster.cs b/src/TouchSocket.Modbus/Components/ModbusRtuOverUdpMaster.cs index 65f698163..6f6261a60 100644 --- a/src/TouchSocket.Modbus/Components/ModbusRtuOverUdpMaster.cs +++ b/src/TouchSocket.Modbus/Components/ModbusRtuOverUdpMaster.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Sockets; namespace TouchSocket.Modbus; @@ -33,8 +30,7 @@ public class ModbusRtuOverUdpMaster : UdpSessionBase, IModbusRtuOverUdpMaster #region 字段 private readonly SemaphoreSlim m_semaphoreSlimForRequest = new SemaphoreSlim(1, 1); - private readonly WaitData m_waitData = new WaitData(); - private readonly WaitDataAsync m_waitDataAsync = new WaitDataAsync(); + private TaskCompletionSource m_waitDataAsync; #endregion 字段 @@ -46,20 +42,19 @@ public class ModbusRtuOverUdpMaster : UdpSessionBase, IModbusRtuOverUdpMaster } /// - public async Task SendModbusRequestAsync(ModbusRequest request, int millisecondsTimeout, CancellationToken token) + public async Task SendModbusRequestAsync(IModbusRequest request, CancellationToken cancellationToken) { - await this.m_semaphoreSlimForRequest.WaitTimeAsync(millisecondsTimeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.m_semaphoreSlimForRequest.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); try { var modbusTcpRequest = new ModbusRtuRequest(request); - await this.ProtectedSendAsync(modbusTcpRequest).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.m_waitDataAsync.SetCancellationToken(token); - var waitDataStatus = await this.m_waitDataAsync.WaitAsync(millisecondsTimeout).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - waitDataStatus.ThrowIfNotRunning(); + await this.ProtectedSendAsync(modbusTcpRequest, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - var response = this.m_waitData.WaitResult; + this.m_waitDataAsync = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + var response = await this.m_waitDataAsync.Task.WithCancellation(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); response.Request = request; TouchSocketModbusThrowHelper.ThrowIfNotSuccess(response.ErrorCode); return response; @@ -75,14 +70,8 @@ public class ModbusRtuOverUdpMaster : UdpSessionBase, IModbusRtuOverUdpMaster { if (e.RequestInfo is ModbusRtuResponse response) { - this.SetRun(response); + this.m_waitDataAsync?.TrySetResult(response); } await base.OnUdpReceived(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - - private void SetRun(ModbusRtuResponse response) - { - this.m_waitData.Set(response); - this.m_waitDataAsync.Set(response); - } } \ No newline at end of file diff --git a/src/TouchSocket.Modbus/Components/ModbusTcpMaster.cs b/src/TouchSocket.Modbus/Components/ModbusTcpMaster.cs index 0d88103cc..5d6d8ec1f 100644 --- a/src/TouchSocket.Modbus/Components/ModbusTcpMaster.cs +++ b/src/TouchSocket.Modbus/Components/ModbusTcpMaster.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Sockets; namespace TouchSocket.Modbus; @@ -22,24 +19,29 @@ namespace TouchSocket.Modbus; /// public class ModbusTcpMaster : TcpClientBase, IModbusTcpMaster { - private readonly WaitHandlePool m_waitHandlePool; + private readonly SemaphoreSlim m_semaphoreForConnect = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim m_semaphoreSlim = new SemaphoreSlim(10); + private readonly WaitHandlePool m_waitHandlePool; + /// /// 基于Tcp协议的Modbus客户端 /// public ModbusTcpMaster() { this.Protocol = TouchSocketModbusUtility.ModbusTcp; - this.m_waitHandlePool = new WaitHandlePool() - { - MaxSign = ushort.MaxValue - }; + this.m_waitHandlePool = new WaitHandlePool(0, ushort.MaxValue); } /// - public async Task SendModbusRequestAsync(ModbusRequest request, int millisecondsTimeout, CancellationToken token) + public Task ConnectAsync(CancellationToken cancellationToken) { - await this.m_semaphoreSlim.WaitTimeAsync(millisecondsTimeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return this.TcpConnectAsync(cancellationToken); + } + + /// + public async Task SendModbusRequestAsync(IModbusRequest request, CancellationToken cancellationToken) + { + await this.m_semaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); var waitData = this.m_waitHandlePool.GetWaitDataAsync(out var sign); try { @@ -49,25 +51,24 @@ public class ModbusTcpMaster : TcpClientBase, IModbusTcpMaster try { modbusTcpRequest.Build(ref valueByteBlock); - await this.ProtectedSendAsync(valueByteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.ProtectedSendAsync(valueByteBlock.Memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } finally { valueByteBlock.Dispose(); } - waitData.SetCancellationToken(token); - var waitDataStatus = await waitData.WaitAsync(millisecondsTimeout).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var waitDataStatus = await waitData.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); waitDataStatus.ThrowIfNotRunning(); - var response = waitData.WaitResult; + var response = waitData.CompletedData; response.Request = request; TouchSocketModbusThrowHelper.ThrowIfNotSuccess(response.ErrorCode); return response; } finally { - this.m_waitHandlePool.Destroy(sign); + waitData.Dispose(); this.m_semaphoreSlim.Release(); } } @@ -84,14 +85,8 @@ public class ModbusTcpMaster : TcpClientBase, IModbusTcpMaster { if (e.RequestInfo is ModbusTcpResponse response) { - this.m_waitHandlePool.SetRun(response); + this.m_waitHandlePool.Set(response); } return EasyTask.CompletedTask; } - - /// - public Task ConnectAsync(int millisecondsTimeout, CancellationToken token) - { - return this.TcpConnectAsync(millisecondsTimeout, token); - } } \ No newline at end of file diff --git a/src/TouchSocket.Modbus/Components/ModbusUdpMaster.cs b/src/TouchSocket.Modbus/Components/ModbusUdpMaster.cs index 970cff90f..8473d01f6 100644 --- a/src/TouchSocket.Modbus/Components/ModbusUdpMaster.cs +++ b/src/TouchSocket.Modbus/Components/ModbusUdpMaster.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Sockets; namespace TouchSocket.Modbus; @@ -30,33 +27,30 @@ public class ModbusUdpMaster : UdpSessionBase, IModbusUdpMaster public ModbusUdpMaster() { this.Protocol = TouchSocketModbusUtility.ModbusUdp; - this.m_waitHandlePool = new WaitHandlePool() - { - MaxSign = ushort.MaxValue - }; + this.m_waitHandlePool = new WaitHandlePool(0, ushort.MaxValue); } /// - public async Task SendModbusRequestAsync(ModbusRequest request, int millisecondsTimeout, CancellationToken token) + public async Task SendModbusRequestAsync(IModbusRequest request, CancellationToken cancellationToken) { var waitData = this.m_waitHandlePool.GetWaitDataAsync(out var sign); try { var modbusTcpRequest = new ModbusTcpRequest((ushort)sign, request); - await this.ProtectedSendAsync(modbusTcpRequest).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - waitData.SetCancellationToken(token); - var waitDataStatus = await waitData.WaitAsync(millisecondsTimeout).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.ProtectedSendAsync(modbusTcpRequest, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + var waitDataStatus = await waitData.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); waitDataStatus.ThrowIfNotRunning(); - var response = waitData.WaitResult; + var response = waitData.CompletedData; response.Request = request; TouchSocketModbusThrowHelper.ThrowIfNotSuccess(response.ErrorCode); return response; } finally { - this.m_waitHandlePool.Destroy(sign); + waitData.Dispose(); } } @@ -72,7 +66,7 @@ public class ModbusUdpMaster : UdpSessionBase, IModbusUdpMaster { if (e.RequestInfo is ModbusTcpResponse response) { - this.m_waitHandlePool.SetRun(response); + this.m_waitHandlePool.Set(response); } await base.OnUdpReceived(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } diff --git a/src/TouchSocket.Modbus/Exceptions/ModbusResponseException.cs b/src/TouchSocket.Modbus/Exceptions/ModbusResponseException.cs index 648efd390..235ab857b 100644 --- a/src/TouchSocket.Modbus/Exceptions/ModbusResponseException.cs +++ b/src/TouchSocket.Modbus/Exceptions/ModbusResponseException.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using TouchSocket.Core; - namespace TouchSocket.Modbus; /// diff --git a/src/TouchSocket.Modbus/Extensions/ModbusIgnoreSlaveMasterExtension.cs b/src/TouchSocket.Modbus/Extensions/ModbusIgnoreSlaveMasterExtension.cs index 97025cb73..fafd4b92b 100644 --- a/src/TouchSocket.Modbus/Extensions/ModbusIgnoreSlaveMasterExtension.cs +++ b/src/TouchSocket.Modbus/Extensions/ModbusIgnoreSlaveMasterExtension.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading; -using System.Threading.Tasks; - namespace TouchSocket.Modbus; /// @@ -31,7 +28,8 @@ public static class ModbusIgnoreSlaveMasterExtension /// 写入位置(从0开始) /// 待写入数据 /// 响应值 - public static IModbusResponse ReadWriteMultipleRegisters(this IIgnoreSlaveIdModbusMaster master, ushort startingAddressForRead, ushort quantityForRead, ushort startingAddress, byte[] bytes) + [AsyncToSyncWarning] + public static IModbusResponse ReadWriteMultipleRegisters(this IIgnoreSlaveIdModbusMaster master, ushort startingAddressForRead, ushort quantityForRead, ushort startingAddress, ReadOnlyMemory bytes) { return master.ReadWriteMultipleRegisters(1, startingAddressForRead, quantityForRead, startingAddress, bytes, 1000, CancellationToken.None); } @@ -45,7 +43,7 @@ public static class ModbusIgnoreSlaveMasterExtension /// 写入位置(从0开始) /// 待写入数据 /// 响应值 - public static Task ReadWriteMultipleRegistersAsync(this IIgnoreSlaveIdModbusMaster master, ushort startingAddressForRead, ushort quantityForRead, ushort startingAddress, byte[] bytes) + public static Task ReadWriteMultipleRegistersAsync(this IIgnoreSlaveIdModbusMaster master, ushort startingAddressForRead, ushort quantityForRead, ushort startingAddress, ReadOnlyMemory bytes) { return master.ReadWriteMultipleRegistersAsync(1, startingAddressForRead, quantityForRead, startingAddress, bytes, 1000, CancellationToken.None); } @@ -61,9 +59,10 @@ public static class ModbusIgnoreSlaveMasterExtension /// 起始位置(从0开始) /// 读取数量 /// 读取到的值集合 - public static bool[] ReadCoils(this IIgnoreSlaveIdModbusMaster master, ushort startingAddress, ushort quantity) + [AsyncToSyncWarning] + public static ReadOnlyMemory ReadCoils(this IIgnoreSlaveIdModbusMaster master, ushort startingAddress, ushort quantity) { - return master.ReadCoils(1, startingAddress, quantity, 1000, CancellationToken.None); + return master.ReadCoilsAsync(1, startingAddress, quantity, 1000, CancellationToken.None).GetFalseAwaitResult(); } /// @@ -73,9 +72,10 @@ public static class ModbusIgnoreSlaveMasterExtension /// 起始位置(从0开始) /// 读取数量 /// 读取到的值集合 - public static bool[] ReadDiscreteInputs(this IIgnoreSlaveIdModbusMaster master, ushort startingAddress, ushort quantity) + [AsyncToSyncWarning] + public static ReadOnlyMemory ReadDiscreteInputs(this IIgnoreSlaveIdModbusMaster master, ushort startingAddress, ushort quantity) { - return master.ReadDiscreteInputs(1, startingAddress, quantity, 1000, CancellationToken.None); + return master.ReadDiscreteInputsAsync(1, startingAddress, quantity, 1000, CancellationToken.None).GetFalseAwaitResult(); } /// @@ -85,9 +85,10 @@ public static class ModbusIgnoreSlaveMasterExtension /// 起始位置(从0开始) /// 读取数量 /// 响应值 + [AsyncToSyncWarning] public static IModbusResponse ReadHoldingRegisters(this IIgnoreSlaveIdModbusMaster master, ushort startingAddress, ushort quantity) { - return master.ReadHoldingRegisters(1, startingAddress, quantity, 1000, CancellationToken.None); + return master.ReadHoldingRegistersAsync(1, startingAddress, quantity, 1000, CancellationToken.None).GetFalseAwaitResult(); } /// @@ -97,9 +98,10 @@ public static class ModbusIgnoreSlaveMasterExtension /// 起始位置(从0开始) /// 读取数量 /// 响应值 + [AsyncToSyncWarning] public static IModbusResponse ReadInputRegisters(this IIgnoreSlaveIdModbusMaster master, ushort startingAddress, ushort quantity) { - return master.ReadInputRegisters(1, startingAddress, quantity, 1000, CancellationToken.None); + return master.ReadInputRegistersAsync(1, startingAddress, quantity, 1000, CancellationToken.None).GetFalseAwaitResult(); } #endregion Read 默认超时 @@ -113,7 +115,7 @@ public static class ModbusIgnoreSlaveMasterExtension /// 起始位置(从0开始) /// 读取数量 /// 读取到的值集合 - public static Task ReadCoilsAsync(this IIgnoreSlaveIdModbusMaster master, ushort startingAddress, ushort quantity) + public static Task> ReadCoilsAsync(this IIgnoreSlaveIdModbusMaster master, ushort startingAddress, ushort quantity) { return master.ReadCoilsAsync(1, startingAddress, quantity, 1000, CancellationToken.None); } @@ -125,7 +127,7 @@ public static class ModbusIgnoreSlaveMasterExtension /// 起始位置(从0开始) /// 读取数量 /// 读取到的值集合 - public static Task ReadDiscreteInputsAsync(this IIgnoreSlaveIdModbusMaster master, ushort startingAddress, ushort quantity) + public static Task> ReadDiscreteInputsAsync(this IIgnoreSlaveIdModbusMaster master, ushort startingAddress, ushort quantity) { return master.ReadDiscreteInputsAsync(1, startingAddress, quantity, 1000, CancellationToken.None); } @@ -164,9 +166,10 @@ public static class ModbusIgnoreSlaveMasterExtension /// 通讯客户端 /// 起始位置(从0开始) /// 待写入集合 - public static IModbusResponse WriteMultipleCoils(this IIgnoreSlaveIdModbusMaster master, ushort startingAddress, bool[] values) + [AsyncToSyncWarning] + public static IModbusResponse WriteMultipleCoils(this IIgnoreSlaveIdModbusMaster master, ushort startingAddress, ReadOnlyMemory values) { - return master.WriteMultipleCoils(1, startingAddress, values, 1000, CancellationToken.None); + return master.WriteMultipleCoilsAsync(1, startingAddress, values, 1000, CancellationToken.None).GetFalseAwaitResult(); } /// @@ -175,9 +178,10 @@ public static class ModbusIgnoreSlaveMasterExtension /// 通讯客户端 /// 起始位置(从0开始) /// 待写入集合 - public static IModbusResponse WriteMultipleRegisters(this IIgnoreSlaveIdModbusMaster master, ushort startingAddress, byte[] bytes) + [AsyncToSyncWarning] + public static IModbusResponse WriteMultipleRegisters(this IIgnoreSlaveIdModbusMaster master, ushort startingAddress, ReadOnlyMemory bytes) { - return master.WriteMultipleRegisters(1, startingAddress, bytes, 1000, CancellationToken.None); + return master.WriteMultipleRegistersAsync(1, startingAddress, bytes, 1000, CancellationToken.None).GetFalseAwaitResult(); } /// @@ -186,9 +190,10 @@ public static class ModbusIgnoreSlaveMasterExtension /// 通讯客户端 /// 起始位置(从0开始) /// 待写入数值 + [AsyncToSyncWarning] public static IModbusResponse WriteSingleCoil(this IIgnoreSlaveIdModbusMaster master, ushort startingAddress, bool value) { - return master.WriteSingleCoil(1, startingAddress, value, 1000, CancellationToken.None); + return master.WriteSingleCoilAsync(1, startingAddress, value, 1000, CancellationToken.None).GetFalseAwaitResult(); } /// @@ -197,9 +202,10 @@ public static class ModbusIgnoreSlaveMasterExtension /// 通讯客户端 /// 起始位置(从0开始) /// 待写入数值 + [AsyncToSyncWarning] public static IModbusResponse WriteSingleRegister(this IIgnoreSlaveIdModbusMaster master, ushort startingAddress, short value) { - return master.WriteSingleRegister(1, startingAddress, value, 1000, CancellationToken.None); + return master.WriteSingleRegisterAsync(1, startingAddress, value, 1000, CancellationToken.None).GetFalseAwaitResult(); } #endregion Write 默认超时 @@ -212,7 +218,7 @@ public static class ModbusIgnoreSlaveMasterExtension /// 通讯客户端 /// 起始位置(从0开始) /// 待写入集合 - public static Task WriteMultipleCoilsAsync(this IIgnoreSlaveIdModbusMaster master, ushort startingAddress, bool[] values) + public static Task WriteMultipleCoilsAsync(this IIgnoreSlaveIdModbusMaster master, ushort startingAddress, ReadOnlyMemory values) { return master.WriteMultipleCoilsAsync(1, startingAddress, values, 1000, CancellationToken.None); } @@ -223,7 +229,7 @@ public static class ModbusIgnoreSlaveMasterExtension /// 通讯客户端 /// 起始位置(从0开始) /// 待写入集合 - public static Task WriteMultipleRegistersAsync(this IIgnoreSlaveIdModbusMaster master, ushort startingAddress, byte[] bytes) + public static Task WriteMultipleRegistersAsync(this IIgnoreSlaveIdModbusMaster master, ushort startingAddress, ReadOnlyMemory bytes) { return master.WriteMultipleRegistersAsync(1, startingAddress, bytes, 1000, CancellationToken.None); } diff --git a/src/TouchSocket.Modbus/Extensions/ModbusMasterExtension.cs b/src/TouchSocket.Modbus/Extensions/ModbusMasterExtension.cs index bebc61d33..1b6707888 100644 --- a/src/TouchSocket.Modbus/Extensions/ModbusMasterExtension.cs +++ b/src/TouchSocket.Modbus/Extensions/ModbusMasterExtension.cs @@ -10,11 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Modbus; /// @@ -30,13 +25,14 @@ public static class ModbusMasterExtension /// Modbus主控接口。 /// 要发送的Modbus请求。 /// 操作超时的时间,以毫秒为单位。 - /// 用于取消操作的取消令牌。 + /// 用于取消操作的取消令牌。 /// 返回从Modbus从机设备接收到的响应。 - public static IModbusResponse SendModbusRequest(this IModbusMaster master, ModbusRequest request, int millisecondsTimeout, CancellationToken token) + [AsyncToSyncWarning] + public static IModbusResponse SendModbusRequest(this IModbusMaster master, ModbusRequest request, int millisecondsTimeout, CancellationToken cancellationToken) { // 使用异步方法 SendModbusRequestAsync 发送请求,并直接获取结果,而不等待异步操作完成。 // 这样做是因为我们假设调用者已经处理了异步相关的逻辑,这里只是一个直接的同步包装。 - return master.SendModbusRequestAsync(request, millisecondsTimeout, token).GetFalseAwaitResult(); + return master.SendModbusRequestAsync(request, millisecondsTimeout, cancellationToken).GetFalseAwaitResult(); } #endregion @@ -52,7 +48,8 @@ public static class ModbusMasterExtension /// 写入位置(从0开始) /// 待写入数据 /// 响应值 - public static IModbusResponse ReadWriteMultipleRegisters(this IModbusMaster master, byte slaveId, ushort startingAddressForRead, ushort quantityForRead, ushort startingAddress, byte[] bytes) + [AsyncToSyncWarning] + public static IModbusResponse ReadWriteMultipleRegisters(this IModbusMaster master, byte slaveId, ushort startingAddressForRead, ushort quantityForRead, ushort startingAddress, ReadOnlyMemory bytes) { return master.ReadWriteMultipleRegisters(slaveId, startingAddressForRead, quantityForRead, startingAddress, bytes, 1000, CancellationToken.None); } @@ -67,7 +64,7 @@ public static class ModbusMasterExtension /// 写入位置(从0开始) /// 待写入数据 /// 响应值 - public static Task ReadWriteMultipleRegistersAsync(this IModbusMaster master, byte slaveId, ushort startingAddressForRead, ushort quantityForRead, ushort startingAddress, byte[] bytes) + public static Task ReadWriteMultipleRegistersAsync(this IModbusMaster master, byte slaveId, ushort startingAddressForRead, ushort quantityForRead, ushort startingAddress, ReadOnlyMemory bytes) { return master.ReadWriteMultipleRegistersAsync(slaveId, startingAddressForRead, quantityForRead, startingAddress, bytes, 1000, CancellationToken.None); } @@ -84,9 +81,10 @@ public static class ModbusMasterExtension /// 起始位置(从0开始) /// 读取数量 /// 读取到的值集合 - public static bool[] ReadCoils(this IModbusMaster master, byte slaveId, ushort startingAddress, ushort quantity) + [AsyncToSyncWarning] + public static ReadOnlyMemory ReadCoils(this IModbusMaster master, byte slaveId, ushort startingAddress, ushort quantity) { - return master.ReadCoils(slaveId, startingAddress, quantity, 1000, CancellationToken.None); + return master.ReadCoilsAsync(slaveId, startingAddress, quantity, 1000, CancellationToken.None).GetFalseAwaitResult(); } /// @@ -97,9 +95,10 @@ public static class ModbusMasterExtension /// 起始位置(从0开始) /// 读取数量 /// 读取到的值集合 - public static bool[] ReadDiscreteInputs(this IModbusMaster master, byte slaveId, ushort startingAddress, ushort quantity) + [AsyncToSyncWarning] + public static ReadOnlyMemory ReadDiscreteInputs(this IModbusMaster master, byte slaveId, ushort startingAddress, ushort quantity) { - return master.ReadDiscreteInputs(slaveId, startingAddress, quantity, 1000, CancellationToken.None); + return master.ReadDiscreteInputsAsync(slaveId, startingAddress, quantity, 1000, CancellationToken.None).GetFalseAwaitResult(); } /// @@ -110,9 +109,10 @@ public static class ModbusMasterExtension /// 起始位置(从0开始) /// 读取数量 /// 响应值 + [AsyncToSyncWarning] public static IModbusResponse ReadHoldingRegisters(this IModbusMaster master, byte slaveId, ushort startingAddress, ushort quantity) { - return master.ReadHoldingRegisters(slaveId, startingAddress, quantity, 1000, CancellationToken.None); + return master.ReadHoldingRegistersAsync(slaveId, startingAddress, quantity, 1000, CancellationToken.None).GetFalseAwaitResult(); } /// @@ -123,9 +123,10 @@ public static class ModbusMasterExtension /// 起始位置(从0开始) /// 读取数量 /// 响应值 + [AsyncToSyncWarning] public static IModbusResponse ReadInputRegisters(this IModbusMaster master, byte slaveId, ushort startingAddress, ushort quantity) { - return master.ReadInputRegisters(slaveId, startingAddress, quantity, 1000, CancellationToken.None); + return master.ReadInputRegistersAsync(slaveId, startingAddress, quantity, 1000, CancellationToken.None).GetFalseAwaitResult(); } #endregion Read 默认超时 @@ -140,7 +141,7 @@ public static class ModbusMasterExtension /// 起始位置(从0开始) /// 读取数量 /// 读取到的值集合 - public static Task ReadCoilsAsync(this IModbusMaster master, byte slaveId, ushort startingAddress, ushort quantity) + public static Task> ReadCoilsAsync(this IModbusMaster master, byte slaveId, ushort startingAddress, ushort quantity) { return master.ReadCoilsAsync(slaveId, startingAddress, quantity, 1000, CancellationToken.None); } @@ -153,7 +154,7 @@ public static class ModbusMasterExtension /// 起始位置(从0开始) /// 读取数量 /// 读取到的值集合 - public static Task ReadDiscreteInputsAsync(this IModbusMaster master, byte slaveId, ushort startingAddress, ushort quantity) + public static Task> ReadDiscreteInputsAsync(this IModbusMaster master, byte slaveId, ushort startingAddress, ushort quantity) { return master.ReadDiscreteInputsAsync(slaveId, startingAddress, quantity, 1000, CancellationToken.None); } @@ -195,9 +196,10 @@ public static class ModbusMasterExtension /// 站点号 /// 起始位置(从0开始) /// 待写入集合 - public static IModbusResponse WriteMultipleCoils(this IModbusMaster master, byte slaveId, ushort startingAddress, bool[] values) + [AsyncToSyncWarning] + public static IModbusResponse WriteMultipleCoils(this IModbusMaster master, byte slaveId, ushort startingAddress, ReadOnlyMemory values) { - return master.WriteMultipleCoils(slaveId, startingAddress, values, 1000, CancellationToken.None); + return master.WriteMultipleCoilsAsync(slaveId, startingAddress, values, 1000, CancellationToken.None).GetFalseAwaitResult(); } /// @@ -207,9 +209,10 @@ public static class ModbusMasterExtension /// 站点号 /// 起始位置(从0开始) /// 待写入集合 - public static IModbusResponse WriteMultipleRegisters(this IModbusMaster master, byte slaveId, ushort startingAddress, byte[] bytes) + [AsyncToSyncWarning] + public static IModbusResponse WriteMultipleRegisters(this IModbusMaster master, byte slaveId, ushort startingAddress, ReadOnlyMemory bytes) { - return master.WriteMultipleRegisters(slaveId, startingAddress, bytes, 1000, CancellationToken.None); + return master.WriteMultipleRegistersAsync(slaveId, startingAddress, bytes, 1000, CancellationToken.None).GetFalseAwaitResult(); } /// @@ -219,9 +222,10 @@ public static class ModbusMasterExtension /// 站点号 /// 起始位置(从0开始) /// 待写入数值 + [AsyncToSyncWarning] public static IModbusResponse WriteSingleCoil(this IModbusMaster master, byte slaveId, ushort startingAddress, bool value) { - return master.WriteSingleCoil(slaveId, startingAddress, value, 1000, CancellationToken.None); + return master.WriteSingleCoilAsync(slaveId, startingAddress, value, 1000, CancellationToken.None).GetFalseAwaitResult(); } /// @@ -231,9 +235,10 @@ public static class ModbusMasterExtension /// 站点号 /// 起始位置(从0开始) /// 待写入数值 + [AsyncToSyncWarning] public static IModbusResponse WriteSingleRegister(this IModbusMaster master, byte slaveId, ushort startingAddress, short value) { - return master.WriteSingleRegister(slaveId, startingAddress, value, 1000, CancellationToken.None); + return master.WriteSingleRegisterAsync(slaveId, startingAddress, value, 1000, CancellationToken.None).GetFalseAwaitResult(); } #endregion Write 默认超时 @@ -247,7 +252,7 @@ public static class ModbusMasterExtension /// 站点号 /// 起始位置(从0开始) /// 待写入集合 - public static Task WriteMultipleCoilsAsync(this IModbusMaster master, byte slaveId, ushort startingAddress, bool[] values) + public static Task WriteMultipleCoilsAsync(this IModbusMaster master, byte slaveId, ushort startingAddress, ReadOnlyMemory values) { return master.WriteMultipleCoilsAsync(slaveId, startingAddress, values, 1000, CancellationToken.None); } @@ -259,7 +264,7 @@ public static class ModbusMasterExtension /// 站点号 /// 起始位置(从0开始) /// 待写入集合 - public static Task WriteMultipleRegistersAsync(this IModbusMaster master, byte slaveId, ushort startingAddress, byte[] bytes) + public static Task WriteMultipleRegistersAsync(this IModbusMaster master, byte slaveId, ushort startingAddress, ReadOnlyMemory bytes) { return master.WriteMultipleRegistersAsync(slaveId, startingAddress, bytes, 1000, CancellationToken.None); } @@ -300,14 +305,15 @@ public static class ModbusMasterExtension /// 起始位置(从0开始) /// 读取数量 /// 超时时间,单位(ms) - /// 可取消令箭 + /// 可取消令箭 /// 读取到的值集合 - public static bool[] ReadCoils(this IModbusMaster master, byte slaveId, ushort startingAddress, ushort quantity, int millisecondsTimeout, CancellationToken token) + [AsyncToSyncWarning] + public static ReadOnlyMemory ReadCoils(this IModbusMaster master, byte slaveId, ushort startingAddress, ushort quantity, int millisecondsTimeout, CancellationToken cancellationToken) { var request = new ModbusRequest(slaveId, FunctionCode.ReadCoils, startingAddress, quantity); - var response = master.SendModbusRequest(request, millisecondsTimeout, token); - return response.CreateReader().ToBoolensFromBit().Take(quantity).ToArray(); + var response = master.SendModbusRequestAsync(request, millisecondsTimeout, cancellationToken).GetFalseAwaitResult(); + return TouchSocketBitConverter.Default.ToValues(response.Data.Span).Slice(0, quantity); } /// @@ -318,14 +324,15 @@ public static class ModbusMasterExtension /// 起始位置(从0开始) /// 读取数量 /// 超时时间,单位(ms) - /// 可取消令箭 + /// 可取消令箭 /// 读取到的值集合 - public static bool[] ReadDiscreteInputs(this IModbusMaster master, byte slaveId, ushort startingAddress, ushort quantity, int millisecondsTimeout, CancellationToken token) + [AsyncToSyncWarning] + public static ReadOnlyMemory ReadDiscreteInputs(this IModbusMaster master, byte slaveId, ushort startingAddress, ushort quantity, int millisecondsTimeout, CancellationToken cancellationToken) { var request = new ModbusRequest(slaveId, FunctionCode.ReadDiscreteInputs, startingAddress, quantity); - var response = master.SendModbusRequest(request, millisecondsTimeout, token); - return response.CreateReader().ToBoolensFromBit().Take(quantity).ToArray(); + var response = master.SendModbusRequestAsync(request, millisecondsTimeout, cancellationToken).GetFalseAwaitResult(); + return TouchSocketBitConverter.Default.ToValues(response.Data.Span).Slice(0, quantity); } /// @@ -336,13 +343,14 @@ public static class ModbusMasterExtension /// 起始位置(从0开始) /// 读取数量 /// 超时时间,单位(ms) - /// 可取消令箭 + /// 可取消令箭 /// 响应值 - public static IModbusResponse ReadHoldingRegisters(this IModbusMaster master, byte slaveId, ushort startingAddress, ushort quantity, int millisecondsTimeout, CancellationToken token) + [AsyncToSyncWarning] + public static IModbusResponse ReadHoldingRegisters(this IModbusMaster master, byte slaveId, ushort startingAddress, ushort quantity, int millisecondsTimeout, CancellationToken cancellationToken) { var request = new ModbusRequest(slaveId, FunctionCode.ReadHoldingRegisters, startingAddress, quantity); - var response = master.SendModbusRequest(request, millisecondsTimeout, token); + var response = master.SendModbusRequestAsync(request, millisecondsTimeout, cancellationToken).GetFalseAwaitResult(); return response; } @@ -354,13 +362,14 @@ public static class ModbusMasterExtension /// 起始位置(从0开始) /// 读取数量 /// 超时时间,单位(ms) - /// 可取消令箭 + /// 可取消令箭 /// 响应值 - public static IModbusResponse ReadInputRegisters(this IModbusMaster master, byte slaveId, ushort startingAddress, ushort quantity, int millisecondsTimeout, CancellationToken token) + [AsyncToSyncWarning] + public static IModbusResponse ReadInputRegisters(this IModbusMaster master, byte slaveId, ushort startingAddress, ushort quantity, int millisecondsTimeout, CancellationToken cancellationToken) { var request = new ModbusRequest(slaveId, FunctionCode.ReadInputRegisters, startingAddress, quantity); - var response = master.SendModbusRequest(request, millisecondsTimeout, token); + var response = master.SendModbusRequestAsync(request, millisecondsTimeout, cancellationToken).GetFalseAwaitResult(); return response; } @@ -376,14 +385,15 @@ public static class ModbusMasterExtension /// 起始位置(从0开始) /// 读取数量 /// 超时时间,单位(ms) - /// 可取消令箭 + /// 可取消令箭 /// 读取到的值集合 - public static async Task ReadCoilsAsync(this IModbusMaster master, byte slaveId, ushort startingAddress, ushort quantity, int millisecondsTimeout, CancellationToken token) + public static async Task> ReadCoilsAsync(this IModbusMaster master, byte slaveId, ushort startingAddress, ushort quantity, int millisecondsTimeout, CancellationToken cancellationToken) { var request = new ModbusRequest(slaveId, FunctionCode.ReadCoils, startingAddress, quantity); - var response = await master.SendModbusRequestAsync(request, millisecondsTimeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - return response.CreateReader().ToBoolensFromBit().Take(quantity).ToArray(); + var response = await master.SendModbusRequestAsync(request, millisecondsTimeout, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + return TouchSocketBitConverter.Default.ToValues(response.Data.Span).Slice(0, quantity); } /// @@ -394,14 +404,15 @@ public static class ModbusMasterExtension /// 起始位置(从0开始) /// 读取数量 /// 超时时间,单位(ms) - /// 可取消令箭 + /// 可取消令箭 /// 读取到的值集合 - public static async Task ReadDiscreteInputsAsync(this IModbusMaster master, byte slaveId, ushort startingAddress, ushort quantity, int millisecondsTimeout, CancellationToken token) + public static async Task> ReadDiscreteInputsAsync(this IModbusMaster master, byte slaveId, ushort startingAddress, ushort quantity, int millisecondsTimeout, CancellationToken cancellationToken) { var request = new ModbusRequest(slaveId, FunctionCode.ReadDiscreteInputs, startingAddress, quantity); - var response = await master.SendModbusRequestAsync(request, millisecondsTimeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - return response.CreateReader().ToBoolensFromBit().Take(quantity).ToArray(); + var response = await master.SendModbusRequestAsync(request, millisecondsTimeout, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + return TouchSocketBitConverter.Default.ToValues(response.Data.Span).Slice(0, quantity); } /// @@ -412,13 +423,13 @@ public static class ModbusMasterExtension /// 起始位置(从0开始) /// 读取数量 /// 超时时间,单位(ms) - /// 可取消令箭 + /// 可取消令箭 /// 响应值 - public static async Task ReadHoldingRegistersAsync(this IModbusMaster master, byte slaveId, ushort startingAddress, ushort quantity, int millisecondsTimeout, CancellationToken token) + public static async Task ReadHoldingRegistersAsync(this IModbusMaster master, byte slaveId, ushort startingAddress, ushort quantity, int millisecondsTimeout, CancellationToken cancellationToken) { var request = new ModbusRequest(slaveId, FunctionCode.ReadHoldingRegisters, startingAddress, quantity); - var response = await master.SendModbusRequestAsync(request, millisecondsTimeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var response = await master.SendModbusRequestAsync(request, millisecondsTimeout, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return response; } @@ -430,13 +441,13 @@ public static class ModbusMasterExtension /// 起始位置(从0开始) /// 读取数量 /// 超时时间,单位(ms) - /// 可取消令箭 + /// 可取消令箭 /// 响应值 - public static async Task ReadInputRegistersAsync(this IModbusMaster master, byte slaveId, ushort startingAddress, ushort quantity, int millisecondsTimeout, CancellationToken token) + public static async Task ReadInputRegistersAsync(this IModbusMaster master, byte slaveId, ushort startingAddress, ushort quantity, int millisecondsTimeout, CancellationToken cancellationToken) { var request = new ModbusRequest(slaveId, FunctionCode.ReadInputRegisters, startingAddress, quantity); - var response = await master.SendModbusRequestAsync(request, millisecondsTimeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var response = await master.SendModbusRequestAsync(request, millisecondsTimeout, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return response; } @@ -452,14 +463,15 @@ public static class ModbusMasterExtension /// 起始位置(从0开始) /// 待写入集合 /// 超时时间,单位(ms) - /// 可取消令箭 - public static IModbusResponse WriteMultipleCoils(this IModbusMaster master, byte slaveId, ushort startingAddress, bool[] values, int millisecondsTimeout, CancellationToken token) + /// 可取消令箭 + [AsyncToSyncWarning] + public static IModbusResponse WriteMultipleCoils(this IModbusMaster master, byte slaveId, ushort startingAddress, ReadOnlyMemory values, int millisecondsTimeout, CancellationToken cancellationToken) { var request = new ModbusRequest(slaveId, FunctionCode.WriteMultipleCoils); request.StartingAddress = startingAddress; - request.SetValue(values); + request.SetValue(values.Span); - return master.SendModbusRequest(request, millisecondsTimeout, token); + return master.SendModbusRequestAsync(request, millisecondsTimeout, cancellationToken).GetFalseAwaitResult(); } /// @@ -470,14 +482,15 @@ public static class ModbusMasterExtension /// 起始位置(从0开始) /// 待写入集合 /// 超时时间,单位(ms) - /// 可取消令箭 - public static IModbusResponse WriteMultipleRegisters(this IModbusMaster master, byte slaveId, ushort startingAddress, byte[] bytes, int millisecondsTimeout, CancellationToken token) + /// 可取消令箭 + [AsyncToSyncWarning] + public static IModbusResponse WriteMultipleRegisters(this IModbusMaster master, byte slaveId, ushort startingAddress, ReadOnlyMemory bytes, int millisecondsTimeout, CancellationToken cancellationToken) { var request = new ModbusRequest(slaveId, FunctionCode.WriteMultipleRegisters); request.StartingAddress = startingAddress; request.SetValue(bytes); - return master.SendModbusRequest(request, millisecondsTimeout, token); + return master.SendModbusRequestAsync(request, millisecondsTimeout, cancellationToken).GetFalseAwaitResult(); } /// @@ -488,14 +501,15 @@ public static class ModbusMasterExtension /// 起始位置(从0开始) /// 待写入数值 /// 超时时间,单位(ms) - /// 可取消令箭 - public static IModbusResponse WriteSingleCoil(this IModbusMaster master, byte slaveId, ushort startingAddress, bool value, int millisecondsTimeout, CancellationToken token) + /// 可取消令箭 + [AsyncToSyncWarning] + public static IModbusResponse WriteSingleCoil(this IModbusMaster master, byte slaveId, ushort startingAddress, bool value, int millisecondsTimeout, CancellationToken cancellationToken) { var request = new ModbusRequest(slaveId, FunctionCode.WriteSingleCoil); request.StartingAddress = startingAddress; request.SetValue(value); - return master.SendModbusRequest(request, millisecondsTimeout, token); + return master.SendModbusRequestAsync(request, millisecondsTimeout, cancellationToken).GetFalseAwaitResult(); } /// @@ -506,14 +520,15 @@ public static class ModbusMasterExtension /// 起始位置(从0开始) /// 待写入数值 /// 超时时间,单位(ms) - /// 可取消令箭 - public static IModbusResponse WriteSingleRegister(this IModbusMaster master, byte slaveId, ushort startingAddress, short value, int millisecondsTimeout, CancellationToken token) + /// 可取消令箭 + [AsyncToSyncWarning] + public static IModbusResponse WriteSingleRegister(this IModbusMaster master, byte slaveId, ushort startingAddress, short value, int millisecondsTimeout, CancellationToken cancellationToken) { var request = new ModbusRequest(slaveId, FunctionCode.WriteSingleRegister); request.StartingAddress = startingAddress; request.SetValue(value); - return master.SendModbusRequest(request, millisecondsTimeout, token); + return master.SendModbusRequestAsync(request, millisecondsTimeout, cancellationToken).GetFalseAwaitResult(); } /// @@ -524,14 +539,15 @@ public static class ModbusMasterExtension /// 起始位置(从0开始) /// 待写入数值 /// 超时时间,单位(ms) - /// 可取消令箭 - public static IModbusResponse WriteSingleRegister(this IModbusMaster master, byte slaveId, ushort startingAddress, ushort value, int millisecondsTimeout, CancellationToken token) + /// 可取消令箭 + [AsyncToSyncWarning] + public static IModbusResponse WriteSingleRegister(this IModbusMaster master, byte slaveId, ushort startingAddress, ushort value, int millisecondsTimeout, CancellationToken cancellationToken) { var request = new ModbusRequest(slaveId, FunctionCode.WriteSingleRegister); request.StartingAddress = startingAddress; request.SetValue(value); - return master.SendModbusRequest(request, millisecondsTimeout, token); + return master.SendModbusRequestAsync(request, millisecondsTimeout, cancellationToken).GetFalseAwaitResult(); } #endregion Write @@ -546,14 +562,14 @@ public static class ModbusMasterExtension /// 起始位置(从0开始) /// 待写入集合 /// 超时时间,单位(ms) - /// 可取消令箭 - public static async Task WriteMultipleCoilsAsync(this IModbusMaster master, byte slaveId, ushort startingAddress, bool[] values, int millisecondsTimeout, CancellationToken token) + /// 可取消令箭 + public static async Task WriteMultipleCoilsAsync(this IModbusMaster master, byte slaveId, ushort startingAddress, ReadOnlyMemory values, int millisecondsTimeout, CancellationToken cancellationToken) { var request = new ModbusRequest(slaveId, FunctionCode.WriteMultipleCoils); request.StartingAddress = startingAddress; - request.SetValue(values); + request.SetValue(values.Span); - return await master.SendModbusRequestAsync(request, millisecondsTimeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return await master.SendModbusRequestAsync(request, millisecondsTimeout, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// @@ -564,14 +580,14 @@ public static class ModbusMasterExtension /// 起始位置(从0开始) /// 待写入集合 /// 超时时间,单位(ms) - /// 可取消令箭 - public static async Task WriteMultipleRegistersAsync(this IModbusMaster master, byte slaveId, ushort startingAddress, byte[] bytes, int millisecondsTimeout, CancellationToken token) + /// 可取消令箭 + public static async Task WriteMultipleRegistersAsync(this IModbusMaster master, byte slaveId, ushort startingAddress, ReadOnlyMemory bytes, int millisecondsTimeout, CancellationToken cancellationToken) { var request = new ModbusRequest(slaveId, FunctionCode.WriteMultipleRegisters); request.StartingAddress = startingAddress; request.SetValue(bytes); - return await master.SendModbusRequestAsync(request, millisecondsTimeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return await master.SendModbusRequestAsync(request, millisecondsTimeout, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// @@ -582,14 +598,14 @@ public static class ModbusMasterExtension /// 起始位置(从0开始) /// 待写入数值 /// 超时时间,单位(ms) - /// 可取消令箭 - public static async Task WriteSingleCoilAsync(this IModbusMaster master, byte slaveId, ushort startingAddress, bool value, int millisecondsTimeout, CancellationToken token) + /// 可取消令箭 + public static async Task WriteSingleCoilAsync(this IModbusMaster master, byte slaveId, ushort startingAddress, bool value, int millisecondsTimeout, CancellationToken cancellationToken) { var request = new ModbusRequest(slaveId, FunctionCode.WriteSingleCoil); request.StartingAddress = startingAddress; request.SetValue(value); - return await master.SendModbusRequestAsync(request, millisecondsTimeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return await master.SendModbusRequestAsync(request, millisecondsTimeout, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// @@ -600,14 +616,14 @@ public static class ModbusMasterExtension /// 起始位置(从0开始) /// 待写入数值 /// 超时时间,单位(ms) - /// 可取消令箭 - public static async Task WriteSingleRegisterAsync(this IModbusMaster master, byte slaveId, ushort startingAddress, short value, int millisecondsTimeout, CancellationToken token) + /// 可取消令箭 + public static async Task WriteSingleRegisterAsync(this IModbusMaster master, byte slaveId, ushort startingAddress, short value, int millisecondsTimeout, CancellationToken cancellationToken) { var request = new ModbusRequest(slaveId, FunctionCode.WriteSingleRegister); request.StartingAddress = startingAddress; request.SetValue(value); - return await master.SendModbusRequestAsync(request, millisecondsTimeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return await master.SendModbusRequestAsync(request, millisecondsTimeout, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// @@ -618,14 +634,14 @@ public static class ModbusMasterExtension /// 起始位置(从0开始) /// 待写入数值 /// 超时时间,单位(ms) - /// 可取消令箭 - public static async Task WriteSingleRegisterAsync(this IModbusMaster master, byte slaveId, ushort startingAddress, ushort value, int millisecondsTimeout, CancellationToken token) + /// 可取消令箭 + public static async Task WriteSingleRegisterAsync(this IModbusMaster master, byte slaveId, ushort startingAddress, ushort value, int millisecondsTimeout, CancellationToken cancellationToken) { var request = new ModbusRequest(slaveId, FunctionCode.WriteSingleRegister); request.StartingAddress = startingAddress; request.SetValue(value); - return await master.SendModbusRequestAsync(request, millisecondsTimeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return await master.SendModbusRequestAsync(request, millisecondsTimeout, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } #endregion WriteAsync @@ -642,16 +658,16 @@ public static class ModbusMasterExtension /// 写入位置(从0开始) /// 待写入数据 /// 超时时间,单位(ms) - /// 可取消令箭 + /// 可取消令箭 /// 响应值 - public static IModbusResponse ReadWriteMultipleRegisters(this IModbusMaster master, byte slaveId, ushort startingAddressForRead, ushort quantityForRead, ushort startingAddress, byte[] bytes, int millisecondsTimeout, CancellationToken token) + public static IModbusResponse ReadWriteMultipleRegisters(this IModbusMaster master, byte slaveId, ushort startingAddressForRead, ushort quantityForRead, ushort startingAddress, ReadOnlyMemory bytes, int millisecondsTimeout, CancellationToken cancellationToken) { var request = new ModbusRequest(slaveId, FunctionCode.ReadWriteMultipleRegisters); request.StartingAddress = startingAddress; request.ReadStartAddress = startingAddressForRead; request.ReadQuantity = quantityForRead; request.SetValue(bytes); - return master.SendModbusRequest(request, millisecondsTimeout, token); + return master.SendModbusRequestAsync(request, millisecondsTimeout, cancellationToken).GetFalseAwaitResult(); } /// @@ -664,17 +680,46 @@ public static class ModbusMasterExtension /// 写入位置(从0开始) /// 待写入数据 /// 超时时间,单位(ms) - /// 可取消令箭 + /// 可取消令箭 /// 响应值 - public static async Task ReadWriteMultipleRegistersAsync(this IModbusMaster master, byte slaveId, ushort startingAddressForRead, ushort quantityForRead, ushort startingAddress, byte[] bytes, int millisecondsTimeout, CancellationToken token) + public static async Task ReadWriteMultipleRegistersAsync(this IModbusMaster master, byte slaveId, ushort startingAddressForRead, ushort quantityForRead, ushort startingAddress, ReadOnlyMemory bytes, int millisecondsTimeout, CancellationToken cancellationToken) { var request = new ModbusRequest(slaveId, FunctionCode.ReadWriteMultipleRegisters); request.StartingAddress = startingAddress; request.ReadStartAddress = startingAddressForRead; request.ReadQuantity = quantityForRead; request.SetValue(bytes); - return await master.SendModbusRequestAsync(request, millisecondsTimeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return await master.SendModbusRequestAsync(request, millisecondsTimeout, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } #endregion ReadWrite + + #region SendModbusRequestAsync + public static async Task SendModbusRequestAsync(this IModbusMaster master, ModbusRequest request, int millisecondsTimeout, CancellationToken cancellationToken) + { + if (millisecondsTimeout == Timeout.Infinite) + { + return await master.SendModbusRequestAsync(request, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + + using (var timeoutCts = new CancellationTokenSource(millisecondsTimeout)) + { + using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token)) + { + try + { + return await master.SendModbusRequestAsync(request, linkedCts.Token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + catch (OperationCanceledException) + { + if (timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested) + { + throw new TimeoutException("The operation has timed out."); + } + throw; + } + } + } + } + #endregion } \ No newline at end of file diff --git a/src/TouchSocket.Modbus/Extensions/ModbusResponseExtension.cs b/src/TouchSocket.Modbus/Extensions/ModbusResponseExtension.cs index a332d4252..d2e2acbf0 100644 --- a/src/TouchSocket.Modbus/Extensions/ModbusResponseExtension.cs +++ b/src/TouchSocket.Modbus/Extensions/ModbusResponseExtension.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Modbus; /// @@ -26,7 +24,6 @@ public static class ModbusResponseExtension /// public static BytesReader CreateReader(this IModbusResponse response) { - // 使用response的数据创建并返回一个新的ValueByteBlock对象 - return new BytesReader(response.Data.Span); + return new BytesReader(response.Data); } } \ No newline at end of file diff --git a/src/TouchSocket.Modbus/Interfaces/IModbusMaster.cs b/src/TouchSocket.Modbus/Interfaces/IModbusMaster.cs index 40c1ec674..7b04cee4e 100644 --- a/src/TouchSocket.Modbus/Interfaces/IModbusMaster.cs +++ b/src/TouchSocket.Modbus/Interfaces/IModbusMaster.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Modbus; /// @@ -25,8 +21,7 @@ public interface IModbusMaster : IDependencyObject /// 异步发送Modbus请求 /// /// 要发送的Modbus请求对象 - /// 请求的超时时间(以毫秒为单位) - /// 用于取消操作的CancellationToken + /// 用于取消操作的CancellationToken /// 返回一个任务,该任务完成后将包含相应的Modbus响应 - Task SendModbusRequestAsync(ModbusRequest request, int millisecondsTimeout, CancellationToken token); + Task SendModbusRequestAsync(IModbusRequest request, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/src/TouchSocket.Core/Mapper/MapperOption.cs b/src/TouchSocket.Modbus/Interfaces/IModbusReadWriteRequest.cs similarity index 71% rename from src/TouchSocket.Core/Mapper/MapperOption.cs rename to src/TouchSocket.Modbus/Interfaces/IModbusReadWriteRequest.cs index 3d46040ee..ea5b4e2ab 100644 --- a/src/TouchSocket.Core/Mapper/MapperOption.cs +++ b/src/TouchSocket.Modbus/Interfaces/IModbusReadWriteRequest.cs @@ -10,22 +10,20 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Collections.Generic; - -namespace TouchSocket.Core; +namespace TouchSocket.Modbus; /// -/// 映射配置 +/// 读写多个寄存器请求接口,专门用于操作 /// -public class MapperOption +public interface IModbusReadWriteRequest : IModbusRequest { /// - /// 需要忽略的属性名称 +/// 读取起始位置 /// - public List IgnoreProperties { get; set; } + ushort ReadStartAddress { get; } - /// - /// 映射属性名称 + /// + /// 读取数量 /// - public Dictionary MapperProperties { get; set; } + ushort ReadQuantity { get; } } \ No newline at end of file diff --git a/src/TouchSocket.Modbus/Interfaces/IModbusRequest.cs b/src/TouchSocket.Modbus/Interfaces/IModbusRequest.cs index 276560933..41655ae95 100644 --- a/src/TouchSocket.Modbus/Interfaces/IModbusRequest.cs +++ b/src/TouchSocket.Modbus/Interfaces/IModbusRequest.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Modbus; /// @@ -43,14 +41,4 @@ public interface IModbusRequest /// 功能码 /// FunctionCode FunctionCode { get; } - - /// - /// 在读起始位置。该属性仅仅在时才表示读取起始位置。 - /// - ushort ReadStartAddress { get; } - - /// - /// 读取长度,该属性仅在时才表示读取数量。 - /// - ushort ReadQuantity { get; } } \ No newline at end of file diff --git a/src/TouchSocket.Modbus/Interfaces/IModbusResponse.cs b/src/TouchSocket.Modbus/Interfaces/IModbusResponse.cs index 798c3dbab..76fa96ffc 100644 --- a/src/TouchSocket.Modbus/Interfaces/IModbusResponse.cs +++ b/src/TouchSocket.Modbus/Interfaces/IModbusResponse.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Modbus; /// @@ -22,7 +20,7 @@ public interface IModbusResponse /// /// 站点号 /// - public byte SlaveId { get; } + byte SlaveId { get; } /// /// 数据 diff --git a/src/TouchSocket.Modbus/Interfaces/IModbusRtuMaster.cs b/src/TouchSocket.Modbus/Interfaces/IModbusRtuMaster.cs index 3e4de38ab..a0fb5de44 100644 --- a/src/TouchSocket.Modbus/Interfaces/IModbusRtuMaster.cs +++ b/src/TouchSocket.Modbus/Interfaces/IModbusRtuMaster.cs @@ -18,6 +18,6 @@ namespace TouchSocket.Modbus; /// /// 基于串口的Modbus主站接口 /// -public interface IModbusRtuMaster : IModbusMaster, ISerialPortSession,IConnectableClient +public interface IModbusRtuMaster : IModbusMaster, ISerialPortSession, IConnectableClient { } \ No newline at end of file diff --git a/src/TouchSocket.Modbus/Interfaces/IModbusTcpMaster.cs b/src/TouchSocket.Modbus/Interfaces/IModbusTcpMaster.cs index eb1cff29a..d3eca03ea 100644 --- a/src/TouchSocket.Modbus/Interfaces/IModbusTcpMaster.cs +++ b/src/TouchSocket.Modbus/Interfaces/IModbusTcpMaster.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; using TouchSocket.Sockets; namespace TouchSocket.Modbus; diff --git a/src/TouchSocket.Modbus/Readme.md b/src/TouchSocket.Modbus/Readme.md index 5ec0c8413..7daf25681 100644 --- a/src/TouchSocket.Modbus/Readme.md +++ b/src/TouchSocket.Modbus/Readme.md @@ -10,13 +10,13 @@ TouchSocket.Modbus 是一个 Modbus 功能库,为开发者提供了多种 Modb 详细的说明文档请访问:[https://touchsocket.net/](https://touchsocket.net/) ## 支持的目标框架 -- net45 + - net462 - net472 - netstandard2.0 - netstandard2.1 - net6.0 -- net9.0 +- net10.0 - net8.0 ## 贡献与反馈 diff --git a/src/TouchSocket.Modbus/TouchSocket.Modbus.csproj b/src/TouchSocket.Modbus/TouchSocket.Modbus.csproj index 86b31715e..9c9ee5136 100644 --- a/src/TouchSocket.Modbus/TouchSocket.Modbus.csproj +++ b/src/TouchSocket.Modbus/TouchSocket.Modbus.csproj @@ -1,6 +1,6 @@ - net45;net462;net472;netstandard2.0;netstandard2.1;net6.0;net9.0;net8.0 + net462;netstandard2.0;netstandard2.1;net6.0;net10.0;net8.0 Modbus;Master;TouchSocket 这是一个Modbus功能库,目前包括ModbusTcpMaster、ModbusUdpMaster、ModbusRtuMaster、ModbusRtuOverTcpMaster、ModbusRtuOverUdpMaster。 diff --git a/src/TouchSocket.Mqtt/Actors/MqttActor.cs b/src/TouchSocket.Mqtt/Actors/MqttActor.cs index d292533ee..bfb985f9c 100644 --- a/src/TouchSocket.Mqtt/Actors/MqttActor.cs +++ b/src/TouchSocket.Mqtt/Actors/MqttActor.cs @@ -10,11 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Sockets; namespace TouchSocket.Mqtt; @@ -25,13 +20,13 @@ public abstract class MqttActor : DisposableObject, IOnlineClient private readonly Dictionary m_qos2MqttArrivedMessage = new(); private readonly CancellationTokenSource m_tokenSource = new(); - private readonly WaitHandlePool m_waitHandlePool = new(); + private readonly WaitHandlePool m_waitHandlePool = new(1, ushort.MaxValue); #endregion 字段 public MqttActor() { - this.m_waitHandlePool.MaxSign = 1; + } protected override void Dispose(bool disposing) @@ -76,150 +71,131 @@ public abstract class MqttActor : DisposableObject, IOnlineClient #region Publish - public async Task PublishAsync(MqttPublishMessage message) + public Task PublishAsync(MqttPublishMessage message, CancellationToken cancellationToken) { switch (message.QosLevel) { - case QosLevel.AtMostOnce: - await this.PublishAtMostOnceMessageAsync(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - break; - case QosLevel.AtLeastOnce: - await this.PublishAtLeastOnceMessageAsync(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - break; - + return this.PublishAtLeastOnceMessageAsync(message, cancellationToken); case QosLevel.ExactlyOnce: - await this.PublishExactlyOnceMessageAsync(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - break; - + return this.PublishExactlyOnceMessageAsync(message, cancellationToken); + case QosLevel.AtMostOnce: default: - break; + return this.PublishAtMostOnceMessageAsync(message, cancellationToken); } } - private async Task PublishAtLeastOnceMessageAsync(MqttPublishMessage message) + private async Task PublishAtLeastOnceMessageAsync(MqttPublishMessage message, CancellationToken cancellationToken) { - var waitDataAsync = this.m_waitHandlePool.GetWaitDataAsync(message); - - try + using (var waitDataAsync = this.m_waitHandlePool.GetWaitDataAsync(message)) { - await this.ProtectedOutputSendAsync(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.ProtectedOutputSendAsync(message, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - var waitDataStatus = await waitDataAsync.WaitAsync(1000 * 10).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var waitDataStatus = await waitDataAsync.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); waitDataStatus.ThrowIfNotRunning(); - if (waitDataAsync.WaitResult is MqttPubAckMessage pubAckMessage) + if (waitDataAsync.CompletedData is MqttPubAckMessage pubAckMessage) { return; } } - finally - { - this.m_waitHandlePool.Destroy(message.MessageId); - } } - private async Task PublishAtMostOnceMessageAsync(MqttPublishMessage message) + private async Task PublishAtMostOnceMessageAsync(MqttPublishMessage message, CancellationToken cancellationToken) { - await this.ProtectedOutputSendAsync(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.ProtectedOutputSendAsync(message, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - private async Task PublishExactlyOnceMessageAsync(MqttPublishMessage message) + private async Task PublishExactlyOnceMessageAsync(MqttPublishMessage message, CancellationToken cancellationToken) { - var waitData_1_Async = this.m_waitHandlePool.GetWaitDataAsync(message); - - try + using (var waitData_1_Async = this.m_waitHandlePool.GetWaitDataAsync(message)) { - await this.ProtectedOutputSendAsync(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - var waitDataStatus = await waitData_1_Async.WaitAsync(1000 * 10).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.ProtectedOutputSendAsync(message, cancellationToken) + .ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var waitDataStatus = await waitData_1_Async.WaitAsync(cancellationToken) + .ConfigureAwait(EasyTask.ContinueOnCapturedContext); waitDataStatus.ThrowIfNotRunning(); } - finally - { - this.m_waitHandlePool.Destroy(message.MessageId); - } + var mqttPubRelMessage = new MqttPubRelMessage() { MessageId = message.MessageId }; - var waitData_2_Async = this.m_waitHandlePool.GetWaitDataAsync(mqttPubRelMessage, false); - - try + using (var waitData_2_Async = this.m_waitHandlePool.GetWaitDataAsync(mqttPubRelMessage, false)) { - await this.ProtectedOutputSendAsync(mqttPubRelMessage).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - var waitDataStatus = await waitData_2_Async.WaitAsync(1000 * 10).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.ProtectedOutputSendAsync(mqttPubRelMessage, cancellationToken) + .ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var waitDataStatus = await waitData_2_Async.WaitAsync(cancellationToken) + .ConfigureAwait(EasyTask.ContinueOnCapturedContext); waitDataStatus.ThrowIfNotRunning(); } - finally - { - this.m_waitHandlePool.Destroy(mqttPubRelMessage.MessageId); - } } #endregion Publish #region InputMqttMessage - public async Task InputMqttMessageAsync(MqttMessage mqttMessage) + public async Task InputMqttMessageAsync(MqttMessage mqttMessage, CancellationToken cancellationToken) { switch (mqttMessage) { case MqttConnectMessage message: - await this.InputMqttConnectMessageAsync(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + this.Version = message.Version; + await this.InputMqttConnectMessageAsync(message, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); break; case MqttConnAckMessage message: - await this.InputMqttConnAckMessageAsync(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.InputMqttConnAckMessageAsync(message, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); break; case MqttPingReqMessage message: - await this.InputMqttPingReqMessageAsync(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.InputMqttPingReqMessageAsync(message, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); break; case MqttPingRespMessage message: - await this.InputMqttPingRespMessageAsync(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.InputMqttPingRespMessageAsync(message, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); break; case MqttPublishMessage message: - await this.InputMqttPublishMessageAsync(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.InputMqttPublishMessageAsync(message, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); break; case MqttPubAckMessage message: - await this.InputMqttPubAckMessageAsync(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.InputMqttPubAckMessageAsync(message, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); break; case MqttPubRecMessage message: - await this.InputMqttPubRecMessageAsync(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.InputMqttPubRecMessageAsync(message, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); break; case MqttPubRelMessage message: - await this.InputMqttPubRelMessageAsync(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.InputMqttPubRelMessageAsync(message, CancellationToken.None).ConfigureAwait(EasyTask.ContinueOnCapturedContext); break; case MqttPubCompMessage message: - await this.InputMqttPubCompMessageAsync(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.InputMqttPubCompMessageAsync(message, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); break; case MqttSubscribeMessage message: - await this.InputMqttSubscribeMessageAsync(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.InputMqttSubscribeMessageAsync(message, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); break; case MqttSubAckMessage message: - await this.InputMqttSubAckMessageAsync(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.InputMqttSubAckMessageAsync(message, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); break; case MqttUnsubscribeMessage message: - await this.InputMqttUnsubscribeMessageAsync(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.InputMqttUnsubscribeMessageAsync(message, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); break; case MqttUnsubAckMessage message: - await this.InputMqttUnsubAckMessageAsync(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.InputMqttUnsubAckMessageAsync(message, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); break; case MqttDisconnectMessage message: - await this.InputMqttDisconnectMessageAsync(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.InputMqttDisconnectMessageAsync(message, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); break; default: @@ -231,43 +207,49 @@ public abstract class MqttActor : DisposableObject, IOnlineClient /// 处理连接确认消息 /// /// 连接确认消息 + /// 可取消令箭 /// 任务 - protected abstract Task InputMqttConnAckMessageAsync(MqttConnAckMessage message); + protected abstract Task InputMqttConnAckMessageAsync(MqttConnAckMessage message, CancellationToken cancellationToken); /// /// 处理连接消息 /// /// 连接消息 + /// 可取消令箭 /// 任务 - protected abstract Task InputMqttConnectMessageAsync(MqttConnectMessage message); + protected abstract Task InputMqttConnectMessageAsync(MqttConnectMessage message, CancellationToken cancellationToken); /// /// 处理PING响应消息 /// /// PING响应消息 + /// 可取消令箭 /// 任务 - protected abstract Task InputMqttPingRespMessageAsync(MqttPingRespMessage message); + protected abstract Task InputMqttPingRespMessageAsync(MqttPingRespMessage message, CancellationToken cancellationToken); /// /// 处理订阅消息 /// /// 订阅消息 + /// 可取消令箭 /// 任务 - protected abstract Task InputMqttSubscribeMessageAsync(MqttSubscribeMessage message); + protected abstract Task InputMqttSubscribeMessageAsync(MqttSubscribeMessage message, CancellationToken cancellationToken); /// /// 处理取消订阅消息 /// /// 取消订阅消息 + /// 可取消令箭 /// 任务 - protected abstract Task InputMqttUnsubscribeMessageAsync(MqttUnsubscribeMessage message); + protected abstract Task InputMqttUnsubscribeMessageAsync(MqttUnsubscribeMessage message, CancellationToken cancellationToken); /// /// 处理断开连接消息 /// /// 断开连接消息 + /// 可取消令箭 /// 任务 - private Task InputMqttDisconnectMessageAsync(MqttDisconnectMessage message) + private Task InputMqttDisconnectMessageAsync(MqttDisconnectMessage message, CancellationToken cancellationToken) { return this.ProtectedMqttOnClosing(message); } @@ -276,21 +258,23 @@ public abstract class MqttActor : DisposableObject, IOnlineClient /// 处理PING请求消息 /// /// PING请求消息 + /// 可取消令箭 /// 任务 - private async Task InputMqttPingReqMessageAsync(MqttPingReqMessage message) + private async Task InputMqttPingReqMessageAsync(MqttPingReqMessage message, CancellationToken cancellationToken) { var contentForAck = new MqttPingRespMessage(); - await this.ProtectedOutputSendAsync(contentForAck).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.ProtectedOutputSendAsync(contentForAck, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// /// 处理发布确认消息 /// /// 发布确认消息 + /// 可取消令箭 /// 任务 - private Task InputMqttPubAckMessageAsync(MqttPubAckMessage message) + private Task InputMqttPubAckMessageAsync(MqttPubAckMessage message, CancellationToken cancellationToken) { - this.m_waitHandlePool.SetRun(message); + this.m_waitHandlePool.Set(message); return EasyTask.CompletedTask; } @@ -298,10 +282,11 @@ public abstract class MqttActor : DisposableObject, IOnlineClient /// 处理发布完成消息 /// /// 发布完成消息 + /// 可取消令箭 /// 任务 - private Task InputMqttPubCompMessageAsync(MqttPubCompMessage message) + private Task InputMqttPubCompMessageAsync(MqttPubCompMessage message, CancellationToken cancellationToken) { - this.m_waitHandlePool.SetRun(message); + this.m_waitHandlePool.Set(message); return EasyTask.CompletedTask; } @@ -309,8 +294,9 @@ public abstract class MqttActor : DisposableObject, IOnlineClient /// 处理发布消息 /// /// 发布消息 + /// 可取消令箭 /// 任务 - private async Task InputMqttPublishMessageAsync(MqttPublishMessage message) + private async Task InputMqttPublishMessageAsync(MqttPublishMessage message, CancellationToken cancellationToken) { //Console.WriteLine($"TopicName:{message.TopicName}"); //Console.WriteLine($"Payload:{Encoding.UTF8.GetString(message.Payload.ToArray())}"); @@ -326,7 +312,7 @@ public abstract class MqttActor : DisposableObject, IOnlineClient { MessageId = message.MessageId }; - await this.ProtectedOutputSendAsync(pubAckMessage).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.ProtectedOutputSendAsync(pubAckMessage, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } else if (message.QosLevel == QosLevel.ExactlyOnce) { @@ -335,7 +321,7 @@ public abstract class MqttActor : DisposableObject, IOnlineClient { MessageId = message.MessageId }; - await this.ProtectedOutputSendAsync(pubRecMessage).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.ProtectedOutputSendAsync(pubRecMessage, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } @@ -343,10 +329,11 @@ public abstract class MqttActor : DisposableObject, IOnlineClient /// 处理发布接收消息 /// /// 发布接收消息 + /// 可取消令箭 /// 任务 - private Task InputMqttPubRecMessageAsync(MqttPubRecMessage message) + private Task InputMqttPubRecMessageAsync(MqttPubRecMessage message, CancellationToken cancellationToken) { - this.m_waitHandlePool.SetRun(message); + this.m_waitHandlePool.Set(message); return EasyTask.CompletedTask; } @@ -354,8 +341,9 @@ public abstract class MqttActor : DisposableObject, IOnlineClient /// 处理发布释放消息 /// /// 发布释放消息 + /// 可取消令箭 /// 任务 - private async Task InputMqttPubRelMessageAsync(MqttPubRelMessage message) + private async Task InputMqttPubRelMessageAsync(MqttPubRelMessage message, CancellationToken cancellationToken) { if (this.m_qos2MqttArrivedMessage.TryGetValue(message.MessageId, out var mqttArrivedMessage)) { @@ -365,7 +353,7 @@ public abstract class MqttActor : DisposableObject, IOnlineClient { MessageId = message.MessageId }; - await this.ProtectedOutputSendAsync(pubCompMessage).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.ProtectedOutputSendAsync(pubCompMessage, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } @@ -373,10 +361,11 @@ public abstract class MqttActor : DisposableObject, IOnlineClient /// 处理订阅确认消息 /// /// 订阅确认消息 + /// 可取消令箭 /// 任务 - private Task InputMqttSubAckMessageAsync(MqttSubAckMessage message) + private Task InputMqttSubAckMessageAsync(MqttSubAckMessage message, CancellationToken cancellationToken) { - this.m_waitHandlePool.SetRun(message); + this.m_waitHandlePool.Set(message); return EasyTask.CompletedTask; } @@ -384,10 +373,11 @@ public abstract class MqttActor : DisposableObject, IOnlineClient /// 处理取消订阅确认消息 /// /// 取消订阅确认消息 + /// 可取消令箭 /// 任务 - private Task InputMqttUnsubAckMessageAsync(MqttUnsubAckMessage message) + private Task InputMqttUnsubAckMessageAsync(MqttUnsubAckMessage message, CancellationToken cancellationToken) { - this.m_waitHandlePool.SetRun(message); + this.m_waitHandlePool.Set(message); return EasyTask.CompletedTask; } @@ -399,7 +389,8 @@ public abstract class MqttActor : DisposableObject, IOnlineClient public Func Connected { get; set; } public Func Connecting { get; set; } public Func MessageArrived { get; set; } - public Func OutputSendAsync { get; set; } + public Func OutputSendAsync { get; set; } + public MqttProtocolVersion Version { get; protected set; } #endregion 委托 @@ -413,9 +404,8 @@ public abstract class MqttActor : DisposableObject, IOnlineClient } } - protected async Task ProtectedMqttOnConnected(object o) + protected async Task ProtectedMqttOnConnected(MqttConnectedEventArgs e) { - var e = (MqttConnectedEventArgs)o; if (this.Connected != null) { await this.Connected.Invoke(this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); @@ -430,9 +420,17 @@ public abstract class MqttActor : DisposableObject, IOnlineClient } } - protected Task ProtectedOutputSendAsync(MqttMessage message) + protected Task ProtectedOutputSendAsync(MqttMessage message, CancellationToken cancellationToken) { - return this.OutputSendAsync(this, message); + if (message.MessageType == MqttMessageType.Connect) + { + this.Version = message.Version; + } + else + { + message.InternalSetVersion(this.Version); + } + return this.OutputSendAsync(this, message, cancellationToken); } #endregion 委托方法 diff --git a/src/TouchSocket.Mqtt/Actors/MqttClientActor.cs b/src/TouchSocket.Mqtt/Actors/MqttClientActor.cs index 41531ae76..ffd4c130c 100644 --- a/src/TouchSocket.Mqtt/Actors/MqttClientActor.cs +++ b/src/TouchSocket.Mqtt/Actors/MqttClientActor.cs @@ -10,18 +10,14 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Mqtt; -public class MqttClientActor : MqttActor +public sealed class MqttClientActor : MqttActor { - private readonly WaitDataAsync m_waitForConnect = new(); + private TaskCompletionSource m_waitForConnect; private readonly WaitDataAsync m_waitForPing = new(); - public async Task DisconnectAsync() + public async Task DisconnectAsync(CancellationToken cancellationToken) { if (!this.Online) { @@ -29,7 +25,7 @@ public class MqttClientActor : MqttActor } try { - await this.ProtectedOutputSendAsync(new MqttDisconnectMessage()).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.ProtectedOutputSendAsync(new MqttDisconnectMessage(), cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } catch { @@ -40,104 +36,92 @@ public class MqttClientActor : MqttActor } } - public async Task PingAsync(int timeout, CancellationToken token) + public async ValueTask PingAsync(CancellationToken cancellationToken) { var contentForAck = new MqttPingReqMessage(); this.m_waitForPing.Reset(); - this.m_waitForPing.SetCancellationToken(token); - await this.ProtectedOutputSendAsync(contentForAck); - var waitDataStatus = await this.m_waitForPing.WaitAsync(timeout).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - waitDataStatus.ThrowIfNotRunning(); + await this.ProtectedOutputSendAsync(contentForAck, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var waitDataStatus = await this.m_waitForPing.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + return waitDataStatus switch + { + WaitDataStatus.Default => Result.UnknownFail, + WaitDataStatus.Success => Result.Success, + WaitDataStatus.Overtime => Result.Overtime, + WaitDataStatus.Canceled => Result.Canceled, + WaitDataStatus.Disposed => Result.Disposed, + _ => Result.UnknownFail, + }; } #region 连接 - public async Task ConnectAsync(MqttConnectMessage message, int millisecondsTimeout, CancellationToken token) + public async Task ConnectAsync(MqttConnectMessage message, CancellationToken cancellationToken) { - this.m_waitForConnect.Reset(); - this.m_waitForConnect.SetCancellationToken(token); + this.m_waitForConnect = new TaskCompletionSource(); - await this.ProtectedOutputSendAsync(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - var waitDataStatus = await this.m_waitForConnect.WaitAsync(millisecondsTimeout); - waitDataStatus.ThrowIfNotRunning(); + await this.ProtectedOutputSendAsync(message, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var connAckMessage = await this.m_waitForConnect.Task.WithCancellation(cancellationToken); this.Online = true; - return this.m_waitForConnect.WaitResult; + return connAckMessage; } #endregion 连接 #region 重写 - protected override Task InputMqttConnAckMessageAsync(MqttConnAckMessage message) + protected override Task InputMqttConnAckMessageAsync(MqttConnAckMessage message, CancellationToken cancellationToken) { - this.m_waitForConnect.Set(message); + this.m_waitForConnect?.SetResult(message); return EasyTask.CompletedTask; } - protected override Task InputMqttConnectMessageAsync(MqttConnectMessage message) + protected override Task InputMqttConnectMessageAsync(MqttConnectMessage message, CancellationToken cancellationToken) { throw ThrowHelper.CreateNotSupportedException($"遇到无法处理的数据报文,Message={message}"); } - protected override Task InputMqttPingRespMessageAsync(MqttPingRespMessage message) + protected override Task InputMqttPingRespMessageAsync(MqttPingRespMessage message, CancellationToken cancellationToken) { this.m_waitForPing.Set(message); return EasyTask.CompletedTask; } - protected override Task InputMqttSubscribeMessageAsync(MqttSubscribeMessage message) + protected override Task InputMqttSubscribeMessageAsync(MqttSubscribeMessage message, CancellationToken cancellationToken) { throw ThrowHelper.CreateNotSupportedException($"遇到无法处理的数据报文,Message={message}"); } - protected override Task InputMqttUnsubscribeMessageAsync(MqttUnsubscribeMessage message) + protected override Task InputMqttUnsubscribeMessageAsync(MqttUnsubscribeMessage message, CancellationToken cancellationToken) { throw ThrowHelper.CreateNotSupportedException($"遇到无法处理的数据报文,Message={message}"); } #endregion 重写 - public async Task SubscribeAsync(MqttSubscribeMessage message, int timeout = 5000, CancellationToken token = default) + public async Task SubscribeAsync(MqttSubscribeMessage message, CancellationToken cancellationToken = default) { - var waitDataAsync = this.WaitHandlePool.GetWaitDataAsync(message); - - try + using (var waitDataAsync = this.WaitHandlePool.GetWaitDataAsync(message)) { - waitDataAsync.SetCancellationToken(token); + await this.ProtectedOutputSendAsync(message, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - await this.ProtectedOutputSendAsync(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - - var waitDataStatus = await waitDataAsync.WaitAsync(timeout).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var waitDataStatus = await waitDataAsync.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); waitDataStatus.ThrowIfNotRunning(); - var subAckMessage = (MqttSubAckMessage)waitDataAsync.WaitResult; + var subAckMessage = (MqttSubAckMessage)waitDataAsync.CompletedData; return subAckMessage; } - finally - { - this.WaitHandlePool.Destroy(message.MessageId); - } - } - public async Task UnsubscribeAsync(MqttUnsubscribeMessage message, int timeout = 5000, CancellationToken token = default) + public async Task UnsubscribeAsync(MqttUnsubscribeMessage message, CancellationToken cancellationToken = default) { - var waitDataAsync = this.WaitHandlePool.GetWaitDataAsync(message); - - try + using (var waitDataAsync = this.WaitHandlePool.GetWaitDataAsync(message)) { - waitDataAsync.SetCancellationToken(token); + await this.ProtectedOutputSendAsync(message, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - await this.ProtectedOutputSendAsync(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - - var waitDataStatus = await waitDataAsync.WaitAsync(timeout).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var waitDataStatus = await waitDataAsync.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); waitDataStatus.ThrowIfNotRunning(); - var subAckMessage = (MqttUnsubAckMessage)waitDataAsync.WaitResult; + var subAckMessage = (MqttUnsubAckMessage)waitDataAsync.CompletedData; return subAckMessage; } - finally - { - this.WaitHandlePool.Destroy(message.MessageId); - } - } } \ No newline at end of file diff --git a/src/TouchSocket.Mqtt/Actors/MqttSessionActor.cs b/src/TouchSocket.Mqtt/Actors/MqttSessionActor.cs index 5933b1ba3..354f1fbda 100644 --- a/src/TouchSocket.Mqtt/Actors/MqttSessionActor.cs +++ b/src/TouchSocket.Mqtt/Actors/MqttSessionActor.cs @@ -10,10 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Threading.Channels; -using System.Threading.Tasks; -using TouchSocket.Core; namespace TouchSocket.Mqtt; @@ -22,7 +19,7 @@ namespace TouchSocket.Mqtt; /// public class MqttSessionActor : MqttActor { - private readonly AsyncResetEvent m_asyncResetEvent = new(false, false); + private readonly AsyncManualResetEvent m_asyncResetEvent = new(false); private readonly Channel m_mqttArrivedMessageQueue = Channel.CreateBounded(new BoundedChannelOptions(1000) { @@ -34,6 +31,7 @@ public class MqttSessionActor : MqttActor }); private readonly MqttBroker m_mqttBroker; + private MqttPublishMessage m_mqttWillMessage; private bool m_sessionPresent; /// @@ -43,7 +41,7 @@ public class MqttSessionActor : MqttActor public MqttSessionActor(MqttBroker messageCenter) { this.m_mqttBroker = messageCenter; - _=EasyTask.SafeRun(this.WaitForReadAsync); + _ = EasyTask.SafeRun(this.WaitForReadAsync); } /// @@ -54,17 +52,40 @@ public class MqttSessionActor : MqttActor /// /// 激活会话。 /// - public void Activate() + public void Activate(MqttConnectMessage mqttConnectMessage) { + if (mqttConnectMessage.WillFlag) + { + var mqttPublishMessage = new MqttPublishMessage(mqttConnectMessage.WillTopic, mqttConnectMessage.WillPayload); + this.m_mqttWillMessage = mqttPublishMessage; + } + else + { + this.m_mqttWillMessage = null; + } this.m_asyncResetEvent.Set(); } /// /// 取消激活会话。 /// - public void Deactivate() + public async Task Deactivate() { this.m_asyncResetEvent.Reset(); + var willMessage = this.m_mqttWillMessage; + this.m_mqttWillMessage = null; + if (willMessage != null) + { + await this.m_mqttBroker.ForwardMessageAsync(new MqttArrivedMessage(willMessage)); + } + } + + /// + /// 设置会话存在标志为 true。 + /// + public void MakeSessionPresent() + { + this.m_sessionPresent = true; } /// @@ -89,20 +110,12 @@ public class MqttSessionActor : MqttActor } } - /// - /// 设置会话存在标志为 true。 - /// - public void MakeSessionPresent() - { - this.m_sessionPresent = true; - } - /// protected override void Dispose(bool disposing) { if (disposing) { - this.m_asyncResetEvent.Dispose(); + this.m_asyncResetEvent.Set(); //this.m_mqttArrivedMessageQueue.Reader.(); } base.Dispose(disposing); @@ -117,12 +130,14 @@ public class MqttSessionActor : MqttActor #endregion 属性 - protected override Task InputMqttConnAckMessageAsync(MqttConnAckMessage message) + /// + protected override Task InputMqttConnAckMessageAsync(MqttConnAckMessage message, CancellationToken cancellationToken) { throw new NotImplementedException(); } - protected override async Task InputMqttConnectMessageAsync(MqttConnectMessage message) + /// + protected override async Task InputMqttConnectMessageAsync(MqttConnectMessage message, CancellationToken cancellationToken) { var mqttConnAckMessage = new MqttConnAckMessage(this.m_sessionPresent) { @@ -143,26 +158,27 @@ public class MqttSessionActor : MqttActor if (mqttConnAckMessage.ReturnCode != MqttReasonCode.ConnectionAccepted) { - await this.ProtectedOutputSendAsync(mqttConnAckMessage).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.ProtectedOutputSendAsync(mqttConnAckMessage, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return; } this.Id = message.ClientId; - await this.ProtectedOutputSendAsync(mqttConnAckMessage).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.ProtectedOutputSendAsync(mqttConnAckMessage, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); this.Online = true; - _ = EasyTask.Run(this.ProtectedMqttOnConnected, new MqttConnectedEventArgs(message, mqttConnAckMessage)); + _ = EasyTask.SafeRun(this.ProtectedMqttOnConnected, new MqttConnectedEventArgs(message, mqttConnAckMessage)); } - protected override Task InputMqttPingRespMessageAsync(MqttPingRespMessage message) + /// + protected override Task InputMqttPingRespMessageAsync(MqttPingRespMessage message, CancellationToken cancellationToken) { throw new NotImplementedException(); } /// - protected override async Task InputMqttSubscribeMessageAsync(MqttSubscribeMessage message) + protected override async Task InputMqttSubscribeMessageAsync(MqttSubscribeMessage message, CancellationToken cancellationToken) { var contentForAck = new MqttSubAckMessage() { @@ -173,11 +189,11 @@ public class MqttSessionActor : MqttActor this.m_mqttBroker.RegisterActor(this.Id, item.Topic, item.QosLevel); contentForAck.AddReturnCode(item.QosLevel); } - await this.ProtectedOutputSendAsync(contentForAck).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.ProtectedOutputSendAsync(contentForAck, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// - protected override async Task InputMqttUnsubscribeMessageAsync(MqttUnsubscribeMessage message) + protected override async Task InputMqttUnsubscribeMessageAsync(MqttUnsubscribeMessage message, CancellationToken cancellationToken) { foreach (var topic in message.TopicFilters) { @@ -187,19 +203,19 @@ public class MqttSessionActor : MqttActor { MessageId = message.MessageId }; - await this.ProtectedOutputSendAsync(contentForAck).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.ProtectedOutputSendAsync(contentForAck, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } + /// protected override async Task PublishMessageArrivedAsync(MqttArrivedMessage message) { await this.m_mqttBroker.ForwardMessageAsync(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext); await base.PublishMessageArrivedAsync(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - private async Task PublishDistributeMessageAsync(DistributeMessage distributeMessage) + private async Task PublishDistributeMessageAsync(DistributeMessage distributeMessage, CancellationToken cancellationToken) { - var token = this.TokenSource.Token; - if (token.IsCancellationRequested) + if (cancellationToken.IsCancellationRequested) { return false; } @@ -209,11 +225,11 @@ public class MqttSessionActor : MqttActor var publishMessage = new MqttPublishMessage(message.TopicName, message.Retain, qosLevel, message.Payload); - await this.m_asyncResetEvent.WaitOneAsync(token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.m_asyncResetEvent.WaitOneAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); try { - await this.PublishAsync(publishMessage).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.PublishAsync(publishMessage, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return true; } catch (Exception ex) @@ -225,19 +241,19 @@ public class MqttSessionActor : MqttActor private async Task WaitForReadAsync() { - var token = this.TokenSource.Token; - var reader=this.m_mqttArrivedMessageQueue.Reader; + var cancellationToken = this.TokenSource.Token; + var reader = this.m_mqttArrivedMessageQueue.Reader; while (true) { try { - if (token.IsCancellationRequested) + if (cancellationToken.IsCancellationRequested) { Console.WriteLine("WaitForReadAsync IsCancellationRequested"); return; } - - var b=await reader.WaitToReadAsync(token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + var b = await reader.WaitToReadAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); if (!b) { return; @@ -248,7 +264,7 @@ public class MqttSessionActor : MqttActor continue; } - var published= await this.PublishDistributeMessageAsync(distributeMessage).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var published = await this.PublishDistributeMessageAsync(distributeMessage, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); if (published) { reader.TryRead(out _); @@ -258,6 +274,10 @@ public class MqttSessionActor : MqttActor { return; } + catch + { + // + } } } } \ No newline at end of file diff --git a/src/TouchSocket.Mqtt/Adapter/MqttAdapter.cs b/src/TouchSocket.Mqtt/Adapter/MqttAdapter.cs index e4c2cd215..38bd2951b 100644 --- a/src/TouchSocket.Mqtt/Adapter/MqttAdapter.cs +++ b/src/TouchSocket.Mqtt/Adapter/MqttAdapter.cs @@ -10,33 +10,76 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Mqtt; -public class MqttAdapter : CustomDataHandlingAdapter +//class MqttAdapter2 : CacheDataHandlingAdapterSlim +//{ +// protected override bool TryParseRequestAfterCacheVerification(ref TReader reader, out MqttMessage message) +// { +// if (reader.BytesRemaining < 2) +// { +// message = default; +// return false; +// } + +// var firstByte = reader.GetSpan(1)[0]; +// var mqttControlPacketType = (MqttMessageType)firstByte.GetHeight4(); +// //var fixHeaderFlags = new FixHeaderFlags(firstByte); + +// var remainingLength = MqttExtension.ReadVariableByteInteger(ref reader); +// if (reader.CanReadLength < remainingLength) +// { +// reader.Position = position; +// return FilterResult.Cache; +// } + +// var mqttMessage = MqttMessage.CreateMqttMessage(mqttControlPacketType); + +// //Console.WriteLine("Rev:"+ mqttMessage.MessageType); + +// if (mqttMessage.MessageType != MqttMessageType.Connect) +// { +// mqttMessage.InternalSetVersion(this.Version); +// } + +// reader.Position = position; +// mqttMessage.Unpack(ref reader); +// if (reader.Position != position + remainingLength + 1 + MqttExtension.GetVariableByteIntegerCount((int)remainingLength)) +// { +// throw new Exception("存在没有读取的数据"); +// } + +// if (mqttMessage.MessageType == MqttMessageType.Connect) +// { +// this.Version = mqttMessage.Version; +// } + +// message = mqttMessage; +// return FilterResult.Success; +// } +//} +internal class MqttAdapter : CustomDataHandlingAdapter { - public override bool CanSendRequestInfo => true; - - public MqttProtocolVersion Version { get; private set; } = MqttProtocolVersion.V311; - - protected override FilterResult Filter(ref TByteBlock byteBlock, bool beCached, ref MqttMessage request, ref int tempCapacity) + public MqttAdapter() { - if (byteBlock.CanReadLength < 2) + } + + public MqttProtocolVersion Version { get; set; } = MqttProtocolVersion.Unknown; + + protected override FilterResult Filter(ref TReader reader, bool beCached, ref MqttMessage request) + { + if (reader.BytesRemaining < 2) { return FilterResult.Cache; } - var position = byteBlock.Position; - var firstByte = byteBlock.ReadByte(); + var position = reader.BytesRead; + var firstByte = ReaderExtension.ReadValue(ref reader); var mqttControlPacketType = (MqttMessageType)firstByte.GetHeight4(); - //var fixHeaderFlags = new FixHeaderFlags(firstByte); - var remainingLength = MqttExtension.ReadVariableByteInteger(ref byteBlock); - if (byteBlock.CanReadLength < remainingLength) + var remainingLength = MqttExtension.ReadVariableByteInteger(ref reader); + if (reader.BytesRemaining < remainingLength) { - byteBlock.Position = position; + reader.BytesRead = position; return FilterResult.Cache; } @@ -44,48 +87,24 @@ public class MqttAdapter : CustomDataHandlingAdapter //Console.WriteLine("Rev:"+ mqttMessage.MessageType); - if (mqttMessage is not MqttConnectMessage) + if (mqttMessage.MessageType != MqttMessageType.Connect) { mqttMessage.InternalSetVersion(this.Version); } - byteBlock.Position = position; - mqttMessage.Unpack(ref byteBlock); - if (byteBlock.Position != position + remainingLength + 1+MqttExtension.GetVariableByteIntegerCount((int)remainingLength)) + reader.BytesRead = position; + mqttMessage.Unpack(ref reader); + if (reader.BytesRead != position + remainingLength + 1 + MqttExtension.GetVariableByteIntegerCount((int)remainingLength)) { throw new Exception("存在没有读取的数据"); } - if (mqttMessage is MqttConnectMessage connectMessage) + if (mqttMessage.MessageType == MqttMessageType.Connect) { - this.Version = connectMessage.Version; + this.Version = mqttMessage.Version; } request = mqttMessage; return FilterResult.Success; } - - protected override async Task PreviewSendAsync(IRequestInfo requestInfo) - { - if (requestInfo is MqttMessage mqttMessage) - { - //Console.WriteLine("Send:" + mqttMessage.MessageType); - - if (mqttMessage.MessageType == MqttMessageType.Connect) - { - this.Version = ((MqttConnectMessage)mqttMessage).Version; - } - var byteBlock = new ValueByteBlock(mqttMessage.MaxLength); - try - { - mqttMessage.InternalSetVersion(this.Version); - mqttMessage.Build(ref byteBlock); - await this.GoSendAsync(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - finally - { - byteBlock.Dispose(); - } - } - } } \ No newline at end of file diff --git a/src/TouchSocket.Mqtt/Broker/MqttBroker.cs b/src/TouchSocket.Mqtt/Broker/MqttBroker.cs index 9c0d05390..724c18881 100644 --- a/src/TouchSocket.Mqtt/Broker/MqttBroker.cs +++ b/src/TouchSocket.Mqtt/Broker/MqttBroker.cs @@ -6,18 +6,11 @@ // Gitee源代码仓库:https://gitee.com/RRQM_Home // Github源代码仓库:https://github.com/RRQM // API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 +//交流QQ群:234762506 +// 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; namespace TouchSocket.Mqtt; @@ -151,6 +144,7 @@ public class MqttBroker public Subscription(string clientId) { this.ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); + this.QosLevel = default; } public string ClientId { get; } @@ -183,7 +177,7 @@ public class MqttBroker } /// - /// 线程安全的主题-订阅者映射管理类(使用读写锁) + ///线程安全的主题-订阅者映射管理类(使用读写锁) /// private class ThreadSafeTopicSubscriptions { @@ -201,11 +195,9 @@ public class MqttBroker /// /// 主题名称(非空) /// 订阅者实例(非空) - /// 主题名称为空 - /// 订阅者为 null public void AddSubscriber(string topic, Subscription subscriber) { - this.ValidateTopic(topic); + ValidateTopic(topic); this.m_lock.EnterWriteLock(); try @@ -246,12 +238,9 @@ public class MqttBroker /// /// 移除指定主题的所有订阅者(保留空主题) /// - /// 主题名称(非空) - /// 是否成功移除(主题存在时为 true) - /// 主题名称为空 public bool ClearTopic(string topic) { - this.ValidateTopic(topic); + ValidateTopic(topic); this.m_lock.EnterWriteLock(); try @@ -267,14 +256,9 @@ public class MqttBroker /// /// 检查指定主题是否包含特定订阅者 /// - /// 主题名称(非空) - /// 要检查的订阅者实例(非空) - /// 是否存在该订阅者 - /// 主题名称为空 - /// 订阅者为 null public bool ContainsSubscriber(string topic, Subscription subscriber) { - this.ValidateTopic(topic); + ValidateTopic(topic); this.m_lock.EnterReadLock(); try @@ -291,7 +275,6 @@ public class MqttBroker /// /// 获取所有存在的主题名称(返回只读副本) /// - /// 主题名称的只读集合 public IReadOnlyList GetAllTopics() { this.m_lock.EnterReadLock(); @@ -307,24 +290,29 @@ public class MqttBroker } /// - /// 获取指定主题的所有订阅者(返回只读副本,防止外部修改) + /// 获取指定主题的所有订阅者(支持通配符匹配) /// - /// 主题名称(非空) - /// 订阅者集合的只读副本(可能为空) - /// 主题名称为空 public IReadOnlyList GetSubscribers(string topic) { - this.ValidateTopic(topic); + ValidateTopic(topic); this.m_lock.EnterReadLock(); try { - if (this.m_topicToSubscribers.TryGetValue(topic, out var subscribers)) + var matchingSubscriptions = new List(); + + foreach (var kv in this.m_topicToSubscribers) { - // 返回副本确保线程安全(避免外部修改内部集合) - return subscribers.ToArray(); + var subscriptionTopic = kv.Key; + var subscribers = kv.Value; + + if (this.IsTopicMatch(topic, subscriptionTopic)) + { + matchingSubscriptions.AddRange(subscribers); + } } - return []; + + return matchingSubscriptions.ToArray(); } finally { @@ -335,14 +323,9 @@ public class MqttBroker /// /// 从指定主题移除订阅者 /// - /// 主题名称(非空) - /// 要移除的订阅者实例(非空) - /// 是否成功移除 - /// 主题名称为空 - /// 订阅者为 null public bool RemoveSubscriber(string topic, Subscription subscriber) { - this.ValidateTopic(topic); + ValidateTopic(topic); this.m_lock.EnterWriteLock(); try @@ -350,7 +333,6 @@ public class MqttBroker if (this.m_topicToSubscribers.TryGetValue(topic, out var subscribers)) { var isRemoved = subscribers.Remove(subscriber); - // 如果主题已无订阅者,清理空主题 if (isRemoved && subscribers.Count == 0) { this.m_topicToSubscribers.Remove(topic); @@ -365,17 +347,176 @@ public class MqttBroker } } - #region 辅助方法 + // topic 匹配逻辑,支持 + 和 # 通配符 + private bool IsTopicMatch(string publishTopic, string subscriptionTopic) + { + if (subscriptionTopic.IndexOf('+') < 0 && subscriptionTopic.IndexOf('#') < 0) + { + return string.Equals(publishTopic, subscriptionTopic, StringComparison.Ordinal); + } - private void ValidateTopic(string topic) + return this.CompareTopicWithWildcard(publishTopic, subscriptionTopic); + } + + private bool CompareTopicWithWildcard(ReadOnlySpan topic, ReadOnlySpan filter) + { + const char LevelSeparator = '/'; + const char MultiLevelWildcard = '#'; + const char SingleLevelWildcard = '+'; + const char ReservedTopicPrefix = '$'; + + if (topic.IsEmpty || filter.IsEmpty) + { + return false; + } + + var filterOffset = 0; + var filterLength = filter.Length; + var topicOffset = 0; + var topicLength = topic.Length; + + // 检查过滤器长度 + if (filterLength > topicLength) + { + var lastFilterChar = filter[filterLength - 1]; + if (lastFilterChar != MultiLevelWildcard && lastFilterChar != SingleLevelWildcard) + { + return false; + } + } + + var isMultiLevelFilter = filter[filterLength - 1] == MultiLevelWildcard; + var isReservedTopic = topic[0] == ReservedTopicPrefix; + + // 保留主题的特殊规则 + if (isReservedTopic && filterLength == 1 && isMultiLevelFilter) + { + return false; // 不允许用 '#' 订阅 '$foo/bar' + } + + if (isReservedTopic && filter[0] == SingleLevelWildcard) + { + return false; // 不允许用 '+/monitor/Clients' 订阅 '$SYS/monitor/Clients' + } + + if (filterLength == 1 && isMultiLevelFilter) + { + return true; // '#' 匹配所有内容 + } + + // 逐字符比较 + while (filterOffset < filterLength && topicOffset < topicLength) + { + // 检查多级通配符是否在最后位置 + if (filter[filterOffset] == MultiLevelWildcard && filterOffset != filterLength - 1) + { + return false; // 多级通配符只能在最后 + } + + if (filter[filterOffset] == topic[topicOffset]) + { + if (topicOffset == topicLength - 1) + { + if (filterOffset == filterLength - 3 && filter[filterOffset + 1] == LevelSeparator && isMultiLevelFilter) + { + return true; + } + + if (filterOffset == filterLength - 2 && filter[filterOffset] == LevelSeparator && isMultiLevelFilter) + { + return true; + } + } + + filterOffset++; + topicOffset++; + + // 检查是否完全匹配 + if (filterOffset == filterLength && topicOffset == topicLength) + { + return true; + } + + var endOfTopic = topicOffset == topicLength; + + if (endOfTopic && filterOffset == filterLength - 1 && filter[filterOffset] == SingleLevelWildcard) + { + if (filterOffset > 0 && filter[filterOffset - 1] != LevelSeparator) + { + return false; + } + return true; + } + } + else + { + if (filter[filterOffset] == SingleLevelWildcard) + { + // 检查单级通配符的有效性 + if (filterOffset > 0 && filter[filterOffset - 1] != LevelSeparator) + { + return false; // 无效的 "+foo" 或 "a/+foo" + } + + if (filterOffset < filterLength - 1 && filter[filterOffset + 1] != LevelSeparator) + { + return false; // 无效的 "foo+" 或 "foo+/a" + } + + filterOffset++; + // 跳过主题中的当前级别 + while (topicOffset < topicLength && topic[topicOffset] != LevelSeparator) + { + topicOffset++; + } + + if (topicOffset == topicLength && filterOffset == filterLength) + { + return true; + } + } + else if (filter[filterOffset] == MultiLevelWildcard) + { + if (filterOffset > 0 && filter[filterOffset - 1] != LevelSeparator) + { + return false; + } + + if (filterOffset + 1 != filterLength) + { + return false; + } + + return true; // 多级通配符匹配剩余所有内容 + } + else + { + // 检查 "foo/bar" 匹配 "foo/+/#" + if (filterOffset > 0 && + filterOffset + 2 == filterLength && + topicOffset == topicLength && + filter[filterOffset - 1] == SingleLevelWildcard && + filter[filterOffset] == LevelSeparator && + isMultiLevelFilter) + { + return true; + } + + return false; + } + } + } + + return false; + } + + private static void ValidateTopic(string topic) { if (string.IsNullOrWhiteSpace(topic)) { throw new ArgumentException("主题名称不能为 null 或空白", nameof(topic)); } } - - #endregion 辅助方法 } #endregion Class diff --git a/src/TouchSocket.Mqtt/Common/MqttArrivedMessage.cs b/src/TouchSocket.Mqtt/Common/MqttArrivedMessage.cs index 9b66d7310..d459db492 100644 --- a/src/TouchSocket.Mqtt/Common/MqttArrivedMessage.cs +++ b/src/TouchSocket.Mqtt/Common/MqttArrivedMessage.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Mqtt; /// diff --git a/src/TouchSocket.Mqtt/Common/MqttSessionActorResult.cs b/src/TouchSocket.Mqtt/Common/MqttSessionActorResult.cs index d831d2f1e..8afd6ccea 100644 --- a/src/TouchSocket.Mqtt/Common/MqttSessionActorResult.cs +++ b/src/TouchSocket.Mqtt/Common/MqttSessionActorResult.cs @@ -12,7 +12,7 @@ namespace TouchSocket.Mqtt; -public struct MqttSessionActorResult +public readonly struct MqttSessionActorResult { public MqttSessionActorResult(MqttSessionActor sessionActor, bool isNew) { diff --git a/src/TouchSocket.Mqtt/Common/MqttUtility.cs b/src/TouchSocket.Mqtt/Common/MqttUtility.cs index eefb68b22..b49f38682 100644 --- a/src/TouchSocket.Mqtt/Common/MqttUtility.cs +++ b/src/TouchSocket.Mqtt/Common/MqttUtility.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Text; - namespace TouchSocket.Mqtt; public static class MqttUtility diff --git a/src/TouchSocket.Mqtt/Common/SubscribeRequest.cs b/src/TouchSocket.Mqtt/Common/SubscribeRequest.cs index 0d0ee7cc7..722527ada 100644 --- a/src/TouchSocket.Mqtt/Common/SubscribeRequest.cs +++ b/src/TouchSocket.Mqtt/Common/SubscribeRequest.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Mqtt; public readonly record struct SubscribeRequest diff --git a/src/TouchSocket.Mqtt/Common/VariableByteIntegerRecorder.cs b/src/TouchSocket.Mqtt/Common/VariableByteIntegerRecorder.cs index 43b02feea..5c0f731ba 100644 --- a/src/TouchSocket.Mqtt/Common/VariableByteIntegerRecorder.cs +++ b/src/TouchSocket.Mqtt/Common/VariableByteIntegerRecorder.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using TouchSocket.Core; - namespace TouchSocket.Mqtt; internal ref struct VariableByteIntegerRecorder @@ -21,41 +18,37 @@ internal ref struct VariableByteIntegerRecorder private int m_minimumCount; private int m_startPosition; - public void CheckOut(ref TByteBlock byteBlock, int minimum = 0) - where TByteBlock : IByteBlock + public void CheckOut(ref BytesWriter writer, int minimum = 0) + { this.m_minimumCount = MqttExtension.GetVariableByteIntegerCount(minimum); - this.m_startPosition = byteBlock.Position; - byteBlock.Position += this.m_minimumCount; - this.m_dataPosition = byteBlock.Position; + this.m_startPosition = writer.Position; + writer.Position += this.m_minimumCount; + this.m_dataPosition = writer.Position; } - public readonly int CheckIn(ref TByteBlock byteBlock) - where TByteBlock : IByteBlock + public readonly int CheckIn(ref BytesWriter writer) + { - var endPosition = byteBlock.Position; + var endPosition = writer.Position; var len = endPosition - this.m_dataPosition; var lenCount = MqttExtension.GetVariableByteIntegerCount(len); if (lenCount > this.m_minimumCount) { var moveCount = lenCount - this.m_minimumCount; - //len += moveCount; - - byteBlock.ExtendSize(moveCount); - var span = byteBlock.TotalMemory.Span.Slice(this.m_dataPosition); + var span = writer.TotalMemory.Span.Slice(this.m_dataPosition); ShiftWithRight(span, moveCount); - byteBlock.Position = this.m_startPosition; - MqttExtension.WriteVariableByteInteger(ref byteBlock, (uint)len); - byteBlock.SetLength(endPosition+ moveCount); - byteBlock.SeekToEnd(); + writer.Position = this.m_startPosition; + MqttExtension.WriteVariableByteInteger(ref writer, (uint)len); + writer.Position = (endPosition + moveCount); } else { - byteBlock.Position = this.m_startPosition; - MqttExtension.WriteVariableByteInteger(ref byteBlock, (uint)len); - byteBlock.SeekToEnd(); + writer.Position = this.m_startPosition; + MqttExtension.WriteVariableByteInteger(ref writer, (uint)len); + writer.Position = endPosition; } return len; } @@ -68,4 +61,63 @@ internal ref struct VariableByteIntegerRecorder span[i] = span[i - shiftCount]; } } -} \ No newline at end of file +} + +//internal ref struct VariableByteIntegerRecorder +//{ +// private int m_dataPosition; +// private int m_minimumCount; +// private int m_startPosition; + +// public void CheckOut(ref TWriter writer, int minimum = 0) +// where TWriter : IByteBlockWriter +//#if AllowsRefStruct +//,allows ref struct +//#endif +// { +// this.m_minimumCount = MqttExtension.GetVariableByteIntegerCount(minimum); +// this.m_startPosition = writer.Position; +// writer.Position += this.m_minimumCount; +// this.m_dataPosition = writer.Position; +// } + +// public readonly int CheckIn(ref TWriter writer) +// where TWriter : IByteBlockWriter +//#if AllowsRefStruct +//,allows ref struct +//#endif +// { +// var endPosition = writer.Position; + +// var len = endPosition - this.m_dataPosition; +// var lenCount = MqttExtension.GetVariableByteIntegerCount((int)len); +// if (lenCount > this.m_minimumCount) +// { +// var moveCount = lenCount - this.m_minimumCount; +// writer.ExtendSize(moveCount); +// var span = writer.TotalMemory.Span.Slice(this.m_dataPosition); +// ShiftWithRight(span, moveCount); + +// writer.Position = this.m_startPosition; +// MqttExtension.WriteVariableByteInteger(ref writer, (uint)len); +// writer.SetLength(endPosition + moveCount); +// writer.SeekToEnd(); +// } +// else +// { +// writer.Position = this.m_startPosition; +// MqttExtension.WriteVariableByteInteger(ref writer, (uint)len); +// writer.SeekToEnd(); +// } +// return len; +// } + +// private static void ShiftWithRight(Span span, int shiftCount) +// { +// var length = span.Length; +// for (var i = length - 1; i >= shiftCount; i--) +// { +// span[i] = span[i - shiftCount]; +// } +// } +//} \ No newline at end of file diff --git a/src/TouchSocket.Mqtt/Components/Base/Interfaces/IMqttClient.cs b/src/TouchSocket.Mqtt/Components/Base/Interfaces/IMqttClient.cs index 08d7e9115..8c5e7dc09 100644 --- a/src/TouchSocket.Mqtt/Components/Base/Interfaces/IMqttClient.cs +++ b/src/TouchSocket.Mqtt/Components/Base/Interfaces/IMqttClient.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 // ------------------------------------------------------------------------------ -using System.Threading; -using System.Threading.Tasks; using TouchSocket.Sockets; namespace TouchSocket.Mqtt; @@ -21,5 +19,34 @@ namespace TouchSocket.Mqtt; /// public interface IMqttClient : IMqttSession, IConnectableClient { - Task PingAsync(int timeout = 5000, CancellationToken token = default); + /// + /// 发送Ping请求。 + /// + /// 取消令牌。 + /// 操作结果。 + ValueTask PingAsync(CancellationToken cancellationToken = default); + + /// + /// 发布Mqtt消息。 + /// + /// 要发布的消息。 + /// 取消令牌。 + /// 异步任务。 + Task PublishAsync(MqttPublishMessage mqttMessage, CancellationToken cancellationToken = default); + + /// + /// 订阅主题。 + /// + /// 订阅消息。 + /// 取消令牌。 + /// 订阅确认消息。 + Task SubscribeAsync(MqttSubscribeMessage message, CancellationToken cancellationToken = default); + + /// + /// 取消订阅主题。 + /// + /// 取消订阅消息。 + /// 取消令牌。 + /// 取消订阅确认消息。 + Task UnsubscribeAsync(MqttUnsubscribeMessage message, CancellationToken cancellationToken = default); } diff --git a/src/TouchSocket.Mqtt/Components/Base/Interfaces/IMqttSession.cs b/src/TouchSocket.Mqtt/Components/Base/Interfaces/IMqttSession.cs index 0e693eaa7..ad82c1ef3 100644 --- a/src/TouchSocket.Mqtt/Components/Base/Interfaces/IMqttSession.cs +++ b/src/TouchSocket.Mqtt/Components/Base/Interfaces/IMqttSession.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 // ------------------------------------------------------------------------------ -using TouchSocket.Core; using TouchSocket.Sockets; namespace TouchSocket.Mqtt; diff --git a/src/TouchSocket.Mqtt/Components/Tcp/Interfaces/IMqttTcpClient.cs b/src/TouchSocket.Mqtt/Components/Tcp/Interfaces/IMqttTcpClient.cs index c82f96136..88ea42ac7 100644 --- a/src/TouchSocket.Mqtt/Components/Tcp/Interfaces/IMqttTcpClient.cs +++ b/src/TouchSocket.Mqtt/Components/Tcp/Interfaces/IMqttTcpClient.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 // ------------------------------------------------------------------------------ -using TouchSocket.Core; using TouchSocket.Sockets; namespace TouchSocket.Mqtt; diff --git a/src/TouchSocket.Mqtt/Components/Tcp/Interfaces/IMqttTcpService.cs b/src/TouchSocket.Mqtt/Components/Tcp/Interfaces/IMqttTcpService.cs index 1c2ba6e87..665cdc0f9 100644 --- a/src/TouchSocket.Mqtt/Components/Tcp/Interfaces/IMqttTcpService.cs +++ b/src/TouchSocket.Mqtt/Components/Tcp/Interfaces/IMqttTcpService.cs @@ -13,6 +13,7 @@ using TouchSocket.Sockets; namespace TouchSocket.Mqtt; + public interface IMqttTcpService : ITcpServiceBase { MqttBroker MqttBroker { get; } diff --git a/src/TouchSocket.Mqtt/Components/Tcp/MqttTcpClient.cs b/src/TouchSocket.Mqtt/Components/Tcp/MqttTcpClient.cs index bf77b24a1..9166cab47 100644 --- a/src/TouchSocket.Mqtt/Components/Tcp/MqttTcpClient.cs +++ b/src/TouchSocket.Mqtt/Components/Tcp/MqttTcpClient.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Sockets; namespace TouchSocket.Mqtt; @@ -21,6 +18,8 @@ public class MqttTcpClient : TcpClientBase, IMqttTcpClient { private readonly MqttClientActor m_mqttActor; + private MqttAdapter m_mqttAdapter; + public MqttTcpClient() { var actor = new MqttClientActor @@ -56,9 +55,24 @@ public class MqttTcpClient : TcpClientBase, IMqttTcpClient await this.PluginManager.RaiseAsync(typeof(IMqttReceivedPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - private Task PrivateMqttOnSend(MqttActor mqttActor, MqttMessage message) + private async Task PrivateMqttOnSend(MqttActor mqttActor, MqttMessage message, CancellationToken cancellationToken) { - return base.ProtectedSendAsync(message); + if (message.MessageType == MqttMessageType.Connect) + { + this.m_mqttAdapter.Version = message.Version; + } + var locker = base.Transport.WriteLocker; + await locker.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + try + { + var writer = new PipeBytesWriter(base.Transport.Writer); + message.Build(ref writer); + await writer.FlushAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + finally + { + locker.Release(); + } } #endregion MqttActor @@ -67,15 +81,15 @@ public class MqttTcpClient : TcpClientBase, IMqttTcpClient public override bool Online => base.Online && this.m_mqttActor.Online; /// - public override async Task CloseAsync(string msg, CancellationToken token = default) + public override async Task CloseAsync(string msg, CancellationToken cancellationToken = default) { try { if (this.Online) { - await this.m_mqttActor.DisconnectAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.m_mqttActor.DisconnectAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - await base.CloseAsync(msg, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await base.CloseAsync(msg, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return Result.Success; } @@ -86,7 +100,7 @@ public class MqttTcpClient : TcpClientBase, IMqttTcpClient } /// - public async Task ConnectAsync(int millisecondsTimeout, CancellationToken token) + public async Task ConnectAsync(CancellationToken cancellationToken) { if (this.Online) { @@ -94,42 +108,42 @@ public class MqttTcpClient : TcpClientBase, IMqttTcpClient } var mqttConnectOptions = this.Config.GetValue(MqttConfigExtension.MqttConnectOptionsProperty); - ThrowHelper.ThrowArgumentNullExceptionIf(mqttConnectOptions, nameof(mqttConnectOptions)); + ThrowHelper.ThrowIfNull(mqttConnectOptions, nameof(mqttConnectOptions)); var connectMessage = new MqttConnectMessage(mqttConnectOptions); await this.PluginManager.RaiseAsync(typeof(IMqttConnectingPlugin), this.Resolver, this, new MqttConnectingEventArgs(connectMessage, default)); - await base.TcpConnectAsync(millisecondsTimeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await base.TcpConnectAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - var connAckMessage = await this.m_mqttActor.ConnectAsync(connectMessage, millisecondsTimeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var connAckMessage = await this.m_mqttActor.ConnectAsync(connectMessage, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); await this.PluginManager.RaiseAsync(typeof(IMqttConnectedPlugin), this.Resolver, this, new MqttConnectedEventArgs(connectMessage, connAckMessage)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// - public async Task PingAsync(int timeout = 5000, CancellationToken token = default) + public ValueTask PingAsync(CancellationToken cancellationToken = default) { - await this.m_mqttActor.PingAsync(timeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return this.m_mqttActor.PingAsync(cancellationToken); } /// - public async Task PublishAsync(MqttPublishMessage mqttMessage) + public Task PublishAsync(MqttPublishMessage mqttMessage, CancellationToken cancellationToken = default) { - await this.m_mqttActor.PublishAsync(mqttMessage).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return this.m_mqttActor.PublishAsync(mqttMessage, cancellationToken); } /// - public async Task SubscribeAsync(MqttSubscribeMessage message) + public Task SubscribeAsync(MqttSubscribeMessage message, CancellationToken cancellationToken = default) { //订阅 - return await this.m_mqttActor.SubscribeAsync(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return this.m_mqttActor.SubscribeAsync(message, cancellationToken); } /// - public async Task UnsubscribeAsync(MqttUnsubscribeMessage message) + public Task UnsubscribeAsync(MqttUnsubscribeMessage message, CancellationToken cancellationToken = default) { - return await this.m_mqttActor.UnsubscribeAsync(message).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return this.m_mqttActor.UnsubscribeAsync(message, cancellationToken); } /// @@ -143,7 +157,8 @@ public class MqttTcpClient : TcpClientBase, IMqttTcpClient protected override async Task OnTcpConnecting(ConnectingEventArgs e) { await base.OnTcpConnecting(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.SetAdapter(new MqttAdapter()); + this.m_mqttAdapter = new MqttAdapter(); + this.SetAdapter(this.m_mqttAdapter); } /// @@ -153,7 +168,7 @@ public class MqttTcpClient : TcpClientBase, IMqttTcpClient { await this.PluginManager.RaiseAsync(typeof(IMqttReceivingPlugin), this.Resolver, this, new MqttReceivingEventArgs(mqttMessage)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - await this.m_mqttActor.InputMqttMessageAsync(mqttMessage).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.m_mqttActor.InputMqttMessageAsync(mqttMessage, CancellationToken.None).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } await base.OnTcpReceived(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } diff --git a/src/TouchSocket.Mqtt/Components/Tcp/MqttTcpSessionClient.cs b/src/TouchSocket.Mqtt/Components/Tcp/MqttTcpSessionClient.cs index 008275a40..95df23032 100644 --- a/src/TouchSocket.Mqtt/Components/Tcp/MqttTcpSessionClient.cs +++ b/src/TouchSocket.Mqtt/Components/Tcp/MqttTcpSessionClient.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Sockets; namespace TouchSocket.Mqtt; @@ -20,6 +18,7 @@ public class MqttTcpSessionClient : TcpSessionClientBase, IMqttTcpSessionClient { private readonly MqttBroker m_mqttBroker; private MqttSessionActor m_mqttActor; + private bool m_cleanSession; public MqttTcpSessionClient(MqttBroker mqttBroker) { @@ -42,7 +41,7 @@ public class MqttTcpSessionClient : TcpSessionClientBase, IMqttTcpSessionClient { if (e.ConnAckMessage.ReturnCode == MqttReasonCode.ConnectionAccepted) { - await this.ResetIdAsync(e.ConnectMessage.ClientId).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.ResetIdAsync(e.ConnectMessage.ClientId, CancellationToken.None).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } await this.PluginManager.RaiseAsync(typeof(IMqttConnectingPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); @@ -53,14 +52,25 @@ public class MqttTcpSessionClient : TcpSessionClientBase, IMqttTcpSessionClient await this.PluginManager.RaiseAsync(typeof(IMqttReceivedPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - private Task PrivateMqttOnSend(MqttActor mqttActor, MqttMessage message) + private async Task PrivateMqttOnSend(MqttActor mqttActor, MqttMessage message, CancellationToken cancellationToken) { - return base.ProtectedSendAsync(message); + var locker = base.Transport.WriteLocker; + await locker.WaitAsync(cancellationToken); + try + { + var writer = new PipeBytesWriter(base.Transport.Writer); + message.Build(ref writer); + await writer.FlushAsync(cancellationToken); + } + finally + { + locker.Release(); + } } #endregion MqttActor - public bool CleanSession { get; private set; } + public bool CleanSession => this.m_cleanSession; public MqttSessionActor MqttActor => this.m_mqttActor; /// @@ -74,15 +84,11 @@ public class MqttTcpSessionClient : TcpSessionClientBase, IMqttTcpSessionClient mqttActor.Connected = null; mqttActor.MessageArrived = null; mqttActor.Closing = null; + await mqttActor.Deactivate().ConfigureAwait(EasyTask.ContinueOnCapturedContext); if (this.CleanSession) { this.m_mqttBroker.RemoveMqttSessionActor(mqttActor); } - else - { - mqttActor.Deactivate(); - } - await this.PluginManager.RaiseAsync(typeof(IMqttClosedPlugin), this.Resolver, this, new MqttClosedEventArgs(e.Manual, e.Message)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } await base.OnTcpClosed(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); @@ -108,7 +114,7 @@ public class MqttTcpSessionClient : TcpSessionClientBase, IMqttTcpSessionClient return; } - this.CleanSession = mqttConnectMessage.CleanSession; + this.m_cleanSession = mqttConnectMessage.CleanSession; var actor = this.m_mqttBroker.GetOrCreateMqttSessionActor(mqttConnectMessage.ClientId); actor.OutputSendAsync = this.PrivateMqttOnSend; @@ -116,13 +122,13 @@ public class MqttTcpSessionClient : TcpSessionClientBase, IMqttTcpSessionClient actor.Connected = this.PrivateMqttOnConnected; actor.MessageArrived = this.PrivateMqttOnMessageArrived; actor.Closing = this.PrivateMqttOnClosing; - actor.Activate(); + actor.Activate(mqttConnectMessage); this.m_mqttActor = actor; } - await this.PluginManager.RaiseAsync(typeof(IMqttReceivingPlugin), this.Resolver, this, new MqttReceivingEventArgs(mqttMessage)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.PluginManager.RaiseIMqttReceivingPluginAsync(this.Resolver, this, new MqttReceivingEventArgs(mqttMessage)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - await this.m_mqttActor.InputMqttMessageAsync(mqttMessage).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.m_mqttActor.InputMqttMessageAsync(mqttMessage, CancellationToken.None).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } await base.OnTcpReceived(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } diff --git a/src/TouchSocket.Mqtt/EventArgs/MqttConnectedEventArgs.cs b/src/TouchSocket.Mqtt/EventArgs/MqttConnectedEventArgs.cs index 134f83e07..3e3e51331 100644 --- a/src/TouchSocket.Mqtt/EventArgs/MqttConnectedEventArgs.cs +++ b/src/TouchSocket.Mqtt/EventArgs/MqttConnectedEventArgs.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Mqtt; /// diff --git a/src/TouchSocket.Mqtt/EventArgs/MqttReceivedEventArgs.cs b/src/TouchSocket.Mqtt/EventArgs/MqttReceivedEventArgs.cs index 688c71e34..1443337d4 100644 --- a/src/TouchSocket.Mqtt/EventArgs/MqttReceivedEventArgs.cs +++ b/src/TouchSocket.Mqtt/EventArgs/MqttReceivedEventArgs.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Mqtt; /// diff --git a/src/TouchSocket.Mqtt/EventArgs/MqttReceivingEventArgs.cs b/src/TouchSocket.Mqtt/EventArgs/MqttReceivingEventArgs.cs index 5cea0e139..1f36a8b74 100644 --- a/src/TouchSocket.Mqtt/EventArgs/MqttReceivingEventArgs.cs +++ b/src/TouchSocket.Mqtt/EventArgs/MqttReceivingEventArgs.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Mqtt; /// diff --git a/src/TouchSocket.Mqtt/Extensions/MqttConfigExtension.cs b/src/TouchSocket.Mqtt/Extensions/MqttConfigExtension.cs index 93004aacb..f4049c10d 100644 --- a/src/TouchSocket.Mqtt/Extensions/MqttConfigExtension.cs +++ b/src/TouchSocket.Mqtt/Extensions/MqttConfigExtension.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using TouchSocket.Core; - namespace TouchSocket.Mqtt; /// @@ -23,19 +20,6 @@ public static class MqttConfigExtension /// /// Mqtt连接选项的依赖属性。 /// + [GeneratorProperty(TargetType = typeof(TouchSocketConfig), ActionMode = true)] public static readonly DependencyProperty MqttConnectOptionsProperty = new DependencyProperty("MqttConnectOptions", null); - - /// - /// 设置Mqtt连接选项。 - /// - /// TouchSocket配置。 - /// 用于配置Mqtt连接选项的操作。 - /// 更新后的TouchSocket配置。 - public static TouchSocketConfig SetMqttConnectOptions(this TouchSocketConfig config, Action options) - { - var mqttConnectOptions = new MqttConnectOptions(); - options?.Invoke(mqttConnectOptions); - config.SetValue(MqttConnectOptionsProperty, mqttConnectOptions); - return config; - } } \ No newline at end of file diff --git a/src/TouchSocket.Mqtt/Extensions/MqttExtension.cs b/src/TouchSocket.Mqtt/Extensions/MqttExtension.cs index 563dee67a..99dd78123 100644 --- a/src/TouchSocket.Mqtt/Extensions/MqttExtension.cs +++ b/src/TouchSocket.Mqtt/Extensions/MqttExtension.cs @@ -10,16 +10,15 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Text; -using TouchSocket.Core; - namespace TouchSocket.Mqtt; /// -/// 提供用于 Mqtt 操作的扩展方法。 +/// 提供用于MQTT协议操作的扩展方法集合。 /// +/// +/// 此类包含了MQTT协议中常用的数据读写、编码解码和协议处理相关的扩展方法, +/// 包括可变字节整数处理、固定头部操作、字符串读写、QoS级别处理以及MQTT v5.0属性操作等。 +/// public static class MqttExtension { #region ByteBlock @@ -27,39 +26,77 @@ public static class MqttExtension private const uint VariableByteIntegerMaxValue = 268435455; /// - /// 从字节块中读取 Mqtt Int16 字符串。 + /// 从字节读取器中读取MQTT Int16字符串。 /// - /// 字节块的类型。 - /// 要读取的字节块。 - /// 读取的字符串。 - public static string ReadMqttInt16String(ref TByteBlock byteBlock) - where TByteBlock : IByteBlock + /// 字节读取器的类型,必须实现接口。 + /// 要读取的字节读取器。 + /// 读取的字符串内容。 + /// + /// MQTT Int16字符串格式:前2个字节表示字符串长度(大端字节序),后跟UTF-8编码的字符串内容。 + /// + public static string ReadMqttInt16String(ref TReader reader) + where TReader : IBytesReader + { - return ReadMqttInt16String(ref byteBlock, out _); + return ReadMqttInt16String(ref reader, out _); } /// - /// 从字节块中读取 Mqtt Int16 字符串。 + /// 从字节读取器中读取MQTT Int16字符串,并输出字符串长度。 /// - /// 字节块的类型。 - /// 要读取的字节块。 - /// 读取的字符串长度。 - /// 读取的字符串。 - public static string ReadMqttInt16String(ref TByteBlock byteBlock, out ushort length) - where TByteBlock : IByteBlock + /// 字节读取器的类型,必须实现接口。 + /// 要读取的字节读取器。 + /// 输出参数,返回读取的字符串字节长度。 + /// 读取的字符串内容。 + /// + /// MQTT Int16字符串格式:前2个字节表示字符串长度(大端字节序),后跟UTF-8编码的字符串内容。 + /// 如果长度为0,则返回空字符串。 + /// + public static string ReadMqttInt16String(ref TReader reader, out ushort length) + where TReader : IBytesReader + { - length = byteBlock.ReadUInt16(EndianType.Big); - return byteBlock.ReadToSpan(length).ToString(Encoding.UTF8); + length = ReaderExtension.ReadValue(ref reader, EndianType.Big); + if (length == 0) + { + return string.Empty; + } + var span = reader.GetSpan(length).Slice(0, length); + + var str = span.ToString(Encoding.UTF8); + reader.Advance(length); + return str; + } + + public static ReadOnlyMemory ReadMqttInt16Memory(ref TReader reader) + where TReader : IBytesReader + + { + var length = ReaderExtension.ReadValue(ref reader, EndianType.Big); + if (length == 0) + { + return ReadOnlyMemory.Empty; + } + var span = reader.GetSpan(length).Slice(0, length); + var memory = new byte[length]; + span.CopyTo(memory); + reader.Advance(length); + return new ReadOnlyMemory(memory); } /// - /// 从字节块中读取可变字节整数。 + /// 从字节读取器中读取MQTT可变字节整数。 /// - /// 字节块的类型。 - /// 要读取的字节块。 - /// 读取的可变字节整数。 - public static uint ReadVariableByteInteger(ref TByteBlock byteBlock) - where TByteBlock : IByteBlock + /// 字节读取器的类型,必须实现接口。 + /// 要读取的字节读取器。 + /// 读取的可变字节整数值。 + /// + /// MQTT可变字节整数使用1-4个字节编码,每个字节的最高位表示是否有后续字节, + /// 低7位存储实际数据。最大值为268,435,455。 + /// + public static uint ReadVariableByteInteger(ref TReader reader) + where TReader : IBytesReader + { var multiplier = 1; var value = 0; @@ -67,7 +104,7 @@ public static class MqttExtension do { - encodedByte = byteBlock.ReadByte(); + encodedByte = ReaderExtension.ReadValue(ref reader); value += (encodedByte & 0x7F) * multiplier; multiplier *= 128; } while ((encodedByte & 0x80) == 0x80); @@ -76,49 +113,82 @@ public static class MqttExtension } /// - /// 将 Mqtt 固定报头写入字节块。 + /// 将MQTT固定头部写入字节写入器。 /// - /// 字节块的类型。 - /// 要写入的字节块。 - /// Mqtt 消息类型。 - /// 要写入的标志。 - public static void WriteMqttFixedHeader(ref TByteBlock byteBlock, MqttMessageType packetType, byte flags = 0) - where TByteBlock : IByteBlock + /// 字节写入器的类型,必须实现接口。 + /// 要写入的字节写入器。 + /// MQTT消息类型。 + /// 要写入的控制标志,默认为0。 + /// + /// MQTT固定头部的第一个字节由消息类型(高4位)和控制标志(低4位)组成。 + /// + public static void WriteMqttFixedHeader(ref TWriter writer, MqttMessageType packetType, byte flags = 0) + where TWriter : IBytesWriter + { var fixedHeader = (int)packetType << 4; fixedHeader |= flags; - byteBlock.WriteByte((byte)fixedHeader); + WriterExtension.WriteValue(ref writer, (byte)fixedHeader); } /// - /// 将 Mqtt Int16 字符串写入字节块。 + /// 将字符串以MQTT Int16格式写入字节写入器。 /// - /// 字节块的类型。 - /// 要写入的字节块。 + /// 字节写入器的类型,必须实现接口。 + /// 要写入的字节写入器。 /// 要写入的字符串值。 - /// 写入的字符串长度。 - public static ushort WriteMqttInt16String(ref TByteBlock byteBlock, string value) - where TByteBlock : IByteBlock + /// 写入的字符串字节长度。 + /// + /// MQTT Int16字符串格式:先写入2字节的长度值(大端字节序),再写入UTF-8编码的字符串内容。 + /// + public static ushort WriteMqttInt16String(ref TWriter writer, string value) + where TWriter : IBytesWriter + { - var pos = byteBlock.Position; - byteBlock.Position += 2; - byteBlock.WriteNormalString(value, Encoding.UTF8); - var lastPos = byteBlock.Position; - var len = byteBlock.Position - pos - 2; - byteBlock.Position = pos; - byteBlock.WriteUInt16((ushort)len, EndianType.Big); - byteBlock.Position = lastPos; + var pos = writer.WrittenCount; + var span = writer.GetSpan(2); + writer.Advance(2); + + WriterExtension.WriteNormalString(ref writer, value, Encoding.UTF8); + var len = writer.WrittenCount - pos - 2; + span.WriteValue((ushort)len, EndianType.Big); return (ushort)len; } /// - /// 将可变字节整数写入字节块。 + /// 以MQTT Int16格式将二进制数据写入字节写入器。 /// - /// 字节块的类型。 - /// 要写入的字节块。 - /// 要写入的值。 - public static void WriteVariableByteInteger(ref TByteBlock byteBlock, uint value) - where TByteBlock : IByteBlock + /// 字节写入器类型,需实现接口。 + /// 要写入的字节写入器。 + /// 要写入的二进制数据。 + /// 写入的字节长度。 + public static ushort WriteMqttInt16Memory(ref TWriter writer, ReadOnlyMemory value) + where TWriter : IBytesWriter + + { + var pos = writer.WrittenCount; + var span = writer.GetSpan(2); + writer.Advance(2); + writer.Write(value.Span); + var len = writer.WrittenCount - pos - 2; + span.WriteValue((ushort)len, EndianType.Big); + return (ushort)len; + } + + /// + /// 将可变字节整数写入字节写入器。 + /// + /// 字节写入器的类型,必须实现接口。 + /// 要写入的字节写入器。 + /// 要写入的整数值。 + /// 当值超过268,435,455时抛出。 + /// + /// MQTT可变字节整数使用1-4个字节编码,每个字节的最高位表示是否有后续字节, + /// 低7位存储实际数据。最大值为268,435,455。 + /// + public static void WriteVariableByteInteger(ref TWriter writer, uint value) + where TWriter : IBytesWriter + { if (value > VariableByteIntegerMaxValue) { @@ -132,17 +202,17 @@ public static class MqttExtension { encodedByte |= 128; } - byteBlock.WriteByte(encodedByte); + WriterExtension.WriteValue(ref writer, encodedByte); } while (value > 0); } #endregion ByteBlock /// - /// 获取字节的高 4 位。 + /// 获取字节的高4位。 /// - /// 要获取高 4 位的字节。 - /// 高 4 位。 + /// 要获取高4位的字节。 + /// 字节的高4位值(0-15)。 public static int GetHeight4(this byte @byte) { int height; @@ -151,10 +221,10 @@ public static class MqttExtension } /// - /// 获取字节的低 4 位。 + /// 获取字节的低4位。 /// - /// 要获取低 4 位的字节。 - /// 低 4 位。 + /// 要获取低4位的字节。 + /// 字节的低4位值(0-15)。 public static int GetLow4(this byte @byte) { int low; @@ -163,10 +233,14 @@ public static class MqttExtension } /// - /// 获取 Mqtt Int16 字符串的长度。 + /// 获取MQTT Int16字符串在传输时所需的总字节长度。 /// /// 字符串值。 - /// 字符串的长度。 + /// 字符串的总传输长度(包括2字节长度前缀)。 + /// + /// 返回值包括2字节的长度前缀和UTF-8编码后的字符串内容长度。 + /// 如果字符串为空或null,则返回2(仅长度前缀)。 + /// public static ushort GetMqttInt16StringLength(string value) { if (value.IsNullOrEmpty()) @@ -176,6 +250,12 @@ public static class MqttExtension return (ushort)(Encoding.UTF8.GetByteCount(value) + 2); } + /// + /// 获取可变字节整数编码后所需的字节数。 + /// + /// 要编码的整数值。 + /// 编码后需要的字节数(1-4字节)。 + /// 当值超过268,435,455时抛出。 public static int GetVariableByteIntegerCount(int value) { if (value > VariableByteIntegerMaxValue) @@ -201,11 +281,24 @@ public static class MqttExtension #region RetainHandling + /// + /// 从字节的指定位置获取保留消息处理方式。 + /// + /// 包含保留处理信息的字节。 + /// 起始位索引。 + /// 保留消息处理方式。 public static MqttRetainHandling GetRetainHandling(this byte @byte, int index) { return (MqttRetainHandling)((@byte.GetBit(index + 1) ? 1 : 0) * 2 + (@byte.GetBit(index) ? 1 : 0)); } + /// + /// 在字节的指定位置设置保留消息处理方式。 + /// + /// 要设置的字节引用。 + /// 起始位索引。 + /// 要设置的保留消息处理方式。 + /// 设置后的字节值。 public static byte SetRetainHandling(this ref byte b, int bitNumber, MqttRetainHandling value) { b = (byte)(b & ~(3 << bitNumber) | (byte)value << bitNumber); @@ -215,64 +308,69 @@ public static class MqttExtension #endregion RetainHandling /// - /// 获取最大 QoS 级别。 + /// 获取两个QoS级别中的最大值。 /// - /// 第一个 QoS 级别。 - /// 第二个 QoS 级别。 - /// 最大 QoS 级别。 + /// 第一个QoS级别。 + /// 第二个QoS级别。 + /// 两个QoS级别中的最大值。 public static QosLevel MaxQosLevel(QosLevel qosLevel1, QosLevel qosLevel2) { return qosLevel1 > qosLevel2 ? qosLevel1 : qosLevel2; } /// - /// 获取最小 QoS 级别。 + /// 获取两个QoS级别中的最小值。 /// - /// 第一个 QoS 级别。 - /// 第二个 QoS 级别。 - /// 最小 QoS 级别。 + /// 第一个QoS级别。 + /// 第二个QoS级别。 + /// 两个QoS级别中的最小值。 public static QosLevel MinQosLevel(QosLevel qosLevel1, QosLevel qosLevel2) { return qosLevel1 < qosLevel2 ? qosLevel1 : qosLevel2; } /// - /// 从字节块中读取 Mqtt 二进制数据。 + /// 从字节读取器中读取MQTT二进制数据。 /// - /// 字节块的类型。 - /// 要读取的字节块。 + /// 字节读取器的类型,必须实现接口。 + /// 要读取的字节读取器。 /// 读取的二进制数据。 - public static byte[] ReadMqttBinaryData(ref TByteBlock byteBlock) where TByteBlock : IByteBlock + /// 当读取的字节数与预期长度不匹配时抛出。 + /// + /// MQTT二进制数据格式:前2个字节表示数据长度(大端字节序),后跟二进制数据内容。 + /// + public static ReadOnlyMemory ReadMqttBinaryData(ref TReader reader) where TReader : IBytesReader { - var length = byteBlock.ReadUInt16(EndianType.Big); + var length = ReaderExtension.ReadValue(ref reader, EndianType.Big); var data = new byte[length]; - var r = byteBlock.Read(data); + var r = reader.Read(data); if (r != length) { - throw new Exception(); + throw new Exception($"Expected {length} bytes, but received {r} bytes."); } - return data; + return new ReadOnlyMemory(data); } #region QosLevel /// - /// 从字节的指定索引获取 QoS 级别。 + /// 从字节的指定索引获取QoS级别。 /// - /// 要获取 QoS 级别的字节。 - /// 要获取 QoS 级别的索引。 - /// QoS 级别。 + /// 包含QoS信息的字节。 + /// 起始位索引。 + /// 解析出的QoS级别。 public static QosLevel GetQosLevel(this byte @byte, int index) { return (QosLevel)((@byte.GetBit(index + 1) ? 1 : 0) * 2 + (@byte.GetBit(index) ? 1 : 0)); } /// - /// 在指定位号设置字节中的 QoS 级别。 + /// 在字节的指定位置设置QoS级别。 /// - /// 要设置 QoS 级别的字节。 - /// 要设置 QoS 级别的位号。 - /// 要设置的 QoS 级别。 + /// 要设置QoS级别的字节引用。 + /// 起始位索引。 + /// 要设置的QoS级别。 + /// 设置后的字节值。 public static byte SetQosLevel(this ref byte b, int bitNumber, QosLevel value) { b = (byte)(b & ~(3 << bitNumber) | (byte)value << bitNumber); @@ -282,11 +380,14 @@ public static class MqttExtension #endregion QosLevel /// - /// 将缓冲区转换为 Mqtt Int16 值。 + /// 将字节数组转换为MQTT Int16值。 /// - /// 要转换的缓冲区。 + /// 包含数据的字节数组。 /// 开始转换的偏移量。 - /// 转换后的 Int16 值。 + /// 转换后的Int16值。 + /// + /// 按照大端字节序从指定偏移量读取2个字节并转换为Int16值。 + /// public static short ToMqttInt16(this byte[] buffer, int offset) { var data = new byte[] { buffer[offset + 1], buffer[offset] }; @@ -295,323 +396,479 @@ public static class MqttExtension #region MqttV5Properties - public static void WriteAssignedClientIdentifier(ref TByteBlock byteBlock, string value) where TByteBlock : IByteBlock + /// + /// 写入分配的客户端标识符属性。 + /// + /// 字节写入器的类型,必须实现接口。 + /// 要写入的字节写入器。 + /// 分配的客户端标识符值。 + public static void WriteAssignedClientIdentifier(ref TWriter writer, string value) + where TWriter : IBytesWriter + { - WriteStringProperty(ref byteBlock, MqttPropertyId.AssignedClientIdentifier, value); + WriteStringProperty(ref writer, MqttPropertyId.AssignedClientIdentifier, value); } /// - /// 写入认证数据。 + /// 写入认证数据属性。 /// - /// 字节块的类型。 - /// 要写入的字节块。 - /// 认证数据的值。 - public static void WriteAuthenticationData(ref TByteBlock byteBlock, ReadOnlySpan value) - where TByteBlock : IByteBlock + /// 字节写入器的类型,必须实现接口。 + /// 要写入的字节写入器。 + /// 认证数据的字节值。 + /// + /// 如果认证数据为空,则不写入任何内容。 + /// + public static void WriteAuthenticationData(ref TWriter writer, ReadOnlySpan value) + where TWriter : IBytesWriter + { if (value.IsEmpty) { return; } - byteBlock.WriteByte((byte)MqttPropertyId.AuthenticationData); - byteBlock.WriteUInt16((ushort)value.Length, EndianType.Big); - byteBlock.Write(value); + WriterExtension.WriteValue(ref writer, (byte)MqttPropertyId.AuthenticationData); + WriterExtension.WriteValue(ref writer, (ushort)value.Length, EndianType.Big); + writer.Write(value); } /// - /// 写入认证方法。 + /// 写入认证方法属性。 /// - /// 字节块的类型。 - /// 要写入的字节块。 - /// 认证方法的值。 - public static void WriteAuthenticationMethod(ref TByteBlock byteBlock, string value) - where TByteBlock : IByteBlock + /// 字节写入器的类型,必须实现接口。 + /// 要写入的字节写入器。 + /// 认证方法的字符串值。 + public static void WriteAuthenticationMethod(ref TWriter writer, string value) + where TWriter : IBytesWriter + { - WriteStringProperty(ref byteBlock, MqttPropertyId.AuthenticationMethod, value); + WriteStringProperty(ref writer, MqttPropertyId.AuthenticationMethod, value); } /// - /// 写入内容类型。 + /// 写入内容类型属性。 /// - /// 字节块的类型。 - /// 要写入的字节块。 - /// 内容类型的值。 - public static void WriteContentType(ref TByteBlock byteBlock, string value) - where TByteBlock : IByteBlock + /// 字节写入器的类型,必须实现接口。 + /// 要写入的字节写入器。 + /// 内容类型的字符串值。 + public static void WriteContentType(ref TWriter writer, string value) + where TWriter : IBytesWriter + { - WriteStringProperty(ref byteBlock, MqttPropertyId.ContentType, value); + WriteStringProperty(ref writer, MqttPropertyId.ContentType, value); } /// - /// 写入相关性数据。 + /// 写入相关性数据属性。 /// - /// 字节块的类型。 - /// 要写入的字节块。 - /// 相关性数据的值。 - public static void WriteCorrelationData(ref TByteBlock byteBlock, ReadOnlySpan value) - where TByteBlock : IByteBlock + /// 字节写入器的类型,必须实现接口。 + /// 要写入的字节写入器。 + /// 相关性数据的字节值。 + /// + /// 如果相关性数据为空,则不写入任何内容。 + /// + public static void WriteCorrelationData(ref TWriter writer, ReadOnlySpan value) + where TWriter : IBytesWriter + { if (value.IsEmpty) { return; } - byteBlock.WriteByte((byte)MqttPropertyId.CorrelationData); - byteBlock.WriteUInt16((ushort)value.Length, EndianType.Big); - byteBlock.Write(value); + WriterExtension.WriteValue(ref writer, (byte)MqttPropertyId.CorrelationData); + WriterExtension.WriteValue(ref writer, (ushort)value.Length, EndianType.Big); + writer.Write(value); } /// - /// 写入最大数据包大小。 + /// 写入最大数据包大小属性。 /// - /// 字节块的类型。 - /// 要写入的字节块。 - /// 最大数据包大小的值。 - public static void WriteMaximumPacketSize(ref TByteBlock byteBlock, uint value) - where TByteBlock : IByteBlock + /// 字节写入器的类型,必须实现接口。 + /// 要写入的字节写入器。 + /// 最大数据包大小值。 + /// + /// 如果值为0,则不写入任何内容。 + /// + public static void WriteMaximumPacketSize(ref TWriter writer, uint value) + where TWriter : IBytesWriter + { if (value == 0) { return; } - byteBlock.WriteByte((byte)MqttPropertyId.MaximumPacketSize); - byteBlock.WriteUInt32(value, EndianType.Big); + WriterExtension.WriteValue(ref writer, (byte)MqttPropertyId.MaximumPacketSize); + WriterExtension.WriteValue(ref writer, value, EndianType.Big); } - public static void WriteMaximumQoS(ref TByteBlock byteBlock, QosLevel value) where TByteBlock : IByteBlock + /// + /// 写入最大QoS属性。 + /// + /// 字节写入器的类型,必须实现接口。 + /// 要写入的字节写入器。 + /// 最大QoS级别值。 + /// + /// 如果值为,则不写入任何内容。 + /// + public static void WriteMaximumQoS(ref TWriter writer, QosLevel value) + where TWriter : IBytesWriter + { if (value == QosLevel.ExactlyOnce) { return; } - byteBlock.WriteByte((byte)MqttPropertyId.MaximumQoS); - byteBlock.WriteByte((byte)value); + WriterExtension.WriteValue(ref writer, (byte)MqttPropertyId.MaximumQoS); + WriterExtension.WriteValue(ref writer, (byte)value); } /// - /// 写入消息过期间隔。 + /// 写入消息过期间隔属性。 /// - /// 字节块的类型。 - /// 要写入的字节块。 - /// 消息过期间隔的值。 - public static void WriteMessageExpiryInterval(ref TByteBlock byteBlock, uint value) - where TByteBlock : IByteBlock + /// 字节写入器的类型,必须实现接口。 + /// 要写入的字节写入器。 + /// 消息过期间隔值(秒)。 + /// + /// 如果值为0,则不写入任何内容。 + /// + public static void WriteMessageExpiryInterval(ref TWriter writer, uint value) + where TWriter : IBytesWriter + { if (value == 0) { return; } - byteBlock.WriteByte((byte)MqttPropertyId.MessageExpiryInterval); - byteBlock.WriteUInt32(value, EndianType.Big); + WriterExtension.WriteValue(ref writer, (byte)MqttPropertyId.MessageExpiryInterval); + WriterExtension.WriteValue(ref writer, value, EndianType.Big); } /// - /// 写入负载格式指示符。 + /// 写入负载格式指示符属性。 /// - /// 字节块的类型。 - /// 要写入的字节块。 - /// 负载格式指示符的值。 - public static void WritePayloadFormatIndicator(ref TByteBlock byteBlock, MqttPayloadFormatIndicator value) - where TByteBlock : IByteBlock + /// 字节写入器的类型,必须实现接口。 + /// 要写入的字节写入器。 + /// 负载格式指示符值。 + /// + /// 如果值为,则不写入任何内容。 + /// + public static void WritePayloadFormatIndicator(ref TWriter writer, MqttPayloadFormatIndicator value) + where TWriter : IBytesWriter + { if (value == MqttPayloadFormatIndicator.Unspecified) { return; } - byteBlock.WriteByte((byte)MqttPropertyId.PayloadFormatIndicator); - byteBlock.WriteByte((byte)value); - } - - public static void WriteReasonString(ref TByteBlock byteBlock, string value) where TByteBlock : IByteBlock - { - WriteStringProperty(ref byteBlock, MqttPropertyId.ReasonString, value); + WriterExtension.WriteValue(ref writer, (byte)MqttPropertyId.PayloadFormatIndicator); + WriterExtension.WriteValue(ref writer, (byte)value); } /// - /// 写入接收最大值。 + /// 写入原因字符串属性。 /// - /// 字节块的类型。 - /// 要写入的字节块。 - /// 接收最大值的值。 - public static void WriteReceiveMaximum(ref TByteBlock byteBlock, ushort value) - where TByteBlock : IByteBlock + /// 字节写入器的类型,必须实现接口。 + /// 要写入的字节写入器。 + /// 原因字符串值。 + public static void WriteReasonString(ref TWriter writer, string value) + where TWriter : IBytesWriter + + { + WriteStringProperty(ref writer, MqttPropertyId.ReasonString, value); + } + + /// + /// 写入接收最大值属性。 + /// + /// 字节写入器的类型,必须实现接口。 + /// 要写入的字节写入器。 + /// 接收最大值。 + /// + /// 如果值为0,则不写入任何内容。 + /// + public static void WriteReceiveMaximum(ref TWriter writer, ushort value) + where TWriter : IBytesWriter + { if (value == 0) { return; } - byteBlock.WriteByte((byte)MqttPropertyId.ReceiveMaximum); - byteBlock.WriteUInt16(value, EndianType.Big); + WriterExtension.WriteValue(ref writer, (byte)MqttPropertyId.ReceiveMaximum); + WriterExtension.WriteValue(ref writer, value, EndianType.Big); } /// - /// 写入请求问题信息。 + /// 写入请求问题信息属性(布尔值版本)。 /// - /// 字节块的类型。 - /// 要写入的字节块。 - /// 请求问题信息的值。 - public static void WriteRequestProblemInformation(ref TByteBlock byteBlock, bool value) - where TByteBlock : IByteBlock + /// 字节写入器的类型,必须实现接口。 + /// 要写入的字节写入器。 + /// 是否请求问题信息。 + /// + /// 只有当值为时才写入属性。 + /// + public static void WriteRequestProblemInformation(ref TWriter writer, bool value) + where TWriter : IBytesWriter + { if (value) { - byteBlock.WriteByte((byte)MqttPropertyId.RequestProblemInformation); - byteBlock.WriteByte(1); + WriterExtension.WriteValue(ref writer, (byte)MqttPropertyId.RequestProblemInformation); + WriterExtension.WriteValue(ref writer, 1); } } /// - /// 写入请求问题信息。 + /// 写入请求问题信息属性(整数值版本)。 /// - /// 字节块的类型。 - /// 要写入的字节块。 - /// 请求问题信息的值。 - public static void WriteRequestProblemInformation(ref TByteBlock byteBlock, uint value) - where TByteBlock : IByteBlock + /// 字节写入器的类型,必须实现接口。 + /// 要写入的字节写入器。 + /// 请求问题信息的整数值。 + /// + /// 如果值为0,则不写入任何内容。 + /// + public static void WriteRequestProblemInformation(ref TWriter writer, uint value) + where TWriter : IBytesWriter + { if (value == 0) { return; } - byteBlock.WriteByte((byte)MqttPropertyId.RequestProblemInformation); - byteBlock.WriteUInt32(value, EndianType.Big); + WriterExtension.WriteValue(ref writer, (byte)MqttPropertyId.RequestProblemInformation); + WriterExtension.WriteValue(ref writer, value, EndianType.Big); } /// - /// 写入请求响应信息。 + /// 写入请求响应信息属性(布尔值版本)。 /// - /// 字节块的类型。 - /// 要写入的字节块。 - /// 请求响应信息的值。 - public static void WriteRequestResponseInformation(ref TByteBlock byteBlock, bool value) - where TByteBlock : IByteBlock + /// 字节写入器的类型,必须实现接口。 + /// 要写入的字节写入器。 + /// 是否请求响应信息。 + /// + /// 只有当值为时才写入属性。 + /// + public static void WriteRequestResponseInformation(ref TWriter writer, bool value) + where TWriter : IBytesWriter + { if (value) { - byteBlock.WriteByte((byte)MqttPropertyId.RequestResponseInformation); - byteBlock.WriteByte(1); + WriterExtension.WriteValue(ref writer, (byte)MqttPropertyId.RequestResponseInformation); + WriterExtension.WriteValue(ref writer, 1); } } /// - /// 写入请求响应信息。 + /// 写入请求响应信息属性(整数值版本)。 /// - /// 字节块的类型。 - /// 要写入的字节块。 - /// 请求响应信息的值。 - public static void WriteRequestResponseInformation(ref TByteBlock byteBlock, uint value) - where TByteBlock : IByteBlock + /// 字节写入器的类型,必须实现接口。 + /// 要写入的字节写入器。 + /// 请求响应信息的整数值。 + /// + /// 如果值为0,则不写入任何内容。 + /// + public static void WriteRequestResponseInformation(ref TWriter writer, uint value) + where TWriter : IBytesWriter + { if (value == 0) { return; } - byteBlock.WriteByte((byte)MqttPropertyId.RequestResponseInformation); - byteBlock.WriteUInt32(value, EndianType.Big); - } - - public static void WriteResponseInformation(ref TByteBlock byteBlock, string value) where TByteBlock : IByteBlock - { - WriteStringProperty(ref byteBlock, MqttPropertyId.ResponseInformation, value); + WriterExtension.WriteValue(ref writer, (byte)MqttPropertyId.RequestResponseInformation); + WriterExtension.WriteValue(ref writer, value, EndianType.Big); } /// - /// 写入响应主题。 + /// 写入响应信息属性。 /// - /// 字节块的类型。 - /// 要写入的字节块。 - /// 响应主题的值。 - public static void WriteResponseTopic(ref TByteBlock byteBlock, string value) - where TByteBlock : IByteBlock + /// 字节写入器的类型,必须实现接口。 + /// 要写入的字节写入器。 + /// 响应信息字符串值。 + public static void WriteResponseInformation(ref TWriter writer, string value) + where TWriter : IBytesWriter + { - WriteStringProperty(ref byteBlock, MqttPropertyId.ResponseTopic, value); + WriteStringProperty(ref writer, MqttPropertyId.ResponseInformation, value); } - public static void WriteRetainAvailable(ref TByteBlock byteBlock, bool value) where TByteBlock : IByteBlock + /// + /// 写入响应主题属性。 + /// + /// 字节写入器的类型,必须实现接口。 + /// 要写入的字节写入器。 + /// 响应主题字符串值。 + public static void WriteResponseTopic(ref TWriter writer, string value) + where TWriter : IBytesWriter + + { + WriteStringProperty(ref writer, MqttPropertyId.ResponseTopic, value); + } + + /// + /// 写入保留可用属性。 + /// + /// 字节写入器的类型,必须实现接口。 + /// 要写入的字节写入器。 + /// 保留功能是否可用。 + /// + /// 只有当值为时才写入属性(写入0)。 + /// + public static void WriteRetainAvailable(ref TWriter writer, bool value) + where TWriter : IBytesWriter + { if (value) { return; } - byteBlock.WriteByte((byte)MqttPropertyId.RetainAvailable); - byteBlock.WriteByte(0); - } - - public static void WriteServerKeepAlive(ref TByteBlock byteBlock, ushort value) where TByteBlock : IByteBlock - { - if (value == 0) - { - return; - } - byteBlock.WriteByte((byte)MqttPropertyId.ServerKeepAlive); - byteBlock.WriteUInt16(value); - } - - public static void WriteServerReference(ref TByteBlock byteBlock, string value) where TByteBlock : IByteBlock - { - WriteStringProperty(ref byteBlock, MqttPropertyId.ServerReference, value); + WriterExtension.WriteValue(ref writer, (byte)MqttPropertyId.RetainAvailable); + WriterExtension.WriteValue(ref writer, 0); } /// - /// 写入会话过期间隔。 + /// 写入服务器保持连接时间属性。 /// - /// 字节块的类型。 - /// 要写入的字节块。 - /// 会话过期间隔的值。 - public static void WriteSessionExpiryInterval(ref TByteBlock byteBlock, uint value) - where TByteBlock : IByteBlock + /// 字节写入器的类型,必须实现接口。 + /// 要写入的字节写入器。 + /// 服务器保持连接时间值。 + /// + /// 如果值为0,则不写入任何内容。 + /// + public static void WriteServerKeepAlive(ref TWriter writer, ushort value) + where TWriter : IBytesWriter + { if (value == 0) { return; } - byteBlock.WriteByte((byte)MqttPropertyId.SessionExpiryInterval); - byteBlock.WriteUInt32(value, EndianType.Big); + WriterExtension.WriteValue(ref writer, (byte)MqttPropertyId.ServerKeepAlive); + WriterExtension.WriteValue(ref writer, value); } - public static void WriteSharedSubscriptionAvailable(ref TByteBlock byteBlock, bool value) where TByteBlock : IByteBlock + /// + /// 写入服务器引用属性。 + /// + /// 字节写入器的类型,必须实现接口。 + /// 要写入的字节写入器。 + /// 服务器引用字符串值。 + public static void WriteServerReference(ref TWriter writer, string value) + where TWriter : IBytesWriter + + { + WriteStringProperty(ref writer, MqttPropertyId.ServerReference, value); + } + + /// + /// 写入会话过期间隔属性。 + /// + /// 字节写入器的类型,必须实现接口。 + /// 要写入的字节写入器。 + /// 会话过期间隔值(秒)。 + /// + /// 如果值为0,则不写入任何内容。 + /// + public static void WriteSessionExpiryInterval(ref TWriter writer, uint value) + where TWriter : IBytesWriter + + { + if (value == 0) + { + return; + } + WriterExtension.WriteValue(ref writer, (byte)MqttPropertyId.SessionExpiryInterval); + WriterExtension.WriteValue(ref writer, value, EndianType.Big); + } + + /// + /// 写入共享订阅可用属性。 + /// + /// 字节写入器的类型,必须实现接口。 + /// 要写入的字节写入器。 + /// 共享订阅功能是否可用。 + /// + /// 只有当值为时才写入属性(写入0)。 + /// + public static void WriteSharedSubscriptionAvailable(ref TWriter writer, bool value) + where TWriter : IBytesWriter + { if (value) { return; } - byteBlock.WriteByte((byte)MqttPropertyId.SharedSubscriptionAvailable); - byteBlock.WriteByte(0); + WriterExtension.WriteValue(ref writer, (byte)MqttPropertyId.SharedSubscriptionAvailable); + WriterExtension.WriteValue(ref writer, 0); } - public static void WriteSubscriptionIdentifier(ref TByteBlock byteBlock, uint subscriptionIdentifier) where TByteBlock : IByteBlock + /// + /// 写入订阅标识符属性。 + /// + /// 字节写入器的类型,必须实现接口。 + /// 要写入的字节写入器。 + /// 订阅标识符值。 + public static void WriteSubscriptionIdentifier(ref TWriter writer, uint subscriptionIdentifier) + where TWriter : IBytesWriter + { - byteBlock.WriteByte((byte)MqttPropertyId.SubscriptionIdentifier); - WriteVariableByteInteger(ref byteBlock, subscriptionIdentifier); + WriterExtension.WriteValue(ref writer, (byte)MqttPropertyId.SubscriptionIdentifier); + WriteVariableByteInteger(ref writer, subscriptionIdentifier); } - public static void WriteSubscriptionIdentifiersAvailable(ref TByteBlock byteBlock, bool value) where TByteBlock : IByteBlock + /// + /// 写入订阅标识符可用属性。 + /// + /// 字节写入器的类型,必须实现接口。 + /// 要写入的字节写入器。 + /// 订阅标识符功能是否可用。 + /// + /// 只有当值为时才写入属性(写入0)。 + /// + public static void WriteSubscriptionIdentifiersAvailable(ref TWriter writer, bool value) + where TWriter : IBytesWriter + { if (value) { return; } - byteBlock.WriteByte((byte)MqttPropertyId.SubscriptionIdentifiersAvailable); - byteBlock.WriteByte(0); + WriterExtension.WriteValue(ref writer, (byte)MqttPropertyId.SubscriptionIdentifiersAvailable); + WriterExtension.WriteValue(ref writer, 0); } /// - /// 写入主题别名最大值。 + /// 写入主题别名最大值属性。 /// - /// 字节块的类型。 - /// 要写入的字节块。 - /// 主题别名最大值的值。 - public static void WriteTopicAliasMaximum(ref TByteBlock byteBlock, ushort value) - where TByteBlock : IByteBlock + /// 字节写入器的类型,必须实现接口。 + /// 要写入的字节写入器。 + /// 主题别名最大值。 + /// + /// 如果值为0,则不写入任何内容。 + /// + public static void WriteTopicAliasMaximum(ref TWriter writer, ushort value) + where TWriter : IBytesWriter + { if (value == 0) { return; } - byteBlock.WriteByte((byte)MqttPropertyId.TopicAliasMaximum); - byteBlock.WriteUInt16(value, EndianType.Big); + WriterExtension.WriteValue(ref writer, (byte)MqttPropertyId.TopicAliasMaximum); + WriterExtension.WriteValue(ref writer, value, EndianType.Big); } - public static void WriteUserProperties(ref TByteBlock byteBlock, IReadOnlyList userProperties) where TByteBlock : IByteBlock + /// + /// 写入多个用户属性。 + /// + /// 字节写入器的类型,必须实现接口。 + /// 要写入的字节写入器。 + /// 用户属性集合。 + /// + /// 如果用户属性集合为null,则不写入任何内容。 + /// + public static void WriteUserProperties(ref TWriter writer, IReadOnlyList userProperties) where TWriter : IBytesWriter + { if (userProperties is null) { @@ -619,75 +876,116 @@ public static class MqttExtension } foreach (var userProperty in userProperties) { - WriteUserProperty(ref byteBlock, userProperty.Name, userProperty.Value); + WriteUserProperty(ref writer, userProperty.Name, userProperty.Value); } } /// /// 写入用户属性。 /// - /// 字节块的类型。 - /// 要写入的字节块。 + /// 字节写入器的类型,必须实现接口。 + /// 要写入的字节写入器。 /// 用户属性的名称。 /// 用户属性的值。 - public static void WriteUserProperty(ref TByteBlock byteBlock, string name, string value) - where TByteBlock : IByteBlock + /// + /// 如果名称或值为空或null,则不写入任何内容。 + /// + public static void WriteUserProperty(ref TWriter writer, string name, string value) + where TWriter : IBytesWriter + { if (name.IsNullOrEmpty() || value.IsNullOrEmpty()) { return; } - byteBlock.WriteByte((byte)MqttPropertyId.UserProperty); - WriteMqttInt16String(ref byteBlock, name); - WriteMqttInt16String(ref byteBlock, value); + WriterExtension.WriteValue(ref writer, (byte)MqttPropertyId.UserProperty); + WriteMqttInt16String(ref writer, name); + WriteMqttInt16String(ref writer, value); } - public static void WriteWildcardSubscriptionAvailable(ref TByteBlock byteBlock, bool value) where TByteBlock : IByteBlock + /// + /// 写入通配符订阅可用属性。 + /// + /// 字节写入器的类型,必须实现接口。 + /// 要写入的字节写入器。 + /// 通配符订阅功能是否可用。 + /// + /// 只有当值为时才写入属性(写入0)。 + /// + public static void WriteWildcardSubscriptionAvailable(ref TWriter writer, bool value) + where TWriter : IBytesWriter + { if (value) { return; } - byteBlock.WriteByte((byte)MqttPropertyId.WildcardSubscriptionAvailable); - byteBlock.WriteByte(0); + WriterExtension.WriteValue(ref writer, (byte)MqttPropertyId.WildcardSubscriptionAvailable); + WriterExtension.WriteValue(ref writer, 0); } /// - /// 写入遗嘱延迟时间间隔。 + /// 写入遗嘱延迟间隔属性。 /// - /// 字节块的类型。 - /// 要写入的字节块。 - /// 遗嘱延迟时间间隔的值。 - public static void WriteWillDelayInterval(ref TByteBlock byteBlock, uint value) - where TByteBlock : IByteBlock + /// 字节写入器的类型,必须实现接口。 + /// 要写入的字节写入器。 + /// 遗嘱延迟间隔值(秒)。 + /// + /// 如果值为0,则不写入任何内容。 + /// + public static void WriteWillDelayInterval(ref TWriter writer, uint value) + where TWriter : IBytesWriter + { if (value == 0) { return; } - byteBlock.WriteByte((byte)MqttPropertyId.WillDelayInterval); - byteBlock.WriteUInt32(value, EndianType.Big); + WriterExtension.WriteValue(ref writer, (byte)MqttPropertyId.WillDelayInterval); + WriterExtension.WriteValue(ref writer, value, EndianType.Big); } - private static void WriteStringProperty(ref TByteBlock byteBlock, MqttPropertyId propertyId, string value) - where TByteBlock : IByteBlock + /// + /// 写入字符串类型的MQTT属性。 + /// + /// 字节写入器的类型,必须实现接口。 + /// 要写入的字节写入器。 + /// 属性标识符。 + /// 属性值。 + /// + /// 如果值为空或null,则不写入任何内容。此方法用于内部实现,统一处理字符串类型属性的写入。 + /// + private static void WriteStringProperty(ref TWriter writer, MqttPropertyId propertyId, string value) + where TWriter : IBytesWriter + { if (value.IsNullOrEmpty()) { return; } - byteBlock.WriteByte((byte)propertyId); - WriteMqttInt16String(ref byteBlock, value); + WriterExtension.WriteValue(ref writer, (byte)propertyId); + WriteMqttInt16String(ref writer, value); } - public static void WriteTopicAlias(ref TByteBlock byteBlock, ushort value) where TByteBlock : IByteBlock + /// + /// 写入主题别名属性。 + /// + /// 字节写入器的类型,必须实现接口。 + /// 要写入的字节写入器。 + /// 主题别名值。 + /// + /// 如果值为0,则不写入任何内容。 + /// + public static void WriteTopicAlias(ref TWriter writer, ushort value) + where TWriter : IBytesWriter + { if (value == 0) { return; } - byteBlock.WriteByte((byte)MqttPropertyId.TopicAlias); - byteBlock.WriteUInt16(value, EndianType.Big); + WriterExtension.WriteValue(ref writer, (byte)MqttPropertyId.TopicAlias); + WriterExtension.WriteValue(ref writer, value, EndianType.Big); } #endregion MqttV5Properties diff --git a/src/TouchSocket.Mqtt/Extensions/PluginRaiseExtension.cs b/src/TouchSocket.Mqtt/Extensions/PluginRaiseExtension.cs new file mode 100644 index 000000000..6c3c88d98 --- /dev/null +++ b/src/TouchSocket.Mqtt/Extensions/PluginRaiseExtension.cs @@ -0,0 +1,23 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +namespace TouchSocket.Mqtt; + +[PluginRaise(typeof(IMqttConnectingPlugin))] +[PluginRaise(typeof(IMqttConnectedPlugin))] +[PluginRaise(typeof(IMqttReceivingPlugin))] +[PluginRaise(typeof(IMqttReceivedPlugin))] +[PluginRaise(typeof(IMqttClosingPlugin))] +[PluginRaise(typeof(IMqttClosedPlugin))] +internal static partial class PluginRaiseExtension +{ +} diff --git a/src/TouchSocket.Mqtt/MqttMessages/Base/MqttIdentifierMessage.cs b/src/TouchSocket.Mqtt/MqttMessages/Base/MqttIdentifierMessage.cs index 091be1c08..793f41b26 100644 --- a/src/TouchSocket.Mqtt/MqttMessages/Base/MqttIdentifierMessage.cs +++ b/src/TouchSocket.Mqtt/MqttMessages/Base/MqttIdentifierMessage.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Mqtt; /// diff --git a/src/TouchSocket.Mqtt/MqttMessages/Base/MqttMessage.cs b/src/TouchSocket.Mqtt/MqttMessages/Base/MqttMessage.cs index ddf2c36e0..65dd9a813 100644 --- a/src/TouchSocket.Mqtt/MqttMessages/Base/MqttMessage.cs +++ b/src/TouchSocket.Mqtt/MqttMessages/Base/MqttMessage.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Mqtt; /// @@ -19,7 +17,7 @@ namespace TouchSocket.Mqtt; /// public abstract class MqttMessage : IRequestInfo, IRequestInfoBuilder { - private int m_endPosition; + private long m_endPosition; /// /// 获取消息的最大长度。 @@ -39,7 +37,7 @@ public abstract class MqttMessage : IRequestInfo, IRequestInfoBuilder /// /// 获取结束位置。 /// - protected int EndPosition => this.m_endPosition; + protected long EndPosition => this.m_endPosition; /// /// 获取或设置标志位。 @@ -80,28 +78,44 @@ public abstract class MqttMessage : IRequestInfo, IRequestInfoBuilder }; } + protected BytesWriter CreateVariableWriter(ref TWriter writer) + where TWriter : IBytesWriter + { + return new BytesWriter(writer.GetMemory(this.GetMinimumRemainingLength() + 1024)); + } + #region Build /// /// 构建Mqtt消息。 /// - /// 字节块类型。 - /// 字节块引用。 - public void Build(ref TByteBlock byteBlock) where TByteBlock : IByteBlock - { - MqttExtension.WriteMqttFixedHeader(ref byteBlock, this.MessageType, this.Flags); - var variableByteIntegerRecorder = new VariableByteIntegerRecorder(); - variableByteIntegerRecorder.CheckOut(ref byteBlock, this.GetMinimumRemainingLength()); + /// 字节块类型。 + /// 字节块引用。 + public void Build(ref TWriter writer) + where TWriter : IBytesWriter - this.BuildVariableBody(ref byteBlock); - this.RemainingLength = (uint)variableByteIntegerRecorder.CheckIn(ref byteBlock); + { + MqttExtension.WriteMqttFixedHeader(ref writer, this.MessageType, this.Flags); + var variableByteIntegerRecorder = new VariableByteIntegerRecorder(); + + var byteBlockWriter = this.CreateVariableWriter(ref writer); + variableByteIntegerRecorder.CheckOut(ref byteBlockWriter, this.GetMinimumRemainingLength()); + + this.BuildVariableBody(ref byteBlockWriter); + this.RemainingLength = (uint)variableByteIntegerRecorder.CheckIn(ref byteBlockWriter); + + writer.Advance(byteBlockWriter.Position); } - protected abstract void BuildVariableBodyWithMqtt3(ref TByteBlock byteBlock) - where TByteBlock : IByteBlock; + protected abstract void BuildVariableBodyWithMqtt3(ref TWriter writer) + where TWriter : IBytesWriter - protected abstract void BuildVariableBodyWithMqtt5(ref TByteBlock byteBlock) - where TByteBlock : IByteBlock; + ; + + protected abstract void BuildVariableBodyWithMqtt5(ref TWriter writer) + where TWriter : IBytesWriter + + ; /// /// 获取最小剩余长度。 @@ -109,17 +123,19 @@ public abstract class MqttMessage : IRequestInfo, IRequestInfoBuilder /// 剩余长度。 protected abstract int GetMinimumRemainingLength(); - private void BuildVariableBody(ref TByteBlock byteBlock) where TByteBlock : IByteBlock + private void BuildVariableBody(ref TWriter writer) + where TWriter : IBytesWriter + { switch (this.Version) { case MqttProtocolVersion.V310: case MqttProtocolVersion.V311: - this.BuildVariableBodyWithMqtt3(ref byteBlock); + this.BuildVariableBodyWithMqtt3(ref writer); break; case MqttProtocolVersion.V500: - this.BuildVariableBodyWithMqtt5(ref byteBlock); + this.BuildVariableBodyWithMqtt5(ref writer); break; case MqttProtocolVersion.Unknown: @@ -136,24 +152,24 @@ public abstract class MqttMessage : IRequestInfo, IRequestInfoBuilder /// /// 解包Mqtt消息。 /// - /// 字节块类型。 - /// 字节块引用。 - public virtual void Unpack(ref TByteBlock byteBlock) - where TByteBlock : IByteBlock + /// 字节块类型。 + /// 字节块引用。 + public virtual void Unpack(ref TReader reader) + where TReader : IBytesReader { - var firstByte = byteBlock.ReadByte(); + var firstByte = ReaderExtension.ReadValue(ref reader); this.SetFlags((byte)firstByte.GetLow4()); - this.RemainingLength = MqttExtension.ReadVariableByteInteger(ref byteBlock); - this.m_endPosition = (int)(byteBlock.Position + this.RemainingLength); + this.RemainingLength = MqttExtension.ReadVariableByteInteger(ref reader); + this.m_endPosition = (reader.BytesRead + this.RemainingLength); switch (this.Version) { case MqttProtocolVersion.V310: case MqttProtocolVersion.V311: - this.UnpackWithMqtt3(ref byteBlock); + this.UnpackWithMqtt3(ref reader); break; case MqttProtocolVersion.V500: - this.UnpackWithMqtt5(ref byteBlock); + this.UnpackWithMqtt5(ref reader); break; case MqttProtocolVersion.Unknown: @@ -163,17 +179,17 @@ public abstract class MqttMessage : IRequestInfo, IRequestInfoBuilder } } - protected bool EndOfByteBlock(in TByteBlock byteBlock) - where TByteBlock : IByteBlock + protected bool EndOfByteBlock(in TReader reader) + where TReader : IBytesReader { - return this.m_endPosition == byteBlock.Position; + return this.m_endPosition == reader.BytesRead; } - protected abstract void UnpackWithMqtt3(ref TByteBlock byteBlock) - where TByteBlock : IByteBlock; + protected abstract void UnpackWithMqtt3(ref TReader reader) + where TReader : IBytesReader; - protected abstract void UnpackWithMqtt5(ref TByteBlock byteBlock) - where TByteBlock : IByteBlock; + protected abstract void UnpackWithMqtt5(ref TReader reader) + where TReader : IBytesReader; #endregion Unpack diff --git a/src/TouchSocket.Mqtt/MqttMessages/Base/MqttUserPropertiesMessage.cs b/src/TouchSocket.Mqtt/MqttMessages/Base/MqttUserPropertiesMessage.cs index 872b3c908..b1f014b77 100644 --- a/src/TouchSocket.Mqtt/MqttMessages/Base/MqttUserPropertiesMessage.cs +++ b/src/TouchSocket.Mqtt/MqttMessages/Base/MqttUserPropertiesMessage.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace TouchSocket.Mqtt; diff --git a/src/TouchSocket.Mqtt/MqttMessages/V311/MqttConnAckMessage_v3.cs b/src/TouchSocket.Mqtt/MqttMessages/V311/MqttConnAckMessage_v3.cs index a66a415ae..0f27b270b 100644 --- a/src/TouchSocket.Mqtt/MqttMessages/V311/MqttConnAckMessage_v3.cs +++ b/src/TouchSocket.Mqtt/MqttMessages/V311/MqttConnAckMessage_v3.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Mqtt; /// @@ -62,10 +60,10 @@ public sealed partial class MqttConnAckMessage : MqttUserPropertiesMessage public bool SessionPresent => this.m_connectAcknowledgeFlags.GetBit(0); /// - protected override void BuildVariableBodyWithMqtt3(ref TByteBlock byteBlock) + protected override void BuildVariableBodyWithMqtt3(ref TWriter writer) { - byteBlock.WriteByte(this.m_connectAcknowledgeFlags); - byteBlock.WriteByte((byte)this.ReturnCode); + WriterExtension.WriteValue(ref writer, this.m_connectAcknowledgeFlags); + WriterExtension.WriteValue(ref writer, (byte)this.ReturnCode); } /// @@ -75,9 +73,9 @@ public sealed partial class MqttConnAckMessage : MqttUserPropertiesMessage } /// - protected override void UnpackWithMqtt3(ref TByteBlock byteBlock) + protected override void UnpackWithMqtt3(ref TReader reader) { - this.m_connectAcknowledgeFlags = byteBlock.ReadByte(); - this.ReturnCode = (MqttReasonCode)byteBlock.ReadByte(); + this.m_connectAcknowledgeFlags = ReaderExtension.ReadValue(ref reader); + this.ReturnCode = (MqttReasonCode)ReaderExtension.ReadValue(ref reader); } } \ No newline at end of file diff --git a/src/TouchSocket.Mqtt/MqttMessages/V311/MqttConnectMessage_v3.cs b/src/TouchSocket.Mqtt/MqttMessages/V311/MqttConnectMessage_v3.cs index 8f073c773..3f26b0426 100644 --- a/src/TouchSocket.Mqtt/MqttMessages/V311/MqttConnectMessage_v3.cs +++ b/src/TouchSocket.Mqtt/MqttMessages/V311/MqttConnectMessage_v3.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Mqtt; /// @@ -61,7 +59,7 @@ public sealed partial class MqttConnectMessage : MqttUserPropertiesMessage this.WillMessageExpiryInterval = options.WillMessageExpiryInterval; this.WillPayloadFormatIndicator = options.WillPayloadFormatIndicator; - this.WillMessage = options.WillMessage; + this.WillPayload = options.WillPayload; this.WillTopic = options.WillTopic; foreach (var item in options.UserProperties.GetSafeEnumerator()) @@ -122,28 +120,28 @@ public sealed partial class MqttConnectMessage : MqttUserPropertiesMessage public string UserName { get; private set; } /// - public override void Unpack(ref TByteBlock byteBlock) + public override void Unpack(ref TReader reader) { - var firstByte = byteBlock.ReadByte(); + var firstByte = ReaderExtension.ReadValue(ref reader); this.SetFlags((byte)firstByte.GetLow4()); - this.RemainingLength = MqttExtension.ReadVariableByteInteger(ref byteBlock); + this.RemainingLength = MqttExtension.ReadVariableByteInteger(ref reader); - this.ProtocolName = MqttExtension.ReadMqttInt16String(ref byteBlock); + this.ProtocolName = MqttExtension.ReadMqttInt16String(ref reader); - this.Version = (MqttProtocolVersion)byteBlock.ReadByte(); + this.Version = (MqttProtocolVersion)ReaderExtension.ReadValue(ref reader); - this.m_connectFlags = byteBlock.ReadByte(); - this.KeepAlive = byteBlock.ReadUInt16(EndianType.Big); + this.m_connectFlags = ReaderExtension.ReadValue(ref reader); + this.KeepAlive = ReaderExtension.ReadValue(ref reader, EndianType.Big); switch (this.Version) { case MqttProtocolVersion.V310: case MqttProtocolVersion.V311: - this.UnpackWithMqtt3(ref byteBlock); + this.UnpackWithMqtt3(ref reader); break; case MqttProtocolVersion.V500: - this.UnpackWithMqtt5(ref byteBlock); + this.UnpackWithMqtt5(ref reader); break; case MqttProtocolVersion.Unknown: @@ -154,27 +152,27 @@ public sealed partial class MqttConnectMessage : MqttUserPropertiesMessage } /// - protected override void BuildVariableBodyWithMqtt3(ref TByteBlock byteBlock) + protected override void BuildVariableBodyWithMqtt3(ref TWriter writer) { - MqttExtension.WriteMqttInt16String(ref byteBlock, this.ProtocolName); - byteBlock.WriteByte((byte)this.Version); - byteBlock.WriteByte(this.m_connectFlags); - byteBlock.WriteUInt16(this.KeepAlive, EndianType.Big); + MqttExtension.WriteMqttInt16String(ref writer, this.ProtocolName); + WriterExtension.WriteValue(ref writer, (byte)this.Version); + WriterExtension.WriteValue(ref writer, this.m_connectFlags); + WriterExtension.WriteValue(ref writer, this.KeepAlive, EndianType.Big); - MqttExtension.WriteMqttInt16String(ref byteBlock, this.ClientId); + MqttExtension.WriteMqttInt16String(ref writer, this.ClientId); if (this.WillFlag) { - MqttExtension.WriteMqttInt16String(ref byteBlock, this.WillTopic); - MqttExtension.WriteMqttInt16String(ref byteBlock, this.WillMessage); + MqttExtension.WriteMqttInt16String(ref writer, this.WillTopic); + MqttExtension.WriteMqttInt16Memory(ref writer, this.WillPayload); } if (this.UserNameFlag) { - MqttExtension.WriteMqttInt16String(ref byteBlock, this.UserName); + MqttExtension.WriteMqttInt16String(ref writer, this.UserName); } if (this.PasswordFlag) { - MqttExtension.WriteMqttInt16String(ref byteBlock, this.Password); + MqttExtension.WriteMqttInt16String(ref writer, this.Password); } } @@ -217,7 +215,7 @@ public sealed partial class MqttConnectMessage : MqttUserPropertiesMessage /// /// 获取或设置遗嘱消息。 /// - public string WillMessage { get; set; } + public ReadOnlyMemory WillPayload { get; set; } /// /// 获取遗嘱服务质量级别。 @@ -237,23 +235,23 @@ public sealed partial class MqttConnectMessage : MqttUserPropertiesMessage #endregion ConnectFlags /// - protected override void UnpackWithMqtt3(ref TByteBlock byteBlock) + protected override void UnpackWithMqtt3(ref TReader reader) { - this.ClientId = MqttExtension.ReadMqttInt16String(ref byteBlock); + this.ClientId = MqttExtension.ReadMqttInt16String(ref reader); if (this.WillFlag) { - this.WillTopic = MqttExtension.ReadMqttInt16String(ref byteBlock); - this.WillMessage = MqttExtension.ReadMqttInt16String(ref byteBlock); + this.WillTopic = MqttExtension.ReadMqttInt16String(ref reader); + this.WillPayload = MqttExtension.ReadMqttInt16Memory(ref reader); } if (this.UserNameFlag) { - this.UserName = MqttExtension.ReadMqttInt16String(ref byteBlock); + this.UserName = MqttExtension.ReadMqttInt16String(ref reader); } if (this.PasswordFlag) { - this.Password = MqttExtension.ReadMqttInt16String(ref byteBlock); + this.Password = MqttExtension.ReadMqttInt16String(ref reader); } } } \ No newline at end of file diff --git a/src/TouchSocket.Mqtt/MqttMessages/V311/MqttDisconnectMessage_v3.cs b/src/TouchSocket.Mqtt/MqttMessages/V311/MqttDisconnectMessage_v3.cs index 5ae3b3557..b1c2dee88 100644 --- a/src/TouchSocket.Mqtt/MqttMessages/V311/MqttDisconnectMessage_v3.cs +++ b/src/TouchSocket.Mqtt/MqttMessages/V311/MqttDisconnectMessage_v3.cs @@ -18,7 +18,7 @@ public sealed partial class MqttDisconnectMessage : MqttUserPropertiesMessage public override MqttMessageType MessageType => MqttMessageType.Disconnect; /// - protected override void BuildVariableBodyWithMqtt3(ref TByteBlock byteBlock) + protected override void BuildVariableBodyWithMqtt3(ref TWriter writer) { } @@ -30,7 +30,7 @@ public sealed partial class MqttDisconnectMessage : MqttUserPropertiesMessage } /// - protected override void UnpackWithMqtt3(ref TByteBlock byteBlock) + protected override void UnpackWithMqtt3(ref TReader reader) { } diff --git a/src/TouchSocket.Mqtt/MqttMessages/V311/MqttPingReqMessage_v3.cs b/src/TouchSocket.Mqtt/MqttMessages/V311/MqttPingReqMessage_v3.cs index 38423d3ed..02a254c3e 100644 --- a/src/TouchSocket.Mqtt/MqttMessages/V311/MqttPingReqMessage_v3.cs +++ b/src/TouchSocket.Mqtt/MqttMessages/V311/MqttPingReqMessage_v3.cs @@ -21,7 +21,7 @@ public sealed partial class MqttPingReqMessage : MqttMessage public override MqttMessageType MessageType => MqttMessageType.PingReq; /// - protected override void BuildVariableBodyWithMqtt3(ref TByteBlock byteBlock) + protected override void BuildVariableBodyWithMqtt3(ref TWriter writer) { } @@ -33,7 +33,7 @@ public sealed partial class MqttPingReqMessage : MqttMessage } /// - protected override void UnpackWithMqtt3(ref TByteBlock byteBlock) + protected override void UnpackWithMqtt3(ref TReader reader) { } diff --git a/src/TouchSocket.Mqtt/MqttMessages/V311/MqttPingRespMessage_v3.cs b/src/TouchSocket.Mqtt/MqttMessages/V311/MqttPingRespMessage_v3.cs index adf154544..c5f8d77cb 100644 --- a/src/TouchSocket.Mqtt/MqttMessages/V311/MqttPingRespMessage_v3.cs +++ b/src/TouchSocket.Mqtt/MqttMessages/V311/MqttPingRespMessage_v3.cs @@ -21,7 +21,7 @@ public sealed partial class MqttPingRespMessage : MqttMessage public override MqttMessageType MessageType => MqttMessageType.PingResp; /// - protected override void BuildVariableBodyWithMqtt3(ref TByteBlock byteBlock) + protected override void BuildVariableBodyWithMqtt3(ref TWriter writer) { } @@ -33,7 +33,7 @@ public sealed partial class MqttPingRespMessage : MqttMessage } /// - protected override void UnpackWithMqtt3(ref TByteBlock byteBlock) + protected override void UnpackWithMqtt3(ref TReader reader) { } diff --git a/src/TouchSocket.Mqtt/MqttMessages/V311/MqttPubAckMessage_v3.cs b/src/TouchSocket.Mqtt/MqttMessages/V311/MqttPubAckMessage_v3.cs index cdd081cf6..bfe8b752d 100644 --- a/src/TouchSocket.Mqtt/MqttMessages/V311/MqttPubAckMessage_v3.cs +++ b/src/TouchSocket.Mqtt/MqttMessages/V311/MqttPubAckMessage_v3.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Mqtt; /// @@ -23,15 +21,15 @@ public sealed partial class MqttPubAckMessage : MqttIdentifierMessage public override MqttMessageType MessageType => MqttMessageType.PubAck; /// - protected override void UnpackWithMqtt3(ref TByteBlock byteBlock) + protected override void UnpackWithMqtt3(ref TReader reader) { - this.MessageId = byteBlock.ReadUInt16(EndianType.Big); + this.MessageId = ReaderExtension.ReadValue(ref reader, EndianType.Big); } /// - protected override void BuildVariableBodyWithMqtt3(ref TByteBlock byteBlock) + protected override void BuildVariableBodyWithMqtt3(ref TWriter writer) { - byteBlock.WriteUInt16(this.MessageId, EndianType.Big); + WriterExtension.WriteValue(ref writer, this.MessageId, EndianType.Big); } /// diff --git a/src/TouchSocket.Mqtt/MqttMessages/V311/MqttPubCompMessage_v3.cs b/src/TouchSocket.Mqtt/MqttMessages/V311/MqttPubCompMessage_v3.cs index 436c64b02..986813157 100644 --- a/src/TouchSocket.Mqtt/MqttMessages/V311/MqttPubCompMessage_v3.cs +++ b/src/TouchSocket.Mqtt/MqttMessages/V311/MqttPubCompMessage_v3.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Mqtt; /// @@ -23,9 +21,9 @@ public sealed partial class MqttPubCompMessage : MqttIdentifierMessage public override MqttMessageType MessageType => MqttMessageType.PubComp; /// - protected override void BuildVariableBodyWithMqtt3(ref TByteBlock byteBlock) + protected override void BuildVariableBodyWithMqtt3(ref TWriter writer) { - byteBlock.WriteUInt16(this.MessageId, EndianType.Big); + WriterExtension.WriteValue(ref writer, this.MessageId, EndianType.Big); } /// @@ -35,8 +33,8 @@ public sealed partial class MqttPubCompMessage : MqttIdentifierMessage } /// - protected override void UnpackWithMqtt3(ref TByteBlock byteBlock) + protected override void UnpackWithMqtt3(ref TReader reader) { - this.MessageId = byteBlock.ReadUInt16(EndianType.Big); + this.MessageId = ReaderExtension.ReadValue(ref reader, EndianType.Big); } } \ No newline at end of file diff --git a/src/TouchSocket.Mqtt/MqttMessages/V311/MqttPubRecMessage_v3.cs b/src/TouchSocket.Mqtt/MqttMessages/V311/MqttPubRecMessage_v3.cs index 11463e3c9..265e9ee2c 100644 --- a/src/TouchSocket.Mqtt/MqttMessages/V311/MqttPubRecMessage_v3.cs +++ b/src/TouchSocket.Mqtt/MqttMessages/V311/MqttPubRecMessage_v3.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Mqtt; /// @@ -23,15 +21,15 @@ public sealed partial class MqttPubRecMessage : MqttIdentifierMessage public override MqttMessageType MessageType => MqttMessageType.PubRec; /// - protected override void BuildVariableBodyWithMqtt3(ref TByteBlock byteBlock) + protected override void BuildVariableBodyWithMqtt3(ref TWriter writer) { - byteBlock.WriteUInt16(this.MessageId, EndianType.Big); + WriterExtension.WriteValue(ref writer, this.MessageId, EndianType.Big); } /// - protected override void UnpackWithMqtt3(ref TByteBlock byteBlock) + protected override void UnpackWithMqtt3(ref TReader reader) { - this.MessageId = byteBlock.ReadUInt16(EndianType.Big); + this.MessageId = ReaderExtension.ReadValue(ref reader, EndianType.Big); } /// protected override int GetMinimumRemainingLength() diff --git a/src/TouchSocket.Mqtt/MqttMessages/V311/MqttPubRelMessage_v3.cs b/src/TouchSocket.Mqtt/MqttMessages/V311/MqttPubRelMessage_v3.cs index 287d26265..0d365bbdc 100644 --- a/src/TouchSocket.Mqtt/MqttMessages/V311/MqttPubRelMessage_v3.cs +++ b/src/TouchSocket.Mqtt/MqttMessages/V311/MqttPubRelMessage_v3.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Mqtt; /// @@ -37,15 +35,15 @@ public sealed partial class MqttPubRelMessage : MqttIdentifierMessage } /// - protected override void BuildVariableBodyWithMqtt3(ref TByteBlock byteBlock) + protected override void BuildVariableBodyWithMqtt3(ref TWriter writer) { - byteBlock.WriteUInt16(this.MessageId, EndianType.Big); + WriterExtension.WriteValue(ref writer, this.MessageId, EndianType.Big); } /// - protected override void UnpackWithMqtt3(ref TByteBlock byteBlock) + protected override void UnpackWithMqtt3(ref TReader reader) { - this.MessageId = byteBlock.ReadUInt16(EndianType.Big); + this.MessageId = ReaderExtension.ReadValue(ref reader, EndianType.Big); } /// diff --git a/src/TouchSocket.Mqtt/MqttMessages/V311/MqttPublishMessage_v3.cs b/src/TouchSocket.Mqtt/MqttMessages/V311/MqttPublishMessage_v3.cs index fac956eda..7e7b1fccb 100644 --- a/src/TouchSocket.Mqtt/MqttMessages/V311/MqttPublishMessage_v3.cs +++ b/src/TouchSocket.Mqtt/MqttMessages/V311/MqttPublishMessage_v3.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using TouchSocket.Core; - namespace TouchSocket.Mqtt; /// @@ -83,24 +80,27 @@ public sealed partial class MqttPublishMessage : MqttIdentifierMessage public string TopicName { get; private set; } /// - protected override void UnpackWithMqtt3(ref TByteBlock byteBlock) + protected override void UnpackWithMqtt3(ref TReader reader) { - this.TopicName = MqttExtension.ReadMqttInt16String(ref byteBlock); + this.TopicName = MqttExtension.ReadMqttInt16String(ref reader); if (this.QosLevel != QosLevel.AtMostOnce) { - this.MessageId = byteBlock.ReadUInt16(EndianType.Big); + this.MessageId = ReaderExtension.ReadValue(ref reader, EndianType.Big); } - this.Payload = this.ReadPayload(ref byteBlock); + this.Payload = this.ReadPayload(ref reader); } - private ReadOnlyMemory ReadPayload(ref TByteBlock byteBlock) - where TByteBlock : IByteBlock + private ReadOnlyMemory ReadPayload(ref TReader reader) + where TReader : IBytesReader { - var payloadLength = this.EndPosition - byteBlock.Position; - var payload = byteBlock.Memory.Slice(byteBlock.Position, payloadLength); - byteBlock.Position = this.EndPosition; - return payload; + var payloadLength = (int)(this.EndPosition - reader.BytesRead); + var payloadArray = new byte[payloadLength]; + var span = reader.GetSpan(payloadLength).Slice(0, payloadLength); + span.CopyTo(payloadArray); + reader.Advance(payloadLength); + + return payloadArray; } @@ -114,19 +114,19 @@ public sealed partial class MqttPublishMessage : MqttIdentifierMessage } /// - protected override void BuildVariableBodyWithMqtt3(ref TByteBlock byteBlock) + protected override void BuildVariableBodyWithMqtt3(ref TWriter writer) { - MqttExtension.WriteMqttInt16String(ref byteBlock, this.TopicName); + MqttExtension.WriteMqttInt16String(ref writer, this.TopicName); if (this.QosLevel != QosLevel.AtMostOnce) { - byteBlock.WriteUInt16(this.MessageId, EndianType.Big); + WriterExtension.WriteValue(ref writer, this.MessageId, EndianType.Big); } - byteBlock.Write(this.Payload.Span); + writer.Write(this.Payload.Span); } /// protected override int GetMinimumRemainingLength() { - return 0; + return this.Payload.Length; } } \ No newline at end of file diff --git a/src/TouchSocket.Mqtt/MqttMessages/V311/MqttSubAckMessage_v3.cs b/src/TouchSocket.Mqtt/MqttMessages/V311/MqttSubAckMessage_v3.cs index f951145ad..977e13dc6 100644 --- a/src/TouchSocket.Mqtt/MqttMessages/V311/MqttSubAckMessage_v3.cs +++ b/src/TouchSocket.Mqtt/MqttMessages/V311/MqttSubAckMessage_v3.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using TouchSocket.Core; - namespace TouchSocket.Mqtt; /// @@ -72,22 +68,22 @@ public sealed partial class MqttSubAckMessage : MqttIdentifierMessage } /// - protected override void BuildVariableBodyWithMqtt3(ref TByteBlock byteBlock) + protected override void BuildVariableBodyWithMqtt3(ref TWriter writer) { - byteBlock.WriteUInt16(this.MessageId, EndianType.Big); + WriterExtension.WriteValue(ref writer, this.MessageId, EndianType.Big); foreach (var item in this.ReturnCodes) { - byteBlock.WriteByte((byte)item); + WriterExtension.WriteValue(ref writer, (byte)item); } } /// - protected override void UnpackWithMqtt3(ref TByteBlock byteBlock) + protected override void UnpackWithMqtt3(ref TReader reader) { - this.MessageId = byteBlock.ReadUInt16(EndianType.Big); - while (!this.EndOfByteBlock(byteBlock)) + this.MessageId = ReaderExtension.ReadValue(ref reader, EndianType.Big); + while (!this.EndOfByteBlock(reader)) { - this.m_returnCodes.Add((MqttReasonCode)byteBlock.ReadByte()); + this.m_returnCodes.Add((MqttReasonCode)ReaderExtension.ReadValue(ref reader)); } } } \ No newline at end of file diff --git a/src/TouchSocket.Mqtt/MqttMessages/V311/MqttSubscribeMessage_v3.cs b/src/TouchSocket.Mqtt/MqttMessages/V311/MqttSubscribeMessage_v3.cs index 3d49b8a43..a8098f11b 100644 --- a/src/TouchSocket.Mqtt/MqttMessages/V311/MqttSubscribeMessage_v3.cs +++ b/src/TouchSocket.Mqtt/MqttMessages/V311/MqttSubscribeMessage_v3.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Collections.Generic; -using TouchSocket.Core; - namespace TouchSocket.Mqtt; /// @@ -45,13 +42,13 @@ public sealed partial class MqttSubscribeMessage : MqttIdentifierMessage public IReadOnlyList SubscribeRequests => this.m_subscribeRequests; /// - protected override void BuildVariableBodyWithMqtt3(ref TByteBlock byteBlock) + protected override void BuildVariableBodyWithMqtt3(ref TWriter writer) { - byteBlock.WriteUInt16(this.MessageId, EndianType.Big); + WriterExtension.WriteValue(ref writer, this.MessageId, EndianType.Big); foreach (var item in this.m_subscribeRequests) { - MqttExtension.WriteMqttInt16String(ref byteBlock, item.Topic); - byteBlock.WriteByte((byte)item.QosLevel); + MqttExtension.WriteMqttInt16String(ref writer, item.Topic); + WriterExtension.WriteValue(ref writer, (byte)item.QosLevel); } } @@ -70,14 +67,14 @@ public sealed partial class MqttSubscribeMessage : MqttIdentifierMessage } /// - protected override void UnpackWithMqtt3(ref TByteBlock byteBlock) + protected override void UnpackWithMqtt3(ref TReader reader) { - this.MessageId = byteBlock.ReadUInt16(EndianType.Big); + this.MessageId = ReaderExtension.ReadValue(ref reader, EndianType.Big); - while (!this.EndOfByteBlock(byteBlock)) + while (!this.EndOfByteBlock(reader)) { - var topic = MqttExtension.ReadMqttInt16String(ref byteBlock); - var options = byteBlock.ReadByte(); + var topic = MqttExtension.ReadMqttInt16String(ref reader); + var options = ReaderExtension.ReadValue(ref reader); this.m_subscribeRequests.Add(new SubscribeRequest(topic, options)); } } diff --git a/src/TouchSocket.Mqtt/MqttMessages/V311/MqttUnsubAckMessage_v3.cs b/src/TouchSocket.Mqtt/MqttMessages/V311/MqttUnsubAckMessage_v3.cs index 71b110543..80b4bbebe 100644 --- a/src/TouchSocket.Mqtt/MqttMessages/V311/MqttUnsubAckMessage_v3.cs +++ b/src/TouchSocket.Mqtt/MqttMessages/V311/MqttUnsubAckMessage_v3.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Mqtt; /// @@ -23,9 +21,9 @@ public sealed partial class MqttUnsubAckMessage : MqttIdentifierMessage public override MqttMessageType MessageType => MqttMessageType.UnsubAck; /// - protected override void BuildVariableBodyWithMqtt3(ref TByteBlock byteBlock) + protected override void BuildVariableBodyWithMqtt3(ref TWriter writer) { - byteBlock.WriteUInt16(this.MessageId, EndianType.Big); + WriterExtension.WriteValue(ref writer, this.MessageId, EndianType.Big); } /// @@ -35,8 +33,8 @@ public sealed partial class MqttUnsubAckMessage : MqttIdentifierMessage } /// - protected override void UnpackWithMqtt3(ref TByteBlock byteBlock) + protected override void UnpackWithMqtt3(ref TReader reader) { - this.MessageId = byteBlock.ReadUInt16(EndianType.Big); + this.MessageId = ReaderExtension.ReadValue(ref reader, EndianType.Big); } } \ No newline at end of file diff --git a/src/TouchSocket.Mqtt/MqttMessages/V311/MqttUnsubscribeMessage_v3.cs b/src/TouchSocket.Mqtt/MqttMessages/V311/MqttUnsubscribeMessage_v3.cs index 4b10342b9..1fbc6e7db 100644 --- a/src/TouchSocket.Mqtt/MqttMessages/V311/MqttUnsubscribeMessage_v3.cs +++ b/src/TouchSocket.Mqtt/MqttMessages/V311/MqttUnsubscribeMessage_v3.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Collections.Generic; -using TouchSocket.Core; - namespace TouchSocket.Mqtt; /// @@ -28,7 +25,7 @@ public sealed partial class MqttUnsubscribeMessage : MqttIdentifierMessage /// 要取消订阅的主题。 public MqttUnsubscribeMessage(params string[] topics) { - ThrowHelper.ThrowArgumentNullExceptionIf(topics, nameof(topics)); + ThrowHelper.ThrowIfNull(topics, nameof(topics)); if (topics.Length < 1) { @@ -52,12 +49,12 @@ public sealed partial class MqttUnsubscribeMessage : MqttIdentifierMessage public IReadOnlyList TopicFilters => this.m_topicFilters; /// - protected override void BuildVariableBodyWithMqtt3(ref TByteBlock byteBlock) + protected override void BuildVariableBodyWithMqtt3(ref TWriter writer) { - byteBlock.WriteUInt16(this.MessageId, EndianType.Big); + WriterExtension.WriteValue(ref writer, this.MessageId, EndianType.Big); foreach (var topicFilter in this.TopicFilters) { - MqttExtension.WriteMqttInt16String(ref byteBlock, topicFilter); + MqttExtension.WriteMqttInt16String(ref writer, topicFilter); } } @@ -75,12 +72,12 @@ public sealed partial class MqttUnsubscribeMessage : MqttIdentifierMessage } /// - protected override void UnpackWithMqtt3(ref TByteBlock byteBlock) + protected override void UnpackWithMqtt3(ref TReader reader) { - this.MessageId = byteBlock.ReadUInt16(EndianType.Big); - while (!this.EndOfByteBlock(byteBlock)) + this.MessageId = ReaderExtension.ReadValue(ref reader, EndianType.Big); + while (!this.EndOfByteBlock(reader)) { - this.m_topicFilters.Add(MqttExtension.ReadMqttInt16String(ref byteBlock)); + this.m_topicFilters.Add(MqttExtension.ReadMqttInt16String(ref reader)); } } } \ No newline at end of file diff --git a/src/TouchSocket.Mqtt/MqttMessages/V5/MqttConnAckMessage_v5.cs b/src/TouchSocket.Mqtt/MqttMessages/V5/MqttConnAckMessage_v5.cs index 2e5f8e3c7..51efa50e4 100644 --- a/src/TouchSocket.Mqtt/MqttMessages/V5/MqttConnAckMessage_v5.cs +++ b/src/TouchSocket.Mqtt/MqttMessages/V5/MqttConnAckMessage_v5.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Mqtt; public partial class MqttConnAckMessage @@ -24,7 +22,7 @@ public partial class MqttConnAckMessage /// /// 获取或设置认证数据。 /// - public byte[] AuthenticationData { get; set; } + public ReadOnlyMemory AuthenticationData { get; set; } /// /// 获取或设置认证方法。 @@ -102,131 +100,134 @@ public partial class MqttConnAckMessage public bool WildcardSubscriptionAvailable { get; set; } /// - protected override void BuildVariableBodyWithMqtt5(ref TByteBlock byteBlock) + protected override void BuildVariableBodyWithMqtt5(ref TWriter writer) { - byteBlock.WriteByte(this.m_connectAcknowledgeFlags); - byteBlock.WriteByte((byte)this.ReturnCode); + WriterExtension.WriteValue(ref writer, this.m_connectAcknowledgeFlags); + WriterExtension.WriteValue(ref writer, (byte)this.ReturnCode); var variableByteIntegerRecorder = new VariableByteIntegerRecorder(); - variableByteIntegerRecorder.CheckOut(ref byteBlock); - MqttExtension.WriteSessionExpiryInterval(ref byteBlock, this.SessionExpiryInterval); - MqttExtension.WriteReceiveMaximum(ref byteBlock, this.ReceiveMaximum); - MqttExtension.WriteMaximumQoS(ref byteBlock, this.MaximumQoS); - MqttExtension.WriteRetainAvailable(ref byteBlock, this.RetainAvailable); - MqttExtension.WriteMaximumPacketSize(ref byteBlock, this.MaximumPacketSize); - MqttExtension.WriteAssignedClientIdentifier(ref byteBlock, this.AssignedClientIdentifier); - MqttExtension.WriteTopicAliasMaximum(ref byteBlock, this.TopicAliasMaximum); - MqttExtension.WriteReasonString(ref byteBlock, this.ReasonString); - MqttExtension.WriteWildcardSubscriptionAvailable(ref byteBlock, this.WildcardSubscriptionAvailable); - MqttExtension.WriteSubscriptionIdentifiersAvailable(ref byteBlock, this.SubscriptionIdentifiersAvailable); - MqttExtension.WriteSharedSubscriptionAvailable(ref byteBlock, this.SharedSubscriptionAvailable); - MqttExtension.WriteServerKeepAlive(ref byteBlock, this.ServerKeepAlive); - MqttExtension.WriteResponseInformation(ref byteBlock, this.ResponseInformation); - MqttExtension.WriteServerReference(ref byteBlock, this.ServerReference); - MqttExtension.WriteAuthenticationMethod(ref byteBlock, this.AuthenticationMethod); - MqttExtension.WriteAuthenticationData(ref byteBlock, this.AuthenticationData); - MqttExtension.WriteUserProperties(ref byteBlock, this.UserProperties); + var byteBlockWriter = this.CreateVariableWriter(ref writer); + variableByteIntegerRecorder.CheckOut(ref byteBlockWriter); - variableByteIntegerRecorder.CheckIn(ref byteBlock); + MqttExtension.WriteSessionExpiryInterval(ref byteBlockWriter, this.SessionExpiryInterval); + MqttExtension.WriteReceiveMaximum(ref byteBlockWriter, this.ReceiveMaximum); + MqttExtension.WriteMaximumQoS(ref byteBlockWriter, this.MaximumQoS); + MqttExtension.WriteRetainAvailable(ref byteBlockWriter, this.RetainAvailable); + MqttExtension.WriteMaximumPacketSize(ref byteBlockWriter, this.MaximumPacketSize); + MqttExtension.WriteAssignedClientIdentifier(ref byteBlockWriter, this.AssignedClientIdentifier); + MqttExtension.WriteTopicAliasMaximum(ref byteBlockWriter, this.TopicAliasMaximum); + MqttExtension.WriteReasonString(ref byteBlockWriter, this.ReasonString); + MqttExtension.WriteWildcardSubscriptionAvailable(ref byteBlockWriter, this.WildcardSubscriptionAvailable); + MqttExtension.WriteSubscriptionIdentifiersAvailable(ref byteBlockWriter, this.SubscriptionIdentifiersAvailable); + MqttExtension.WriteSharedSubscriptionAvailable(ref byteBlockWriter, this.SharedSubscriptionAvailable); + MqttExtension.WriteServerKeepAlive(ref byteBlockWriter, this.ServerKeepAlive); + MqttExtension.WriteResponseInformation(ref byteBlockWriter, this.ResponseInformation); + MqttExtension.WriteServerReference(ref byteBlockWriter, this.ServerReference); + MqttExtension.WriteAuthenticationMethod(ref byteBlockWriter, this.AuthenticationMethod); + MqttExtension.WriteAuthenticationData(ref byteBlockWriter, this.AuthenticationData.Span); + MqttExtension.WriteUserProperties(ref byteBlockWriter, this.UserProperties); + + variableByteIntegerRecorder.CheckIn(ref byteBlockWriter); + writer.Advance(byteBlockWriter.Position); } /// - protected override void UnpackWithMqtt5(ref TByteBlock byteBlock) + protected override void UnpackWithMqtt5(ref TReader reader) { - this.m_connectAcknowledgeFlags = byteBlock.ReadByte(); - this.ReturnCode = (MqttReasonCode)byteBlock.ReadByte(); + this.m_connectAcknowledgeFlags = ReaderExtension.ReadValue(ref reader); + this.ReturnCode = (MqttReasonCode)ReaderExtension.ReadValue(ref reader); - var propertiesReader = new MqttV5PropertiesReader(ref byteBlock); + var propertiesReader = new MqttV5PropertiesReader(ref reader); - while (propertiesReader.TryRead(ref byteBlock, out var mqttPropertyId)) + while (propertiesReader.TryRead(ref reader, out var mqttPropertyId)) { switch (mqttPropertyId) { case MqttPropertyId.SessionExpiryInterval: { - this.SessionExpiryInterval = propertiesReader.ReadSessionExpiryInterval(ref byteBlock); + this.SessionExpiryInterval = propertiesReader.ReadSessionExpiryInterval(ref reader); break; } case MqttPropertyId.ReceiveMaximum: { - this.ReceiveMaximum = propertiesReader.ReadReceiveMaximum(ref byteBlock); + this.ReceiveMaximum = propertiesReader.ReadReceiveMaximum(ref reader); break; } case MqttPropertyId.MaximumQoS: { - this.MaximumQoS = propertiesReader.ReadMaximumQoS(ref byteBlock); + this.MaximumQoS = propertiesReader.ReadMaximumQoS(ref reader); break; } case MqttPropertyId.RetainAvailable: { - this.RetainAvailable = propertiesReader.ReadRetainAvailable(ref byteBlock); + this.RetainAvailable = propertiesReader.ReadRetainAvailable(ref reader); break; } case MqttPropertyId.MaximumPacketSize: { - this.MaximumPacketSize = propertiesReader.ReadMaximumPacketSize(ref byteBlock); + this.MaximumPacketSize = propertiesReader.ReadMaximumPacketSize(ref reader); break; } case MqttPropertyId.AssignedClientIdentifier: { - this.AssignedClientIdentifier = propertiesReader.ReadAssignedClientIdentifier(ref byteBlock); + this.AssignedClientIdentifier = propertiesReader.ReadAssignedClientIdentifier(ref reader); break; } case MqttPropertyId.TopicAliasMaximum: { - this.TopicAliasMaximum = propertiesReader.ReadTopicAliasMaximum(ref byteBlock); + this.TopicAliasMaximum = propertiesReader.ReadTopicAliasMaximum(ref reader); break; } case MqttPropertyId.ReasonString: { - this.ReasonString = propertiesReader.ReadReasonString(ref byteBlock); + this.ReasonString = propertiesReader.ReadReasonString(ref reader); break; } case MqttPropertyId.WildcardSubscriptionAvailable: { - this.WildcardSubscriptionAvailable = propertiesReader.ReadWildcardSubscriptionAvailable(ref byteBlock); + this.WildcardSubscriptionAvailable = propertiesReader.ReadWildcardSubscriptionAvailable(ref reader); break; } case MqttPropertyId.SubscriptionIdentifiersAvailable: { - this.SubscriptionIdentifiersAvailable = propertiesReader.ReadSubscriptionIdentifiersAvailable(ref byteBlock); + this.SubscriptionIdentifiersAvailable = propertiesReader.ReadSubscriptionIdentifiersAvailable(ref reader); break; } case MqttPropertyId.SharedSubscriptionAvailable: { - this.SharedSubscriptionAvailable = propertiesReader.ReadSharedSubscriptionAvailable(ref byteBlock); + this.SharedSubscriptionAvailable = propertiesReader.ReadSharedSubscriptionAvailable(ref reader); break; } case MqttPropertyId.ServerKeepAlive: { - this.ServerKeepAlive = propertiesReader.ReadServerKeepAlive(ref byteBlock); + this.ServerKeepAlive = propertiesReader.ReadServerKeepAlive(ref reader); break; } case MqttPropertyId.ResponseInformation: { - this.ResponseInformation = propertiesReader.ReadResponseInformation(ref byteBlock); + this.ResponseInformation = propertiesReader.ReadResponseInformation(ref reader); break; } case MqttPropertyId.ServerReference: { - this.ServerReference = propertiesReader.ReadServerReference(ref byteBlock); + this.ServerReference = propertiesReader.ReadServerReference(ref reader); break; } case MqttPropertyId.AuthenticationMethod: { - this.AuthenticationMethod = propertiesReader.ReadAuthenticationMethod(ref byteBlock); + this.AuthenticationMethod = propertiesReader.ReadAuthenticationMethod(ref reader); break; } case MqttPropertyId.AuthenticationData: { - this.AuthenticationData = propertiesReader.ReadAuthenticationData(ref byteBlock); + this.AuthenticationData = propertiesReader.ReadAuthenticationData(ref reader); break; } case MqttPropertyId.UserProperty: { - this.AddUserProperty(propertiesReader.ReadUserProperty(ref byteBlock)); + this.AddUserProperty(propertiesReader.ReadUserProperty(ref reader)); break; } default: diff --git a/src/TouchSocket.Mqtt/MqttMessages/V5/MqttConnectMessage_v5.cs b/src/TouchSocket.Mqtt/MqttMessages/V5/MqttConnectMessage_v5.cs index 553a6c124..4d98df770 100644 --- a/src/TouchSocket.Mqtt/MqttMessages/V5/MqttConnectMessage_v5.cs +++ b/src/TouchSocket.Mqtt/MqttMessages/V5/MqttConnectMessage_v5.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Collections.Generic; -using TouchSocket.Core; - namespace TouchSocket.Mqtt; public partial class MqttConnectMessage @@ -27,7 +24,7 @@ public partial class MqttConnectMessage /// /// Mqtt 5.0.0以上 /// - public byte[] AuthenticationData { get; private set; } + public ReadOnlyMemory AuthenticationData { get; private set; } /// /// 获取或设置认证方法。 @@ -103,7 +100,7 @@ public partial class MqttConnectMessage /// /// Mqtt 5.0.0以上 /// - public byte[] WillCorrelationData { get; private set; } + public ReadOnlyMemory WillCorrelationData { get; private set; } /// /// 获取或设置遗嘱延迟时间间隔。 @@ -156,105 +153,105 @@ public partial class MqttConnectMessage public bool CleanStart => this.m_connectFlags.GetBit(1); /// - protected override void BuildVariableBodyWithMqtt5(ref TByteBlock byteBlock) + protected override void BuildVariableBodyWithMqtt5(ref TWriter writer) { - MqttExtension.WriteMqttInt16String(ref byteBlock, this.ProtocolName); - byteBlock.WriteByte((byte)this.Version); - byteBlock.WriteByte(this.m_connectFlags); - byteBlock.WriteUInt16(this.KeepAlive, EndianType.Big); + MqttExtension.WriteMqttInt16String(ref writer, this.ProtocolName); + WriterExtension.WriteValue(ref writer, (byte)this.Version); + WriterExtension.WriteValue(ref writer, this.m_connectFlags); + WriterExtension.WriteValue(ref writer, this.KeepAlive, EndianType.Big); #region Properties - MqttExtension.WriteSessionExpiryInterval(ref byteBlock, this.SessionExpiryInterval); - MqttExtension.WriteAuthenticationMethod(ref byteBlock, this.AuthenticationMethod); - MqttExtension.WriteAuthenticationData(ref byteBlock, this.AuthenticationData); - MqttExtension.WriteReceiveMaximum(ref byteBlock, this.ReceiveMaximum); - MqttExtension.WriteTopicAliasMaximum(ref byteBlock, this.TopicAliasMaximum); - MqttExtension.WriteMaximumPacketSize(ref byteBlock, this.MaximumPacketSize); - MqttExtension.WriteRequestResponseInformation(ref byteBlock, this.RequestResponseInformation); - MqttExtension.WriteRequestProblemInformation(ref byteBlock, this.RequestProblemInformation); - MqttExtension.WriteUserProperties(ref byteBlock, this.UserProperties); + MqttExtension.WriteSessionExpiryInterval(ref writer, this.SessionExpiryInterval); + MqttExtension.WriteAuthenticationMethod(ref writer, this.AuthenticationMethod); + MqttExtension.WriteAuthenticationData(ref writer, this.AuthenticationData.Span); + MqttExtension.WriteReceiveMaximum(ref writer, this.ReceiveMaximum); + MqttExtension.WriteTopicAliasMaximum(ref writer, this.TopicAliasMaximum); + MqttExtension.WriteMaximumPacketSize(ref writer, this.MaximumPacketSize); + MqttExtension.WriteRequestResponseInformation(ref writer, this.RequestResponseInformation); + MqttExtension.WriteRequestProblemInformation(ref writer, this.RequestProblemInformation); + MqttExtension.WriteUserProperties(ref writer, this.UserProperties); #endregion Properties - MqttExtension.WriteMqttInt16String(ref byteBlock, this.ClientId); + MqttExtension.WriteMqttInt16String(ref writer, this.ClientId); if (this.WillFlag) { - MqttExtension.WritePayloadFormatIndicator(ref byteBlock, this.WillPayloadFormatIndicator); - MqttExtension.WriteMessageExpiryInterval(ref byteBlock, this.WillMessageExpiryInterval); - MqttExtension.WriteResponseTopic(ref byteBlock, this.WillResponseTopic); - MqttExtension.WriteCorrelationData(ref byteBlock, this.WillCorrelationData); - MqttExtension.WriteContentType(ref byteBlock, this.WillContentType); - MqttExtension.WriteWillDelayInterval(ref byteBlock, this.WillDelayInterval); - MqttExtension.WriteMqttInt16String(ref byteBlock, this.WillTopic); - MqttExtension.WriteMqttInt16String(ref byteBlock, this.WillMessage); - MqttExtension.WriteUserProperties(ref byteBlock, this.WillUserProperties); + MqttExtension.WritePayloadFormatIndicator(ref writer, this.WillPayloadFormatIndicator); + MqttExtension.WriteMessageExpiryInterval(ref writer, this.WillMessageExpiryInterval); + MqttExtension.WriteResponseTopic(ref writer, this.WillResponseTopic); + MqttExtension.WriteCorrelationData(ref writer, this.WillCorrelationData.Span); + MqttExtension.WriteContentType(ref writer, this.WillContentType); + MqttExtension.WriteWillDelayInterval(ref writer, this.WillDelayInterval); + MqttExtension.WriteMqttInt16String(ref writer, this.WillTopic); + MqttExtension.WriteMqttInt16Memory(ref writer, this.WillPayload); + MqttExtension.WriteUserProperties(ref writer, this.WillUserProperties); } if (this.UserNameFlag) { - MqttExtension.WriteMqttInt16String(ref byteBlock, this.UserName); + MqttExtension.WriteMqttInt16String(ref writer, this.UserName); } if (this.PasswordFlag) { - MqttExtension.WriteMqttInt16String(ref byteBlock, this.Password); + MqttExtension.WriteMqttInt16String(ref writer, this.Password); } } /// - protected override void UnpackWithMqtt5(ref TByteBlock byteBlock) + protected override void UnpackWithMqtt5(ref TReader reader) { #region Properties - var propertiesReader = new MqttV5PropertiesReader(ref byteBlock); + var propertiesReader = new MqttV5PropertiesReader(ref reader); - while (propertiesReader.TryRead(ref byteBlock, out var mqttPropertyId)) + while (propertiesReader.TryRead(ref reader, out var mqttPropertyId)) { switch (mqttPropertyId) { case MqttPropertyId.SessionExpiryInterval: { - this.SessionExpiryInterval = propertiesReader.ReadSessionExpiryInterval(ref byteBlock); + this.SessionExpiryInterval = propertiesReader.ReadSessionExpiryInterval(ref reader); break; } case MqttPropertyId.AuthenticationMethod: { - this.AuthenticationMethod = propertiesReader.ReadAuthenticationMethod(ref byteBlock); + this.AuthenticationMethod = propertiesReader.ReadAuthenticationMethod(ref reader); break; } case MqttPropertyId.AuthenticationData: { - this.AuthenticationData = propertiesReader.ReadAuthenticationData(ref byteBlock); + this.AuthenticationData = propertiesReader.ReadAuthenticationData(ref reader); break; } case MqttPropertyId.ReceiveMaximum: { - this.ReceiveMaximum = propertiesReader.ReadReceiveMaximum(ref byteBlock); + this.ReceiveMaximum = propertiesReader.ReadReceiveMaximum(ref reader); break; } case MqttPropertyId.TopicAliasMaximum: { - this.TopicAliasMaximum = propertiesReader.ReadTopicAliasMaximum(ref byteBlock); + this.TopicAliasMaximum = propertiesReader.ReadTopicAliasMaximum(ref reader); break; } case MqttPropertyId.MaximumPacketSize: { - this.MaximumPacketSize = propertiesReader.ReadMaximumPacketSize(ref byteBlock); + this.MaximumPacketSize = propertiesReader.ReadMaximumPacketSize(ref reader); break; } case MqttPropertyId.RequestResponseInformation: { - this.RequestResponseInformation = propertiesReader.ReadRequestResponseInformation(ref byteBlock); + this.RequestResponseInformation = propertiesReader.ReadRequestResponseInformation(ref reader); break; } case MqttPropertyId.RequestProblemInformation: { - this.RequestProblemInformation = propertiesReader.ReadRequestProblemInformation(ref byteBlock); + this.RequestProblemInformation = propertiesReader.ReadRequestProblemInformation(ref reader); break; } case MqttPropertyId.UserProperty: { - this.AddUserProperty(propertiesReader.ReadUserProperty(ref byteBlock)); + this.AddUserProperty(propertiesReader.ReadUserProperty(ref reader)); break; } default: @@ -275,48 +272,48 @@ public partial class MqttConnectMessage #endregion Properties - this.ClientId = MqttExtension.ReadMqttInt16String(ref byteBlock); + this.ClientId = MqttExtension.ReadMqttInt16String(ref reader); if (this.WillFlag) { - var willPropertiesReader = new MqttV5PropertiesReader(ref byteBlock); + var willPropertiesReader = new MqttV5PropertiesReader(ref reader); - while (willPropertiesReader.TryRead(ref byteBlock, out var mqttPropertyId)) + while (willPropertiesReader.TryRead(ref reader, out var mqttPropertyId)) { switch (mqttPropertyId) { case MqttPropertyId.PayloadFormatIndicator: { - this.WillPayloadFormatIndicator = willPropertiesReader.ReadPayloadFormatIndicator(ref byteBlock); + this.WillPayloadFormatIndicator = willPropertiesReader.ReadPayloadFormatIndicator(ref reader); break; } case MqttPropertyId.MessageExpiryInterval: { - this.WillMessageExpiryInterval = willPropertiesReader.ReadMessageExpiryInterval(ref byteBlock); + this.WillMessageExpiryInterval = willPropertiesReader.ReadMessageExpiryInterval(ref reader); break; } case MqttPropertyId.ResponseTopic: { - this.WillResponseTopic = willPropertiesReader.ReadResponseTopic(ref byteBlock); + this.WillResponseTopic = willPropertiesReader.ReadResponseTopic(ref reader); break; } case MqttPropertyId.CorrelationData: { - this.WillCorrelationData = willPropertiesReader.ReadCorrelationData(ref byteBlock); + this.WillCorrelationData = willPropertiesReader.ReadCorrelationData(ref reader); break; } case MqttPropertyId.ContentType: { - this.WillContentType = willPropertiesReader.ReadContentType(ref byteBlock); + this.WillContentType = willPropertiesReader.ReadContentType(ref reader); break; } case MqttPropertyId.WillDelayInterval: { - this.WillDelayInterval = willPropertiesReader.ReadWillDelayInterval(ref byteBlock); + this.WillDelayInterval = willPropertiesReader.ReadWillDelayInterval(ref reader); break; } case MqttPropertyId.UserProperty: { - this.AddWillUserProperties(willPropertiesReader.ReadUserProperty(ref byteBlock)); + this.AddWillUserProperties(willPropertiesReader.ReadUserProperty(ref reader)); break; } default: @@ -325,18 +322,18 @@ public partial class MqttConnectMessage } } - this.WillTopic = MqttExtension.ReadMqttInt16String(ref byteBlock); - this.WillMessage = MqttExtension.ReadMqttInt16String(ref byteBlock); + this.WillTopic = MqttExtension.ReadMqttInt16String(ref reader); + this.WillPayload = MqttExtension.ReadMqttInt16Memory(ref reader); } if (this.UserNameFlag) { - this.UserName = MqttExtension.ReadMqttInt16String(ref byteBlock); + this.UserName = MqttExtension.ReadMqttInt16String(ref reader); } if (this.PasswordFlag) { - this.Password = MqttExtension.ReadMqttInt16String(ref byteBlock); + this.Password = MqttExtension.ReadMqttInt16String(ref reader); } } diff --git a/src/TouchSocket.Mqtt/MqttMessages/V5/MqttDisconnectMessage_v5.cs b/src/TouchSocket.Mqtt/MqttMessages/V5/MqttDisconnectMessage_v5.cs index 0019bc068..a12b571ef 100644 --- a/src/TouchSocket.Mqtt/MqttMessages/V5/MqttDisconnectMessage_v5.cs +++ b/src/TouchSocket.Mqtt/MqttMessages/V5/MqttDisconnectMessage_v5.cs @@ -38,53 +38,56 @@ public partial class MqttDisconnectMessage public uint SessionExpiryInterval { get; set; } /// - protected override void BuildVariableBodyWithMqtt5(ref TByteBlock byteBlock) + protected override void BuildVariableBodyWithMqtt5(ref TWriter writer) { - byteBlock.WriteByte((byte)this.ReasonCode); + WriterExtension.WriteValue(ref writer, (byte)this.ReasonCode); var variableByteIntegerRecorder = new VariableByteIntegerRecorder(); - variableByteIntegerRecorder.CheckOut(ref byteBlock); - MqttExtension.WriteServerReference(ref byteBlock, this.ServerReference); - MqttExtension.WriteReasonString(ref byteBlock, this.ReasonString); - MqttExtension.WriteSessionExpiryInterval(ref byteBlock, this.SessionExpiryInterval); - MqttExtension.WriteUserProperties(ref byteBlock, this.UserProperties); + var byteBlockWriter = this.CreateVariableWriter(ref writer); + variableByteIntegerRecorder.CheckOut(ref byteBlockWriter); - variableByteIntegerRecorder.CheckIn(ref byteBlock); + MqttExtension.WriteServerReference(ref byteBlockWriter, this.ServerReference); + MqttExtension.WriteReasonString(ref byteBlockWriter, this.ReasonString); + MqttExtension.WriteSessionExpiryInterval(ref byteBlockWriter, this.SessionExpiryInterval); + MqttExtension.WriteUserProperties(ref byteBlockWriter, this.UserProperties); + + variableByteIntegerRecorder.CheckIn(ref byteBlockWriter); + writer.Advance(byteBlockWriter.Position); } /// - protected override void UnpackWithMqtt5(ref TByteBlock byteBlock) + protected override void UnpackWithMqtt5(ref TReader reader) { - this.ReasonCode = (MqttReasonCode)byteBlock.ReadByte(); + this.ReasonCode = (MqttReasonCode)ReaderExtension.ReadValue(ref reader); - var propertiesReader = new MqttV5PropertiesReader(ref byteBlock); + var propertiesReader = new MqttV5PropertiesReader(ref reader); - while (propertiesReader.TryRead(ref byteBlock, out var mqttPropertyId)) + while (propertiesReader.TryRead(ref reader, out var mqttPropertyId)) { switch (mqttPropertyId) { case MqttPropertyId.ServerReference: { - this.ServerReference = propertiesReader.ReadServerReference(ref byteBlock); + this.ServerReference = propertiesReader.ReadServerReference(ref reader); break; } case MqttPropertyId.ReasonString: { - this.ReasonString = propertiesReader.ReadReasonString(ref byteBlock); + this.ReasonString = propertiesReader.ReadReasonString(ref reader); break; } case MqttPropertyId.SessionExpiryInterval: { - this.SessionExpiryInterval = propertiesReader.ReadSessionExpiryInterval(ref byteBlock); + this.SessionExpiryInterval = propertiesReader.ReadSessionExpiryInterval(ref reader); break; } case MqttPropertyId.UserProperty: { - this.AddUserProperty(propertiesReader.ReadUserProperty(ref byteBlock)); + this.AddUserProperty(propertiesReader.ReadUserProperty(ref reader)); break; } diff --git a/src/TouchSocket.Mqtt/MqttMessages/V5/MqttPingReqMessage_v5.cs b/src/TouchSocket.Mqtt/MqttMessages/V5/MqttPingReqMessage_v5.cs index 15ac9c213..893469ac6 100644 --- a/src/TouchSocket.Mqtt/MqttMessages/V5/MqttPingReqMessage_v5.cs +++ b/src/TouchSocket.Mqtt/MqttMessages/V5/MqttPingReqMessage_v5.cs @@ -15,13 +15,13 @@ namespace TouchSocket.Mqtt; public partial class MqttPingReqMessage { /// - protected override void BuildVariableBodyWithMqtt5(ref TByteBlock byteBlock) + protected override void BuildVariableBodyWithMqtt5(ref TWriter writer) { } /// - protected override void UnpackWithMqtt5(ref TByteBlock byteBlock) + protected override void UnpackWithMqtt5(ref TReader reader) { } diff --git a/src/TouchSocket.Mqtt/MqttMessages/V5/MqttPingRespMessage_v5.cs b/src/TouchSocket.Mqtt/MqttMessages/V5/MqttPingRespMessage_v5.cs index 064fa755b..59746c94c 100644 --- a/src/TouchSocket.Mqtt/MqttMessages/V5/MqttPingRespMessage_v5.cs +++ b/src/TouchSocket.Mqtt/MqttMessages/V5/MqttPingRespMessage_v5.cs @@ -15,13 +15,13 @@ namespace TouchSocket.Mqtt; public partial class MqttPingRespMessage { /// - protected override void BuildVariableBodyWithMqtt5(ref TByteBlock byteBlock) + protected override void BuildVariableBodyWithMqtt5(ref TWriter writer) { } /// - protected override void UnpackWithMqtt5(ref TByteBlock byteBlock) + protected override void UnpackWithMqtt5(ref TReader reader) { } diff --git a/src/TouchSocket.Mqtt/MqttMessages/V5/MqttPubAckMessage_v5.cs b/src/TouchSocket.Mqtt/MqttMessages/V5/MqttPubAckMessage_v5.cs index 249449f65..0fbcbb067 100644 --- a/src/TouchSocket.Mqtt/MqttMessages/V5/MqttPubAckMessage_v5.cs +++ b/src/TouchSocket.Mqtt/MqttMessages/V5/MqttPubAckMessage_v5.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Mqtt; public partial class MqttPubAckMessage @@ -21,55 +19,58 @@ public partial class MqttPubAckMessage public string ReasonString { get; set; } /// - protected override void BuildVariableBodyWithMqtt5(ref TByteBlock byteBlock) + protected override void BuildVariableBodyWithMqtt5(ref TWriter writer) { - byteBlock.WriteUInt16(this.MessageId, EndianType.Big); + WriterExtension.WriteValue(ref writer, this.MessageId, EndianType.Big); if (this.ReasonCode != MqttReasonCode.Success || this.ReasonString.HasValue() || this.UserProperties.Count > 0) { return; } - byteBlock.WriteByte((byte)this.ReasonCode); + WriterExtension.WriteValue(ref writer, (byte)this.ReasonCode); var variableByteIntegerRecorder = new VariableByteIntegerRecorder(); - variableByteIntegerRecorder.CheckOut(ref byteBlock); - MqttExtension.WriteReasonString(ref byteBlock, this.ReasonString); - MqttExtension.WriteUserProperties(ref byteBlock, this.UserProperties); - variableByteIntegerRecorder.CheckIn(ref byteBlock); + var byteBlockWriter = this.CreateVariableWriter(ref writer); + variableByteIntegerRecorder.CheckOut(ref byteBlockWriter); + MqttExtension.WriteReasonString(ref byteBlockWriter, this.ReasonString); + MqttExtension.WriteUserProperties(ref byteBlockWriter, this.UserProperties); + variableByteIntegerRecorder.CheckIn(ref byteBlockWriter); + + writer.Advance(byteBlockWriter.Position); //this.ReasonString = propertiesReader.ReasonString; //this.UserProperties = propertiesReader.UserProperties; } /// - protected override void UnpackWithMqtt5(ref TByteBlock byteBlock) + protected override void UnpackWithMqtt5(ref TReader reader) { - this.MessageId = byteBlock.ReadUInt16(EndianType.Big); - if (this.EndOfByteBlock(byteBlock)) + this.MessageId = ReaderExtension.ReadValue(ref reader, EndianType.Big); + if (this.EndOfByteBlock(reader)) { return; } - this.ReasonCode = (MqttReasonCode)byteBlock.ReadByte(); + this.ReasonCode = (MqttReasonCode)ReaderExtension.ReadValue(ref reader); - if (this.EndOfByteBlock(byteBlock)) + if (this.EndOfByteBlock(reader)) { return; } - var propertiesReader = new Mqtt.MqttV5PropertiesReader(ref byteBlock); + var propertiesReader = new Mqtt.MqttV5PropertiesReader(ref reader); - while (propertiesReader.TryRead(ref byteBlock, out var mqttPropertyId)) + while (propertiesReader.TryRead(ref reader, out var mqttPropertyId)) { switch (mqttPropertyId) { case MqttPropertyId.ReasonString: - this.ReasonString = propertiesReader.ReadReasonString(ref byteBlock); + this.ReasonString = propertiesReader.ReadReasonString(ref reader); break; case MqttPropertyId.UserProperty: - this.AddUserProperty(propertiesReader.ReadUserProperty(ref byteBlock)); + this.AddUserProperty(propertiesReader.ReadUserProperty(ref reader)); break; default: diff --git a/src/TouchSocket.Mqtt/MqttMessages/V5/MqttPubCompMessage_v5.cs b/src/TouchSocket.Mqtt/MqttMessages/V5/MqttPubCompMessage_v5.cs index 6cd48beb7..368adb7b0 100644 --- a/src/TouchSocket.Mqtt/MqttMessages/V5/MqttPubCompMessage_v5.cs +++ b/src/TouchSocket.Mqtt/MqttMessages/V5/MqttPubCompMessage_v5.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Mqtt; public partial class MqttPubCompMessage @@ -21,51 +19,53 @@ public partial class MqttPubCompMessage public string ReasonString { get; set; } /// - protected override void BuildVariableBodyWithMqtt5(ref TByteBlock byteBlock) + protected override void BuildVariableBodyWithMqtt5(ref TWriter writer) { - byteBlock.WriteUInt16(this.MessageId, EndianType.Big); + WriterExtension.WriteValue(ref writer, this.MessageId, EndianType.Big); if (this.ReasonCode != MqttReasonCode.Success || this.ReasonString.HasValue() || this.UserProperties.Count > 0) { - byteBlock.WriteByte((byte)this.ReasonCode); + WriterExtension.WriteValue(ref writer, (byte)this.ReasonCode); var variableByteIntegerRecorder = new VariableByteIntegerRecorder(); - variableByteIntegerRecorder.CheckOut(ref byteBlock); - MqttExtension.WriteReasonString(ref byteBlock, this.ReasonString); - MqttExtension.WriteUserProperties(ref byteBlock, this.UserProperties); - variableByteIntegerRecorder.CheckIn(ref byteBlock); + var byteBlockWriter = this.CreateVariableWriter(ref writer); + variableByteIntegerRecorder.CheckOut(ref byteBlockWriter); + MqttExtension.WriteReasonString(ref byteBlockWriter, this.ReasonString); + MqttExtension.WriteUserProperties(ref byteBlockWriter, this.UserProperties); + variableByteIntegerRecorder.CheckIn(ref byteBlockWriter); + writer.Advance(byteBlockWriter.Position); } } /// - protected override void UnpackWithMqtt5(ref TByteBlock byteBlock) + protected override void UnpackWithMqtt5(ref TReader reader) { - this.MessageId = byteBlock.ReadUInt16(EndianType.Big); - if (this.EndOfByteBlock(byteBlock)) + this.MessageId = ReaderExtension.ReadValue(ref reader, EndianType.Big); + if (this.EndOfByteBlock(reader)) { return; } - this.ReasonCode = (MqttReasonCode)byteBlock.ReadByte(); + this.ReasonCode = (MqttReasonCode)ReaderExtension.ReadValue(ref reader); - if (this.EndOfByteBlock(byteBlock)) + if (this.EndOfByteBlock(reader)) { return; } - var propertiesReader = new Mqtt.MqttV5PropertiesReader(ref byteBlock); + var propertiesReader = new Mqtt.MqttV5PropertiesReader(ref reader); - while (propertiesReader.TryRead(ref byteBlock, out var mqttPropertyId)) + while (propertiesReader.TryRead(ref reader, out var mqttPropertyId)) { switch (mqttPropertyId) { case MqttPropertyId.ReasonString: - this.ReasonString = propertiesReader.ReadReasonString(ref byteBlock); + this.ReasonString = propertiesReader.ReadReasonString(ref reader); break; case MqttPropertyId.UserProperty: - this.AddUserProperty(propertiesReader.ReadUserProperty(ref byteBlock)); + this.AddUserProperty(propertiesReader.ReadUserProperty(ref reader)); break; default: diff --git a/src/TouchSocket.Mqtt/MqttMessages/V5/MqttPubRecMessage_v5.cs b/src/TouchSocket.Mqtt/MqttMessages/V5/MqttPubRecMessage_v5.cs index 4ba34b832..1d8a4fcc0 100644 --- a/src/TouchSocket.Mqtt/MqttMessages/V5/MqttPubRecMessage_v5.cs +++ b/src/TouchSocket.Mqtt/MqttMessages/V5/MqttPubRecMessage_v5.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Mqtt; public partial class MqttPubRecMessage @@ -21,50 +19,52 @@ public partial class MqttPubRecMessage public string ReasonString { get; set; } /// - protected override void BuildVariableBodyWithMqtt5(ref TByteBlock byteBlock) + protected override void BuildVariableBodyWithMqtt5(ref TWriter writer) { - byteBlock.WriteUInt16(this.MessageId, EndianType.Big); + WriterExtension.WriteValue(ref writer, this.MessageId, EndianType.Big); if (this.ReasonCode != MqttReasonCode.Success || this.ReasonString.HasValue() || this.UserProperties.Count > 0) { - byteBlock.WriteByte((byte)this.ReasonCode); + WriterExtension.WriteValue(ref writer, (byte)this.ReasonCode); var variableByteIntegerRecorder = new VariableByteIntegerRecorder(); - variableByteIntegerRecorder.CheckOut(ref byteBlock); - MqttExtension.WriteReasonString(ref byteBlock, this.ReasonString); - MqttExtension.WriteUserProperties(ref byteBlock, this.UserProperties); - variableByteIntegerRecorder.CheckIn(ref byteBlock); + var byteBlockWriter = this.CreateVariableWriter(ref writer); + variableByteIntegerRecorder.CheckOut(ref byteBlockWriter); + MqttExtension.WriteReasonString(ref byteBlockWriter, this.ReasonString); + MqttExtension.WriteUserProperties(ref byteBlockWriter, this.UserProperties); + variableByteIntegerRecorder.CheckIn(ref byteBlockWriter); + writer.Advance(byteBlockWriter.Position); } } /// - protected override void UnpackWithMqtt5(ref TByteBlock byteBlock) + protected override void UnpackWithMqtt5(ref TReader reader) { - this.MessageId = byteBlock.ReadUInt16(EndianType.Big); - if (this.EndOfByteBlock(byteBlock)) + this.MessageId = ReaderExtension.ReadValue(ref reader, EndianType.Big); + if (this.EndOfByteBlock(reader)) { return; } - this.ReasonCode = (MqttReasonCode)byteBlock.ReadByte(); + this.ReasonCode = (MqttReasonCode)ReaderExtension.ReadValue(ref reader); - if (this.EndOfByteBlock(byteBlock)) + if (this.EndOfByteBlock(reader)) { return; } - var propertiesReader = new Mqtt.MqttV5PropertiesReader(ref byteBlock); + var propertiesReader = new Mqtt.MqttV5PropertiesReader(ref reader); - while (propertiesReader.TryRead(ref byteBlock, out var mqttPropertyId)) + while (propertiesReader.TryRead(ref reader, out var mqttPropertyId)) { switch (mqttPropertyId) { case MqttPropertyId.ReasonString: - this.ReasonString = propertiesReader.ReadReasonString(ref byteBlock); + this.ReasonString = propertiesReader.ReadReasonString(ref reader); break; case MqttPropertyId.UserProperty: - this.AddUserProperty(propertiesReader.ReadUserProperty(ref byteBlock)); + this.AddUserProperty(propertiesReader.ReadUserProperty(ref reader)); break; default: diff --git a/src/TouchSocket.Mqtt/MqttMessages/V5/MqttPubRelMessage_v5.cs b/src/TouchSocket.Mqtt/MqttMessages/V5/MqttPubRelMessage_v5.cs index 1562200e6..dceb89399 100644 --- a/src/TouchSocket.Mqtt/MqttMessages/V5/MqttPubRelMessage_v5.cs +++ b/src/TouchSocket.Mqtt/MqttMessages/V5/MqttPubRelMessage_v5.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Mqtt; public partial class MqttPubRelMessage @@ -21,50 +19,52 @@ public partial class MqttPubRelMessage public string ReasonString { get; set; } /// - protected override void BuildVariableBodyWithMqtt5(ref TByteBlock byteBlock) + protected override void BuildVariableBodyWithMqtt5(ref TWriter writer) { - byteBlock.WriteUInt16(this.MessageId, EndianType.Big); + WriterExtension.WriteValue(ref writer, this.MessageId, EndianType.Big); if (this.ReasonCode != MqttReasonCode.Success || this.ReasonString.HasValue() || this.UserProperties.Count > 0) { - byteBlock.WriteByte((byte)this.ReasonCode); + WriterExtension.WriteValue(ref writer, (byte)this.ReasonCode); var variableByteIntegerRecorder = new VariableByteIntegerRecorder(); - variableByteIntegerRecorder.CheckOut(ref byteBlock); - MqttExtension.WriteReasonString(ref byteBlock, this.ReasonString); - MqttExtension.WriteUserProperties(ref byteBlock, this.UserProperties); - variableByteIntegerRecorder.CheckIn(ref byteBlock); + var byteBlockWriter = this.CreateVariableWriter(ref writer); + variableByteIntegerRecorder.CheckOut(ref byteBlockWriter); + MqttExtension.WriteReasonString(ref byteBlockWriter, this.ReasonString); + MqttExtension.WriteUserProperties(ref byteBlockWriter, this.UserProperties); + variableByteIntegerRecorder.CheckIn(ref byteBlockWriter); + writer.Advance(byteBlockWriter.Position); } } /// - protected override void UnpackWithMqtt5(ref TByteBlock byteBlock) + protected override void UnpackWithMqtt5(ref TReader reader) { - this.MessageId = byteBlock.ReadUInt16(EndianType.Big); + this.MessageId = ReaderExtension.ReadValue(ref reader, EndianType.Big); - if (this.EndOfByteBlock(byteBlock)) + if (this.EndOfByteBlock(reader)) { return; } - this.ReasonCode = (MqttReasonCode)byteBlock.ReadByte(); + this.ReasonCode = (MqttReasonCode)ReaderExtension.ReadValue(ref reader); - if (this.EndOfByteBlock(byteBlock)) + if (this.EndOfByteBlock(reader)) { return; } - var propertiesReader = new Mqtt.MqttV5PropertiesReader(ref byteBlock); + var propertiesReader = new Mqtt.MqttV5PropertiesReader(ref reader); - while (propertiesReader.TryRead(ref byteBlock, out var mqttPropertyId)) + while (propertiesReader.TryRead(ref reader, out var mqttPropertyId)) { switch (mqttPropertyId) { case MqttPropertyId.ReasonString: - this.ReasonString = propertiesReader.ReadReasonString(ref byteBlock); + this.ReasonString = propertiesReader.ReadReasonString(ref reader); break; case MqttPropertyId.UserProperty: - this.AddUserProperty(propertiesReader.ReadUserProperty(ref byteBlock)); + this.AddUserProperty(propertiesReader.ReadUserProperty(ref reader)); break; default: ThrowHelper.ThrowInvalidEnumArgumentException(mqttPropertyId); diff --git a/src/TouchSocket.Mqtt/MqttMessages/V5/MqttPublishMessage_v5.cs b/src/TouchSocket.Mqtt/MqttMessages/V5/MqttPublishMessage_v5.cs index f86a45e31..c8ac9ed38 100644 --- a/src/TouchSocket.Mqtt/MqttMessages/V5/MqttPublishMessage_v5.cs +++ b/src/TouchSocket.Mqtt/MqttMessages/V5/MqttPublishMessage_v5.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Collections.Generic; -using TouchSocket.Core; - namespace TouchSocket.Mqtt; public partial class MqttPublishMessage @@ -21,7 +18,7 @@ public partial class MqttPublishMessage public string ContentType { get; set; } - public byte[] CorrelationData { get; set; } + public ReadOnlyMemory CorrelationData { get; set; } public uint MessageExpiryInterval { get; set; } @@ -34,78 +31,80 @@ public partial class MqttPublishMessage public ushort TopicAlias { get; set; } /// - protected override void BuildVariableBodyWithMqtt5(ref TByteBlock byteBlock) + protected override void BuildVariableBodyWithMqtt5(ref TWriter writer) { - MqttExtension.WriteMqttInt16String(ref byteBlock, this.TopicName); + MqttExtension.WriteMqttInt16String(ref writer, this.TopicName); if (this.QosLevel != QosLevel.AtMostOnce) { - byteBlock.WriteUInt16(this.MessageId, EndianType.Big); + WriterExtension.WriteValue(ref writer, this.MessageId, EndianType.Big); } #region properties var variableByteIntegerRecorder = new VariableByteIntegerRecorder(); - variableByteIntegerRecorder.CheckOut(ref byteBlock); + var byteBlockWriter = this.CreateVariableWriter(ref writer); + variableByteIntegerRecorder.CheckOut(ref byteBlockWriter); - MqttExtension.WritePayloadFormatIndicator(ref byteBlock, this.PayloadFormatIndicator); - MqttExtension.WriteMessageExpiryInterval(ref byteBlock, this.MessageExpiryInterval); - MqttExtension.WriteTopicAlias(ref byteBlock, this.TopicAlias); - MqttExtension.WriteResponseTopic(ref byteBlock, this.ResponseTopic); - MqttExtension.WriteCorrelationData(ref byteBlock, this.CorrelationData); + MqttExtension.WritePayloadFormatIndicator(ref byteBlockWriter, this.PayloadFormatIndicator); + MqttExtension.WriteMessageExpiryInterval(ref byteBlockWriter, this.MessageExpiryInterval); + MqttExtension.WriteTopicAlias(ref byteBlockWriter, this.TopicAlias); + MqttExtension.WriteResponseTopic(ref byteBlockWriter, this.ResponseTopic); + MqttExtension.WriteCorrelationData(ref byteBlockWriter, this.CorrelationData.Span); foreach (var item in this.m_subscriptionIdentifiers) { - MqttExtension.WriteSubscriptionIdentifier(ref byteBlock, item); + MqttExtension.WriteSubscriptionIdentifier(ref byteBlockWriter, item); } - MqttExtension.WriteContentType(ref byteBlock, this.ContentType); - MqttExtension.WriteUserProperties(ref byteBlock, this.UserProperties); - variableByteIntegerRecorder.CheckIn(ref byteBlock); + MqttExtension.WriteContentType(ref byteBlockWriter, this.ContentType); + MqttExtension.WriteUserProperties(ref byteBlockWriter, this.UserProperties); + variableByteIntegerRecorder.CheckIn(ref byteBlockWriter); + writer.Advance(byteBlockWriter.Position); #endregion properties - byteBlock.Write(this.Payload.Span); + writer.Write(this.Payload.Span); } /// - protected override void UnpackWithMqtt5(ref TByteBlock byteBlock) + protected override void UnpackWithMqtt5(ref TReader reader) { - this.TopicName = MqttExtension.ReadMqttInt16String(ref byteBlock); + this.TopicName = MqttExtension.ReadMqttInt16String(ref reader); if (this.QosLevel != QosLevel.AtMostOnce) { - this.MessageId = byteBlock.ReadUInt16(EndianType.Big); + this.MessageId = ReaderExtension.ReadValue(ref reader, EndianType.Big); } #region properties - var propertiesReader = new MqttV5PropertiesReader(ref byteBlock); + var propertiesReader = new MqttV5PropertiesReader(ref reader); - while (propertiesReader.TryRead(ref byteBlock, out var mqttPropertyId)) + while (propertiesReader.TryRead(ref reader, out var mqttPropertyId)) { switch (mqttPropertyId) { case MqttPropertyId.PayloadFormatIndicator: - this.PayloadFormatIndicator = propertiesReader.ReadPayloadFormatIndicator(ref byteBlock); + this.PayloadFormatIndicator = propertiesReader.ReadPayloadFormatIndicator(ref reader); break; case MqttPropertyId.MessageExpiryInterval: - this.MessageExpiryInterval = propertiesReader.ReadMessageExpiryInterval(ref byteBlock); + this.MessageExpiryInterval = propertiesReader.ReadMessageExpiryInterval(ref reader); break; case MqttPropertyId.TopicAlias: - this.TopicAlias = propertiesReader.ReadTopicAlias(ref byteBlock); + this.TopicAlias = propertiesReader.ReadTopicAlias(ref reader); break; case MqttPropertyId.ResponseTopic: - this.ResponseTopic = propertiesReader.ReadResponseTopic(ref byteBlock); + this.ResponseTopic = propertiesReader.ReadResponseTopic(ref reader); break; case MqttPropertyId.CorrelationData: - this.CorrelationData = propertiesReader.ReadCorrelationData(ref byteBlock); + this.CorrelationData = propertiesReader.ReadCorrelationData(ref reader); break; case MqttPropertyId.SubscriptionIdentifier: - this.m_subscriptionIdentifiers.Add(propertiesReader.ReadSubscriptionIdentifier(ref byteBlock)); + this.m_subscriptionIdentifiers.Add(propertiesReader.ReadSubscriptionIdentifier(ref reader)); break; case MqttPropertyId.ContentType: - this.ContentType = propertiesReader.ReadContentType(ref byteBlock); + this.ContentType = propertiesReader.ReadContentType(ref reader); break; case MqttPropertyId.UserProperty: - this.AddUserProperty(propertiesReader.ReadUserProperty(ref byteBlock)); + this.AddUserProperty(propertiesReader.ReadUserProperty(ref reader)); break; default: ThrowHelper.ThrowInvalidEnumArgumentException(mqttPropertyId); @@ -113,24 +112,9 @@ public partial class MqttPublishMessage } } - //this.PayloadFormatIndicator = propertiesReader.PayloadFormatIndicator; - - //this.MessageExpiryInterval = propertiesReader.MessageExpiryInterval; - - //this.TopicAlias = propertiesReader.TopicAlias; - - //this.ResponseTopic = propertiesReader.ResponseTopic; - - //this.CorrelationData = propertiesReader.CorrelationData; - - //this.m_subscriptionIdentifiers.Add(propertiesReader.SubscriptionIdentifier); - - //this.ContentType = propertiesReader.ContentType; - - //this.UserProperties = propertiesReader.UserProperties; #endregion properties - this.Payload = this.ReadPayload(ref byteBlock); + this.Payload = this.ReadPayload(ref reader); } } \ No newline at end of file diff --git a/src/TouchSocket.Mqtt/MqttMessages/V5/MqttSubAckMessage_v5.cs b/src/TouchSocket.Mqtt/MqttMessages/V5/MqttSubAckMessage_v5.cs index 79b183500..76941e594 100644 --- a/src/TouchSocket.Mqtt/MqttMessages/V5/MqttSubAckMessage_v5.cs +++ b/src/TouchSocket.Mqtt/MqttMessages/V5/MqttSubAckMessage_v5.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Mqtt; public partial class MqttSubAckMessage @@ -19,37 +17,39 @@ public partial class MqttSubAckMessage public string ReasonString { get; set; } /// - protected override void BuildVariableBodyWithMqtt5(ref TByteBlock byteBlock) + protected override void BuildVariableBodyWithMqtt5(ref TWriter writer) { - byteBlock.WriteUInt16(this.MessageId, EndianType.Big); + WriterExtension.WriteValue(ref writer, this.MessageId, EndianType.Big); var variableByteIntegerRecorder = new VariableByteIntegerRecorder(); - variableByteIntegerRecorder.CheckOut(ref byteBlock); - MqttExtension.WriteReasonString(ref byteBlock, this.ReasonString); - MqttExtension.WriteUserProperties(ref byteBlock, this.UserProperties); - variableByteIntegerRecorder.CheckIn(ref byteBlock); + var byteBlockWriter = this.CreateVariableWriter(ref writer); + variableByteIntegerRecorder.CheckOut(ref byteBlockWriter); + MqttExtension.WriteReasonString(ref byteBlockWriter, this.ReasonString); + MqttExtension.WriteUserProperties(ref byteBlockWriter, this.UserProperties); + variableByteIntegerRecorder.CheckIn(ref byteBlockWriter); + writer.Advance(byteBlockWriter.Position); foreach (var item in this.ReturnCodes) { - byteBlock.WriteByte((byte)item); + WriterExtension.WriteValue(ref writer, (byte)item); } } /// - protected override void UnpackWithMqtt5(ref TByteBlock byteBlock) + protected override void UnpackWithMqtt5(ref TReader reader) { - this.MessageId = byteBlock.ReadUInt16(EndianType.Big); - var propertiesReader = new MqttV5PropertiesReader(ref byteBlock); + this.MessageId = ReaderExtension.ReadValue(ref reader, EndianType.Big); + var propertiesReader = new MqttV5PropertiesReader(ref reader); - while (propertiesReader.TryRead(ref byteBlock, out var mqttPropertyId)) + while (propertiesReader.TryRead(ref reader, out var mqttPropertyId)) { switch (mqttPropertyId) { case MqttPropertyId.ReasonString: - this.ReasonString = propertiesReader.ReadReasonString(ref byteBlock); + this.ReasonString = propertiesReader.ReadReasonString(ref reader); break; case MqttPropertyId.UserProperty: - this.AddUserProperty(propertiesReader.ReadUserProperty(ref byteBlock)); + this.AddUserProperty(propertiesReader.ReadUserProperty(ref reader)); break; default: ThrowHelper.ThrowInvalidEnumArgumentException(mqttPropertyId); @@ -59,9 +59,9 @@ public partial class MqttSubAckMessage //this.ReasonString = propertiesReader.ReasonString; //this.UserProperties = propertiesReader.UserProperties; - while (!this.EndOfByteBlock(byteBlock)) + while (!this.EndOfByteBlock(reader)) { - this.m_returnCodes.Add((MqttReasonCode)byteBlock.ReadByte()); + this.m_returnCodes.Add((MqttReasonCode)ReaderExtension.ReadValue(ref reader)); } } } \ No newline at end of file diff --git a/src/TouchSocket.Mqtt/MqttMessages/V5/MqttSubscribeMessage_v5.cs b/src/TouchSocket.Mqtt/MqttMessages/V5/MqttSubscribeMessage_v5.cs index 7a6df960d..c1c633b5f 100644 --- a/src/TouchSocket.Mqtt/MqttMessages/V5/MqttSubscribeMessage_v5.cs +++ b/src/TouchSocket.Mqtt/MqttMessages/V5/MqttSubscribeMessage_v5.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Mqtt; public partial class MqttSubscribeMessage @@ -19,38 +17,41 @@ public partial class MqttSubscribeMessage public uint SubscriptionIdentifier { get; set; } /// - protected override void BuildVariableBodyWithMqtt5(ref TByteBlock byteBlock) + protected override void BuildVariableBodyWithMqtt5(ref TWriter writer) { - byteBlock.WriteUInt16(this.MessageId, EndianType.Big); + WriterExtension.WriteValue(ref writer, this.MessageId, EndianType.Big); var variableByteIntegerRecorder = new VariableByteIntegerRecorder(); - variableByteIntegerRecorder.CheckOut(ref byteBlock); - MqttExtension.WriteSubscriptionIdentifier(ref byteBlock, this.SubscriptionIdentifier); - MqttExtension.WriteUserProperties(ref byteBlock, this.UserProperties); - variableByteIntegerRecorder.CheckIn(ref byteBlock); + var byteBlockWriter = this.CreateVariableWriter(ref writer); + variableByteIntegerRecorder.CheckOut(ref byteBlockWriter); + MqttExtension.WriteSubscriptionIdentifier(ref byteBlockWriter, this.SubscriptionIdentifier); + MqttExtension.WriteUserProperties(ref byteBlockWriter, this.UserProperties); + variableByteIntegerRecorder.CheckIn(ref byteBlockWriter); + writer.Advance(byteBlockWriter.Position); foreach (var item in this.m_subscribeRequests) { - MqttExtension.WriteMqttInt16String(ref byteBlock, item.Topic); - byteBlock.WriteByte((byte)item.QosLevel); + MqttExtension.WriteMqttInt16String(ref writer, item.Topic); + WriterExtension.WriteValue(ref writer, (byte)item.QosLevel); } } /// - protected override void UnpackWithMqtt5(ref TByteBlock byteBlock) + protected override void UnpackWithMqtt5(ref TReader reader) { - this.MessageId = byteBlock.ReadUInt16(EndianType.Big); - var propertiesReader = new MqttV5PropertiesReader(ref byteBlock); + this.MessageId = ReaderExtension.ReadValue(ref reader, EndianType.Big); + this.MessageId = ReaderExtension.ReadValue(ref reader, EndianType.Big); + var propertiesReader = new MqttV5PropertiesReader(ref reader); - while (propertiesReader.TryRead(ref byteBlock, out var mqttPropertyId)) + while (propertiesReader.TryRead(ref reader, out var mqttPropertyId)) { switch (mqttPropertyId) { case MqttPropertyId.SubscriptionIdentifier: - this.SubscriptionIdentifier = propertiesReader.ReadSubscriptionIdentifier(ref byteBlock); + this.SubscriptionIdentifier = propertiesReader.ReadSubscriptionIdentifier(ref reader); break; case MqttPropertyId.UserProperty: - this.AddUserProperty(propertiesReader.ReadUserProperty(ref byteBlock)); + this.AddUserProperty(propertiesReader.ReadUserProperty(ref reader)); break; default: ThrowHelper.ThrowInvalidEnumArgumentException(mqttPropertyId); @@ -59,10 +60,10 @@ public partial class MqttSubscribeMessage } //this.SubscriptionIdentifier = propertiesReader.SubscriptionIdentifier; //this.UserProperties = propertiesReader.UserProperties; - while (!this.EndOfByteBlock(byteBlock)) + while (!this.EndOfByteBlock(reader)) { - var topic = MqttExtension.ReadMqttInt16String(ref byteBlock); - var options = byteBlock.ReadByte(); + var topic = MqttExtension.ReadMqttInt16String(ref reader); + var options = ReaderExtension.ReadValue(ref reader); this.m_subscribeRequests.Add(new SubscribeRequest(topic, options)); } } diff --git a/src/TouchSocket.Mqtt/MqttMessages/V5/MqttUnsubAckMessage_v5.cs b/src/TouchSocket.Mqtt/MqttMessages/V5/MqttUnsubAckMessage_v5.cs index f40bc695d..e526c2a37 100644 --- a/src/TouchSocket.Mqtt/MqttMessages/V5/MqttUnsubAckMessage_v5.cs +++ b/src/TouchSocket.Mqtt/MqttMessages/V5/MqttUnsubAckMessage_v5.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Collections.Generic; -using TouchSocket.Core; - namespace TouchSocket.Mqtt; public partial class MqttUnsubAckMessage @@ -23,37 +20,39 @@ public partial class MqttUnsubAckMessage public string ReasonString { get; set; } /// - protected override void BuildVariableBodyWithMqtt5(ref TByteBlock byteBlock) + protected override void BuildVariableBodyWithMqtt5(ref TWriter writer) { - byteBlock.WriteUInt16(this.MessageId, EndianType.Big); + WriterExtension.WriteValue(ref writer, this.MessageId, EndianType.Big); var variableByteIntegerRecorder = new VariableByteIntegerRecorder(); - variableByteIntegerRecorder.CheckOut(ref byteBlock); - MqttExtension.WriteReasonString(ref byteBlock, this.ReasonString); - MqttExtension.WriteUserProperties(ref byteBlock, this.UserProperties); - variableByteIntegerRecorder.CheckIn(ref byteBlock); + var byteBlockWriter = this.CreateVariableWriter(ref writer); + variableByteIntegerRecorder.CheckOut(ref byteBlockWriter); + MqttExtension.WriteReasonString(ref byteBlockWriter, this.ReasonString); + MqttExtension.WriteUserProperties(ref byteBlockWriter, this.UserProperties); + variableByteIntegerRecorder.CheckIn(ref byteBlockWriter); + writer.Advance(byteBlockWriter.Position); foreach (var item in this.ReasonCodes) { - byteBlock.WriteByte((byte)item); + WriterExtension.WriteValue(ref writer, (byte)item); } } /// - protected override void UnpackWithMqtt5(ref TByteBlock byteBlock) + protected override void UnpackWithMqtt5(ref TReader reader) { - this.MessageId = byteBlock.ReadUInt16(EndianType.Big); + this.MessageId = ReaderExtension.ReadValue(ref reader, EndianType.Big); - var propertiesReader = new MqttV5PropertiesReader(ref byteBlock); + var propertiesReader = new MqttV5PropertiesReader(ref reader); - while (propertiesReader.TryRead(ref byteBlock, out var mqttPropertyId)) + while (propertiesReader.TryRead(ref reader, out var mqttPropertyId)) { switch (mqttPropertyId) { case MqttPropertyId.ReasonString: - this.ReasonString = propertiesReader.ReadReasonString(ref byteBlock); + this.ReasonString = propertiesReader.ReadReasonString(ref reader); break; case MqttPropertyId.UserProperty: - this.AddUserProperty(propertiesReader.ReadUserProperty(ref byteBlock)); + this.AddUserProperty(propertiesReader.ReadUserProperty(ref reader)); break; default: ThrowHelper.ThrowInvalidEnumArgumentException(mqttPropertyId); @@ -64,9 +63,9 @@ public partial class MqttUnsubAckMessage //this.ReasonString = propertiesReader.ReasonString; //this.UserProperties = propertiesReader.UserProperties; - while (!this.EndOfByteBlock(byteBlock)) + while (!this.EndOfByteBlock(reader)) { - this.m_reasonCodes.Add((MqttReasonCode)byteBlock.ReadByte()); + this.m_reasonCodes.Add((MqttReasonCode)ReaderExtension.ReadValue(ref reader)); } } } \ No newline at end of file diff --git a/src/TouchSocket.Mqtt/MqttMessages/V5/MqttUnsubscribeMessage_v5.cs b/src/TouchSocket.Mqtt/MqttMessages/V5/MqttUnsubscribeMessage_v5.cs index 8f6929329..f0eabaf91 100644 --- a/src/TouchSocket.Mqtt/MqttMessages/V5/MqttUnsubscribeMessage_v5.cs +++ b/src/TouchSocket.Mqtt/MqttMessages/V5/MqttUnsubscribeMessage_v5.cs @@ -10,41 +10,41 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Mqtt; public partial class MqttUnsubscribeMessage { /// - protected override void BuildVariableBodyWithMqtt5(ref TByteBlock byteBlock) + protected override void BuildVariableBodyWithMqtt5(ref TWriter writer) { - byteBlock.WriteUInt16(this.MessageId, EndianType.Big); + WriterExtension.WriteValue(ref writer, this.MessageId, EndianType.Big); var variableByteIntegerRecorder = new VariableByteIntegerRecorder(); - variableByteIntegerRecorder.CheckOut(ref byteBlock); - MqttExtension.WriteUserProperties(ref byteBlock, this.UserProperties); - variableByteIntegerRecorder.CheckIn(ref byteBlock); + var byteBlockWriter = this.CreateVariableWriter(ref writer); + variableByteIntegerRecorder.CheckOut(ref byteBlockWriter); + MqttExtension.WriteUserProperties(ref byteBlockWriter, this.UserProperties); + variableByteIntegerRecorder.CheckIn(ref byteBlockWriter); + writer.Advance(byteBlockWriter.Position); foreach (var topicFilter in this.TopicFilters) { - MqttExtension.WriteMqttInt16String(ref byteBlock, topicFilter); + MqttExtension.WriteMqttInt16String(ref writer, topicFilter); } } /// - protected override void UnpackWithMqtt5(ref TByteBlock byteBlock) + protected override void UnpackWithMqtt5(ref TReader reader) { - this.MessageId = byteBlock.ReadUInt16(EndianType.Big); + this.MessageId = ReaderExtension.ReadValue(ref reader, EndianType.Big); - var propertiesReader = new MqttV5PropertiesReader(ref byteBlock); + var propertiesReader = new MqttV5PropertiesReader(ref reader); - while (propertiesReader.TryRead(ref byteBlock, out var mqttPropertyId)) + while (propertiesReader.TryRead(ref reader, out var mqttPropertyId)) { switch (mqttPropertyId) { case MqttPropertyId.UserProperty: - this.AddUserProperty(propertiesReader.ReadUserProperty(ref byteBlock)); + this.AddUserProperty(propertiesReader.ReadUserProperty(ref reader)); break; default: ThrowHelper.ThrowInvalidEnumArgumentException(mqttPropertyId); @@ -54,9 +54,9 @@ public partial class MqttUnsubscribeMessage //this.UserProperties = propertiesReader.UserProperties; - while (!this.EndOfByteBlock(byteBlock)) + while (!this.EndOfByteBlock(reader)) { - this.m_topicFilters.Add(MqttExtension.ReadMqttInt16String(ref byteBlock)); + this.m_topicFilters.Add(MqttExtension.ReadMqttInt16String(ref reader)); } } } \ No newline at end of file diff --git a/src/TouchSocket.Mqtt/MqttProperties/MqttProperty.cs b/src/TouchSocket.Mqtt/MqttProperties/MqttProperty.cs index 34fda4b9f..687aa3ce0 100644 --- a/src/TouchSocket.Mqtt/MqttProperties/MqttProperty.cs +++ b/src/TouchSocket.Mqtt/MqttProperties/MqttProperty.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Mqtt; /// diff --git a/src/TouchSocket.Mqtt/MqttProperties/MqttV5PropertiesReader.cs b/src/TouchSocket.Mqtt/MqttProperties/MqttV5PropertiesReader.cs index 615ca08ab..4c8260577 100644 --- a/src/TouchSocket.Mqtt/MqttProperties/MqttV5PropertiesReader.cs +++ b/src/TouchSocket.Mqtt/MqttProperties/MqttV5PropertiesReader.cs @@ -11,42 +11,41 @@ //------------------------------------------------------------------------------ using System.Diagnostics; -using TouchSocket.Core; namespace TouchSocket.Mqtt; /// /// 读取Mqtt v5属性的类。 /// -/// 实现IByteBlock接口的类型。 -public readonly ref struct MqttV5PropertiesReader where TByteBlock : IByteBlock +/// 实现IByteBlock接口的类型。 +public readonly ref struct MqttV5PropertiesReader where TReader : IBytesReader { private readonly int m_endPosition; /// /// 初始化MqttV5PropertiesReader类的新实例。 /// - /// 字节块引用。 - public MqttV5PropertiesReader(ref TByteBlock byteBlock) + /// 字节块引用。 + public MqttV5PropertiesReader(ref TReader reader) { - var length = MqttExtension.ReadVariableByteInteger(ref byteBlock); - this.m_endPosition = (int)(byteBlock.Position + length); + var length = MqttExtension.ReadVariableByteInteger(ref reader); + this.m_endPosition = (int)(reader.BytesRead + length); } /// /// 尝试读取属性标识符。 /// - /// 字节块引用。 + /// 字节块引用。 /// 读取的属性标识符。 /// 如果读取成功,则返回;否则返回 - public bool TryRead(ref TByteBlock byteBlock, out MqttPropertyId identifier) + public bool TryRead(ref TReader reader, out MqttPropertyId identifier) { - if (byteBlock.Position >= this.m_endPosition) + if (reader.BytesRead >= this.m_endPosition) { identifier = MqttPropertyId.None; return false; } - identifier = (MqttPropertyId)byteBlock.ReadByte(); + identifier = (MqttPropertyId)ReaderExtension.ReadValue(ref reader); Debug.WriteLine($"PropertyId:{identifier}"); return true; } @@ -54,273 +53,273 @@ public readonly ref struct MqttV5PropertiesReader where TByteBlock : /// /// 读取有效载荷格式指示符。 /// - /// 字节块引用。 + /// 字节块引用。 /// 有效载荷格式指示符。 - public MqttPayloadFormatIndicator ReadPayloadFormatIndicator(ref TByteBlock byteBlock) + public MqttPayloadFormatIndicator ReadPayloadFormatIndicator(ref TReader reader) { - return (MqttPayloadFormatIndicator)byteBlock.ReadByte(); + return (MqttPayloadFormatIndicator)ReaderExtension.ReadValue(ref reader); } /// /// 读取消息过期间隔。 /// - /// 字节块引用。 + /// 字节块引用。 /// 消息过期间隔。 - public uint ReadMessageExpiryInterval(ref TByteBlock byteBlock) + public uint ReadMessageExpiryInterval(ref TReader reader) { - return byteBlock.ReadUInt32(EndianType.Big); + return ReaderExtension.ReadValue(ref reader, EndianType.Big); } /// /// 读取内容类型。 /// - /// 字节块引用。 + /// 字节块引用。 /// 内容类型。 - public string ReadContentType(ref TByteBlock byteBlock) + public string ReadContentType(ref TReader reader) { - return MqttExtension.ReadMqttInt16String(ref byteBlock); + return MqttExtension.ReadMqttInt16String(ref reader); } /// /// 读取响应主题。 /// - /// 字节块引用。 + /// 字节块引用。 /// 响应主题。 - public string ReadResponseTopic(ref TByteBlock byteBlock) + public string ReadResponseTopic(ref TReader reader) { - return MqttExtension.ReadMqttInt16String(ref byteBlock); + return MqttExtension.ReadMqttInt16String(ref reader); } /// /// 读取关联数据。 /// - /// 字节块引用。 + /// 字节块引用。 /// 关联数据。 - public byte[] ReadCorrelationData(ref TByteBlock byteBlock) + public ReadOnlyMemory ReadCorrelationData(ref TReader reader) { - return MqttExtension.ReadMqttBinaryData(ref byteBlock); + return MqttExtension.ReadMqttBinaryData(ref reader); } /// /// 读取订阅标识符。 /// - /// 字节块引用。 + /// 字节块引用。 /// 订阅标识符。 - public uint ReadSubscriptionIdentifier(ref TByteBlock byteBlock) + public uint ReadSubscriptionIdentifier(ref TReader reader) { - return MqttExtension.ReadVariableByteInteger(ref byteBlock); + return MqttExtension.ReadVariableByteInteger(ref reader); } /// /// 读取会话过期间隔。 /// - /// 字节块引用。 + /// 字节块引用。 /// 会话过期间隔。 - public uint ReadSessionExpiryInterval(ref TByteBlock byteBlock) + public uint ReadSessionExpiryInterval(ref TReader reader) { - return byteBlock.ReadUInt32(EndianType.Big); + return ReaderExtension.ReadValue(ref reader, EndianType.Big); } /// /// 读取分配的客户端标识符。 /// - /// 字节块引用。 + /// 字节块引用。 /// 分配的客户端标识符。 - public string ReadAssignedClientIdentifier(ref TByteBlock byteBlock) + public string ReadAssignedClientIdentifier(ref TReader reader) { - return MqttExtension.ReadMqttInt16String(ref byteBlock); + return MqttExtension.ReadMqttInt16String(ref reader); } /// /// 读取服务器保持连接时间。 /// - /// 字节块引用。 + /// 字节块引用。 /// 服务器保持连接时间。 - public ushort ReadServerKeepAlive(ref TByteBlock byteBlock) + public ushort ReadServerKeepAlive(ref TReader reader) { - return byteBlock.ReadUInt16(EndianType.Big); + return ReaderExtension.ReadValue(ref reader, EndianType.Big); } /// /// 读取认证方法。 /// - /// 字节块引用。 + /// 字节块引用。 /// 认证方法。 - public string ReadAuthenticationMethod(ref TByteBlock byteBlock) + public string ReadAuthenticationMethod(ref TReader reader) { - return MqttExtension.ReadMqttInt16String(ref byteBlock); + return MqttExtension.ReadMqttInt16String(ref reader); } /// /// 读取认证数据。 /// - /// 字节块引用。 + /// 字节块引用。 /// 认证数据。 - public byte[] ReadAuthenticationData(ref TByteBlock byteBlock) + public ReadOnlyMemory ReadAuthenticationData(ref TReader reader) { - return MqttExtension.ReadMqttBinaryData(ref byteBlock); + return MqttExtension.ReadMqttBinaryData(ref reader); } /// /// 读取请求问题信息标志。 /// - /// 字节块引用。 + /// 字节块引用。 /// 请求问题信息标志。 - public bool ReadRequestProblemInformation(ref TByteBlock byteBlock) + public bool ReadRequestProblemInformation(ref TReader reader) { - return byteBlock.ReadByte() == 1; + return ReaderExtension.ReadValue(ref reader) == 1; } /// /// 读取遗嘱延迟间隔。 /// - /// 字节块引用。 + /// 字节块引用。 /// 遗嘱延迟间隔。 - public uint ReadWillDelayInterval(ref TByteBlock byteBlock) + public uint ReadWillDelayInterval(ref TReader reader) { - return byteBlock.ReadUInt32(EndianType.Big); + return ReaderExtension.ReadValue(ref reader, EndianType.Big); } /// /// 读取请求响应信息标志。 /// - /// 字节块引用。 + /// 字节块引用。 /// 请求响应信息标志。 - public bool ReadRequestResponseInformation(ref TByteBlock byteBlock) + public bool ReadRequestResponseInformation(ref TReader reader) { - return byteBlock.ReadByte() == 1; + return ReaderExtension.ReadValue(ref reader) == 1; } /// /// 读取响应信息。 /// - /// 字节块引用。 + /// 字节块引用。 /// 响应信息。 - public string ReadResponseInformation(ref TByteBlock byteBlock) + public string ReadResponseInformation(ref TReader reader) { - return MqttExtension.ReadMqttInt16String(ref byteBlock); + return MqttExtension.ReadMqttInt16String(ref reader); } /// /// 读取服务器引用。 /// - /// 字节块引用。 + /// 字节块引用。 /// 服务器引用。 - public string ReadServerReference(ref TByteBlock byteBlock) + public string ReadServerReference(ref TReader reader) { - return MqttExtension.ReadMqttInt16String(ref byteBlock); + return MqttExtension.ReadMqttInt16String(ref reader); } /// /// 读取原因字符串。 /// - /// 字节块引用。 + /// 字节块引用。 /// 原因字符串。 - public string ReadReasonString(ref TByteBlock byteBlock) + public string ReadReasonString(ref TReader reader) { - return MqttExtension.ReadMqttInt16String(ref byteBlock); + return MqttExtension.ReadMqttInt16String(ref reader); } /// /// 读取接收最大值。 /// - /// 字节块引用。 + /// 字节块引用。 /// 接收最大值。 - public ushort ReadReceiveMaximum(ref TByteBlock byteBlock) + public ushort ReadReceiveMaximum(ref TReader reader) { - return byteBlock.ReadUInt16(EndianType.Big); + return ReaderExtension.ReadValue(ref reader, EndianType.Big); } /// /// 读取主题别名最大值。 /// - /// 字节块引用。 + /// 字节块引用。 /// 主题别名最大值。 - public ushort ReadTopicAliasMaximum(ref TByteBlock byteBlock) + public ushort ReadTopicAliasMaximum(ref TReader reader) { - return byteBlock.ReadUInt16(EndianType.Big); + return ReaderExtension.ReadValue(ref reader, EndianType.Big); } /// /// 读取主题别名。 /// - /// 字节块引用。 + /// 字节块引用。 /// 主题别名。 - public ushort ReadTopicAlias(ref TByteBlock byteBlock) + public ushort ReadTopicAlias(ref TReader reader) { - return byteBlock.ReadUInt16(EndianType.Big); + return ReaderExtension.ReadValue(ref reader, EndianType.Big); } /// /// 读取最大服务质量。 /// - /// 字节块引用。 + /// 字节块引用。 /// 最大服务质量。 - public QosLevel ReadMaximumQoS(ref TByteBlock byteBlock) + public QosLevel ReadMaximumQoS(ref TReader reader) { - return (QosLevel)byteBlock.ReadByte(); + return (QosLevel)ReaderExtension.ReadValue(ref reader); } /// /// 读取保留可用标志。 /// - /// 字节块引用。 + /// 字节块引用。 /// 保留可用标志。 - public bool ReadRetainAvailable(ref TByteBlock byteBlock) + public bool ReadRetainAvailable(ref TReader reader) { - return byteBlock.ReadByte() == 1; + return ReaderExtension.ReadValue(ref reader) == 1; } /// /// 读取用户属性。 /// - /// 字节块引用。 + /// 字节块引用。 /// 用户属性。 - public MqttUserProperty ReadUserProperty(ref TByteBlock byteBlock) + public MqttUserProperty ReadUserProperty(ref TReader reader) { - var name = MqttExtension.ReadMqttInt16String(ref byteBlock); - var value = MqttExtension.ReadMqttInt16String(ref byteBlock); + var name = MqttExtension.ReadMqttInt16String(ref reader); + var value = MqttExtension.ReadMqttInt16String(ref reader); return new MqttUserProperty(name, value); } /// /// 读取最大数据包大小。 /// - /// 字节块引用。 + /// 字节块引用。 /// 最大数据包大小。 - public uint ReadMaximumPacketSize(ref TByteBlock byteBlock) + public uint ReadMaximumPacketSize(ref TReader reader) { - return byteBlock.ReadUInt32(EndianType.Big); + return ReaderExtension.ReadValue(ref reader, EndianType.Big); } /// /// 读取通配符订阅可用标志。 /// - /// 字节块引用。 + /// 字节块引用。 /// 通配符订阅可用标志。 - public bool ReadWildcardSubscriptionAvailable(ref TByteBlock byteBlock) + public bool ReadWildcardSubscriptionAvailable(ref TReader reader) { - return byteBlock.ReadByte() == 1; + return ReaderExtension.ReadValue(ref reader) == 1; } /// /// 读取订阅标识符可用标志。 /// - /// 字节块引用。 + /// 字节块引用。 /// 订阅标识符可用标志。 - public bool ReadSubscriptionIdentifiersAvailable(ref TByteBlock byteBlock) + public bool ReadSubscriptionIdentifiersAvailable(ref TReader reader) { - return byteBlock.ReadByte() == 1; + return ReaderExtension.ReadValue(ref reader) == 1; } /// /// 读取共享订阅可用标志。 /// - /// 字节块引用。 + /// 字节块引用。 /// 共享订阅可用标志。 - public bool ReadSharedSubscriptionAvailable(ref TByteBlock byteBlock) + public bool ReadSharedSubscriptionAvailable(ref TReader reader) { - return byteBlock.ReadByte() == 1; + return ReaderExtension.ReadValue(ref reader) == 1; } } @@ -334,15 +333,15 @@ public readonly ref struct MqttV5PropertiesReader where TByteBlock : // /// // /// 初始化MqttV5PropertiesReader类的新实例。 // /// -// /// 字节块引用。 -// public MqttV5PropertiesReader(ref TByteBlock byteBlock) +// /// 字节块引用。 +// public MqttV5PropertiesReader(ref TByteBlock reader) // { -// var length = MqttExtension.ReadVariableByteInteger(ref byteBlock); -// var endPosition = byteBlock.Position + length; +// var length = MqttExtension.ReadVariableByteInteger(ref reader); +// var endPosition = reader.Position + length; // var properties = new List(); -// while (byteBlock.Position < endPosition) +// while (reader.Position < endPosition) // { -// var identifier = (MqttPropertyId)byteBlock.ReadByte(); +// var identifier = (MqttPropertyId)ReaderExtension.ReadValue(ref reader); // Debug.WriteLine($"PropertyId:{identifier}"); // switch (identifier) // { @@ -351,165 +350,165 @@ public readonly ref struct MqttV5PropertiesReader where TByteBlock : // case MqttPropertyId.PayloadFormatIndicator: // { -// this.PayloadFormatIndicator = (MqttPayloadFormatIndicator)byteBlock.ReadByte(); +// this.PayloadFormatIndicator = (MqttPayloadFormatIndicator)ReaderExtension.ReadValue(ref reader); // } // break; // case MqttPropertyId.MessageExpiryInterval: // { -// this.MessageExpiryInterval = byteBlock.ReadUInt32(EndianType.Big); +// this.MessageExpiryInterval = ReaderExtension.ReadValue(ref reader,EndianType.Big); // } // break; // case MqttPropertyId.ContentType: // { -// this.ContentType = MqttExtension.ReadMqttInt16String(ref byteBlock); +// this.ContentType = MqttExtension.ReadMqttInt16String(ref reader); // } // break; // case MqttPropertyId.ResponseTopic: // { -// this.ResponseTopic = MqttExtension.ReadMqttInt16String(ref byteBlock); +// this.ResponseTopic = MqttExtension.ReadMqttInt16String(ref reader); // } // break; // case MqttPropertyId.CorrelationData: // { -// this.CorrelationData = MqttExtension.ReadMqttBinaryData(ref byteBlock); +// this.CorrelationData = MqttExtension.ReadMqttBinaryData(ref reader); // } // break; // case MqttPropertyId.SubscriptionIdentifier: // { -// this.SubscriptionIdentifier = MqttExtension.ReadVariableByteInteger(ref byteBlock); +// this.SubscriptionIdentifier = MqttExtension.ReadVariableByteInteger(ref reader); // } // break; // case MqttPropertyId.SessionExpiryInterval: // { -// this.SessionExpiryInterval = byteBlock.ReadUInt32(EndianType.Big); +// this.SessionExpiryInterval = ReaderExtension.ReadValue(ref reader,EndianType.Big); // } // break; // case MqttPropertyId.AssignedClientIdentifier: // { -// this.AssignedClientIdentifier = MqttExtension.ReadMqttInt16String(ref byteBlock); +// this.AssignedClientIdentifier = MqttExtension.ReadMqttInt16String(ref reader); // } // break; // case MqttPropertyId.ServerKeepAlive: // { -// this.ServerKeepAlive = byteBlock.ReadUInt16(EndianType.Big); +// this.ServerKeepAlive = ReaderExtension.ReadValue(ref reader,EndianType.Big); // } // break; // case MqttPropertyId.AuthenticationMethod: // { -// this.AuthenticationMethod = MqttExtension.ReadMqttInt16String(ref byteBlock); +// this.AuthenticationMethod = MqttExtension.ReadMqttInt16String(ref reader); // } // break; // case MqttPropertyId.AuthenticationData: // { -// this.AuthenticationData = MqttExtension.ReadMqttBinaryData(ref byteBlock); +// this.AuthenticationData = MqttExtension.ReadMqttBinaryData(ref reader); // } // break; // case MqttPropertyId.RequestProblemInformation: // { -// this.RequestProblemInformation = byteBlock.ReadByte() == 1; +// this.RequestProblemInformation = ReaderExtension.ReadValue(ref reader) == 1; // } // break; // case MqttPropertyId.WillDelayInterval: // { -// this.WillDelayInterval = byteBlock.ReadUInt32(EndianType.Big); +// this.WillDelayInterval = ReaderExtension.ReadValue(ref reader,EndianType.Big); // } // break; // case MqttPropertyId.RequestResponseInformation: // { -// this.RequestResponseInformation = byteBlock.ReadByte() == 1; +// this.RequestResponseInformation = ReaderExtension.ReadValue(ref reader) == 1; // } // break; // case MqttPropertyId.ResponseInformation: // { -// this.ResponseInformation = MqttExtension.ReadMqttInt16String(ref byteBlock); +// this.ResponseInformation = MqttExtension.ReadMqttInt16String(ref reader); // } // break; // case MqttPropertyId.ServerReference: // { -// this.ServerReference = MqttExtension.ReadMqttInt16String(ref byteBlock); +// this.ServerReference = MqttExtension.ReadMqttInt16String(ref reader); // } // break; // case MqttPropertyId.ReasonString: // { -// this.ReasonString = MqttExtension.ReadMqttInt16String(ref byteBlock); +// this.ReasonString = MqttExtension.ReadMqttInt16String(ref reader); // } // break; // case MqttPropertyId.ReceiveMaximum: // { -// this.ReceiveMaximum = byteBlock.ReadUInt16(EndianType.Big); +// this.ReceiveMaximum = ReaderExtension.ReadValue(ref reader,EndianType.Big); // } // break; // case MqttPropertyId.TopicAliasMaximum: // { -// this.TopicAliasMaximum = byteBlock.ReadUInt16(EndianType.Big); +// this.TopicAliasMaximum = ReaderExtension.ReadValue(ref reader,EndianType.Big); // } // break; // case MqttPropertyId.TopicAlias: // { -// this.TopicAlias = byteBlock.ReadUInt16(EndianType.Big); +// this.TopicAlias = ReaderExtension.ReadValue(ref reader,EndianType.Big); // } // break; // case MqttPropertyId.MaximumQoS: // { -// this.MaximumQoS = (QosLevel)byteBlock.ReadByte(); +// this.MaximumQoS = (QosLevel)ReaderExtension.ReadValue(ref reader); // } // break; // case MqttPropertyId.RetainAvailable: // { -// this.RetainAvailable = byteBlock.ReadByte() == 1; +// this.RetainAvailable = ReaderExtension.ReadValue(ref reader) == 1; // } // break; // case MqttPropertyId.UserProperty: // { -// var name = MqttExtension.ReadMqttInt16String(ref byteBlock); -// var value = MqttExtension.ReadMqttInt16String(ref byteBlock); +// var name = MqttExtension.ReadMqttInt16String(ref reader); +// var value = MqttExtension.ReadMqttInt16String(ref reader); // properties.Add(new MqttUserProperty(name, value)); // } // break; // case MqttPropertyId.MaximumPacketSize: // { -// this.MaximumPacketSize = byteBlock.ReadUInt32(EndianType.Big); +// this.MaximumPacketSize = ReaderExtension.ReadValue(ref reader,EndianType.Big); // } // break; // case MqttPropertyId.WildcardSubscriptionAvailable: // { -// this.WildcardSubscriptionAvailable = byteBlock.ReadByte() == 1; +// this.WildcardSubscriptionAvailable = ReaderExtension.ReadValue(ref reader) == 1; // } // break; // case MqttPropertyId.SubscriptionIdentifiersAvailable: // { -// this.SubscriptionIdentifiersAvailable = byteBlock.ReadByte() == 1; +// this.SubscriptionIdentifiersAvailable = ReaderExtension.ReadValue(ref reader) == 1; // } // break; // case MqttPropertyId.SharedSubscriptionAvailable: // { -// this.SharedSubscriptionAvailable = byteBlock.ReadByte() == 1; +// this.SharedSubscriptionAvailable = ReaderExtension.ReadValue(ref reader) == 1; // } // break; diff --git a/src/TouchSocket.Mqtt/Options/MqttConnectOptions.cs b/src/TouchSocket.Mqtt/Options/MqttConnectOptions.cs index 97b6ea9b1..22a4d1dbf 100644 --- a/src/TouchSocket.Mqtt/Options/MqttConnectOptions.cs +++ b/src/TouchSocket.Mqtt/Options/MqttConnectOptions.cs @@ -10,9 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using TouchSocket.Core; namespace TouchSocket.Mqtt; @@ -216,8 +214,8 @@ public class MqttConnectOptions /// Mqtt 5.0.0以上 /// public IReadOnlyList WillUserProperties { get; set; } - public string WillMessage { get; internal set; } - public string WillTopic { get; internal set; } + public ReadOnlyMemory WillPayload { get; set; } + public string WillTopic { get; set; } #endregion Will Properties diff --git a/src/TouchSocket.Mqtt/Plugins/IMqttClosedPlugin.cs b/src/TouchSocket.Mqtt/Plugins/IMqttClosedPlugin.cs index 054b1d75c..fe0defb85 100644 --- a/src/TouchSocket.Mqtt/Plugins/IMqttClosedPlugin.cs +++ b/src/TouchSocket.Mqtt/Plugins/IMqttClosedPlugin.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 // ------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Mqtt; /// /// 定义一个插件接口,当Mqtt客户端关闭时调用。 diff --git a/src/TouchSocket.Mqtt/Plugins/IMqttClosingPlugin.cs b/src/TouchSocket.Mqtt/Plugins/IMqttClosingPlugin.cs index 3d08a4a92..18db18399 100644 --- a/src/TouchSocket.Mqtt/Plugins/IMqttClosingPlugin.cs +++ b/src/TouchSocket.Mqtt/Plugins/IMqttClosingPlugin.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 // ------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Mqtt; /// diff --git a/src/TouchSocket.Mqtt/Plugins/IMqttConnectedPlugin.cs b/src/TouchSocket.Mqtt/Plugins/IMqttConnectedPlugin.cs index 78b0ff803..2ae85e784 100644 --- a/src/TouchSocket.Mqtt/Plugins/IMqttConnectedPlugin.cs +++ b/src/TouchSocket.Mqtt/Plugins/IMqttConnectedPlugin.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 // ------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Mqtt; /// diff --git a/src/TouchSocket.Mqtt/Plugins/IMqttConnectingPlugin.cs b/src/TouchSocket.Mqtt/Plugins/IMqttConnectingPlugin.cs index cf89fb4b5..9118606bf 100644 --- a/src/TouchSocket.Mqtt/Plugins/IMqttConnectingPlugin.cs +++ b/src/TouchSocket.Mqtt/Plugins/IMqttConnectingPlugin.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 // ------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Mqtt; /// diff --git a/src/TouchSocket.Mqtt/Plugins/IMqttReceivedPlugin.cs b/src/TouchSocket.Mqtt/Plugins/IMqttReceivedPlugin.cs index 53de93dc7..6cffdf441 100644 --- a/src/TouchSocket.Mqtt/Plugins/IMqttReceivedPlugin.cs +++ b/src/TouchSocket.Mqtt/Plugins/IMqttReceivedPlugin.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Mqtt; /// diff --git a/src/TouchSocket.Mqtt/Plugins/IMqttReceivingPlugin.cs b/src/TouchSocket.Mqtt/Plugins/IMqttReceivingPlugin.cs index ed4982ebb..8b05caac7 100644 --- a/src/TouchSocket.Mqtt/Plugins/IMqttReceivingPlugin.cs +++ b/src/TouchSocket.Mqtt/Plugins/IMqttReceivingPlugin.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Mqtt; /// diff --git a/src/TouchSocket.Mqtt/Readme.md b/src/TouchSocket.Mqtt/Readme.md index 91db80077..b068b048e 100644 --- a/src/TouchSocket.Mqtt/Readme.md +++ b/src/TouchSocket.Mqtt/Readme.md @@ -18,7 +18,7 @@ TouchSocket.Mqtt 是一个提供 Mqtt 服务器和客户端的组件库。借助 - netstandard2.0 - netstandard2.1 - net6.0 -- net9.0 +- net10.0 - net8.0 ## 贡献与反馈 diff --git a/src/TouchSocket.Mqtt/TouchSocket.Mqtt.csproj b/src/TouchSocket.Mqtt/TouchSocket.Mqtt.csproj index b91e9d7a0..185a242e4 100644 --- a/src/TouchSocket.Mqtt/TouchSocket.Mqtt.csproj +++ b/src/TouchSocket.Mqtt/TouchSocket.Mqtt.csproj @@ -1,16 +1,16 @@ - net481;net462;net472;netstandard2.0;netstandard2.1;net6.0;net9.0;net8.0 + net462;netstandard2.0;netstandard2.1;net6.0;net10.0;net8.0 Mqtt;TouchSocket 这是一个提供Mqtt服务器和客户端的组件库。可以通过该组件创建基于Tcp、WebSocket协议的Mqtt服务器和客户端,支持Mqtt全部功能,可与Web,Android等平台无缝对接。 说明文档:https://touchsocket.net/ TouchSocket.Mqtt - $(TouchSocketVersion)-Alpha + - + @@ -22,17 +22,11 @@ - - - - + - - - - + @@ -45,7 +39,7 @@ - + diff --git a/src/TouchSocket.NamedPipe/Common/NamedPipeListenOption.cs b/src/TouchSocket.NamedPipe/Common/NamedPipeListenOption.cs index e205c2164..8188ebdda 100644 --- a/src/TouchSocket.NamedPipe/Common/NamedPipeListenOption.cs +++ b/src/TouchSocket.NamedPipe/Common/NamedPipeListenOption.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using TouchSocket.Core; - namespace TouchSocket.NamedPipe; /// @@ -24,15 +21,10 @@ public class NamedPipeListenOption /// 名称 /// public string Name { get; set; } - - /// - /// 发送超时时间 - /// - public int SendTimeout { get; set; } + public string PipeName { get; set; } /// /// 配置适配器 /// - public Func Adapter { get; set; } = - () => new NormalDataHandlingAdapter(); + public Func Adapter { get; set; } } \ No newline at end of file diff --git a/src/TouchSocket.NamedPipe/Common/NamedPipeMonitor.cs b/src/TouchSocket.NamedPipe/Common/NamedPipeMonitor.cs index d58fc868f..c296117f9 100644 --- a/src/TouchSocket.NamedPipe/Common/NamedPipeMonitor.cs +++ b/src/TouchSocket.NamedPipe/Common/NamedPipeMonitor.cs @@ -15,7 +15,7 @@ namespace TouchSocket.NamedPipe; /// /// 命名管道监听器 /// -public class NamedPipeMonitor +public class NamedPipeMonitor : DisposableObject { /// /// 命名管道监听器 @@ -30,4 +30,21 @@ public class NamedPipeMonitor /// 命名管道监听配置 /// public NamedPipeListenOption Option { get; } + + private readonly CancellationTokenSource m_cancellationTokenSource = new CancellationTokenSource(); + + /// + /// 获取用于监听的。 + /// + public CancellationToken MonitorToken => this.m_cancellationTokenSource.Token; + + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + this.m_cancellationTokenSource.SafeCancel(); + } + base.Dispose(disposing); + } } \ No newline at end of file diff --git a/src/TouchSocket.NamedPipe/Components/NamedPipeClient.cs b/src/TouchSocket.NamedPipe/Components/NamedPipeClient.cs index e385f8cbe..5d620873b 100644 --- a/src/TouchSocket.NamedPipe/Components/NamedPipeClient.cs +++ b/src/TouchSocket.NamedPipe/Components/NamedPipeClient.cs @@ -10,13 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; -using TouchSocket.Sockets; - namespace TouchSocket.NamedPipe; /// @@ -156,9 +149,9 @@ public class NamedPipeClient : NamedPipeClientBase, INamedPipeClient #region Connect /// - public virtual Task ConnectAsync(int millisecondsTimeout, CancellationToken token) + public virtual Task ConnectAsync(CancellationToken cancellationToken) { - return this.PipeConnectAsync(millisecondsTimeout, token); + return this.PipeConnectAsync(cancellationToken); } #endregion Connect @@ -182,22 +175,15 @@ public class NamedPipeClient : NamedPipeClientBase, INamedPipeClient #region 异步发送 /// - public virtual Task SendAsync(ReadOnlyMemory memory) + public virtual Task SendAsync(ReadOnlyMemory memory, CancellationToken cancellationToken = default) { - return this.ProtectedSendAsync(memory); + return this.ProtectedSendAsync(memory, cancellationToken); } /// - public virtual Task SendAsync(IRequestInfo requestInfo) + public virtual Task SendAsync(IRequestInfo requestInfo, CancellationToken cancellationToken = default) { - return this.ProtectedSendAsync(requestInfo); + return this.ProtectedSendAsync(requestInfo, cancellationToken); } - - /// - public virtual Task SendAsync(IList> transferBytes) - { - return this.ProtectedSendAsync(transferBytes); - } - #endregion 异步发送 } \ No newline at end of file diff --git a/src/TouchSocket.NamedPipe/Components/NamedPipeClientBase.cs b/src/TouchSocket.NamedPipe/Components/NamedPipeClientBase.cs index 24e54e790..a95f983df 100644 --- a/src/TouchSocket.NamedPipe/Components/NamedPipeClientBase.cs +++ b/src/TouchSocket.NamedPipe/Components/NamedPipeClientBase.cs @@ -10,23 +10,18 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; using System.IO.Pipes; using System.Runtime.CompilerServices; using System.Security.Principal; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Resources; -using TouchSocket.Sockets; namespace TouchSocket.NamedPipe; /// /// 命名管道客户端客户端基类 /// -public abstract class NamedPipeClientBase : SetupConfigObject, INamedPipeSession +[CodeInject.RegionInject(FileName = "TcpClientBase.cs", RegionName = "ReceiveLoopAsync", Placeholders = new[] { "OnTcpReceiving", "OnNamedPipeReceiving" })] +public abstract partial class NamedPipeClientBase : SetupConfigObject, INamedPipeSession { /// /// 命名管道客户端客户端基类 @@ -34,47 +29,21 @@ public abstract class NamedPipeClientBase : SetupConfigObject, INamedPipeSession public NamedPipeClientBase() { this.Protocol = Protocol.NamedPipe; - this.m_receiveCounter = new ValueCounter - { - Period = TimeSpan.FromSeconds(1), - OnPeriod = this.OnPeriod - }; } #region 变量 private readonly SemaphoreSlim m_semaphoreSlimForConnect = new SemaphoreSlim(1, 1); - private readonly SemaphoreSlim m_semaphoreSlimForSend = new SemaphoreSlim(1, 1); - private volatile bool m_online; - private Task m_receiveTask; - private NamedPipeClientStream m_pipeStream; - private int m_receiveBufferSize = 1024 * 10; - private ValueCounter m_receiveCounter; - private InternalReceiver m_receiver; private SingleStreamDataHandlingAdapter m_dataHandlingAdapter; - private readonly Lock m_lockForAbort = new Lock(); + private volatile bool m_online; + private InternalReceiver m_receiver; + private Task m_runTask; + private NamedPipeTransport m_transport; + #endregion 变量 #region 事件 - /// - /// 已经建立管道连接 - /// - /// - protected virtual async Task OnNamedPipeConnected(ConnectedEventArgs e) - { - await this.PluginManager.RaiseAsync(typeof(INamedPipeConnectedPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - - /// - /// 准备连接的时候 - /// - /// - protected virtual async Task OnNamedPipeConnecting(ConnectingEventArgs e) - { - await this.PluginManager.RaiseAsync(typeof(INamedPipeConnectingPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - /// /// 断开连接。在客户端未设置连接状态时,不会触发 /// @@ -93,9 +62,46 @@ public abstract class NamedPipeClientBase : SetupConfigObject, INamedPipeSession await this.PluginManager.RaiseAsync(typeof(INamedPipeClosingPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - private async Task PrivateOnNamedPipeConnected(ConnectedEventArgs e) + /// + /// 已经建立管道连接 + /// + /// + protected virtual async Task OnNamedPipeConnected(ConnectedEventArgs e) { - await this.OnNamedPipeConnected(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.PluginManager.RaiseAsync(typeof(INamedPipeConnectedPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + + /// + /// 准备连接的时候 + /// + /// + protected virtual async Task OnNamedPipeConnecting(ConnectingEventArgs e) + { + await this.PluginManager.RaiseAsync(typeof(INamedPipeConnectingPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + + private async Task PrivateConnected(NamedPipeTransport transport) + { + var receiveTask = EasyTask.SafeRun(this.ReceiveLoopAsync, transport); + + var e_connected = new ConnectedEventArgs(); + await this.OnNamedPipeConnected(e_connected).SafeWaitAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await receiveTask.SafeWaitAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + transport.SafeDispose(); + + var e_closed = transport.ClosedEventArgs; + this.m_online = false; + var adapter = this.m_dataHandlingAdapter; + this.m_dataHandlingAdapter = default; + adapter.SafeDispose(); + + await this.OnNamedPipeClosed(e_closed).SafeWaitAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + + private async Task PrivateOnNamedPipeClosing(ClosingEventArgs e) + { + await this.OnNamedPipeClosing(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } private async Task PrivateOnNamedPipeConnecting(ConnectingEventArgs e) @@ -111,105 +117,52 @@ public abstract class NamedPipeClientBase : SetupConfigObject, INamedPipeSession } } - private async Task PrivateOnNamedPipeClosed((ClosedEventArgs e, Task receiveTask, InternalReceiver receiver) value) - { - await value.receiveTask.SafeWaitAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - if (value.receiver != null) - { - await value.receiver.Complete(value.e.Message).SafeWaitAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - - await this.OnNamedPipeClosed(value.e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - - private async Task PrivateOnNamedPipeClosing(ClosingEventArgs e) - { - await this.OnNamedPipeClosing(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - #endregion 事件 #region 属性 /// - public bool IsClient => true; - - /// - public DateTimeOffset LastReceivedTime { get; private set; } - - /// - public DateTimeOffset LastSentTime { get; private set; } - - /// - public PipeStream PipeStream => this.m_pipeStream; - - /// - public Protocol Protocol { get; protected set; } + public CancellationToken ClosedToken => this.m_transport == null ? new CancellationToken(true) : this.m_transport.ClosedToken; /// public SingleStreamDataHandlingAdapter DataHandlingAdapter => this.m_dataHandlingAdapter; + /// + public bool IsClient => true; + + /// + public DateTimeOffset LastReceivedTime => this.m_transport?.ReceiveCounter.LastIncrement ?? default; + + /// + public DateTimeOffset LastSentTime => this.m_transport?.SendCounter.LastIncrement ?? default; + /// public virtual bool Online => this.m_online; + /// + public Protocol Protocol { get; protected set; } + #endregion 属性 #region 断开操作 - /// - /// 中断链接 - /// - /// - /// - protected void Abort(bool manual, string msg) - { - lock (this.m_lockForAbort) - { - if (this.m_online) - { - this.m_online = false; - this.m_pipeStream.SafeDispose(); - - this.m_dataHandlingAdapter.SafeDispose(); - this.m_dataHandlingAdapter = default; - - _ = EasyTask.SafeRun(this.PrivateOnNamedPipeClosed, (new ClosedEventArgs(manual, msg), this.m_receiveTask, this.m_receiver)); - } - } - } - - /// - protected override void Dispose(bool disposing) - { - if (this.DisposedValue) - { - return; - } - - if (disposing) - { - this.Abort(true, TouchSocketResource.DisposeClose); - } - - base.Dispose(disposing); - } - - /// - public virtual async Task CloseAsync(string msg, CancellationToken token = default) + public virtual async Task CloseAsync(string msg, CancellationToken cancellationToken = default) { try { - if (this.m_online) + if (!this.m_online) { - await this.PrivateOnNamedPipeClosing(new ClosedEventArgs(true, msg)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - - lock (this.m_lockForAbort) - { - this.m_pipeStream.Close(); - this.Abort(true, msg); - } + return Result.Success; } + + await this.PrivateOnNamedPipeClosing(new ClosingEventArgs(msg)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var transport = this.m_transport; + if (transport != null) + { + await transport.CloseAsync(msg, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + await this.WaitClearConnect().ConfigureAwait(EasyTask.ContinueOnCapturedContext); return Result.Success; } catch (Exception ex) @@ -218,6 +171,16 @@ public abstract class NamedPipeClientBase : SetupConfigObject, INamedPipeSession } } + /// + protected override void SafetyDispose(bool disposing) + { + if (disposing) + { + _ = EasyTask.SafeRun(async () => await this.CloseAsync(TouchSocketResource.DisposeClose).ConfigureAwait(EasyTask.ContinueOnCapturedContext)); + } + base.SafetyDispose(disposing); + } + #endregion 断开操作 #region Connect @@ -225,15 +188,14 @@ public abstract class NamedPipeClientBase : SetupConfigObject, INamedPipeSession /// /// 建立管道的连接。 /// - /// - /// + /// 可取消令箭 /// /// /// /// - protected async Task PipeConnectAsync(int millisecondsTimeout, CancellationToken token) + protected async Task PipeConnectAsync(CancellationToken cancellationToken) { - await this.m_semaphoreSlimForConnect.WaitTimeAsync(millisecondsTimeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.m_semaphoreSlimForConnect.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); try { if (this.m_online) @@ -243,31 +205,27 @@ public abstract class NamedPipeClientBase : SetupConfigObject, INamedPipeSession this.ThrowIfDisposed(); this.ThrowIfConfigIsNull(); + await this.WaitClearConnect().ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var pipeName = this.Config.GetValue(NamedPipeConfigExtension.PipeNameProperty); - ThrowHelper.ThrowArgumentNullExceptionIf(pipeName, nameof(pipeName)); + ThrowHelper.ThrowIfNull(pipeName, nameof(pipeName)); var serverName = this.Config.GetValue(NamedPipeConfigExtension.PipeServerNameProperty); - this.m_pipeStream.SafeDispose(); var namedPipe = CreatePipeClient(serverName, pipeName); await this.PrivateOnNamedPipeConnecting(new ConnectingEventArgs()).ConfigureAwait(EasyTask.ContinueOnCapturedContext); -#if NET45 - namedPipe.Connect(millisecondsTimeout); -#else - await namedPipe.ConnectAsync(millisecondsTimeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); -#endif + await namedPipe.ConnectAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - if (namedPipe.IsConnected) + if (!namedPipe.IsConnected) { - this.m_pipeStream = namedPipe; - this.m_online = true; - this.m_receiveTask = EasyTask.Run(this.BeginReceive); - _ = EasyTask.SafeRun(this.PrivateOnNamedPipeConnected, new ConnectedEventArgs()); - return; + ThrowHelper.ThrowException(TouchSocketCoreResource.UnknownError); } - ThrowHelper.ThrowException(TouchSocketCoreResource.UnknownError); + this.m_transport = new NamedPipeTransport(namedPipe, this.Config.GetValue(TouchSocketConfigExtension.TransportOptionProperty)); + this.m_online = true; + // 启动新任务,处理连接后的操作 + this.m_runTask = EasyTask.SafeRun(this.PrivateConnected, this.m_transport); } finally { @@ -275,6 +233,16 @@ public abstract class NamedPipeClientBase : SetupConfigObject, INamedPipeSession } } + private async Task WaitClearConnect() + { + // 确保上次接收任务已经结束 + var runTask = this.m_runTask; + if (runTask != null) + { + await runTask.ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + } + #endregion Connect #region Receiver @@ -307,14 +275,13 @@ public abstract class NamedPipeClientBase : SetupConfigObject, INamedPipeSession /// /// 当收到原始数据 /// - /// + /// /// 如果返回则表示数据已被处理,且不会再向下传递。 - protected virtual ValueTask OnNamedPipeReceiving(ByteBlock byteBlock) + protected virtual ValueTask OnNamedPipeReceiving(IBytesReader reader) { - return this.PluginManager.RaiseAsync(typeof(INamedPipeReceivingPlugin), this.Resolver, this, new ByteBlockEventArgs(byteBlock)); + return this.PluginManager.RaiseAsync(typeof(INamedPipeReceivingPlugin), this.Resolver, this, new BytesReaderEventArgs(reader)); } - /// /// 触发命名管道发送事件的异步方法。 /// @@ -335,23 +302,19 @@ public abstract class NamedPipeClientBase : SetupConfigObject, INamedPipeSession // 检查当前实例是否已被释放 this.ThrowIfDisposed(); - ThrowHelper.ThrowArgumentNullExceptionIf(adapter, nameof(adapter)); + if (adapter is null) + { + this.m_dataHandlingAdapter = null;//允许Null赋值 + return; + } - // 如果当前实例已有配置,则将配置应用到新适配器上 if (this.Config != null) { adapter.Config(this.Config); } - // 将当前实例的日志记录器设置到适配器上 - adapter.Logger = this.Logger; - // 调用适配器的OnLoaded方法,通知适配器已被加载 adapter.OnLoaded(this); - // 设置适配器接收数据时的回调方法 adapter.ReceivedAsyncCallBack = this.PrivateHandleReceivedData; - // 设置适配器发送数据时的异步回调方法 - adapter.SendAsyncCallBack = this.ProtectedDefaultSendAsync; - // 将适配器实例设置为当前数据处理适配器 this.m_dataHandlingAdapter = adapter; } @@ -361,85 +324,15 @@ public abstract class NamedPipeClientBase : SetupConfigObject, INamedPipeSession return pipeClient; } - private async Task BeginReceive() - { - while (true) - { - using (var byteBlock = new ByteBlock(this.GetReceiveBufferSize())) - { - try - { - var r = await this.m_pipeStream.ReadAsync(byteBlock.TotalMemory, CancellationToken.None).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - if (r == 0) - { - this.Abort(false, "远程终端主动关闭"); - return; - } - - byteBlock.SetLength(r); - await this.HandleReceivingData(byteBlock).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - catch (Exception ex) - { - this.Abort(false, ex.Message); - return; - } - } - } - } - - private int GetReceiveBufferSize() - { - var minBufferSize = this.Config.GetValue(TouchSocketConfigExtension.MinBufferSizeProperty) ?? 1024 * 10; - var maxBufferSize = this.Config.GetValue(TouchSocketConfigExtension.MaxBufferSizeProperty) ?? 1024 * 512; - return Math.Min(Math.Max(this.m_receiveBufferSize, minBufferSize), maxBufferSize); - } - - private async Task HandleReceivingData(ByteBlock byteBlock) - { - try - { - if (this.DisposedValue) - { - return; - } - - this.m_receiveCounter.Increment(byteBlock.Length); - - if (await this.OnNamedPipeReceiving(byteBlock).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) - { - return; - } - - if (this.m_dataHandlingAdapter == null) - { - await this.PrivateHandleReceivedData(byteBlock, default).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - else - { - await this.m_dataHandlingAdapter.ReceivedInputAsync(byteBlock).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - } - catch (Exception ex) - { - this.Logger?.Log(LogLevel.Error, this, "在处理数据时发生错误", ex); - } - } - - private void OnPeriod(long value) - { - this.m_receiveBufferSize = TouchSocketCoreUtility.HitBufferLength(value); - } - - private async Task PrivateHandleReceivedData(ByteBlock byteBlock, IRequestInfo requestInfo) + private async Task PrivateHandleReceivedData(ReadOnlyMemory memory, IRequestInfo requestInfo) { var receiver = this.m_receiver; if (receiver != null) { - await receiver.InputReceiveAsync(byteBlock, requestInfo).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await receiver.InputReceiveAsync(memory, requestInfo, CancellationToken.None).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return; } - await this.OnNamedPipeReceived(new ReceivedDataEventArgs(byteBlock, requestInfo)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.OnNamedPipeReceived(new ReceivedDataEventArgs(memory, requestInfo)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } #region Throw @@ -466,111 +359,79 @@ public abstract class NamedPipeClientBase : SetupConfigObject, INamedPipeSession #endregion Throw - #region 直接发送 - /// - protected async Task ProtectedDefaultSendAsync(ReadOnlyMemory memory) + #region 发送 + + /// + /// 异步发送数据,通过适配器模式灵活处理数据发送。 + /// + /// 待发送的只读字节内存块。 + /// 可取消令箭 + /// 一个异步任务,表示发送操作。 + protected async Task ProtectedSendAsync(ReadOnlyMemory memory, CancellationToken cancellationToken) { this.ThrowIfDisposed(); this.ThrowIfClientNotConnected(); + await this.OnNamedPipeSending(memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + var transport = this.m_transport; + var adapter = this.m_dataHandlingAdapter; + var locker = transport.WriteLocker; + + await locker.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); try { - await this.m_semaphoreSlimForSend.WaitAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - await this.m_pipeStream.WriteAsync(memory, CancellationToken.None).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.LastSentTime = DateTimeOffset.UtcNow; + // 如果数据处理适配器未设置,则使用默认发送方式。 + if (adapter == null) + { + await transport.Writer.WriteAsync(memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + else + { + var writer = new PipeBytesWriter(transport.Writer); + adapter.SendInput(ref writer, in memory); + await writer.FlushAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } } finally { - this.m_semaphoreSlimForSend.Release(); + locker.Release(); } } - #endregion 直接发送 - - #region 异步发送 - /// - /// 异步发送数据,根据是否配置了数据处理适配器来决定数据的发送方式。 + /// 异步发送请求信息的受保护方法。 + /// + /// 此方法首先检查当前对象是否能够发送请求信息,如果不能,则抛出异常。 + /// 如果可以发送,它将使用数据处理适配器来异步发送输入请求。 /// - /// 待发送的字节内存。 - /// 一个异步任务,表示发送操作。 - protected Task ProtectedSendAsync(in ReadOnlyMemory memory) + /// 要发送的请求信息。 + /// 可取消令箭 + /// 返回一个任务,该任务代表异步操作的结果。 + protected async Task ProtectedSendAsync(IRequestInfo requestInfo, CancellationToken cancellationToken) { - // 如果未配置数据处理适配器,则使用默认的发送方式。 - if (this.m_dataHandlingAdapter == null) - { - return this.ProtectedDefaultSendAsync(memory); - } - else - { - // 如果配置了数据处理适配器,则使用适配器指定的发送方式。 - return this.m_dataHandlingAdapter.SendInputAsync(memory); - } - } - - - /// - /// 异步安全发送请求信息。 - /// - /// 请求信息对象,包含要发送的数据。 - /// 返回一个任务,表示异步操作的结果。 - /// - /// 此方法用于在发送请求之前验证是否可以发送请求信息, - /// 并通过适配器安全处理发送过程。 - /// - protected Task ProtectedSendAsync(IRequestInfo requestInfo) - { - // 验证当前状态是否允许发送请求信息,如果不允许则抛出异常。 + // 检查是否具备发送请求的条件,如果不具备则抛出异常 this.ThrowIfCannotSendRequestInfo(); - // 调用ProtectedDataHandlingAdapter的SendInputAsync方法异步发送请求信息。 - return this.m_dataHandlingAdapter.SendInputAsync(requestInfo); - } - /// - /// 异步发送经过处理的数据。 - /// 如果ProtectedDataHandlingAdapter未设置或者不支持拼接发送,则将transferBytes合并到一个连续的内存块中再发送。 - /// 如果ProtectedDataHandlingAdapter已设置且支持拼接发送,则直接发送transferBytes。 - /// - /// 待发送的字节数据列表,每个元素包含要传输的字节片段。 - /// 发送任务。 - protected async Task ProtectedSendAsync(IList> transferBytes) - { - // 检查ProtectedDataHandlingAdapter是否已设置且支持拼接发送 - if (this.m_dataHandlingAdapter == null || !this.m_dataHandlingAdapter.CanSplicingSend) + this.ThrowIfDisposed(); + this.ThrowIfClientNotConnected(); + + var transport = this.m_transport; + var adapter = this.m_dataHandlingAdapter; + var locker = transport.WriteLocker; + + await locker.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + try { - // 如果不支持拼接发送,计算所有字节片段的总长度 - 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(item.Array, item.Offset, item.Count)); - } - // 根据ProtectedDataHandlingAdapter的状态选择发送方法 - if (this.m_dataHandlingAdapter == null) - { - // 如果未设置ProtectedDataHandlingAdapter,使用默认发送方法 - await this.ProtectedDefaultSendAsync(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - else - { - // 如果已设置ProtectedDataHandlingAdapter但不支持拼接发送,使用Adapter的发送方法 - await this.m_dataHandlingAdapter.SendInputAsync(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - } + var writer = new PipeBytesWriter(transport.Writer); + adapter.SendInput(ref writer, requestInfo); + await writer.FlushAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - else + finally { - // 如果已设置ProtectedDataHandlingAdapter且支持拼接发送,直接使用Adapter的发送方法 - await this.m_dataHandlingAdapter.SendInputAsync(transferBytes).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + locker.Release(); } } - #endregion 异步发送 + #endregion 发送 } \ No newline at end of file diff --git a/src/TouchSocket.NamedPipe/Components/NamedPipeServiceBaseT.cs b/src/TouchSocket.NamedPipe/Components/NamedPipeServiceBaseT.cs index ab773b08b..61d898d0d 100644 --- a/src/TouchSocket.NamedPipe/Components/NamedPipeServiceBaseT.cs +++ b/src/TouchSocket.NamedPipe/Components/NamedPipeServiceBaseT.cs @@ -10,14 +10,8 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; using System.IO.Pipes; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Resources; -using TouchSocket.Sockets; namespace TouchSocket.NamedPipe; @@ -31,12 +25,15 @@ public abstract class NamedPipeServiceBase : ConnectableService m_clients = new InternalClientCollection(); private readonly List m_monitors = new List(); + private readonly CancellationTokenSource m_cancellationTokenSource; private ServerState m_serverState; - #endregion 字段 #region 属性 + /// + public override IClientCollection Clients => this.m_clients; + /// public override int Count => this.m_clients.Count; @@ -45,22 +42,20 @@ public abstract class NamedPipeServiceBase : ConnectableService public override ServerState ServerState => this.m_serverState; - - /// - public override IClientCollection Clients => this.m_clients; - #endregion 属性 /// public void AddListen(NamedPipeListenOption option) { - ThrowHelper.ThrowArgumentNullExceptionIf(option, nameof(option)); this.ThrowIfDisposed(); + ThrowHelper.ThrowIfNull(option, nameof(option)); + ThrowHelper.ThrowArgumentNullExceptionIfStringIsNullOrEmpty(option.PipeName, nameof(option.PipeName)); + var networkMonitor = new NamedPipeMonitor(option); - _ = EasyTask.Run(this.ThreadBegin, networkMonitor); + _ = EasyTask.SafeRun(this.ThreadBegin, networkMonitor); this.m_monitors.Add(networkMonitor); } @@ -77,6 +72,12 @@ public abstract class NamedPipeServiceBase : ConnectableService + public override bool ClientExists(string id) + { + return this.m_clients.ClientExist(id); + } + /// public override IEnumerable GetIds() { @@ -87,17 +88,18 @@ public abstract class NamedPipeServiceBase : ConnectableService - public override async Task ResetIdAsync(string sourceId, string targetId) + public override async Task ResetIdAsync(string sourceId, string targetId, CancellationToken cancellationToken = default) { this.ThrowIfDisposed(); ThrowHelper.ThrowArgumentNullExceptionIfStringIsNullOrEmpty(sourceId, nameof(sourceId)); @@ -109,39 +111,27 @@ public abstract class NamedPipeServiceBase : ConnectableService - public override bool ClientExists(string id) - { - return this.m_clients.ClientExist(id); - } - /// public override async Task StartAsync() { this.ThrowIfConfigIsNull(); - var optionList = new List(); - if (this.Config.GetValue(NamedPipeConfigExtension.NamedPipeListenOptionProperty) is Action> action) - { - action.Invoke(optionList); - } + var optionList = this.Config.NamedPipeListenOption ?? new List(); var pipeName = this.Config.GetValue(NamedPipeConfigExtension.PipeNameProperty); if (pipeName != null) { var option = new NamedPipeListenOption { - Name = pipeName, + PipeName = pipeName, Adapter = this.Config.GetValue(NamedPipeConfigExtension.NamedPipeDataHandlingAdapterProperty), - SendTimeout = this.Config.GetValue(TouchSocketConfigExtension.SendTimeoutProperty) }; optionList.Add(option); @@ -156,7 +146,9 @@ public abstract class NamedPipeServiceBase : ConnectableService : ConnectableService : ConnectableService - public override async Task StopAsync(CancellationToken token = default) + public override async Task StopAsync(CancellationToken cancellationToken = default) { try { @@ -220,38 +207,44 @@ public abstract class NamedPipeServiceBase : ConnectableService : ConnectableService : ConnectableService @@ -159,15 +154,15 @@ public abstract class NamedPipeService : NamedPipeServiceBase, //} /// - public Task SendAsync(string id, ReadOnlyMemory memory) + public Task SendAsync(string id, ReadOnlyMemory memory, CancellationToken cancellationToken = default) { - return this.GetClientOrThrow(id).SendAsync(memory); + return this.GetClientOrThrow(id).SendAsync(memory, cancellationToken); } /// - public Task SendAsync(string id, IRequestInfo requestInfo) + public Task SendAsync(string id, IRequestInfo requestInfo, CancellationToken cancellationToken = default) { - return this.GetClientOrThrow(id).SendAsync(requestInfo); + return this.GetClientOrThrow(id).SendAsync(requestInfo, cancellationToken); } private NamedPipeSessionClient GetClientOrThrow(string id) diff --git a/src/TouchSocket.NamedPipe/Components/NamedPipeSessionClient.cs b/src/TouchSocket.NamedPipe/Components/NamedPipeSessionClient.cs index e41bf18ad..18bd1b1be 100644 --- a/src/TouchSocket.NamedPipe/Components/NamedPipeSessionClient.cs +++ b/src/TouchSocket.NamedPipe/Components/NamedPipeSessionClient.cs @@ -10,12 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using TouchSocket.Core; -using TouchSocket.Sockets; - namespace TouchSocket.NamedPipe; /// @@ -177,37 +171,30 @@ public class NamedPipeSessionClient : NamedPipeSessionClientBase, INamedPipeSess #region 异步发送 /// - public virtual Task SendAsync(ReadOnlyMemory memory) + public virtual Task SendAsync(ReadOnlyMemory memory, CancellationToken cancellationToken = default) { - return this.ProtectedSendAsync(memory); + return this.ProtectedSendAsync(memory, cancellationToken); } /// - public virtual Task SendAsync(IRequestInfo requestInfo) + public virtual Task SendAsync(IRequestInfo requestInfo, CancellationToken cancellationToken = default) { - return this.ProtectedSendAsync(requestInfo); + return this.ProtectedSendAsync(requestInfo, cancellationToken); } - - /// - public virtual Task SendAsync(IList> transferBytes) - { - return this.ProtectedSendAsync(transferBytes); - } - #endregion 异步发送 #region Id发送 /// - public Task SendAsync(string id, ReadOnlyMemory memory) + public Task SendAsync(string id, ReadOnlyMemory memory, CancellationToken cancellationToken = default) { - return this.GetClientOrThrow(id).ProtectedSendAsync(memory); + return this.GetClientOrThrow(id).ProtectedSendAsync(memory, cancellationToken); } /// - public Task SendAsync(string id, IRequestInfo requestInfo) + public Task SendAsync(string id, IRequestInfo requestInfo, CancellationToken cancellationToken = default) { - return this.GetClientOrThrow(id).ProtectedSendAsync(requestInfo); + return this.GetClientOrThrow(id).ProtectedSendAsync(requestInfo, cancellationToken); } private NamedPipeSessionClient GetClientOrThrow(string id) diff --git a/src/TouchSocket.NamedPipe/Components/NamedPipeSessionClientBase.cs b/src/TouchSocket.NamedPipe/Components/NamedPipeSessionClientBase.cs index 7920f6bd8..5529e2e0d 100644 --- a/src/TouchSocket.NamedPipe/Components/NamedPipeSessionClientBase.cs +++ b/src/TouchSocket.NamedPipe/Components/NamedPipeSessionClientBase.cs @@ -10,16 +10,9 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.IO.Pipes; using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Resources; -using TouchSocket.Sockets; namespace TouchSocket.NamedPipe; @@ -27,31 +20,25 @@ namespace TouchSocket.NamedPipe; /// 命名管道服务器辅助客户端类 /// [DebuggerDisplay("Id={Id},IP={IP},Port={Port}")] -public abstract class NamedPipeSessionClientBase : ResolverConfigObject, INamedPipeSession, INamedPipeListenableClient, IIdClient +[CodeInject.RegionInject(FileName = "TcpClientBase.cs", RegionName = "ReceiveLoopAsync", Placeholders = new[] { "OnTcpReceiving", "OnNamedPipeReceiving" })] +public abstract partial class NamedPipeSessionClientBase : ResolverConfigObject, INamedPipeSession, INamedPipeListenableClient, IIdClient { #region 字段 - private readonly Lock m_lockForAbort = new Lock(); - private readonly SemaphoreSlim m_semaphoreSlimForSend = new SemaphoreSlim(1, 1); private TouchSocketConfig m_config; private SingleStreamDataHandlingAdapter m_dataHandlingAdapter; + private string m_id; private NamedPipeListenOption m_listenOption; private bool m_online; - private NamedPipeServerStream m_pipeStream; private IPluginManager m_pluginManager; - private int m_receiveBufferSize = 1024 * 10; - private ValueCounter m_receiveCounter; private InternalReceiver m_receiver; - private Task m_receiveTask; + private Task m_runTask; private IScopedResolver m_scopedResolver; - - //private IResolver m_resolver; private INamedPipeServiceBase m_service; - + private NamedPipeTransport m_transport; private Func m_tryAddAction; private TryOutEventHandler m_tryGet; private TryOutEventHandler m_tryRemoveAction; - private DateTimeOffset m_lastSentTime; #endregion 字段 @@ -61,27 +48,28 @@ public abstract class NamedPipeSessionClientBase : ResolverConfigObject, INamedP public NamedPipeSessionClientBase() { this.Protocol = Protocol.NamedPipe; - this.m_receiveCounter = new ValueCounter - { - Period = TimeSpan.FromSeconds(1), - OnPeriod = this.OnPeriod - }; } + /// + public CancellationToken ClosedToken => this.m_transport == null ? new CancellationToken(true) : this.m_transport.ClosedToken; + /// public override TouchSocketConfig Config => this.m_config; /// - public string Id { get; private set; } + public SingleStreamDataHandlingAdapter DataHandlingAdapter => this.m_dataHandlingAdapter; + + /// + public string Id => this.m_id; /// public bool IsClient => false; /// - public DateTimeOffset LastReceivedTime => this.m_receiveCounter.LastIncrement; + public DateTimeOffset LastReceivedTime => this.m_transport?.ReceiveCounter.LastIncrement ?? default; /// - public DateTimeOffset LastSentTime => this.m_lastSentTime; + public DateTimeOffset LastSentTime => this.m_transport?.SendCounter.LastIncrement ?? default; /// public NamedPipeListenOption ListenOption => this.m_listenOption; @@ -89,9 +77,6 @@ public abstract class NamedPipeSessionClientBase : ResolverConfigObject, INamedP /// public virtual bool Online => this.m_online; - /// - public PipeStream PipeStream => this.m_pipeStream; - /// public override IPluginManager PluginManager => this.m_pluginManager; @@ -104,22 +89,33 @@ public abstract class NamedPipeSessionClientBase : ResolverConfigObject, INamedP /// public INamedPipeServiceBase Service => this.m_service; - /// - public SingleStreamDataHandlingAdapter DataHandlingAdapter => this.m_dataHandlingAdapter; - #region Internal - internal Task InternalInitialized() + internal async Task InternalInitialized(TouchSocketConfig config, NamedPipeListenOption option, IResolver resolver, IPluginManager pluginManager, INamedPipeServiceBase serviceBase, Func tryAddAction, TryOutEventHandler tryRemoveAction, TryOutEventHandler tryGet) { - this.m_lastSentTime = DateTimeOffset.UtcNow; - return this.OnInitialized(); + this.m_config = config; + this.m_pluginManager = pluginManager; + + this.m_listenOption = option; + + this.m_scopedResolver = resolver.CreateScopedResolver(); + this.Logger ??= this.m_scopedResolver.Resolver.Resolve(); + + this.m_service = serviceBase; + + this.m_tryAddAction = tryAddAction; + this.m_tryRemoveAction = tryRemoveAction; + this.m_tryGet = tryGet; + await this.OnInitialized().ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - internal async Task InternalNamedPipeConnected(ConnectedEventArgs e) + internal async Task InternalNamedPipeConnected(NamedPipeTransport transport) { this.m_online = true; - this.m_receiveTask = EasyTask.Run(this.BeginReceive); - await this.OnNamedPipeConnected(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + this.m_transport = transport; + this.m_runTask = EasyTask.SafeRun(this.PrivateConnected, transport); + await this.m_runTask.ConfigureAwait(EasyTask.ContinueOnCapturedContext); + transport.SafeDispose(); } internal async Task InternalNamedPipeConnecting(ConnectingEventArgs e) @@ -135,66 +131,47 @@ public abstract class NamedPipeSessionClientBase : ResolverConfigObject, INamedP } } - internal void InternalSetAction(Func tryAddAction, TryOutEventHandler tryRemoveAction, TryOutEventHandler tryGet) - { - this.m_tryAddAction = tryAddAction; - this.m_tryRemoveAction = tryRemoveAction; - this.m_tryGet = tryGet; - } - - internal void InternalSetConfig(TouchSocketConfig config) - { - this.m_config = config; - } - - internal void InternalSetContainer(IResolver resolver) - { - this.m_scopedResolver = resolver.CreateScopedResolver(); - this.Logger ??= this.m_scopedResolver.Resolver.Resolve(); - } - internal void InternalSetId(string id) { - this.Id = id; + ThrowHelper.ThrowArgumentNullExceptionIfStringIsNullOrEmpty(id, nameof(id)); + this.m_id = id; } - internal void InternalSetListenOption(NamedPipeListenOption option) + private async Task PrivateConnected(ITransport transport) { - this.m_listenOption = option; - } + var receiveTask = EasyTask.SafeRun(this.ReceiveLoopAsync, transport); + var e_connected = new ConnectedEventArgs(); + await this.OnNamedPipeConnected(e_connected).SafeWaitAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await receiveTask.SafeWaitAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var e_closed = transport.ClosedEventArgs; + this.m_online = false; + var adapter = this.m_dataHandlingAdapter; + this.m_dataHandlingAdapter = default; + adapter.SafeDispose(); - internal void InternalSetNamedPipe(NamedPipeServerStream namedPipe) - { - this.m_pipeStream = namedPipe; - } - - internal void InternalSetPluginManager(IPluginManager pluginManager) - { - this.m_pluginManager = pluginManager; - } - - internal void InternalSetService(INamedPipeServiceBase serviceBase) - { - this.m_service = serviceBase; + await this.OnNamedPipeClosed(e_closed).SafeWaitAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); + this.m_tryRemoveAction.Invoke(this.m_id, out var _); } #endregion Internal /// - public virtual async Task CloseAsync(string msg, CancellationToken token = default) + public virtual async Task CloseAsync(string msg, CancellationToken cancellationToken = default) { try { - if (this.m_online) + if (!this.m_online) { - await this.PrivateOnNamedPipeClosing(new ClosingEventArgs(msg)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - - lock (this.m_lockForAbort) - { - this.m_pipeStream.Close(); - this.Abort(true, msg); - } + return Result.Success; } + + await this.PrivateOnNamedPipeClosing(new ClosingEventArgs(msg)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var transport = this.m_transport; + if (transport != null) + { + await transport.CloseAsync(msg, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + await this.WaitClearConnect().ConfigureAwait(EasyTask.ContinueOnCapturedContext); return Result.Success; } catch (Exception ex) @@ -204,54 +181,11 @@ public abstract class NamedPipeSessionClientBase : ResolverConfigObject, INamedP } /// - public virtual Task ResetIdAsync(string newId) + public virtual Task ResetIdAsync(string newId, CancellationToken cancellationToken = default) { return this.ProtectedResetIdAsync(newId); } - /// - /// 中止当前操作,并安全地关闭相关资源。 - /// - /// 指示中止操作是否是手动触发的。 - /// 中止操作的消息说明。 - protected void Abort(bool manual, string msg) - { - // 使用锁对象 m_lockForAbort 来防止并发访问,确保线程安全 - lock (this.m_lockForAbort) - { - // 尝试从管理器中移除当前操作,如果成功且当前状态为在线,则进行中止操作 - if (this.m_tryRemoveAction(this.Id, out _) && this.m_online) - { - // 设置在线状态为 false,表示当前操作已离线 - this.m_online = false; - - // 安全地释放管道流资源,避免资源泄露 - this.m_pipeStream.SafeDispose(); - - // 安全地释放保护数据处理适配器资源,避免资源泄露 - this.m_dataHandlingAdapter.SafeDispose(); - - // 启动一个新的任务来处理管道关闭后的操作,传递中止操作的参数 - _ = EasyTask.SafeRun(this.PrivateOnNamedPipeClosed, this.m_receiveTask, new ClosedEventArgs(manual, msg)); - } - } - } - - /// - protected override void Dispose(bool disposing) - { - if (this.DisposedValue) - { - return; - } - if (disposing) - { - this.m_scopedResolver.SafeDispose(); - this.Abort(true, TouchSocketResource.DisposeClose); - } - base.Dispose(disposing); - } - /// /// 处理已接收到的数据。 /// 根据不同的数据处理适配器,会传递不同的数据 @@ -264,11 +198,11 @@ public abstract class NamedPipeSessionClientBase : ResolverConfigObject, INamedP /// /// 当收到原始数据 /// - /// + /// /// 如果返回则表示数据已被处理,且不会再向下传递。 - protected virtual ValueTask OnNamedPipeReceiving(ByteBlock byteBlock) + protected virtual ValueTask OnNamedPipeReceiving(IBytesReader reader) { - return this.PluginManager.RaiseAsync(typeof(INamedPipeReceivingPlugin), this.Resolver, this, new ByteBlockEventArgs(byteBlock)); + return this.PluginManager.RaiseAsync(typeof(INamedPipeReceivingPlugin), this.Resolver, this, new BytesReaderEventArgs(reader)); } /// @@ -307,7 +241,7 @@ public abstract class NamedPipeSessionClientBase : ResolverConfigObject, INamedP if (this.m_tryRemoveAction(this.Id, out var sessionClient)) { // 更新Socket客户端的Id - sessionClient.Id = newId; + sessionClient.m_id = newId; // 尝试将更新后的Socket客户端添加回集合 if (this.m_tryAddAction(sessionClient)) @@ -323,7 +257,7 @@ public abstract class NamedPipeSessionClientBase : ResolverConfigObject, INamedP else { // 如果添加失败,恢复原来的Id - sessionClient.Id = sourceId; + sessionClient.m_id = sourceId; // 再次尝试添加,如果失败,则抛出异常 if (this.m_tryAddAction(sessionClient)) @@ -356,17 +290,28 @@ public abstract class NamedPipeSessionClientBase : ResolverConfigObject, INamedP return this.m_tryGet(id, out sessionClient); } + /// + protected override void SafetyDispose(bool disposing) + { + if (disposing) + { + _ = EasyTask.SafeRun(async () => await this.CloseAsync(TouchSocketResource.DisposeClose).ConfigureAwait(EasyTask.ContinueOnCapturedContext)); + } + base.SafetyDispose(disposing); + } + /// /// 设置适配器 /// /// 要设置的适配器实例 protected void SetAdapter(SingleStreamDataHandlingAdapter adapter) { - // 检查传入的适配器实例是否为 + this.ThrowIfDisposed(); + if (adapter is null) { - // 如果为,则抛出ArgumentNullException异常 - throw new ArgumentNullException(nameof(adapter)); + this.m_dataHandlingAdapter = null;//允许Null赋值 + return; } // 检查当前实例是否已有配置 @@ -376,43 +321,30 @@ public abstract class NamedPipeSessionClientBase : ResolverConfigObject, INamedP adapter.Config(this.Config); } - // 将当前实例的日志记录器设置到适配器上 - adapter.Logger = this.Logger; - // 调用适配器的OnLoaded方法,通知适配器已被加载 adapter.OnLoaded(this); - // 设置适配器接收数据时的回调方法 + adapter.ReceivedAsyncCallBack = this.PrivateHandleReceivedData; - // 设置适配器发送数据时的回调方法 - //adapter.SendCallBack = this.ProtectedDefaultSend; - adapter.SendAsyncCallBack = this.ProtectedDefaultSendAsync; - // 将适配器实例设置为当前实例的数据处理适配器 this.m_dataHandlingAdapter = adapter; } - private async Task BeginReceive() + private async Task PrivateHandleReceivedData(ReadOnlyMemory memory, IRequestInfo requestInfo) { - while (true) + var receiver = this.m_receiver; + if (receiver != null) { - using (var byteBlock = new ByteBlock(this.GetReceiveBufferSize())) - { - try - { - var r = await this.m_pipeStream.ReadAsync(byteBlock.TotalMemory, CancellationToken.None).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - if (r == 0) - { - this.Abort(false, "远程终端主动关闭"); - return; - } + await receiver.InputReceiveAsync(memory, requestInfo, CancellationToken.None).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return; + } + await this.OnNamedPipeReceived(new ReceivedDataEventArgs(memory, requestInfo)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } - byteBlock.SetLength(r); - await this.HandleReceivingData(byteBlock).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - catch (Exception ex) - { - this.Abort(false, ex.Message); - return; - } - } + private async Task WaitClearConnect() + { + // 确保上次接收任务已经结束 + var runTask = this.m_runTask; + if (runTask != null) + { + await runTask.ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } @@ -461,23 +393,6 @@ public abstract class NamedPipeSessionClientBase : ResolverConfigObject, INamedP await this.PluginManager.RaiseAsync(typeof(INamedPipeConnectingPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - private int GetReceiveBufferSize() - { - var minBufferSize = this.Config.GetValue(TouchSocketConfigExtension.MinBufferSizeProperty) ?? 1024 * 10; - var maxBufferSize = this.Config.GetValue(TouchSocketConfigExtension.MaxBufferSizeProperty) ?? 1024 * 512; - return Math.Min(Math.Max(this.m_receiveBufferSize, minBufferSize), maxBufferSize); - } - - private async Task PrivateOnNamedPipeClosed(Task receiveTask, ClosedEventArgs e) - { - var receiver = this.m_receiver; - if (receiver != null) - { - await receiver.Complete(e.Message).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - await this.OnNamedPipeClosed(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - private Task PrivateOnNamedPipeClosing(ClosingEventArgs e) { return this.OnNamedPipeClosing(e); @@ -501,53 +416,6 @@ public abstract class NamedPipeSessionClientBase : ResolverConfigObject, INamedP #endregion Receiver - private async Task HandleReceivingData(ByteBlock byteBlock) - { - try - { - if (this.DisposedValue) - { - return; - } - - this.m_receiveCounter.Increment(byteBlock.Length); - - if (await this.OnNamedPipeReceiving(byteBlock).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) - { - return; - } - - if (this.m_dataHandlingAdapter == null) - { - await this.PrivateHandleReceivedData(byteBlock, default).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - else - { - await this.m_dataHandlingAdapter.ReceivedInputAsync(byteBlock).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - } - catch (Exception ex) - { - this.Logger?.Exception(this, ex); - } - } - - private void OnPeriod(long value) - { - this.m_receiveBufferSize = TouchSocketCoreUtility.HitBufferLength(value); - } - - private async Task PrivateHandleReceivedData(ByteBlock byteBlock, IRequestInfo requestInfo) - { - var receiver = this.m_receiver; - if (receiver != null) - { - await receiver.InputReceiveAsync(byteBlock, requestInfo).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - return; - } - await this.OnNamedPipeReceived(new ReceivedDataEventArgs(byteBlock, requestInfo)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - #region Throw [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -572,111 +440,80 @@ public abstract class NamedPipeSessionClientBase : ResolverConfigObject, INamedP #endregion Throw - #region 直接发送 + #region 发送 - /// - protected async Task ProtectedDefaultSendAsync(ReadOnlyMemory memory) - { - this.ThrowIfDisposed(); - this.ThrowIfClientNotConnected(); - await this.OnNamedPipeSending(memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - try - { - await this.m_semaphoreSlimForSend.WaitAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - await this.m_pipeStream.WriteAsync(memory, CancellationToken.None).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.m_lastSentTime = DateTimeOffset.UtcNow; - } - finally - { - this.m_semaphoreSlimForSend.Release(); - } - } - - #endregion 直接发送 - - #region 异步发送 /// /// 异步发送数据,通过适配器模式灵活处理数据发送。 /// /// 待发送的只读字节内存块。 + /// 可取消令箭 /// 一个异步任务,表示发送操作。 - protected Task ProtectedSendAsync(in ReadOnlyMemory memory) + protected async Task ProtectedSendAsync(ReadOnlyMemory memory, CancellationToken cancellationToken) { - // 如果数据处理适配器未设置,则使用默认发送方式。 - if (this.m_dataHandlingAdapter == null) + this.ThrowIfDisposed(); + this.ThrowIfClientNotConnected(); + + + await this.OnNamedPipeSending(memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + var transport = this.m_transport; + var adapter = this.m_dataHandlingAdapter; + var locker = transport.WriteLocker; + + await locker.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + try { - return this.ProtectedDefaultSendAsync(memory); + // 如果数据处理适配器未设置,则使用默认发送方式。 + if (adapter == null) + { + await transport.Writer.WriteAsync(memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + else + { + var writer = new PipeBytesWriter(transport.Writer); + adapter.SendInput(ref writer, in memory); + await writer.FlushAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } } - else + finally { - // 否则,使用适配器的发送方法进行数据发送。 - return this.m_dataHandlingAdapter.SendInputAsync(memory); + locker.Release(); } } /// - /// 异步安全发送请求信息。 + /// 异步发送请求信息的受保护方法。 + /// + /// 此方法首先检查当前对象是否能够发送请求信息,如果不能,则抛出异常。 + /// 如果可以发送,它将使用数据处理适配器来异步发送输入请求。 /// - /// 请求信息,用于发送。 - /// 返回一个任务,该任务表示发送操作的异步结果。 - /// - /// 此方法用于在执行实际的数据处理之前,确保当前状态允许发送请求信息。 - /// 它使用了数据处理适配器来异步发送输入请求。 - /// - protected Task ProtectedSendAsync(in IRequestInfo requestInfo) + /// 要发送的请求信息。 + /// 可取消令箭 + /// 返回一个任务,该任务代表异步操作的结果。 + protected async Task ProtectedSendAsync(IRequestInfo requestInfo, CancellationToken cancellationToken) { - // 检查当前状态是否允许发送请求信息,如果不允许则抛出异常。 + // 检查是否具备发送请求的条件,如果不具备则抛出异常 this.ThrowIfCannotSendRequestInfo(); - // 使用数据处理适配器异步发送请求信息。 - return this.m_dataHandlingAdapter.SendInputAsync(requestInfo); - } - /// - /// 异步发送数据。 - /// 如果数据处理适配器不存在或无法拼接发送,则将所有传输字节合并到一个连续的内存块中发送。 - /// 如果数据处理适配器存在且支持拼接发送,则直接发送传输字节列表。 - /// - /// 要发送的字节数据列表,每个项代表一个字节片段。 - /// 发送任务。 - protected async Task ProtectedSendAsync(IList> transferBytes) - { - // 检查数据处理适配器是否存在且支持拼接发送 - if (this.m_dataHandlingAdapter == null || !this.m_dataHandlingAdapter.CanSplicingSend) + this.ThrowIfDisposed(); + this.ThrowIfClientNotConnected(); + + var transport = this.m_transport; + var adapter = this.m_dataHandlingAdapter; + var locker = transport.WriteLocker; + + await locker.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + try { - // 如果不支持拼接发送,则计算所有字节片段的总长度 - 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(item.Array, item.Offset, item.Count)); - } - // 根据数据处理适配器的存在与否,选择不同的发送方式 - if (this.m_dataHandlingAdapter == null) - { - // 如果没有数据处理适配器,则使用默认方式发送 - await this.ProtectedDefaultSendAsync(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - else - { - // 如果有数据处理适配器,则通过适配器发送 - await this.m_dataHandlingAdapter.SendInputAsync(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - } + var writer = new PipeBytesWriter(transport.Writer); + adapter.SendInput(ref writer, requestInfo); + await writer.FlushAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - else + finally { - // 如果数据处理适配器支持拼接发送,则直接发送字节列表 - await this.m_dataHandlingAdapter.SendInputAsync(transferBytes).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + locker.Release(); } } - - #endregion 异步发送 + #endregion 发送 } \ No newline at end of file diff --git a/src/TouchSocket.NamedPipe/EventArgs/NamedPipeConnectingEventArgs.cs b/src/TouchSocket.NamedPipe/EventArgs/NamedPipeConnectingEventArgs.cs index e5ba4e452..4563ffe0e 100644 --- a/src/TouchSocket.NamedPipe/EventArgs/NamedPipeConnectingEventArgs.cs +++ b/src/TouchSocket.NamedPipe/EventArgs/NamedPipeConnectingEventArgs.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.NamedPipe; /// diff --git a/src/TouchSocket.NamedPipe/Extensions/NamedPipeClientExtension.cs b/src/TouchSocket.NamedPipe/Extensions/NamedPipeClientExtension.cs index d34c14e60..c22fa3974 100644 --- a/src/TouchSocket.NamedPipe/Extensions/NamedPipeClientExtension.cs +++ b/src/TouchSocket.NamedPipe/Extensions/NamedPipeClientExtension.cs @@ -10,42 +10,66 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.NamedPipe; /// -/// NamedPipeClientExtension +/// 命名管道客户端扩展方法类 +/// 提供命名管道客户端的便捷连接方法 /// public static class NamedPipeClientExtension { #region 连接 /// - /// 异步连接到指定的命名管道 + /// 异步连接到指定的命名管道服务器 /// - /// - /// - /// 管道名称 - /// 超时设定 - /// - public static async Task ConnectAsync(this TClient client, string pipeName, int millisecondsTimeout = 5000) where TClient : INamedPipeClient + /// 命名管道客户端类型,必须实现接口 + /// 命名管道客户端实例 + /// 要连接的命名管道名称 + /// 连接超时时间(毫秒),默认为 5000ms + /// 用于取消操作的取消令牌 + /// 返回连接成功的客户端实例 + /// 当连接操作被取消或超时时抛出 + /// 当 pipeName 为空时抛出 + /// 当客户端状态不允许连接时抛出 + public static async Task ConnectAsync(this TClient client, string pipeName, int millisecondsTimeout = 5000, CancellationToken cancellationToken = default) where TClient : INamedPipeClient { + // 配置客户端连接参数 TouchSocketConfig config; if (client.Config == null) { + // 客户端未配置时,创建新的配置对象 config = new TouchSocketConfig(); config.SetPipeName(pipeName); await client.SetupAsync(config).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } else { + // 使用现有配置并设置管道名称 config = client.Config; config.SetPipeName(pipeName); } - await client.ConnectAsync(millisecondsTimeout, CancellationToken.None).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + // 创建超时控制器,结合用户提供的取消令牌 + using var timeoutTokenSource = new TimeoutTokenSource(millisecondsTimeout, cancellationToken); + + try + { + // 执行异步连接操作 + await client.ConnectAsync(timeoutTokenSource.Token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + catch (OperationCanceledException ex) + { + // 处理取消异常,判断是超时还是用户主动取消 + timeoutTokenSource.HandleCancellation(ex); + } + catch + { + // 重新抛出其他异常 + throw; + } + + // 返回已连接的客户端实例 return client; } diff --git a/src/TouchSocket.NamedPipe/Extensions/NamedPipeConfigExtension.cs b/src/TouchSocket.NamedPipe/Extensions/NamedPipeConfigExtension.cs index 5d26bb8d7..471b28e6b 100644 --- a/src/TouchSocket.NamedPipe/Extensions/NamedPipeConfigExtension.cs +++ b/src/TouchSocket.NamedPipe/Extensions/NamedPipeConfigExtension.cs @@ -10,115 +10,35 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using TouchSocket.Core; -using TouchSocket.Sockets; - namespace TouchSocket.NamedPipe; /// -/// NamedPipeConfigExtension +/// 命名管道配置扩展。 /// public static class NamedPipeConfigExtension { /// - /// 数据处理适配器,默认为获取 - /// 所需类型 + /// 命名管道数据处理适配器属性。 /// + [GeneratorProperty(TargetType =typeof(TouchSocketConfig))] public static readonly DependencyProperty> NamedPipeDataHandlingAdapterProperty = new("NamedPipeDataHandlingAdapter", null ); /// - /// 直接单个配置命名管道监听的地址组。所需类型 + /// 直接单个配置命名管道监听的地址组。所需类型。 /// - public static readonly DependencyProperty>> NamedPipeListenOptionProperty = new("NamedPipeListenOption", null); + [GeneratorProperty(TargetType = typeof(TouchSocketConfig),ActionMode =true)] + public static readonly DependencyProperty> NamedPipeListenOptionProperty = new("NamedPipeListenOption", null); /// - /// 命名管道名称 + /// 命名管道名称。 /// + [GeneratorProperty(TargetType = typeof(TouchSocketConfig))] public static readonly DependencyProperty PipeNameProperty = new("PipeName", null); /// /// 命名管道的服务主机名称。 /// + [GeneratorProperty(TargetType = typeof(TouchSocketConfig))] public static readonly DependencyProperty PipeServerNameProperty = new("PipeServerName", "."); - - /// - /// 设置(命名管道系)数据处理适配器。 - /// - /// - /// - /// - public static TouchSocketConfig SetNamedPipeDataHandlingAdapter(this TouchSocketConfig config, Func value) - { - config.SetValue(NamedPipeDataHandlingAdapterProperty, value); - return config; - } - - /// - /// 直接单个配置命名管道监听的地址组。 - /// - /// - /// - /// - public static TouchSocketConfig SetNamedPipeListenOptions(this TouchSocketConfig config, Action> value) - { - config.SetValue(NamedPipeListenOptionProperty, value); - return config; - } - - /// - /// 当管道在本机时,仅设置管道名称即可。 - /// - /// - /// - /// - public static TouchSocketConfig SetPipeName(this TouchSocketConfig config, string value) - { - config.SetValue(PipeNameProperty, value); - return config; - } - - /// - /// 设置命名管道的主机名称。 - /// - /// - /// - /// - public static TouchSocketConfig SetPipeServer(this TouchSocketConfig config, string value) - { - config.SetValue(PipeServerNameProperty, value); - return config; - } - - #region Reconnection - - /// - /// 使用命名管道断线重连。 - /// - /// - /// - /// - public static ReconnectionPlugin UseNamedPipeReconnection(this IPluginManager pluginManager) where TClient : INamedPipeClient - { - var reconnectionPlugin = new NamedPipeReconnectionPlugin(); - pluginManager.Add(reconnectionPlugin); - return reconnectionPlugin; - } - - /// - /// 使用命名管道断线重连。 - /// - /// - /// - public static ReconnectionPlugin UseNamedPipeReconnection(this IPluginManager pluginManager) - { - var reconnectionPlugin = new NamedPipeReconnectionPlugin(); - pluginManager.Add(reconnectionPlugin); - return reconnectionPlugin; - } - - - #endregion Reconnection } \ No newline at end of file diff --git a/src/TouchSocket.NamedPipe/Extensions/NamedPipePluginManagerExtension.cs b/src/TouchSocket.NamedPipe/Extensions/NamedPipePluginManagerExtension.cs index 56329d36e..2d2d8aa0e 100644 --- a/src/TouchSocket.NamedPipe/Extensions/NamedPipePluginManagerExtension.cs +++ b/src/TouchSocket.NamedPipe/Extensions/NamedPipePluginManagerExtension.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 // ------------------------------------------------------------------------------ -using TouchSocket.Core; -using TouchSocket.Sockets; - namespace TouchSocket.NamedPipe; /// /// 提供命名管道插件管理器的扩展方法。 @@ -26,8 +23,8 @@ public static class NamedPipePluginManagerExtension /// /// 插件管理器对象,用于管理插件。 /// 返回一个类型的插件实例,用于执行客户端活性检查及清理操作。 - public static CheckClearPlugin UseNamedPipeSessionCheckClear(this IPluginManager pluginManager) + public static CheckClearPlugin UseNamedPipeSessionCheckClear(this IPluginManager pluginManager, Action> options = null) { - return pluginManager.UseCheckClear(); + return pluginManager.UseCheckClear(options); } } diff --git a/src/TouchSocket.NamedPipe/Extensions/NamedPipeServiceExtension.cs b/src/TouchSocket.NamedPipe/Extensions/NamedPipeServiceExtension.cs index 848249c9f..d844338ec 100644 --- a/src/TouchSocket.NamedPipe/Extensions/NamedPipeServiceExtension.cs +++ b/src/TouchSocket.NamedPipe/Extensions/NamedPipeServiceExtension.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; -using TouchSocket.Sockets; - namespace TouchSocket.NamedPipe; /// diff --git a/src/TouchSocket.NamedPipe/Extensions/WaitingClientExtension.cs b/src/TouchSocket.NamedPipe/Extensions/WaitingClientExtension.cs index 91dce5759..98f2144ab 100644 --- a/src/TouchSocket.NamedPipe/Extensions/WaitingClientExtension.cs +++ b/src/TouchSocket.NamedPipe/Extensions/WaitingClientExtension.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Sockets; - namespace TouchSocket.NamedPipe; /// diff --git a/src/TouchSocket.NamedPipe/Interfaces/INamedPipeClient.cs b/src/TouchSocket.NamedPipe/Interfaces/INamedPipeClient.cs index 2f1d7fa34..de87d318a 100644 --- a/src/TouchSocket.NamedPipe/Interfaces/INamedPipeClient.cs +++ b/src/TouchSocket.NamedPipe/Interfaces/INamedPipeClient.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; -using TouchSocket.Sockets; - namespace TouchSocket.NamedPipe; /// diff --git a/src/TouchSocket.NamedPipe/Interfaces/INamedPipeService.cs b/src/TouchSocket.NamedPipe/Interfaces/INamedPipeService.cs index 18a9c20ff..ea7ff0498 100644 --- a/src/TouchSocket.NamedPipe/Interfaces/INamedPipeService.cs +++ b/src/TouchSocket.NamedPipe/Interfaces/INamedPipeService.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Sockets; - namespace TouchSocket.NamedPipe; /// diff --git a/src/TouchSocket.NamedPipe/Interfaces/INamedPipeServiceBase.cs b/src/TouchSocket.NamedPipe/Interfaces/INamedPipeServiceBase.cs index 941d405b5..53b8a70ef 100644 --- a/src/TouchSocket.NamedPipe/Interfaces/INamedPipeServiceBase.cs +++ b/src/TouchSocket.NamedPipe/Interfaces/INamedPipeServiceBase.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Collections.Generic; -using TouchSocket.Sockets; - namespace TouchSocket.NamedPipe; /// diff --git a/src/TouchSocket.NamedPipe/Interfaces/INamedPipeServiceBaseT.cs b/src/TouchSocket.NamedPipe/Interfaces/INamedPipeServiceBaseT.cs index b9590fc50..76bf9df6c 100644 --- a/src/TouchSocket.NamedPipe/Interfaces/INamedPipeServiceBaseT.cs +++ b/src/TouchSocket.NamedPipe/Interfaces/INamedPipeServiceBaseT.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Sockets; - namespace TouchSocket.NamedPipe; /// diff --git a/src/TouchSocket.NamedPipe/Interfaces/INamedPipeSession.cs b/src/TouchSocket.NamedPipe/Interfaces/INamedPipeSession.cs index 834270ac8..2c6ed4def 100644 --- a/src/TouchSocket.NamedPipe/Interfaces/INamedPipeSession.cs +++ b/src/TouchSocket.NamedPipe/Interfaces/INamedPipeSession.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; -using TouchSocket.Sockets; - namespace TouchSocket.NamedPipe; /// diff --git a/src/TouchSocket.NamedPipe/Interfaces/INamedPipeSessionClient.cs b/src/TouchSocket.NamedPipe/Interfaces/INamedPipeSessionClient.cs index c9a038ba3..e9e3de77f 100644 --- a/src/TouchSocket.NamedPipe/Interfaces/INamedPipeSessionClient.cs +++ b/src/TouchSocket.NamedPipe/Interfaces/INamedPipeSessionClient.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Sockets; - namespace TouchSocket.NamedPipe; /// diff --git a/src/TouchSocket.NamedPipe/Interfaces/INamedPipeStreamClient.cs b/src/TouchSocket.NamedPipe/Interfaces/INamedPipeStreamClient.cs index 518c7777e..02bb5c9b6 100644 --- a/src/TouchSocket.NamedPipe/Interfaces/INamedPipeStreamClient.cs +++ b/src/TouchSocket.NamedPipe/Interfaces/INamedPipeStreamClient.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.IO.Pipes; - namespace TouchSocket.NamedPipe; /// @@ -19,8 +17,4 @@ namespace TouchSocket.NamedPipe; /// public interface INamedPipeStreamClient { - /// - /// 用于通讯的管道流。 - /// - PipeStream PipeStream { get; } } \ No newline at end of file diff --git a/src/TouchSocket.NamedPipe/ObsoleteClass.cs b/src/TouchSocket.NamedPipe/ObsoleteClass.cs index 21b0741f4..d86ca8f3b 100644 --- a/src/TouchSocket.NamedPipe/ObsoleteClass.cs +++ b/src/TouchSocket.NamedPipe/ObsoleteClass.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.NamedPipe; /// diff --git a/src/TouchSocket.NamedPipe/Plugins/Interfaces/INamedPipeClosedPlugin.cs b/src/TouchSocket.NamedPipe/Plugins/Interfaces/INamedPipeClosedPlugin.cs index cab617fdb..8bddd95a4 100644 --- a/src/TouchSocket.NamedPipe/Plugins/Interfaces/INamedPipeClosedPlugin.cs +++ b/src/TouchSocket.NamedPipe/Plugins/Interfaces/INamedPipeClosedPlugin.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; -using TouchSocket.Sockets; - namespace TouchSocket.NamedPipe; /// diff --git a/src/TouchSocket.NamedPipe/Plugins/Interfaces/INamedPipeClosingPlugin.cs b/src/TouchSocket.NamedPipe/Plugins/Interfaces/INamedPipeClosingPlugin.cs index 400e63381..e9d6ce961 100644 --- a/src/TouchSocket.NamedPipe/Plugins/Interfaces/INamedPipeClosingPlugin.cs +++ b/src/TouchSocket.NamedPipe/Plugins/Interfaces/INamedPipeClosingPlugin.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; -using TouchSocket.Sockets; - namespace TouchSocket.NamedPipe; /// diff --git a/src/TouchSocket.NamedPipe/Plugins/Interfaces/INamedPipeConnectedPlugin.cs b/src/TouchSocket.NamedPipe/Plugins/Interfaces/INamedPipeConnectedPlugin.cs index 820bb0023..eb3c3ad98 100644 --- a/src/TouchSocket.NamedPipe/Plugins/Interfaces/INamedPipeConnectedPlugin.cs +++ b/src/TouchSocket.NamedPipe/Plugins/Interfaces/INamedPipeConnectedPlugin.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; -using TouchSocket.Sockets; - namespace TouchSocket.NamedPipe; /// diff --git a/src/TouchSocket.NamedPipe/Plugins/Interfaces/INamedPipeConnectingPlugin.cs b/src/TouchSocket.NamedPipe/Plugins/Interfaces/INamedPipeConnectingPlugin.cs index ebcde6bce..7612b544e 100644 --- a/src/TouchSocket.NamedPipe/Plugins/Interfaces/INamedPipeConnectingPlugin.cs +++ b/src/TouchSocket.NamedPipe/Plugins/Interfaces/INamedPipeConnectingPlugin.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; -using TouchSocket.Sockets; - namespace TouchSocket.NamedPipe; /// diff --git a/src/TouchSocket.NamedPipe/Plugins/Interfaces/INamedPipeReceivedPlugin.cs b/src/TouchSocket.NamedPipe/Plugins/Interfaces/INamedPipeReceivedPlugin.cs index 8230aa66e..a0505eae4 100644 --- a/src/TouchSocket.NamedPipe/Plugins/Interfaces/INamedPipeReceivedPlugin.cs +++ b/src/TouchSocket.NamedPipe/Plugins/Interfaces/INamedPipeReceivedPlugin.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; -using TouchSocket.Sockets; - namespace TouchSocket.NamedPipe; diff --git a/src/TouchSocket.NamedPipe/Plugins/Interfaces/INamedPipeReceivingPlugin.cs b/src/TouchSocket.NamedPipe/Plugins/Interfaces/INamedPipeReceivingPlugin.cs index 933fd2dba..13c43bfb5 100644 --- a/src/TouchSocket.NamedPipe/Plugins/Interfaces/INamedPipeReceivingPlugin.cs +++ b/src/TouchSocket.NamedPipe/Plugins/Interfaces/INamedPipeReceivingPlugin.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; -using TouchSocket.Sockets; - namespace TouchSocket.NamedPipe; /// @@ -28,5 +24,5 @@ public interface INamedPipeReceivingPlugin : IPlugin /// 触发事件的命名管道会话客户端。 /// 包含收到的数据及其他相关信息的事件参数。 /// 一个任务对象,表示异步操作。 - Task OnNamedPipeReceiving(INamedPipeSession client, ByteBlockEventArgs e); + Task OnNamedPipeReceiving(INamedPipeSession client, BytesReaderEventArgs e); } \ No newline at end of file diff --git a/src/TouchSocket.NamedPipe/Plugins/Interfaces/INamedPipeSendingPlugin.cs b/src/TouchSocket.NamedPipe/Plugins/Interfaces/INamedPipeSendingPlugin.cs index 465b1938d..b5edfbba2 100644 --- a/src/TouchSocket.NamedPipe/Plugins/Interfaces/INamedPipeSendingPlugin.cs +++ b/src/TouchSocket.NamedPipe/Plugins/Interfaces/INamedPipeSendingPlugin.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; -using TouchSocket.Sockets; - namespace TouchSocket.NamedPipe; diff --git a/src/TouchSocket.NamedPipe/Plugins/NamedPipeReconnectionPlugin.cs b/src/TouchSocket.NamedPipe/Plugins/NamedPipeReconnectionPlugin.cs deleted file mode 100644 index 915da5118..000000000 --- a/src/TouchSocket.NamedPipe/Plugins/NamedPipeReconnectionPlugin.cs +++ /dev/null @@ -1,76 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; -using System.Threading.Tasks; -using TouchSocket.Core; -using TouchSocket.Sockets; - -namespace TouchSocket.NamedPipe; - -/// -/// 命名管道重连插件 -/// -[PluginOption(Singleton = true)] -public sealed class NamedPipeReconnectionPlugin : ReconnectionPlugin, INamedPipeClosedPlugin where TClient : INamedPipeClient -{ - /// - public override Func> ActionForCheck { get; set; } - - /// - /// 构造函数,用于初始化NamedPipeReconnectionPlugin实例。 - /// - /// - /// 该构造函数通过设置ActionForCheck属性来定义检查管道连接状态的操作。 - /// - public NamedPipeReconnectionPlugin() - { - // 定义一个lambda表达式,用于检查连接状态。 - // 参数c表示当前连接对象,参数i表示重试次数,但在此场景中未使用。 - // 返回连接对象的Online属性值,表示连接状态。 - this.ActionForCheck = (c, i) => - { - return Task.FromResult(c.Online); - }; - } - - /// - public async Task OnNamedPipeClosed(INamedPipeSession client, ClosedEventArgs e) - { - await e.InvokeNext().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - - if (client is not TClient tClient) - { - return; - } - - _ = Task.Run(async () => - { - if (e.Manual) - { - return; - } - - while (true) - { - if (this.DisposedValue) - { - return; - } - if (await this.ActionForConnect.Invoke(tClient).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) - { - return; - } - } - }); - } -} \ No newline at end of file diff --git a/src/TouchSocket.NamedPipe/Readme.md b/src/TouchSocket.NamedPipe/Readme.md index 9dc861678..958ab6a6d 100644 --- a/src/TouchSocket.NamedPipe/Readme.md +++ b/src/TouchSocket.NamedPipe/Readme.md @@ -12,14 +12,14 @@ TouchSocket.NamedPipe 是一个基于命名管道的组件库。它模仿 Tcp 详细的说明文档请访问:[https://touchsocket.net/](https://touchsocket.net/) ## 支持的目标框架 + - net481 -- net45 - net462 - net472 - netstandard2.0 - netstandard2.1 - net6.0 -- net9.0 +- net10.0 - net8.0 ## 贡献与反馈 diff --git a/src/TouchSocket.NamedPipe/TouchSocket.NamedPipe.csproj b/src/TouchSocket.NamedPipe/TouchSocket.NamedPipe.csproj index d27788502..499f7ad42 100644 --- a/src/TouchSocket.NamedPipe/TouchSocket.NamedPipe.csproj +++ b/src/TouchSocket.NamedPipe/TouchSocket.NamedPipe.csproj @@ -1,6 +1,6 @@ - net481;net45;net462;net472;netstandard2.0;netstandard2.1;net6.0;net9.0;net8.0 + net462;netstandard2.0;netstandard2.1;net6.0;net10.0;net8.0 Pipeline;NamedPipe;Ipc;TouchSocket 这是一个基于命名管道的组件库。它模仿Tcp封装了命名管道的服务器和客户端,以及连接、断开连接等消息。功能上实现了多管道名称监听、流式数据解析,以极致接近Tcp的体验使用命名管道。 @@ -10,9 +10,11 @@ + + @@ -20,7 +22,16 @@ + + + + + + + + + diff --git a/src/TouchSocket.NamedPipe/Transports/NamedPipeTransport.cs b/src/TouchSocket.NamedPipe/Transports/NamedPipeTransport.cs new file mode 100644 index 000000000..8a3285a6e --- /dev/null +++ b/src/TouchSocket.NamedPipe/Transports/NamedPipeTransport.cs @@ -0,0 +1,23 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.IO.Pipes; + +namespace TouchSocket.NamedPipe; + +internal sealed class NamedPipeTransport : StreamTransport +{ + public NamedPipeTransport(PipeStream stream, TransportOption option) : base(stream, option) + { + + } +} \ No newline at end of file diff --git a/src/TouchSocket.Rpc.RateLimiting/Attribute/EnableRateLimitingAttribute.cs b/src/TouchSocket.Rpc.RateLimiting/Attribute/EnableRateLimitingAttribute.cs index 988de1717..8a8831924 100644 --- a/src/TouchSocket.Rpc.RateLimiting/Attribute/EnableRateLimitingAttribute.cs +++ b/src/TouchSocket.Rpc.RateLimiting/Attribute/EnableRateLimitingAttribute.cs @@ -10,10 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Threading.RateLimiting; -using System.Threading.Tasks; -using TouchSocket.Core; namespace TouchSocket.Rpc.RateLimiting; diff --git a/src/TouchSocket.Rpc.RateLimiting/Common/RateLimiterOptions.cs b/src/TouchSocket.Rpc.RateLimiting/Common/RateLimiterOptions.cs index 9e4d6d0e3..aa1df7bcb 100644 --- a/src/TouchSocket.Rpc.RateLimiting/Common/RateLimiterOptions.cs +++ b/src/TouchSocket.Rpc.RateLimiting/Common/RateLimiterOptions.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Collections.Generic; - namespace TouchSocket.Rpc.RateLimiting; diff --git a/src/TouchSocket.Rpc.RateLimiting/Extensions/RateLimiterOptionsExtensions.cs b/src/TouchSocket.Rpc.RateLimiting/Extensions/RateLimiterOptionsExtensions.cs index 70f08fa9f..e18ef7c8e 100644 --- a/src/TouchSocket.Rpc.RateLimiting/Extensions/RateLimiterOptionsExtensions.cs +++ b/src/TouchSocket.Rpc.RateLimiting/Extensions/RateLimiterOptionsExtensions.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Threading.RateLimiting; namespace TouchSocket.Rpc.RateLimiting; diff --git a/src/TouchSocket.Rpc.RateLimiting/Extensions/RateLimitingContainerExtension.cs b/src/TouchSocket.Rpc.RateLimiting/Extensions/RateLimitingContainerExtension.cs index f35e14d80..b7edb4760 100644 --- a/src/TouchSocket.Rpc.RateLimiting/Extensions/RateLimitingContainerExtension.cs +++ b/src/TouchSocket.Rpc.RateLimiting/Extensions/RateLimitingContainerExtension.cs @@ -10,29 +10,22 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using TouchSocket.Core; - namespace TouchSocket.Rpc.RateLimiting; /// -/// 提供扩展方法以方便注册限流策略。 +/// 提供扩展方法以方便注册限流策略 /// public static class RateLimitingContainerExtension { /// - /// 向注册器中注册限流策略。 + /// 向注册器中注册限流策略 /// - /// 需要注册限流策略的注册器。 - /// 用于配置限流策略选项的委托。 - /// 返回配置了限流策略的注册器。 - public static IRegistrator AddRateLimiter(this IRegistrator registrator, Action action) + /// 需要注册限流策略的注册器 + /// 用于配置限流策略选项的委托 + public static void AddRateLimiter(this IRegistrator registrator, Action action) { - // 创建一个新的RateLimiterOptions实例以供配置。 var options = new RateLimiterOptions(); - // 使用提供的委托配置RateLimiterOptions实例。 action.Invoke(options); - // 将配置好的限流服务注册到注册器中。 - return registrator.RegisterSingleton(new RateLimitService(options)); + registrator.RegisterSingleton(new RateLimitService(options)); } } \ No newline at end of file diff --git a/src/TouchSocket.Rpc.RateLimiting/RateLimiters/RateLimiterPolicy.cs b/src/TouchSocket.Rpc.RateLimiting/RateLimiters/RateLimiterPolicy.cs index 0bed8374f..3ed757b59 100644 --- a/src/TouchSocket.Rpc.RateLimiting/RateLimiters/RateLimiterPolicy.cs +++ b/src/TouchSocket.Rpc.RateLimiting/RateLimiters/RateLimiterPolicy.cs @@ -10,10 +10,8 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Collections.Concurrent; using System.Threading.RateLimiting; -using TouchSocket.Core; namespace TouchSocket.Rpc.RateLimiting; @@ -42,11 +40,7 @@ public abstract class RateLimiterPolicy : IRateLimiterPolicy { // 初始化一个值计数器实例,并设置其周期为最大生命周期(m_maxLifetime), // 同时指定周期事件处理方法为当前实例的OnPeriod方法。 - this.m_valueCounter = new ValueCounter() - { - Period = this.m_maxLifetime, - OnPeriod = this.OnPeriod - }; + this.m_valueCounter = new ValueCounter(this.m_maxLifetime, this.OnPeriod); } /// diff --git a/src/TouchSocket.Rpc.RateLimiting/Readme.md b/src/TouchSocket.Rpc.RateLimiting/Readme.md index 5e19ed761..21ba2ac9d 100644 --- a/src/TouchSocket.Rpc.RateLimiting/Readme.md +++ b/src/TouchSocket.Rpc.RateLimiting/Readme.md @@ -16,7 +16,7 @@ TouchSocket.Rpc.RateLimiting 是一个扩展于 Rpc 管理平台的限流包。 - netstandard2.0 - netstandard2.1 - net6.0 -- net9.0 +- net10.0 - net8.0 ## 贡献与反馈 diff --git a/src/TouchSocket.Rpc.RateLimiting/Services/RateLimitService.cs b/src/TouchSocket.Rpc.RateLimiting/Services/RateLimitService.cs index 411866f74..af3cdf0a3 100644 --- a/src/TouchSocket.Rpc.RateLimiting/Services/RateLimitService.cs +++ b/src/TouchSocket.Rpc.RateLimiting/Services/RateLimitService.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Collections.Generic; - namespace TouchSocket.Rpc.RateLimiting; internal class RateLimitService : IRateLimitService diff --git a/src/TouchSocket.Rpc.RateLimiting/TouchSocket.Rpc.RateLimiting.csproj b/src/TouchSocket.Rpc.RateLimiting/TouchSocket.Rpc.RateLimiting.csproj index bf4db5584..c9ce157f0 100644 --- a/src/TouchSocket.Rpc.RateLimiting/TouchSocket.Rpc.RateLimiting.csproj +++ b/src/TouchSocket.Rpc.RateLimiting/TouchSocket.Rpc.RateLimiting.csproj @@ -1,6 +1,6 @@ - net462;net472;netstandard2.0;netstandard2.1;net6.0;net9.0;net8.0 + net462;netstandard2.0;netstandard2.1;net6.0;net10.0;net8.0 Rpc;RateLimiting;TouchSocket 这是一个扩展于Rpc管理平台的限流包。目前支持开发DmtpRpc、XmlRpc、JsonRpc、WebApi等所有Rpc部分。 diff --git a/src/TouchSocket.Rpc.SourceGenerator/AnalyzerReleases.Shipped.md b/src/TouchSocket.Rpc.SourceGenerator/AnalyzerReleases.Shipped.md index 05376e817..9140ffbe1 100644 --- a/src/TouchSocket.Rpc.SourceGenerator/AnalyzerReleases.Shipped.md +++ b/src/TouchSocket.Rpc.SourceGenerator/AnalyzerReleases.Shipped.md @@ -11,4 +11,5 @@ | Rpc0002 | Rpc | Error | Rpc0002_AnalyzerName | | Rpc0003 | Rpc | Error | Rpc0003_AnalyzerName | | Rpc0004 | Rpc | Error | Rpc0004_AnalyzerName | -| Rpc0005 | Rpc | Error | Rpc0005_AnalyzerName | \ No newline at end of file +| Rpc0005 | Rpc | Error | Rpc0005_AnalyzerName | +| Rpc0006 | Rpc | Error | Rpc0006_AnalyzerName | \ No newline at end of file diff --git a/src/TouchSocket.Rpc.SourceGenerator/Analyzers/RpcAnalyzer.cs b/src/TouchSocket.Rpc.SourceGenerator/Analyzers/RpcAnalyzer.cs index 78397b778..eb896b337 100644 --- a/src/TouchSocket.Rpc.SourceGenerator/Analyzers/RpcAnalyzer.cs +++ b/src/TouchSocket.Rpc.SourceGenerator/Analyzers/RpcAnalyzer.cs @@ -54,6 +54,12 @@ public class RpcAnalyzer : DiagnosticAnalyzer "Rpc中{0}函数出现ref、out、in参数,这是不允许的。", "Rpc", DiagnosticSeverity.Error, isEnabledByDefault: true); + private static readonly DiagnosticDescriptor m_rule_Rpc0006 = new DiagnosticDescriptor( + "Rpc0006", + "用于判断Rpc函数参数是否为接口或抽象类且没有标识[FromServices]", + "Rpc中{0}函数{1}参数为接口或抽象类,且没有标识[FromServices],这是不允许的。", + "Rpc", DiagnosticSeverity.Error, isEnabledByDefault: true); + #endregion DiagnosticDescriptors public override ImmutableArray SupportedDiagnostics @@ -65,7 +71,8 @@ public class RpcAnalyzer : DiagnosticAnalyzer m_rule_Rpc0002, m_rule_Rpc0003, m_rule_Rpc0004, - m_rule_Rpc0005 + m_rule_Rpc0005, + m_rule_Rpc0006 ); } } @@ -97,7 +104,7 @@ public class RpcAnalyzer : DiagnosticAnalyzer } } //Debugger.Launch(); - if (namedTypeSymbol.AllInterfaces.Any(a => a.ToDisplayString() == RpcServerSyntaxReceiver.IRpcServerTypeName)) + if (namedTypeSymbol.AllInterfaces.Any(a => a.ToDisplayString() == RpcServerSourceGenerator.IRpcServerTypeName)) { var names = new List(); foreach (var methodSymbol in namedTypeSymbol.GetMembers().OfType()) @@ -116,6 +123,15 @@ public class RpcAnalyzer : DiagnosticAnalyzer { context.ReportDiagnostic(Diagnostic.Create(m_rule_Rpc0005, methodSymbol.Locations[0], methodSymbol.Name)); } + + var type = item.Type; + if ((type.TypeKind == TypeKind.Interface || type.IsAbstract) && (!type.IsInheritFrom(RpcUtils.ICallContextTypeName))) + { + if (!item.HasAttribute(RpcUtils.FromServicesAttributeName)) + { + context.ReportDiagnostic(Diagnostic.Create(m_rule_Rpc0006, methodSymbol.Locations[0], methodSymbol.Name, item.Name)); + } + } } if (names.Contains(methodSymbol.Name))//有重载 diff --git a/src/TouchSocket.Rpc.SourceGenerator/Client/RpcClientSourceGenerator.cs b/src/TouchSocket.Rpc.SourceGenerator/Client/RpcClientSourceGenerator.cs index f3a1b00c9..107fd12bd 100644 --- a/src/TouchSocket.Rpc.SourceGenerator/Client/RpcClientSourceGenerator.cs +++ b/src/TouchSocket.Rpc.SourceGenerator/Client/RpcClientSourceGenerator.cs @@ -16,7 +16,7 @@ using System.Reflection; namespace TouchSocket; [Generator] -public class RpcClientSourceGenerator : ISourceGenerator +public class RpcClientSourceGenerator : IIncrementalGenerator { private readonly string m_generatorRpcProxyAttribute = @" @@ -113,18 +113,13 @@ namespace TouchSocket.Rpc } "; - public void Initialize(GeneratorInitializationContext context) + + public void Initialize(IncrementalGeneratorInitializationContext context) { - context.RegisterForPostInitialization(a => + context.RegisterPostInitializationOutput(a => { var sourceCode = this.m_generatorRpcProxyAttribute.Replace("/*GeneratedCode*/", $"[global::System.CodeDom.Compiler.GeneratedCode(\"TouchSocket.SourceGenerator\",\"{Assembly.GetExecutingAssembly().GetName().Version.ToString()}\")]"); - a.AddSource(nameof(this.m_generatorRpcProxyAttribute) + ".g.cs", sourceCode); }); } - - void ISourceGenerator.Execute(GeneratorExecutionContext context) - { - - } } \ No newline at end of file diff --git a/src/TouchSocket.Rpc.SourceGenerator/Server/RegisterRpcServerCodeBuilder.cs b/src/TouchSocket.Rpc.SourceGenerator/Server/RegisterRpcServerCodeBuilder.cs index b7a764918..f826d839f 100644 --- a/src/TouchSocket.Rpc.SourceGenerator/Server/RegisterRpcServerCodeBuilder.cs +++ b/src/TouchSocket.Rpc.SourceGenerator/Server/RegisterRpcServerCodeBuilder.cs @@ -39,93 +39,89 @@ internal class RegisterRpcServerCodeBuilder : CodeBuilder return $"Register{this.GetAssemblyName()}RpcServerGenerator"; } - public override string ToString() - { - var codeString = new StringBuilder(); - codeString.AppendLine("/*"); - codeString.AppendLine("此代码由Rpc工具直接生成,非必要请不要修改此处代码"); - codeString.AppendLine("*/"); - codeString.AppendLine("#pragma warning disable"); - codeString.AppendLine($"namespace TouchSocket.Rpc"); - codeString.AppendLine("{"); - codeString.AppendLine("/// "); - codeString.AppendLine($"/// {this.GetClassName()}"); - codeString.AppendLine("/// "); - codeString.AppendLine($"public static class {this.GetClassName()}"); - codeString.AppendLine("{"); + + protected override bool GeneratorCode(StringBuilder codeBuilder) + { + codeBuilder.AppendLine($"namespace TouchSocket.Rpc"); + codeBuilder.AppendLine("{"); + codeBuilder.AppendLine("/// "); + codeBuilder.AppendLine($"/// {this.GetClassName()}"); + codeBuilder.AppendLine("/// "); + codeBuilder.AppendLine($"public static class {this.GetClassName()}"); + codeBuilder.AppendLine("{"); var registers = this.RpcApis.Select(a => this.GetRegister(a)); switch (this.GetAccessibility()) { case Rpc.Accessibility.Both: - this.BuildInternal(codeString, registers); - this.BuildPublic(codeString, registers.Where(a => a.Item2.DeclaredAccessibility == Microsoft.CodeAnalysis.Accessibility.Public)); + this.BuildInternal(codeBuilder, registers); + this.BuildPublic(codeBuilder, registers.Where(a => a.Item2.DeclaredAccessibility == Microsoft.CodeAnalysis.Accessibility.Public)); break; case Rpc.Accessibility.Internal: - this.BuildInternal(codeString, registers); + this.BuildInternal(codeBuilder, registers); break; case Rpc.Accessibility.Public: - this.BuildPublic(codeString, registers.Where(a => a.Item2.DeclaredAccessibility == Microsoft.CodeAnalysis.Accessibility.Public)); + this.BuildPublic(codeBuilder, registers.Where(a => a.Item2.DeclaredAccessibility == Microsoft.CodeAnalysis.Accessibility.Public)); break; default: break; } - codeString.AppendLine("}"); - codeString.AppendLine("}"); + codeBuilder.AppendLine("}"); + codeBuilder.AppendLine("}"); - return codeString.ToString(); + return true; } - private void BuildInternal(StringBuilder codeString, IEnumerable<(INamedTypeSymbol, INamedTypeSymbol)> values) + private void BuildInternal(StringBuilder codeBuilder, IEnumerable<(INamedTypeSymbol, INamedTypeSymbol)> values) { if (values.Count() > 0) { - codeString.AppendLine("/// "); - codeString.AppendLine($"/// 注册程序集{this.m_assemblySymbol.Name}中的所有Rpc服务。包括:"); - codeString.AppendLine("/// "); + codeBuilder.AppendLine("/// "); + codeBuilder.AppendLine($"/// 注册程序集{this.m_assemblySymbol.Name}中的所有Rpc服务。包括:"); + codeBuilder.AppendLine("/// "); foreach (var item in values) { - codeString.AppendLine($"/// :"); + codeBuilder.AppendLine($"/// :"); } - codeString.AppendLine("/// "); - codeString.AppendLine("/// "); - codeString.AppendLine("/// "); - codeString.AppendLine($"internal static void Internal{this.GetMethodName()}(this RpcStore rpcStore)"); - codeString.AppendLine("{"); + codeBuilder.AppendLine("/// "); + codeBuilder.AppendLine("/// "); + codeBuilder.AppendLine("/// "); + codeBuilder.AppendLine($"internal static void Internal{this.GetMethodName()}(this RpcStore rpcStore)"); + codeBuilder.AppendLine("{"); foreach (var item in values) { - codeString.AppendLine($"rpcStore.RegisterServer<{item.Item1.ToDisplayString()},{item.Item2.ToDisplayString()}>();"); + codeBuilder.AppendLine($"rpcStore.RegisterServer<{item.Item1.ToDisplayString()},{item.Item2.ToDisplayString()}>();"); } - codeString.AppendLine("}"); + codeBuilder.AppendLine("}"); } } - private void BuildPublic(StringBuilder codeString, IEnumerable<(INamedTypeSymbol, INamedTypeSymbol)> values) + private void BuildPublic(StringBuilder codeBuilder, IEnumerable<(INamedTypeSymbol, INamedTypeSymbol)> values) { if (values.Count() > 0) { - codeString.AppendLine("/// "); - codeString.AppendLine($"/// 注册程序集{this.m_assemblySymbol.Name}中的所有公共Rpc服务。包括:"); - codeString.AppendLine("/// "); + codeBuilder.AppendLine("/// "); + codeBuilder.AppendLine($"/// 注册程序集{this.m_assemblySymbol.Name}中的所有公共Rpc服务。包括:"); + codeBuilder.AppendLine("/// "); foreach (var item in values) { - codeString.AppendLine($"/// :"); + codeBuilder.AppendLine($"/// :"); } - codeString.AppendLine("/// "); - codeString.AppendLine("/// "); - codeString.AppendLine("/// "); - codeString.AppendLine($"public static void {this.GetMethodName()}(this RpcStore rpcStore)"); - codeString.AppendLine("{"); + codeBuilder.AppendLine("/// "); + codeBuilder.AppendLine("/// "); + codeBuilder.AppendLine("/// "); + codeBuilder.AppendLine($"public static void {this.GetMethodName()}(this RpcStore rpcStore)"); + codeBuilder.AppendLine("{"); foreach (var item in values) { - codeString.AppendLine($"rpcStore.RegisterServer<{item.Item1.ToDisplayString()},{item.Item2.ToDisplayString()}>();"); + codeBuilder.AppendLine($"rpcStore.RegisterServer<{item.Item1.ToDisplayString()},{item.Item2.ToDisplayString()}>();"); } - codeString.AppendLine("}"); + codeBuilder.AppendLine("}"); } } @@ -166,7 +162,7 @@ internal class RegisterRpcServerCodeBuilder : CodeBuilder private (INamedTypeSymbol, INamedTypeSymbol) GetRegister(INamedTypeSymbol namedTypeSymbol) { - var symbolsInterface = namedTypeSymbol.Interfaces.Where(a => a.IsInheritFrom(RpcServerSyntaxReceiver.IRpcServerTypeName) && a.ToDisplayString() != RpcServerSyntaxReceiver.IRpcServerTypeName).FirstOrDefault(); + var symbolsInterface = namedTypeSymbol.Interfaces.Where(a => a.IsInheritFrom(RpcServerSourceGenerator.IRpcServerTypeName) && a.ToDisplayString() != RpcServerSourceGenerator.IRpcServerTypeName).FirstOrDefault(); if (symbolsInterface == null) { diff --git a/src/TouchSocket.Rpc.SourceGenerator/Server/RpcServerSourceGenerator.cs b/src/TouchSocket.Rpc.SourceGenerator/Server/RpcServerSourceGenerator.cs index fb036da05..f1a014d86 100644 --- a/src/TouchSocket.Rpc.SourceGenerator/Server/RpcServerSourceGenerator.cs +++ b/src/TouchSocket.Rpc.SourceGenerator/Server/RpcServerSourceGenerator.cs @@ -11,16 +11,19 @@ //------------------------------------------------------------------------------ using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; +using System.Collections.Immutable; using System.Linq; -using System.Reflection; +using System.Text; namespace TouchSocket; -[Generator] -public class RpcServerSourceGenerator : ISourceGenerator -{ - public const string GeneratorRpcServerRegisterAttributeTypeName = "TouchSocket.Rpc.GeneratorRpcServerRegisterAttribute"; +[Generator] +public class RpcServerSourceGenerator : IIncrementalGenerator +{ private readonly string m_generatorServerAttribute = @" /* @@ -81,42 +84,187 @@ namespace TouchSocket.Rpc } "; - public void Execute(GeneratorExecutionContext context) + private const string GeneratorRpcServerRegisterAttributeTypeName = "TouchSocket.Rpc.GeneratorRpcServerRegisterAttribute"; + public const string IRpcServerTypeName = "TouchSocket.Rpc.IRpcServer"; + + public void Initialize(IncrementalGeneratorInitializationContext context) { - var s = context.Compilation.GetMetadataReference(context.Compilation.Assembly); - - if (context.SyntaxReceiver is RpcServerSyntaxReceiver receiver) + // 添加必要的属性定义 + context.RegisterPostInitializationOutput(ctx => { - var rpcServerTypes = receiver - .GetRpcServerTypes(context.Compilation) - .Distinct(SymbolEqualityComparer.Default) - .Cast(); - // Debugger.Launch(); - - var generatorRpcServerRegisterAttribute = context.Compilation.GetTypeByMetadataName(GeneratorRpcServerRegisterAttributeTypeName); - if (generatorRpcServerRegisterAttribute == null) - { - return; - } - if (context.Compilation.Assembly.HasAttribute(GeneratorRpcServerRegisterAttributeTypeName, out var attributeData)) - { - var registerCodeBuilder = new RegisterRpcServerCodeBuilder(context.Compilation.Assembly, attributeData, rpcServerTypes.Where(a => RpcServerSyntaxReceiver.IsRegisterRpcServer(a)).ToArray()); - if (registerCodeBuilder.RpcApis.Length > 0) - { - context.AddSource($"{registerCodeBuilder.GetFileName()}.g.cs", registerCodeBuilder.ToSourceText()); - } - } - } - } - - public void Initialize(GeneratorInitializationContext context) - { - context.RegisterForPostInitialization(a => - { - var sourceCode = this.m_generatorServerAttribute.Replace("/*GeneratedCode*/", $"[global::System.CodeDom.Compiler.GeneratedCode(\"TouchSocket.SourceGenerator\",\"{Assembly.GetExecutingAssembly().GetName().Version.ToString()}\")]"); - - a.AddSource(nameof(this.m_generatorServerAttribute), sourceCode); + ctx.AddSource("RpcServerAttributes.g.cs", + SourceText.From(this.m_generatorServerAttribute, Encoding.UTF8)); }); - context.RegisterForSyntaxNotifications(() => new RpcServerSyntaxReceiver()); + + // 步骤1:收集候选类型(类和接口) + var candidateTypes = context.SyntaxProvider + .CreateSyntaxProvider( + predicate: static (s, _) => IsCandidateType(s), + transform: static (ctx, _) => GetTypeSymbol(ctx)) + .Where(static t => t is not null); + + // 步骤2:组合编译信息 + var compilationAndTypes = context.CompilationProvider.Combine(candidateTypes.Collect()); + + // 步骤3:生成最终代码 + context.RegisterSourceOutput( + compilationAndTypes, + (ctx, source) => this.GenerateCode(ctx, source.Left, source.Right)); } -} \ No newline at end of file + + private static bool IsCandidateType(SyntaxNode node) + { + return node is TypeDeclarationSyntax { TypeParameterList: null }; + } + + private static INamedTypeSymbol GetTypeSymbol(GeneratorSyntaxContext context) + { + var symbol = context.SemanticModel.GetDeclaredSymbol(context.Node) as INamedTypeSymbol; + return IsValidRpcServer(symbol) ? symbol : null; + } + + private static bool IsValidRpcServer(INamedTypeSymbol symbol) + { + return symbol != null && + !symbol.IsAbstract && + symbol.AllInterfaces.Any(i => + i.ToDisplayString() == IRpcServerTypeName); + } + + private void GenerateCode( + SourceProductionContext context, + Compilation compilation, + ImmutableArray rpcTypes) + { + // 检查程序集是否包含注册属性 + if (!compilation.Assembly.HasAttribute( + GeneratorRpcServerRegisterAttributeTypeName, + out var attributeData)) + { + return; + } + + var validTypes = rpcTypes + .Where(t => IsValidRpcServer(t)) + .ToArray(); + + if (validTypes.Length == 0) + { + return; + } + + var registerCodeBuilder = new RegisterRpcServerCodeBuilder( + compilation.Assembly, // 直接使用Compilation的Assembly + attributeData, + validTypes); + + context.AddSource(registerCodeBuilder); + } + + +} + +//[Generator] +//public class RpcServerSourceGenerator : ISourceGenerator +//{ +// public const string GeneratorRpcServerRegisterAttributeTypeName = "TouchSocket.Rpc.GeneratorRpcServerRegisterAttribute"; + +// private readonly string m_generatorServerAttribute = @" + +///* +//此代码由SourceGenerator工具直接生成,非必要请不要修改此处代码 +//*/ + +//#pragma warning disable + +//using System; + +//namespace TouchSocket.Rpc +//{ +// /// +// /// 标识将通过源生成器生成Rpc服务的调用委托。 +// /// +// [AttributeUsage(AttributeTargets.Class|AttributeTargets.Interface | AttributeTargets.Assembly)] +// [Obsolete(""此特性已被弃用,默认已支持AOT"")] +// /*GeneratedCode*/ +// internal class GeneratorRpcServerAttribute : Attribute +// { +// } + +// /// +// /// 标识将通过源生成器生成Rpc服务的注册代码。 +// /// +// [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)] +// /*GeneratedCode*/ +// internal class GeneratorRpcServerRegisterAttribute : Attribute +// { +// /// +// /// 方法名称。默认是“RegisterAllFrom+AssemblyName” +// /// +// public string MethodName { get; set; } + +// /// +// /// 扩展类类名,默认是“RegisterRpcServerFrom+AssemblyName+Extension” +// /// +// public string ClassName { get; set; } + +// /// +// /// 访问修饰。 +// /// +// /// 如果为,将生成注册公共Rpc服务与非公共服务两个方法。其中非公共方法会在之前以Internal开头。 +// /// 如果为,将只生成注册非公共Rpc服务。 +// /// 如果为,将只生成注册公共Rpc服务。 +// /// +// /// +// public Accessibility Accessibility { get; set; } +// } + +// /*GeneratedCode*/ +// internal enum Accessibility +// { +// Both, +// Internal, +// Public +// } +//} +//"; + +// public void Execute(GeneratorExecutionContext context) +// { +// var s = context.Compilation.GetMetadataReference(context.Compilation.Assembly); + +// if (context.SyntaxReceiver is RpcServerSyntaxReceiver receiver) +// { +// var rpcServerTypes = receiver +// .GetRpcServerTypes(context.Compilation) +// .Distinct(SymbolEqualityComparer.Default) +// .Cast(); +// // Debugger.Launch(); + +// var generatorRpcServerRegisterAttribute = context.Compilation.GetTypeByMetadataName(GeneratorRpcServerRegisterAttributeTypeName); +// if (generatorRpcServerRegisterAttribute == null) +// { +// return; +// } +// if (context.Compilation.Assembly.HasAttribute(GeneratorRpcServerRegisterAttributeTypeName, out var attributeData)) +// { +// var registerCodeBuilder = new RegisterRpcServerCodeBuilder(context.Compilation.Assembly, attributeData, rpcServerTypes.Where(a => RpcServerSyntaxReceiver.IsRegisterRpcServer(a)).ToArray()); +// if (registerCodeBuilder.RpcApis.Length > 0) +// { +// context.AddSource($"{registerCodeBuilder.GetFileName()}.g.cs", registerCodeBuilder.ToSourceText()); +// } +// } +// } +// } + +// public void Initialize(GeneratorInitializationContext context) +// { +// context.RegisterForPostInitialization(a => +// { +// var sourceCode = this.m_generatorServerAttribute.Replace("/*GeneratedCode*/", $"[global::System.CodeDom.Compiler.GeneratedCode(\"TouchSocket.SourceGenerator\",\"{Assembly.GetExecutingAssembly().GetName().Version.ToString()}\")]"); + +// a.AddSource(nameof(this.m_generatorServerAttribute), sourceCode); +// }); +// context.RegisterForSyntaxNotifications(() => new RpcServerSyntaxReceiver()); +// } +//} \ No newline at end of file diff --git a/src/TouchSocket.Rpc.SourceGenerator/Server/RpcServerSyntaxReceiver.cs b/src/TouchSocket.Rpc.SourceGenerator/Server/RpcServerSyntaxReceiver.cs deleted file mode 100644 index fb22caeaa..000000000 --- a/src/TouchSocket.Rpc.SourceGenerator/Server/RpcServerSyntaxReceiver.cs +++ /dev/null @@ -1,67 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using System.Collections.Generic; -using System.Linq; - -namespace TouchSocket; - -internal sealed class RpcServerSyntaxReceiver : ISyntaxReceiver -{ - public const string IRpcServerTypeName = "TouchSocket.Rpc.IRpcServer"; - - private readonly List m_classDeclarationSyntaxes = new List(); - - public static bool IsRegisterRpcServer(INamedTypeSymbol @class) - { - if (!@class.AllInterfaces.Any(a => a.ToDisplayString() == IRpcServerTypeName)) - { - return false; - } - - if (@class.IsAbstract) - { - return false; - } - - return true; - } - - public IEnumerable GetRpcServerTypes(Compilation compilation) - { - //Debugger.Launch(); - foreach (var interfaceSyntax in this.m_classDeclarationSyntaxes) - { - var @class = compilation.GetSemanticModel(interfaceSyntax.SyntaxTree).GetDeclaredSymbol(interfaceSyntax); - if (@class != null) - { - yield return @class; - } - } - } - - void ISyntaxReceiver.OnVisitSyntaxNode(SyntaxNode syntaxNode) - { - if (syntaxNode is ClassDeclarationSyntax syntax) - { - this.m_classDeclarationSyntaxes.Add(syntax); - } - - if (syntaxNode is InterfaceDeclarationSyntax interfaceDeclarationSyntax) - { - this.m_classDeclarationSyntaxes.Add(interfaceDeclarationSyntax); - } - } -} \ No newline at end of file diff --git a/src/TouchSocket.Rpc.SourceGenerator/TouchSocket.Rpc.SourceGenerator.csproj b/src/TouchSocket.Rpc.SourceGenerator/TouchSocket.Rpc.SourceGenerator.csproj index 8f8cffb77..73ae18a06 100644 --- a/src/TouchSocket.Rpc.SourceGenerator/TouchSocket.Rpc.SourceGenerator.csproj +++ b/src/TouchSocket.Rpc.SourceGenerator/TouchSocket.Rpc.SourceGenerator.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/TouchSocket.Rpc/Attribute/FromServicesAttribute.cs b/src/TouchSocket.Rpc/Attribute/FromServicesAttribute.cs index 9dcc27bae..e8ec37362 100644 --- a/src/TouchSocket.Rpc/Attribute/FromServicesAttribute.cs +++ b/src/TouchSocket.Rpc/Attribute/FromServicesAttribute.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Rpc; /// diff --git a/src/TouchSocket.Rpc/Attribute/IRpcActionFilter.cs b/src/TouchSocket.Rpc/Attribute/IRpcActionFilter.cs index 9395436aa..601a9b27c 100644 --- a/src/TouchSocket.Rpc/Attribute/IRpcActionFilter.cs +++ b/src/TouchSocket.Rpc/Attribute/IRpcActionFilter.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; - namespace TouchSocket.Rpc; /// diff --git a/src/TouchSocket.Rpc/Attribute/ReenterableAttribute.cs b/src/TouchSocket.Rpc/Attribute/ReenterableAttribute.cs index 79f8bf672..6e4670384 100644 --- a/src/TouchSocket.Rpc/Attribute/ReenterableAttribute.cs +++ b/src/TouchSocket.Rpc/Attribute/ReenterableAttribute.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Rpc; /// diff --git a/src/TouchSocket.Rpc/Attribute/RpcActionFilterAttribute.cs b/src/TouchSocket.Rpc/Attribute/RpcActionFilterAttribute.cs index 46fbc7cfe..05433b67a 100644 --- a/src/TouchSocket.Rpc/Attribute/RpcActionFilterAttribute.cs +++ b/src/TouchSocket.Rpc/Attribute/RpcActionFilterAttribute.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; - namespace TouchSocket.Rpc; diff --git a/src/TouchSocket.Rpc/Attribute/RpcAttribute.cs b/src/TouchSocket.Rpc/Attribute/RpcAttribute.cs index 3056cb277..4837964ed 100644 --- a/src/TouchSocket.Rpc/Attribute/RpcAttribute.cs +++ b/src/TouchSocket.Rpc/Attribute/RpcAttribute.cs @@ -10,12 +10,8 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Diagnostics.CodeAnalysis; using System.Reflection; -using System.Text; -using TouchSocket.Core; namespace TouchSocket.Rpc; @@ -416,7 +412,7 @@ public abstract class RpcAttribute : Attribute /// public virtual string GetInvokeOption() { - return "IInvokeOption invokeOption = default"; + return "InvokeOption invokeOption = default"; } /// @@ -537,6 +533,7 @@ public abstract class RpcAttribute : Attribute } } + [RequiresUnreferencedCode("此方法使用反射动态加载程序集,与剪裁不兼容。请改用安全的替代方法。")] internal void SetClassCodeGenerator(ClassCodeGenerator classCodeGenerator) { this.ClassCodeGenerator = classCodeGenerator; @@ -567,7 +564,7 @@ public abstract class RpcAttribute : Attribute foreach (var parameter in parameters) { codeString.Append(parameter.Name); - if (parameter != parameters[parameters.Length - 1]) + if (parameter != parameters[^1]) { codeString.Append(','); } @@ -615,7 +612,7 @@ public abstract class RpcAttribute : Attribute foreach (var parameter in parameters) { codeString.Append(parameter.Name); - if (parameter != parameters[parameters.Length - 1]) + if (parameter != parameters[^1]) { codeString.Append(','); } @@ -687,7 +684,7 @@ public abstract class RpcAttribute : Attribute foreach (var parameter in parameters) { codeString.Append(parameter.Name); - if (parameter != parameters[parameters.Length - 1]) + if (parameter != parameters[^1]) { codeString.Append(','); } @@ -735,7 +732,7 @@ public abstract class RpcAttribute : Attribute foreach (var parameter in parameters) { codeString.Append(parameter.Name); - if (parameter != parameters[parameters.Length - 1]) + if (parameter != parameters[^1]) { codeString.Append(','); } @@ -779,6 +776,7 @@ public abstract class RpcAttribute : Attribute return codeString.ToString(); } + [RequiresUnreferencedCode("此方法使用反射动态加载程序集,与剪裁不兼容。请改用安全的替代方法。")] private Dictionary GetPublicPropertiesAsDictionary() { var propertiesDict = new Dictionary(); diff --git a/src/TouchSocket.Rpc/Attribute/RpcProxyAttribute.cs b/src/TouchSocket.Rpc/Attribute/RpcProxyAttribute.cs index e27c74607..d96e3be8e 100644 --- a/src/TouchSocket.Rpc/Attribute/RpcProxyAttribute.cs +++ b/src/TouchSocket.Rpc/Attribute/RpcProxyAttribute.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Rpc; /// diff --git a/src/TouchSocket.Rpc/CallContextAccessor/RpcCallContextAccessor.cs b/src/TouchSocket.Rpc/CallContextAccessor/RpcCallContextAccessor.cs index 254a95d73..5cafa5399 100644 --- a/src/TouchSocket.Rpc/CallContextAccessor/RpcCallContextAccessor.cs +++ b/src/TouchSocket.Rpc/CallContextAccessor/RpcCallContextAccessor.cs @@ -10,14 +10,8 @@ // 感谢您的下载和使用 // ------------------------------------------------------------------------------ -#if AsyncLocal -using System; -using System.Collections.Generic; + using System.Diagnostics; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; namespace TouchSocket.Rpc; @@ -52,6 +46,4 @@ internal class RpcCallContextAccessor : IRpcCallContextAccessor { public ICallContext Context; } -} - -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/src/TouchSocket.Rpc/Code/ClassCodeGenerator.cs b/src/TouchSocket.Rpc/Code/ClassCodeGenerator.cs index 02babfd4d..3be7c9f79 100644 --- a/src/TouchSocket.Rpc/Code/ClassCodeGenerator.cs +++ b/src/TouchSocket.Rpc/Code/ClassCodeGenerator.cs @@ -10,14 +10,9 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; +using System.Diagnostics.CodeAnalysis; using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using TouchSocket.Core; namespace TouchSocket.Rpc; @@ -55,6 +50,7 @@ public class ClassCodeGenerator /// 添加类型字符串 /// /// + [RequiresUnreferencedCode("此方法使用反射动态加载程序集,与剪裁不兼容。请改用安全的替代方法。")] public void AddTypeString(Type type) { var list = new List(); @@ -298,6 +294,7 @@ public class ClassCodeGenerator return nonGenericType; } + [RequiresUnreferencedCode("此方法使用反射动态加载程序集,与剪裁不兼容。请改用安全的替代方法。")] private void GetTransmitTypes(Type type, ref List types) { if (type.IsByRef) @@ -365,6 +362,7 @@ public class ClassCodeGenerator } } + [RequiresUnreferencedCode("此方法使用反射动态加载程序集,与剪裁不兼容。请改用安全的替代方法。")] private void PrivateAddTypeString(Type type) { if (CodeGenerator.m_ignoreTypes.Contains(type)) diff --git a/src/TouchSocket.Rpc/Code/CodeGenerator.cs b/src/TouchSocket.Rpc/Code/CodeGenerator.cs index 621ab2c6f..226bb6468 100644 --- a/src/TouchSocket.Rpc/Code/CodeGenerator.cs +++ b/src/TouchSocket.Rpc/Code/CodeGenerator.cs @@ -10,12 +10,8 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Diagnostics.CodeAnalysis; using System.Reflection; -using System.Text; -using TouchSocket.Core; namespace TouchSocket.Rpc; @@ -63,6 +59,7 @@ public static class CodeGenerator /// /// /// + [RequiresUnreferencedCode("此方法使用反射动态加载程序集,与剪裁不兼容。请改用安全的替代方法。")] public static void AddProxyType(Type type, bool deepSearch = true) { if (type.IsPrimitive || type == typeof(string)) @@ -89,6 +86,7 @@ public static class CodeGenerator /// /// /// + [RequiresUnreferencedCode("此方法使用反射动态加载程序集,与剪裁不兼容。请改用安全的替代方法。")] public static void AddProxyType(bool deepSearch = true) { AddProxyType(typeof(T), deepSearch); @@ -250,6 +248,7 @@ public static class CodeGenerator /// 服务类型 /// 属性标签 /// + [RequiresUnreferencedCode("此方法使用反射动态加载程序集,与剪裁不兼容。请改用安全的替代方法。")] public static ServerCellCode Generator() where TServer : IRpcServer where TAttribute : RpcAttribute { return Generator(typeof(TServer), typeof(TAttribute)); @@ -261,6 +260,7 @@ public static class CodeGenerator /// 服务类型 /// /// + [RequiresUnreferencedCode("此方法使用反射动态加载程序集,与剪裁不兼容。请改用安全的替代方法。")] public static ServerCellCode Generator(Type serverType, Type attributeType) { var serverCellCode = new ServerCellCode(); @@ -396,7 +396,7 @@ public static class CodeGenerator /// /// /// - public static void GetMethodInfos(Type type, ref Dictionary methods) + public static void GetMethodInfos([DynamicallyAccessedMembers(AOT.RpcRegister)] Type type, ref Dictionary methods) { foreach (var item in type.GetInterfaces()) { @@ -429,7 +429,7 @@ public static class CodeGenerator /// 目标服务类型。 /// 映射到目标类型的方法信息。 /// 如果未找到映射方法,则抛出异常。 - public static MethodInfo GetToMethodInfo(MethodInfo method, Type serverFromType, Type serverToType) + public static MethodInfo GetToMethodInfo(MethodInfo method, [DynamicallyAccessedMembers(AOT.RpcRegister)] Type serverFromType, [DynamicallyAccessedMembers(AOT.RpcRegister)] Type serverToType) { if (serverFromType == serverToType) { @@ -453,6 +453,7 @@ public static class CodeGenerator /// /// /// + [RequiresUnreferencedCode("此方法使用反射动态加载程序集,与剪裁不兼容。请改用安全的替代方法。")] public static RpcMethod[] GetRpcMethods() where TServer : IRpcServer { return GetRpcMethods(typeof(TServer), typeof(TServer)); @@ -464,7 +465,8 @@ public static class CodeGenerator /// /// /// - public static RpcMethod[] GetRpcMethods(Type serverFromType, Type serverToType) + + public static RpcMethod[] GetRpcMethods([DynamicallyAccessedMembers(AOT.RpcRegister)] Type serverFromType, [DynamicallyAccessedMembers(AOT.RpcRegister)] Type serverToType) { if (!typeof(IRpcServer).IsAssignableFrom(serverFromType)) { @@ -504,6 +506,7 @@ public static class CodeGenerator /// /// /// + [RequiresUnreferencedCode("此方法使用反射动态加载程序集,与剪裁不兼容。请改用安全的替代方法。")] public static string GetProxyCodes(string @namespace, Type[] serverTypes, Type[] attributeTypes) { var serverCellCodeList = new List(); diff --git a/src/TouchSocket.Rpc/Code/ServerCellCode.cs b/src/TouchSocket.Rpc/Code/ServerCellCode.cs index d529630ec..d3187e7ba 100644 --- a/src/TouchSocket.Rpc/Code/ServerCellCode.cs +++ b/src/TouchSocket.Rpc/Code/ServerCellCode.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Collections.Generic; - namespace TouchSocket.Rpc; /// diff --git a/src/TouchSocket.Rpc/Common/ActionMap.cs b/src/TouchSocket.Rpc/Common/ActionMap.cs index 1c3fd9caf..a577d4122 100644 --- a/src/TouchSocket.Rpc/Common/ActionMap.cs +++ b/src/TouchSocket.Rpc/Common/ActionMap.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; - namespace TouchSocket.Rpc; /// diff --git a/src/TouchSocket.Rpc/Common/CallContext.cs b/src/TouchSocket.Rpc/Common/CallContext.cs index 5a86833c8..063144c66 100644 --- a/src/TouchSocket.Rpc/Common/CallContext.cs +++ b/src/TouchSocket.Rpc/Common/CallContext.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading; -using TouchSocket.Core; - namespace TouchSocket.Rpc; /// diff --git a/src/TouchSocket.Rpc/Common/InvokeOption.cs b/src/TouchSocket.Rpc/Common/InvokeOption.cs index af52ff5f2..86e8bb636 100644 --- a/src/TouchSocket.Rpc/Common/InvokeOption.cs +++ b/src/TouchSocket.Rpc/Common/InvokeOption.cs @@ -10,14 +10,16 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading; - namespace TouchSocket.Rpc; /// -/// Rpc调用设置 +/// 表示RPC调用的选项配置,用于控制调用行为和反馈机制。 /// -public class InvokeOption : IInvokeOption +/// +/// InvokeOption提供了对RPC调用过程的精细控制,包括超时设置、反馈类型和取消机制。 +/// 通过预定义的静态属性可以快速使用常见的配置组合。 +/// +public class InvokeOption { static InvokeOption() { @@ -38,51 +40,96 @@ public class InvokeOption : IInvokeOption } /// - /// 构造函数 + /// 初始化类的新实例,使用默认配置。 /// + /// + /// 默认配置:超时时间为5000毫秒,反馈类型为。 + /// public InvokeOption() { } /// - /// 构造函数 + /// 使用指定的超时时间初始化类的新实例。 /// - /// + /// 调用超时时间,以毫秒为单位。 public InvokeOption(int millisecondsTimeout) { this.Timeout = millisecondsTimeout; } /// - /// 默认设置。 - /// Timeout=5000ms + /// 获取仅发送模式的预定义调用选项。 /// + /// + /// 配置为仅发送模式的实例, + /// 超时时间为5000毫秒,反馈类型为。 + /// + /// + /// 在此模式下,调用方发送请求后立即返回,不等待任何响应。 + /// 适用于单向通信或不需要响应结果的场景。 + /// public static InvokeOption OnlySend { get; } /// - /// 默认设置。 - /// Timeout=5000ms + /// 获取等待调用完成模式的预定义调用选项。 /// + /// + /// 配置为等待调用完成模式的实例, + /// 超时时间为5000毫秒,反馈类型为。 + /// + /// + /// 在此模式下,调用方会等待直到远程方法执行完成并返回结果。 + /// 这是最常用的RPC调用模式,提供完整的请求-响应语义。 + /// public static InvokeOption WaitInvoke { get; } /// - /// 默认设置。 - /// Timeout=5000 ms + /// 获取等待发送完成模式的预定义调用选项。 /// + /// + /// 配置为等待发送完成模式的实例, + /// 超时时间为5000毫秒,反馈类型为。 + /// + /// + /// 在此模式下,调用方会等待直到请求数据成功发送到对方,但不等待执行结果。 + /// 适用于需要确保数据传输成功但不需要等待执行结果的场景。 + /// public static InvokeOption WaitSend { get; } /// - /// 调用反馈 + /// 获取或设置调用反馈类型。 /// + /// 指定RPC调用的反馈机制,默认为 + /// + /// 反馈类型决定了调用方等待响应的方式: + /// + /// :仅发送,不等待响应 + /// :等待发送完成 + /// :等待调用完成 + /// + /// public FeedbackType FeedbackType { get; set; } = FeedbackType.WaitInvoke; /// - /// 调用超时, + /// 获取或设置调用超时时间。 /// + /// 调用超时时间,以毫秒为单位,默认为5000毫秒。 + /// + /// 超时时间用于控制RPC调用的最长等待时间。 + /// 如果在指定时间内没有收到响应,调用将被取消并抛出超时异常。 + /// 设置合理的超时时间可以避免调用无限期等待。 + /// public int Timeout { get; set; } = 5000; /// - /// 可以取消的调用令箭 + /// 获取或设置用于取消调用的取消令牌。 /// - public CancellationToken Token { get; set; } = CancellationToken.None; + /// 用于取消RPC调用操作的 + /// + /// 取消令牌允许调用方在任何时候主动取消正在进行的RPC调用。 + /// 当令牌被触发时,调用将立即停止并抛出。 + /// 这提供了比超时机制更精确的调用控制能力。 + /// + public CancellationToken Token { get; set; } } \ No newline at end of file diff --git a/src/TouchSocket.Rpc/Common/InvokeResult.cs b/src/TouchSocket.Rpc/Common/InvokeResult.cs index 7b33e687a..1604a74b7 100644 --- a/src/TouchSocket.Rpc/Common/InvokeResult.cs +++ b/src/TouchSocket.Rpc/Common/InvokeResult.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Rpc; /// diff --git a/src/TouchSocket.Rpc/Common/RpcMethod.cs b/src/TouchSocket.Rpc/Common/RpcMethod.cs index 424c1e210..dbf3fb58a 100644 --- a/src/TouchSocket.Rpc/Common/RpcMethod.cs +++ b/src/TouchSocket.Rpc/Common/RpcMethod.cs @@ -10,13 +10,10 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; using System.ComponentModel; -using System.Linq; +using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Runtime.CompilerServices; -using TouchSocket.Core; namespace TouchSocket.Rpc; @@ -33,6 +30,7 @@ public sealed class RpcMethod : Method /// 实例化一个Rpc调用函数,并在方法声明的类上操作 /// /// + [RequiresUnreferencedCode("此方法使用反射动态加载程序集,与剪裁不兼容。请改用安全的替代方法。")] public RpcMethod(MethodInfo methodInfo) : this(methodInfo, methodInfo.DeclaringType, methodInfo.DeclaringType) { } @@ -43,7 +41,7 @@ public sealed class RpcMethod : Method /// /// /// - public RpcMethod(MethodInfo method, Type serverFromType, Type serverToType) : base(method) + public RpcMethod(MethodInfo method, [DynamicallyAccessedMembers(AOT.RpcRegister)] Type serverFromType, [DynamicallyAccessedMembers(AOT.RpcRegister)] Type serverToType) : base(method) { this.ServerFromType = serverFromType; this.ServerToType = serverToType; @@ -107,12 +105,6 @@ public sealed class RpcMethod : Method return null; } - /// - /// 是否可用 - /// - [Obsolete("此属性已被弃用", true)] - public bool IsEnable { get; set; } = true; - /// /// 参数名集合 /// @@ -248,6 +240,7 @@ public sealed class RpcMethod : Method /// 筛选器 /// /// 全局筛选器 + /// /// public IReadOnlyList GetFilters(IReadOnlyList filters, IResolver resolver) { @@ -260,7 +253,7 @@ public sealed class RpcMethod : Method foreach (var item in this.Info.GetCustomAttributes(typeof(IRpcActionFilter), false)) { this.m_hasFilters[0] = true; - this.AddActionFilter((IRpcActionFilter)item, ref actionFilters); + AddActionFilter((IRpcActionFilter)item, ref actionFilters); } } @@ -272,7 +265,7 @@ public sealed class RpcMethod : Method foreach (var item in this.ToMethodInfo.GetCustomAttributes(typeof(IRpcActionFilter), false)) { this.m_hasFilters[1] = true; - this.AddActionFilter((IRpcActionFilter)item, ref actionFilters); + AddActionFilter((IRpcActionFilter)item, ref actionFilters); } } } @@ -283,7 +276,7 @@ public sealed class RpcMethod : Method foreach (var item in this.ServerFromType.GetCustomAttributes(typeof(IRpcActionFilter), false)) { this.m_hasFilters[2] = true; - this.AddActionFilter((IRpcActionFilter)item, ref actionFilters); + AddActionFilter((IRpcActionFilter)item, ref actionFilters); } } @@ -295,7 +288,7 @@ public sealed class RpcMethod : Method foreach (var item in this.ServerToType.GetCustomAttributes(typeof(IRpcActionFilter), false)) { this.m_hasFilters[3] = true; - this.AddActionFilter((IRpcActionFilter)item, ref actionFilters); + AddActionFilter((IRpcActionFilter)item, ref actionFilters); } } } @@ -304,14 +297,14 @@ public sealed class RpcMethod : Method foreach (var filterType in filters) { var filter = resolver.Resolve(filterType); - this.AddActionFilter(Unsafe.As(ref filter), ref actionFilters); + AddActionFilter(Unsafe.As(ref filter), ref actionFilters); } return actionFilters; } return []; } - private void AddActionFilter(IRpcActionFilter filter, ref List filters) + private static void AddActionFilter(IRpcActionFilter filter, ref List filters) { foreach (var item in filters) { diff --git a/src/TouchSocket.Rpc/Common/RpcParameter.cs b/src/TouchSocket.Rpc/Common/RpcParameter.cs index 087696f8b..6ccdd1e1f 100644 --- a/src/TouchSocket.Rpc/Common/RpcParameter.cs +++ b/src/TouchSocket.Rpc/Common/RpcParameter.cs @@ -10,9 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Reflection; -using TouchSocket.Core; namespace TouchSocket.Rpc; diff --git a/src/TouchSocket.Rpc/Common/RpcStore.cs b/src/TouchSocket.Rpc/Common/RpcStore.cs index ef51905ac..dab8b5290 100644 --- a/src/TouchSocket.Rpc/Common/RpcStore.cs +++ b/src/TouchSocket.Rpc/Common/RpcStore.cs @@ -10,12 +10,8 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Collections.Concurrent; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; -using TouchSocket.Core; namespace TouchSocket.Rpc; @@ -40,7 +36,10 @@ public sealed class RpcStore /// public Type[] ServerTypes => this.m_serverTypes.Keys.ToArray(); - public IReadOnlyList Filters => this.m_filters; + /// + /// 全局筛选器 + /// + public List Filters => this.m_filters; /// /// 获取所有已注册的函数。 @@ -59,9 +58,10 @@ public sealed class RpcStore /// /// 本地获取代理 /// - /// - /// - /// + /// 生成代理代码的目标命名空间 + /// Rpc特性类型数组,用于指定要生成代理的Rpc方法特性 + /// 生成的代理代码字符串 + [RequiresUnreferencedCode("此方法使用反射动态加载程序集,与剪裁不兼容。请改用安全的替代方法。")] public string GetProxyCodes(string @namespace, params Type[] attributeTypes) { var cellCodes = this.GetProxyInfo(attributeTypes); @@ -71,9 +71,10 @@ public sealed class RpcStore /// /// 获取生成的代理 /// - /// - /// - /// + /// Rpc特性类型,必须继承自 + /// 生成代理代码的目标命名空间 + /// 生成的代理代码字符串 + [RequiresUnreferencedCode("此方法使用反射动态加载程序集,与剪裁不兼容。请改用安全的替代方法。")] public string GetProxyCodes(string @namespace) where TAttribute : RpcAttribute { var cellCodes = this.GetProxyInfo(new Type[] { typeof(TAttribute) }); @@ -83,8 +84,9 @@ public sealed class RpcStore /// /// 从本地获取代理 /// - /// - /// + /// Rpc特性类型数组,用于指定要生成代理的Rpc方法特性 + /// 服务单元代码数组,包含所有匹配特性的服务代码 + [RequiresUnreferencedCode("此方法使用反射动态加载程序集,与剪裁不兼容。请改用安全的替代方法。")] public ServerCellCode[] GetProxyInfo(Type[] attributeType) { var codes = new List(); @@ -103,8 +105,8 @@ public sealed class RpcStore /// /// 获取服务类型对应的服务方法。 /// - /// - /// + /// 要查询的服务类型 + /// 该服务类型对应的所有Rpc方法数组 public RpcMethod[] GetServerRpcMethods(Type serverType) { return this.m_serverTypes[serverType].ToArray(); @@ -117,8 +119,8 @@ public sealed class RpcStore /// /// 添加全局筛选器 /// - /// - public void AddFilter() where TFilter : class, IRpcActionFilter + /// 要添加的筛选器类型,必须实现接口 + public void AddFilter<[DynamicallyAccessedMembers(AOT.Container)] TFilter>() where TFilter : class, IRpcActionFilter { var filterType = typeof(TFilter); this.AddFilter(filterType); @@ -127,7 +129,8 @@ public sealed class RpcStore /// /// 添加全局过滤器 /// - public void AddFilter(Type filterType) + /// 要添加的筛选器类型,必须实现接口 + public void AddFilter([DynamicallyAccessedMembers(AOT.Container)] Type filterType) { if (!typeof(IRpcActionFilter).IsAssignableFrom(filterType)) { @@ -144,17 +147,15 @@ public sealed class RpcStore #endregion - #region 注册 - /// /// 注册为单例服务 /// - /// - /// - /// - public void RegisterServer(Type serverFromType, IRpcServer rpcServer) + /// 要注册的服务接口或基类类型,必须实现接口 + /// 服务的具体实例,类型必须与相匹配或可赋值 + public void RegisterServer<[DynamicallyAccessedMembers(AOT.RpcRegister)] TRpcServer>([DynamicallyAccessedMembers(AOT.RpcRegister)] Type serverFromType, TRpcServer rpcServer) + where TRpcServer : IRpcServer { if (!typeof(IRpcServer).IsAssignableFrom(serverFromType)) { @@ -168,25 +169,24 @@ public sealed class RpcStore foreach (var item in this.m_serverTypes.Keys) { - if (item.FullName == serverFromType.FullName) + if (item == serverFromType) { return; } } - var rpcMethods = CodeGenerator.GetRpcMethods(serverFromType, rpcServer.GetType()); + var rpcMethods = CodeGenerator.GetRpcMethods(serverFromType, typeof(TRpcServer)); this.m_serverTypes.AddOrUpdate(serverFromType, new List(rpcMethods)); - this.m_registrator.RegisterSingleton(serverFromType, rpcServer); + this.m_registrator.Register(new DependencyDescriptor(serverFromType, typeof(TRpcServer), rpcServer)); } /// /// 注册服务 /// - /// - /// - /// - public void RegisterServer(Type serverFromType, [DynamicallyAccessedMembers(RpcStoreExtension.DynamicallyAccessed)] Type serverToType) + /// 要注册的服务接口或基类类型,必须实现接口 + /// 服务的实现类型,必须与相匹配或可赋值。根据类型实现的接口确定生命周期(瞬态、作用域或单例) + public void RegisterServer([DynamicallyAccessedMembers(AOT.RpcRegister)] Type serverFromType, [DynamicallyAccessedMembers(AOT.RpcRegister)] Type serverToType) { if (!typeof(IRpcServer).IsAssignableFrom(serverFromType)) { diff --git a/src/TouchSocket.Rpc/Dispatcher/ConcurrencyRpcDispatcher.cs b/src/TouchSocket.Rpc/Dispatcher/ConcurrencyRpcDispatcher.cs index 2c53d8da6..19eecdb22 100644 --- a/src/TouchSocket.Rpc/Dispatcher/ConcurrencyRpcDispatcher.cs +++ b/src/TouchSocket.Rpc/Dispatcher/ConcurrencyRpcDispatcher.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Rpc; /// diff --git a/src/TouchSocket.Rpc/Dispatcher/IRpcDispatcher.cs b/src/TouchSocket.Rpc/Dispatcher/IRpcDispatcher.cs index acd87a8c3..29ac1125b 100644 --- a/src/TouchSocket.Rpc/Dispatcher/IRpcDispatcher.cs +++ b/src/TouchSocket.Rpc/Dispatcher/IRpcDispatcher.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Rpc; /// diff --git a/src/TouchSocket.Rpc/Dispatcher/ImmediateRpcDispatcher.cs b/src/TouchSocket.Rpc/Dispatcher/ImmediateRpcDispatcher.cs index 98a3d405d..a65a571a3 100644 --- a/src/TouchSocket.Rpc/Dispatcher/ImmediateRpcDispatcher.cs +++ b/src/TouchSocket.Rpc/Dispatcher/ImmediateRpcDispatcher.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Rpc; /// diff --git a/src/TouchSocket.Rpc/Dispatcher/QueueRpcDispatcher.cs b/src/TouchSocket.Rpc/Dispatcher/QueueRpcDispatcher.cs index 6e3a7084b..f9ca79b81 100644 --- a/src/TouchSocket.Rpc/Dispatcher/QueueRpcDispatcher.cs +++ b/src/TouchSocket.Rpc/Dispatcher/QueueRpcDispatcher.cs @@ -10,11 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Rpc; /// @@ -53,7 +48,7 @@ public class QueueRpcDispatcher : DisposableObject, IRp { if (disposing) { - this.m_cancellationTokenSource.Cancel(); + this.m_cancellationTokenSource.SafeCancel(); this.m_cancellationTokenSource.SafeDispose(); } base.Dispose(disposing); diff --git a/src/TouchSocket.Rpc/Enum/CodeGeneratorFlag.cs b/src/TouchSocket.Rpc/Enum/CodeGeneratorFlag.cs index 9597cda4c..e04cb64bc 100644 --- a/src/TouchSocket.Rpc/Enum/CodeGeneratorFlag.cs +++ b/src/TouchSocket.Rpc/Enum/CodeGeneratorFlag.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Rpc; /// diff --git a/src/TouchSocket.Rpc/Exceptions/RpcExceptions.cs b/src/TouchSocket.Rpc/Exceptions/RpcExceptions.cs index 78a14acaf..c540405c0 100644 --- a/src/TouchSocket.Rpc/Exceptions/RpcExceptions.cs +++ b/src/TouchSocket.Rpc/Exceptions/RpcExceptions.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Rpc; /// diff --git a/src/TouchSocket.Rpc/Exceptions/RpcInvokeException.cs b/src/TouchSocket.Rpc/Exceptions/RpcInvokeException.cs index 898e38c70..89c9fe2c3 100644 --- a/src/TouchSocket.Rpc/Exceptions/RpcInvokeException.cs +++ b/src/TouchSocket.Rpc/Exceptions/RpcInvokeException.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Rpc; /// diff --git a/src/TouchSocket.Rpc/Extensions/RpcClientExtension.cs b/src/TouchSocket.Rpc/Extensions/RpcClientExtension.cs index 53139c584..f0d3f3f6b 100644 --- a/src/TouchSocket.Rpc/Extensions/RpcClientExtension.cs +++ b/src/TouchSocket.Rpc/Extensions/RpcClientExtension.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Rpc; /// @@ -23,22 +19,22 @@ public static class RpcClientExtension { #region RpcClient - /// + /// [AsyncToSyncWarning] - public static object Invoke(this IRpcClient client, string invokeKey, Type returnType, IInvokeOption invokeOption, params object[] parameters) + public static object Invoke(this IRpcClient client, string invokeKey, Type returnType, InvokeOption invokeOption, params object[] parameters) { return client.InvokeAsync(invokeKey, returnType, invokeOption, parameters).GetFalseAwaitResult(); } - /// + /// [AsyncToSyncWarning] - public static T InvokeT(this IRpcClient client, string invokeKey, IInvokeOption invokeOption, params object[] parameters) + public static T InvokeT(this IRpcClient client, string invokeKey, InvokeOption invokeOption, params object[] parameters) { return (T)(client.InvokeAsync(invokeKey, typeof(T), invokeOption, parameters).GetFalseAwaitResult()); } - /// - public static async Task InvokeTAsync(this IRpcClient client, string invokeKey, IInvokeOption invokeOption, params object[] parameters) + /// + public static async Task InvokeTAsync(this IRpcClient client, string invokeKey, InvokeOption invokeOption, params object[] parameters) { return (T)(await client.InvokeAsync(invokeKey, typeof(T), invokeOption, parameters).ConfigureAwait(EasyTask.ContinueOnCapturedContext)); } @@ -47,22 +43,22 @@ public static class RpcClientExtension #region ITargetRpcClient - /// + /// [AsyncToSyncWarning] - public static object Invoke(this ITargetRpcClient client, string targetId, string invokeKey, Type returnType, IInvokeOption invokeOption, params object[] parameters) + public static object Invoke(this ITargetRpcClient client, string targetId, string invokeKey, Type returnType, InvokeOption invokeOption, params object[] parameters) { return client.InvokeAsync(targetId, invokeKey, returnType, invokeOption, parameters).GetFalseAwaitResult(); } - /// + /// [AsyncToSyncWarning] - public static T InvokeT(this ITargetRpcClient client, string targetId, string invokeKey, IInvokeOption invokeOption, params object[] parameters) + public static T InvokeT(this ITargetRpcClient client, string targetId, string invokeKey, InvokeOption invokeOption, params object[] parameters) { return (T)(client.InvokeAsync(targetId, invokeKey, typeof(T), invokeOption, parameters).GetFalseAwaitResult()); } - /// - public static async Task InvokeTAsync(this ITargetRpcClient client, string targetId, string invokeKey, IInvokeOption invokeOption, params object[] parameters) + /// + public static async Task InvokeTAsync(this ITargetRpcClient client, string targetId, string invokeKey, InvokeOption invokeOption, params object[] parameters) { return (T)(await client.InvokeAsync(targetId, invokeKey, typeof(T), invokeOption, parameters)); } diff --git a/src/TouchSocket.Rpc/Extensions/RpcContainerExtension.cs b/src/TouchSocket.Rpc/Extensions/RpcContainerExtension.cs index 47b9d8ad0..d7e12cac2 100644 --- a/src/TouchSocket.Rpc/Extensions/RpcContainerExtension.cs +++ b/src/TouchSocket.Rpc/Extensions/RpcContainerExtension.cs @@ -10,9 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Diagnostics.CodeAnalysis; -using TouchSocket.Core; namespace TouchSocket.Rpc; @@ -42,7 +40,7 @@ public static partial class RpcContainerExtension /// /// /// - public static IRegistrator AddRpcServerProvider<[DynamicallyAccessedMembers(CoreContainerExtension.DynamicallyAccessed)] TRpcServerProvider>(this IRegistrator registrator) where TRpcServerProvider : class, IRpcServerProvider + public static IRegistrator AddRpcServerProvider<[DynamicallyAccessedMembers(AOT.Container)] TRpcServerProvider>(this IRegistrator registrator) where TRpcServerProvider : class, IRpcServerProvider { registrator.RegisterSingleton(); return registrator; diff --git a/src/TouchSocket.Rpc/Extensions/RpcContainerExtension_AsyncLocal.cs b/src/TouchSocket.Rpc/Extensions/RpcContainerExtension_AsyncLocal.cs index 879eaad30..b9d0122c3 100644 --- a/src/TouchSocket.Rpc/Extensions/RpcContainerExtension_AsyncLocal.cs +++ b/src/TouchSocket.Rpc/Extensions/RpcContainerExtension_AsyncLocal.cs @@ -10,13 +10,7 @@ // 感谢您的下载和使用 // ------------------------------------------------------------------------------ -#if AsyncLocal -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using TouchSocket.Core; +using System.Diagnostics.CodeAnalysis; namespace TouchSocket.Rpc; /// @@ -31,7 +25,7 @@ public static partial class RpcContainerExtension /// 实现 的类型。 /// 用于注册依赖项的 实例。 /// 返回 实例以支持链式调用。 - public static IRegistrator AddRpcCallContextAccessor(this IRegistrator registrator) + public static IRegistrator AddRpcCallContextAccessor<[DynamicallyAccessedMembers(AOT.Container)] TRpcCallContextAccessor>(this IRegistrator registrator) where TRpcCallContextAccessor : class, IRpcCallContextAccessor { registrator.RegisterSingleton(); @@ -49,5 +43,3 @@ public static partial class RpcContainerExtension } #endregion } - -#endif diff --git a/src/TouchSocket.Rpc/Extensions/RpcStoreExtension.cs b/src/TouchSocket.Rpc/Extensions/RpcStoreExtension.cs index c9ddec25d..4c7d48916 100644 --- a/src/TouchSocket.Rpc/Extensions/RpcStoreExtension.cs +++ b/src/TouchSocket.Rpc/Extensions/RpcStoreExtension.cs @@ -10,10 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Reflection; namespace TouchSocket.Rpc; @@ -23,15 +20,11 @@ namespace TouchSocket.Rpc; /// public static class RpcStoreExtension { - /// - /// DynamicallyAccessed - /// - public const DynamicallyAccessedMemberTypes DynamicallyAccessed = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicProperties; - /// /// 注册已加载程序集的所有Rpc服务 /// /// 返回搜索到的服务数 + [RequiresUnreferencedCode("此方法使用反射动态加载程序集,与剪裁不兼容。请改用安全的替代方法。")] public static void RegisterAllServer(this RpcStore rpcStore) { var types = new List(); @@ -47,6 +40,7 @@ public static class RpcStoreExtension /// /// /// + [RequiresUnreferencedCode("此方法使用反射动态加载程序集,与剪裁不兼容。请改用安全的替代方法。")] public static void RegisterAllServer(this RpcStore rpcStore, Assembly assembly) { foreach (var type in assembly.ExportedTypes.Where(p => typeof(IRpcServer).IsAssignableFrom(p) && !p.IsAbstract && p.IsClass).ToArray()) @@ -60,7 +54,7 @@ public static class RpcStoreExtension /// /// /// - public static void RegisterServer<[DynamicallyAccessedMembers(DynamicallyAccessed)] T>(this RpcStore rpcStore) where T : IRpcServer + public static void RegisterServer<[DynamicallyAccessedMembers(AOT.RpcRegister)] T>(this RpcStore rpcStore) where T : IRpcServer { rpcStore.RegisterServer(typeof(T)); } @@ -71,7 +65,7 @@ public static class RpcStoreExtension /// /// /// - public static void RegisterServer(this RpcStore rpcStore, [DynamicallyAccessedMembers(DynamicallyAccessed)] Type providerType) + public static void RegisterServer(this RpcStore rpcStore, [DynamicallyAccessedMembers(AOT.RpcRegister)] Type providerType) { rpcStore.RegisterServer(providerType, providerType); } @@ -82,7 +76,7 @@ public static class RpcStoreExtension /// /// /// - public static void RegisterServer<[DynamicallyAccessedMembers(DynamicallyAccessed)] TFrom, [DynamicallyAccessedMembers(DynamicallyAccessed)] TTo>(this RpcStore rpcStore) where TFrom : class, IRpcServer where TTo : TFrom + public static void RegisterServer<[DynamicallyAccessedMembers(AOT.RpcRegister)] TFrom, [DynamicallyAccessedMembers(AOT.RpcRegister)] TTo>(this RpcStore rpcStore) where TFrom : class, IRpcServer where TTo : TFrom { rpcStore.RegisterServer(typeof(TFrom), typeof(TTo)); } @@ -92,7 +86,7 @@ public static class RpcStoreExtension /// /// /// - public static void RegisterServer<[DynamicallyAccessedMembers(DynamicallyAccessed)] TFrom>(this RpcStore rpcStore, [DynamicallyAccessedMembers(DynamicallyAccessed)] TFrom rpcServer) where TFrom : class, IRpcServer + public static void RegisterServer<[DynamicallyAccessedMembers(AOT.RpcRegister)] TFrom>(this RpcStore rpcStore, TFrom rpcServer) where TFrom : class, IRpcServer { rpcStore.RegisterServer(typeof(TFrom), rpcServer); } diff --git a/src/TouchSocket.Rpc/Interface/ICallContext.cs b/src/TouchSocket.Rpc/Interface/ICallContext.cs index 7d4610923..d7ea0337b 100644 --- a/src/TouchSocket.Rpc/Interface/ICallContext.cs +++ b/src/TouchSocket.Rpc/Interface/ICallContext.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading; -using TouchSocket.Core; - namespace TouchSocket.Rpc; /// diff --git a/src/TouchSocket.Rpc/Interface/IInvokeOption.cs b/src/TouchSocket.Rpc/Interface/IInvokeOption.cs deleted file mode 100644 index e3c79dc6b..000000000 --- a/src/TouchSocket.Rpc/Interface/IInvokeOption.cs +++ /dev/null @@ -1,36 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System.Threading; - -namespace TouchSocket.Rpc; - -/// -/// 调用配置接口 -/// -public interface IInvokeOption -{ - /// - /// 可以取消的调用令箭 - /// - CancellationToken Token { get; set; } - - /// - /// 调用反馈 - /// - FeedbackType FeedbackType { get; set; } - - /// - /// 调用超时 - /// - int Timeout { get; set; } -} \ No newline at end of file diff --git a/src/TouchSocket.Rpc/Interface/IRpcClient.cs b/src/TouchSocket.Rpc/Interface/IRpcClient.cs index 2197d4ecf..19d0d695c 100644 --- a/src/TouchSocket.Rpc/Interface/IRpcClient.cs +++ b/src/TouchSocket.Rpc/Interface/IRpcClient.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; - namespace TouchSocket.Rpc; @@ -30,5 +27,5 @@ public interface IRpcClient /// 调用选项,用于指定调用的特定选项。 /// 传递给操作的参数。 /// 一个任务,其结果是操作的返回值。 - Task InvokeAsync(string invokeKey, Type returnType, IInvokeOption invokeOption, params object[] parameters); + Task InvokeAsync(string invokeKey, Type returnType, InvokeOption invokeOption, params object[] parameters); } \ No newline at end of file diff --git a/src/TouchSocket.Rpc/Interface/ITargetRpcClient.cs b/src/TouchSocket.Rpc/Interface/ITargetRpcClient.cs index e31b433c5..0f9336b47 100644 --- a/src/TouchSocket.Rpc/Interface/ITargetRpcClient.cs +++ b/src/TouchSocket.Rpc/Interface/ITargetRpcClient.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; - namespace TouchSocket.Rpc; /// @@ -30,5 +27,5 @@ public interface ITargetRpcClient /// 调用选项,可能包含超时、重试等调用策略。 /// 调用方法的参数,按顺序传递给远程方法。 /// 返回一个异步任务,包含调用结果对象。返回类型由returnType参数指定。 - Task InvokeAsync(string targetId, string invokeKey, Type returnType, IInvokeOption invokeOption, params object[] parameters); + Task InvokeAsync(string targetId, string invokeKey, Type returnType, InvokeOption invokeOption, params object[] parameters); } \ No newline at end of file diff --git a/src/TouchSocket.Rpc/ObsoleteClass.cs b/src/TouchSocket.Rpc/ObsoleteClass.cs index cea3b201b..4c985e3ad 100644 --- a/src/TouchSocket.Rpc/ObsoleteClass.cs +++ b/src/TouchSocket.Rpc/ObsoleteClass.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 // ------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Rpc; /// diff --git a/src/TouchSocket.Rpc/Proxy/ProxyModel.cs b/src/TouchSocket.Rpc/Proxy/ProxyModel.cs index d08e6bdbe..c4a0887c9 100644 --- a/src/TouchSocket.Rpc/Proxy/ProxyModel.cs +++ b/src/TouchSocket.Rpc/Proxy/ProxyModel.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Rpc; internal class ProxyModel diff --git a/src/TouchSocket.Rpc/Proxy/RpcDispatchProxy.cs b/src/TouchSocket.Rpc/Proxy/RpcDispatchProxy.cs index 5a1e6d192..453f4e3f1 100644 --- a/src/TouchSocket.Rpc/Proxy/RpcDispatchProxy.cs +++ b/src/TouchSocket.Rpc/Proxy/RpcDispatchProxy.cs @@ -10,20 +10,16 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -#if NET6_0_OR_GREATER || NET481_OR_GREATER -using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; using System.Reflection; -using System.Threading.Tasks; -using TouchSocket.Core; +using System.Diagnostics.CodeAnalysis; namespace TouchSocket.Rpc; /// /// RpcDispatchProxy /// +[RequiresUnreferencedCode("动态代理不支持AOT环境")] public abstract class RpcDispatchProxy : DispatchProxy where TClient : IRpcClient where TAttribute : RpcAttribute { private readonly ConcurrentDictionary m_methods = new ConcurrentDictionary(); @@ -32,6 +28,7 @@ public abstract class RpcDispatchProxy : DispatchProxy wher /// /// RpcDispatchProxy /// + [UnconditionalSuppressMessage("Trimming", "IL3050:使用动态代码可能与修剪不兼容")] public RpcDispatchProxy() { this.m_fromResultMethod = typeof(Task).GetMethod("FromResult"); @@ -49,11 +46,12 @@ public abstract class RpcDispatchProxy : DispatchProxy wher var rpcMethod = value.RpcMethod; var invokeKey = value.InvokeKey; - var invokeOption = value.InvokeOption ? (IInvokeOption)args.Last() : InvokeOption.WaitInvoke; + InvokeOption invokeOption; object[] ps; if (value.InvokeOption) { + invokeOption = (InvokeOption)args.Last(); var pslist = new List(); for (var i = 0; i < args.Length; i++) @@ -68,6 +66,7 @@ public abstract class RpcDispatchProxy : DispatchProxy wher } else { + invokeOption = default; ps = args; } @@ -101,13 +100,14 @@ public abstract class RpcDispatchProxy : DispatchProxy wher } + [UnconditionalSuppressMessage("Trimming", "IL3050:使用动态代码可能与修剪不兼容")] private ProxyModel AddMethod(MethodInfo info) { var attribute = info.GetCustomAttribute(true) ?? throw new Exception($"在方法{info.Name}中没有找到{typeof(TAttribute)}的特性。"); var rpcMethod = new RpcMethod(info); var invokeKey = attribute.GetInvokeKey(rpcMethod); var invokeOption = false; - if (info.GetParameters().Length > 0 && typeof(IInvokeOption).IsAssignableFrom(info.GetParameters().Last().ParameterType)) + if (info.GetParameters().Length > 0 && typeof(InvokeOption).IsAssignableFrom(info.GetParameters().Last().ParameterType)) { invokeOption = true; } @@ -143,5 +143,4 @@ public abstract class RpcDispatchProxy : DispatchProxy wher { } -} -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/src/TouchSocket.Rpc/Proxy/RpcRealityProxy.cs b/src/TouchSocket.Rpc/Proxy/RpcRealityProxy.cs deleted file mode 100644 index d800d6d68..000000000 --- a/src/TouchSocket.Rpc/Proxy/RpcRealityProxy.cs +++ /dev/null @@ -1,162 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -#if NET45_OR_GREATER -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Runtime.Remoting.Messaging; -using System.Runtime.Remoting.Proxies; -using System.Text; -using System.Threading.Tasks; -using TouchSocket.Core; - -namespace TouchSocket.Rpc; - -/// -/// Rpc透明代理 -/// -/// -/// -/// -public abstract class RpcRealityProxy : RpcRealityProxyBase where TClient : IRpcClient where TAttribute : RpcAttribute -{ - private readonly ConcurrentDictionary m_methods = new ConcurrentDictionary(); - private readonly MethodInfo m_fromResultMethod; - - /// - /// RpcRealityProxy - /// - public RpcRealityProxy() - { - this.m_fromResultMethod = typeof(Task).GetMethod("FromResult"); - } - - /// - /// 获取调用Rpc的客户端。 - /// - public abstract TClient GetClient(); - - - /// - /// 调用过程 - /// - /// - /// - public override IMessage Invoke(IMessage msg) - { - //方法信息 - var methodCall = msg as IMethodCallMessage; - var targetMethod = methodCall.MethodBase as MethodInfo; - var args = methodCall.Args; - - var value = this.m_methods.GetOrAdd(targetMethod, this.AddMethod); - var rpcMethod = value.RpcMethod; - var invokeKey = value.InvokeKey; - - var invokeOption = value.InvokeOption ? (IInvokeOption)args.Last() : InvokeOption.WaitInvoke; - - object[] ps; - if (value.InvokeOption) - { - var pslist = new List(); - - for (var i = 0; i < args.Length; i++) - { - if (i < args.Length - 1) - { - pslist.Add(args[i]); - } - } - - ps = pslist.ToArray(); - } - else - { - ps = args; - } - - this.OnBefore(targetMethod, value.InvokeKey, ref ps); - - object result; - - switch (rpcMethod.ReturnKind) - { - case MethodReturnKind.Awaitable: - { - result = this.GetClient().InvokeAsync(invokeKey, rpcMethod.RealReturnType, invokeOption, ps); - break; - } - case MethodReturnKind.AwaitableObject: - { - result = this.GetClient().InvokeAsync(invokeKey, rpcMethod.RealReturnType, invokeOption, ps).GetFalseAwaitResult(); - result = value.GenericMethod.Invoke(default, result); - break; - } - default: - { - result = this.GetClient().InvokeAsync(invokeKey, rpcMethod.RealReturnType, invokeOption, ps).GetFalseAwaitResult(); - break; - } - } - - this.OnAfter(targetMethod, invokeKey, ref args, ref result); - - return new ReturnMessage(result, args, args.Length, methodCall.LogicalCallContext, methodCall); - } - - private ProxyModel AddMethod(MethodInfo info) - { - var attribute = info.GetCustomAttribute(true) ?? throw new Exception($"在方法{info.Name}中没有找到{typeof(TAttribute)}的特性。"); - var rpcMethod = new RpcMethod(info); - var invokeKey = attribute.GetInvokeKey(rpcMethod); - var invokeOption = false; - if (info.GetParameters().Length > 0 && typeof(IInvokeOption).IsAssignableFrom(info.GetParameters().Last().ParameterType)) - { - invokeOption = true; - } - return new ProxyModel() - { - InvokeKey = invokeKey, - RpcMethod = rpcMethod, - InvokeOption = invokeOption, - GenericMethod = rpcMethod.ReturnKind == MethodReturnKind.AwaitableObject ? new Method(this.m_fromResultMethod.MakeGenericMethod(rpcMethod.RealReturnType)) : default - }; - } - - /// - /// 方法调用前 - /// - /// - /// - /// - /// - protected virtual void OnBefore(MethodInfo method, string invokeKey, ref object[] args) - { - - } - - /// - /// 方法调用后 - /// - /// - /// - /// - /// - protected virtual void OnAfter(MethodInfo method, string invokeKey, ref object[] args, ref object result) - { - - } -} -#endif \ No newline at end of file diff --git a/src/TouchSocket.Rpc/Proxy/RpcRealityProxyBase.cs b/src/TouchSocket.Rpc/Proxy/RpcRealityProxyBase.cs deleted file mode 100644 index 747e40f77..000000000 --- a/src/TouchSocket.Rpc/Proxy/RpcRealityProxyBase.cs +++ /dev/null @@ -1,46 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -#if NET45_OR_GREATER -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Remoting.Proxies; -using System.Text; -using System.Threading.Tasks; - -namespace TouchSocket.Rpc; - -/// -/// RpcRealityProxyBase -/// -public abstract class RpcRealityProxyBase : RealProxy -{ - /// - /// RpcRealityProxyBase - /// - public RpcRealityProxyBase() : base(typeof(T)) - { - - } - - /// - /// 返回当前实例的透明代理。 - /// - /// - public new T GetTransparentProxy() - { - return (T)base.GetTransparentProxy(); - } -} - -#endif \ No newline at end of file diff --git a/src/TouchSocket.Rpc/Readme.md b/src/TouchSocket.Rpc/Readme.md index 936259d62..5b23fee04 100644 --- a/src/TouchSocket.Rpc/Readme.md +++ b/src/TouchSocket.Rpc/Readme.md @@ -12,14 +12,14 @@ TouchSocket.Rpc 是一个超轻量、高性能、可扩展的 Rpc 管理平台 详细的说明文档请访问:[https://touchsocket.net/](https://touchsocket.net/) ## 支持的目标框架 + - net481 -- net45 - net462 - net472 - netstandard2.0 - netstandard2.1 - net6.0 -- net9.0 +- net10.0 - net8.0 ## 贡献与反馈 diff --git a/src/TouchSocket.Rpc/RpcServerProvider/IRpcServerProvider.cs b/src/TouchSocket.Rpc/RpcServerProvider/IRpcServerProvider.cs index 321226720..84842cb8e 100644 --- a/src/TouchSocket.Rpc/RpcServerProvider/IRpcServerProvider.cs +++ b/src/TouchSocket.Rpc/RpcServerProvider/IRpcServerProvider.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; - namespace TouchSocket.Rpc; /// diff --git a/src/TouchSocket.Rpc/RpcServerProvider/InternalRpcServerProvider.cs b/src/TouchSocket.Rpc/RpcServerProvider/InternalRpcServerProvider.cs index 747f9dccc..248e69e86 100644 --- a/src/TouchSocket.Rpc/RpcServerProvider/InternalRpcServerProvider.cs +++ b/src/TouchSocket.Rpc/RpcServerProvider/InternalRpcServerProvider.cs @@ -10,10 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Reflection; -using System.Threading.Tasks; -using TouchSocket.Core; namespace TouchSocket.Rpc; @@ -87,10 +84,7 @@ internal sealed class InternalRpcServerProvider : IRpcServerProvider .ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - if (rpcCallContextAccessor is not null) - { - rpcCallContextAccessor.CallContext = default; - } + rpcCallContextAccessor?.CallContext = default; } return invokeResult; diff --git a/src/TouchSocket.Rpc/TouchSocket.Rpc.csproj b/src/TouchSocket.Rpc/TouchSocket.Rpc.csproj index 558028708..a479a66c9 100644 --- a/src/TouchSocket.Rpc/TouchSocket.Rpc.csproj +++ b/src/TouchSocket.Rpc/TouchSocket.Rpc.csproj @@ -1,6 +1,6 @@ - net481;net45;net462;net472;netstandard2.0;netstandard2.1;net6.0;net9.0;net8.0 + net462;netstandard2.0;netstandard2.1;net6.0;net10.0;net8.0 Rpc;TouchSocket 这是一个超轻量、高性能、可扩展的Rpc管理平台框架。您可以基于该框架,快速开发出Rpc执行。目前已扩展开发DmtpRpc、XmlRpc、JsonRpc、WebApi部分。 @@ -16,7 +16,11 @@ - + + + + + diff --git a/src/TouchSocket.SerialPorts/Common/SerialPortOption.cs b/src/TouchSocket.SerialPorts/Common/SerialPortOption.cs index 80b5f5f26..d6f9b12be 100644 --- a/src/TouchSocket.SerialPorts/Common/SerialPortOption.cs +++ b/src/TouchSocket.SerialPorts/Common/SerialPortOption.cs @@ -29,6 +29,12 @@ public class SerialPortOption /// public int DataBits { get; set; } = 8; + /// + public bool DtrEnable { get; set; } + + /// + public Handshake Handshake { get; set; } + /// /// 校验位 /// @@ -39,22 +45,16 @@ public class SerialPortOption /// public string PortName { get; set; } = "COM1"; + /// + public bool RtsEnable { get; set; } + /// /// 停止位 /// public StopBits StopBits { get; set; } = StopBits.One; - /// - public Handshake Handshake { get; set; } - - /// - public bool DtrEnable { get; set; } - - /// - public bool RtsEnable { get; set; } - /// - /// 流异步操作 + /// 是否异步流模式。 /// public bool StreamAsync { get; set; } diff --git a/src/TouchSocket.SerialPorts/Common/SerialPortUtility.cs b/src/TouchSocket.SerialPorts/Common/SerialPortUtility.cs index 4514d6e8b..cf46b2d47 100644 --- a/src/TouchSocket.SerialPorts/Common/SerialPortUtility.cs +++ b/src/TouchSocket.SerialPorts/Common/SerialPortUtility.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Sockets; - namespace TouchSocket.SerialPorts; /// diff --git a/src/TouchSocket.SerialPorts/Components/SerialCore.cs b/src/TouchSocket.SerialPorts/Components/SerialCore.cs index 72357cb63..53a1d9b2f 100644 --- a/src/TouchSocket.SerialPorts/Components/SerialCore.cs +++ b/src/TouchSocket.SerialPorts/Components/SerialCore.cs @@ -10,56 +10,32 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.IO.Ports; -using System.Threading; -using System.Threading.Tasks; using System.Threading.Tasks.Sources; -using TouchSocket.Core; namespace TouchSocket.SerialPorts; /// /// Serial核心 /// -internal class SerialCore : DisposableObject, IValueTaskSource +internal sealed class SerialCore : SafetyDisposableObject, IValueTaskSource { private readonly CancellationTokenSource m_cancellationTokenSource = new(); - private readonly SemaphoreSlim m_receiveLock = new(0, 1); - private readonly SemaphoreSlim m_semaphoreForSend = new SemaphoreSlim(1, 1); + private readonly CircularBuffer m_circularBuffer = new CircularBuffer(1024 * 4); + private readonly Lock m_lock = new Lock(); private readonly SerialPort m_serialPort; private readonly bool m_streamAsync; - private ByteBlock m_byteBlock; private ManualResetValueTaskSourceCore m_core = new ManualResetValueTaskSourceCore(); - - //private SerialData m_eventType; - private int m_receiveBufferSize = 1024 * 10; - - private ValueCounter m_receiveCounter; - - private int m_sendBufferSize = 1024 * 10; - - private ValueCounter m_sendCounter; + private Memory m_memory; + private volatile bool m_disposed = false; + private volatile bool m_hasWaitingReceiver = false; // 标记是否有等待中的接收者 /// /// Serial核心 /// - public SerialCore(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits, bool streamAsync) + public SerialCore(SerialPort serialPort, bool streamAsync) { - this.m_receiveCounter = new ValueCounter - { - Period = TimeSpan.FromSeconds(1), - OnPeriod = this.OnReceivePeriod - }; - - this.m_sendCounter = new ValueCounter - { - Period = TimeSpan.FromSeconds(1), - OnPeriod = this.OnSendPeriod - }; - - this.m_serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits); - + this.m_serialPort = serialPort; this.m_streamAsync = streamAsync; if (!streamAsync) @@ -68,189 +44,197 @@ internal class SerialCore : DisposableObject, IValueTaskSource - /// 最大缓存尺寸 - /// - public int MaxBufferSize { get; set; } = 1024 * 1024 * 10; - - /// - /// 最小缓存尺寸 - /// - public int MinBufferSize { get; set; } = 1024 * 10; - - /// - /// 接收缓存池,运行时的值会根据流速自动调整 - /// - public int ReceiveBufferSize => this.m_receiveBufferSize; - - /// - /// 接收计数器 - /// - public ValueCounter ReceiveCounter => this.m_receiveCounter; - - /// - /// 发送缓存池,运行时的值会根据流速自动调整 - /// - public int SendBufferSize => this.m_sendBufferSize; - - /// - /// 发送计数器 - /// - public ValueCounter SendCounter => this.m_sendCounter; - public SerialPort SerialPort => this.m_serialPort; - SerialOperationResult IValueTaskSource.GetResult(short token) + SerialOperationResult IValueTaskSource.GetResult(short cancellationToken) { - return this.m_core.GetResult(token); + return this.m_core.GetResult(cancellationToken); } - ValueTaskSourceStatus IValueTaskSource.GetStatus(short token) + ValueTaskSourceStatus IValueTaskSource.GetStatus(short cancellationToken) { - return this.m_core.GetStatus(token); + return this.m_core.GetStatus(cancellationToken); } - void IValueTaskSource.OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) + void IValueTaskSource.OnCompleted(Action continuation, object state, short cancellationToken, ValueTaskSourceOnCompletedFlags flags) { - try - { - this.m_core.OnCompleted(continuation, state, token, flags); - } - catch (Exception ex) - { - // 记录异常并尝试设置异常状态,防止continuation执行异常影响整个应用 - // 这里可以根据需要添加日志记录 - // Logger?.LogError(ex, "Error in OnCompleted continuation execution"); - - // 如果可能,尝试通知异常 - try - { - this.m_core.SetException(ex); - } - catch - { - // 忽略SetException的异常,避免递归异常 - } - } + this.m_core.OnCompleted(continuation, state, cancellationToken, flags); } - public async Task ReceiveAsync(ByteBlock byteBlock) + public async ValueTask ReceiveAsync(Memory memory, CancellationToken cancellationToken) { + this.ThrowIfDisposed(); + if (this.m_streamAsync) { + this.m_cancellationTokenSource.Token.ThrowIfCancellationRequested(); // 如果是异步流,则使用异步读取方式 - var token = this.m_cancellationTokenSource.Token; var stream = this.m_serialPort.BaseStream; - var memory = byteBlock.TotalMemory; - var r = await stream.ReadAsync(memory, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.m_receiveCounter.Increment(r); + var r = await stream.ReadAsync(memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return new SerialOperationResult(r, SerialData.Chars); } else { - return await this.ValueReceiveAsync(byteBlock).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return await this.ValueReceiveAsync(memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } - public virtual async Task SendAsync(ReadOnlyMemory memory) + public async Task SendAsync(ReadOnlyMemory memory, CancellationToken cancellationToken = default) { - var token = this.m_cancellationTokenSource.Token; - await this.m_semaphoreForSend.WaitAsync(token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - try + this.ThrowIfDisposed(); + this.m_cancellationTokenSource.Token.ThrowIfCancellationRequested(); + + if (this.m_streamAsync) { var stream = this.m_serialPort.BaseStream; - await stream.WriteAsync(memory, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.m_sendCounter.Increment(memory.Length); + await stream.WriteAsync(memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - finally + else { - this.m_semaphoreForSend.Release(); + var bytes = memory.GetArray(); + this.m_serialPort.Write(bytes.Array, bytes.Offset, bytes.Count); } } - protected override void Dispose(bool disposing) + protected override void SafetyDispose(bool disposing) { - if (this.DisposedValue) - { - return; - } if (disposing) { + this.m_disposed = true; + try { - // 取消未完成的任务源 - this.m_cancellationTokenSource.Cancel(); - this.m_cancellationTokenSource.Dispose(); + // 首先取消令牌,阻止新的操作 + this.m_cancellationTokenSource.SafeCancel(); + + // 在持有锁的情况下处理任务源,确保线程安全 + lock (this.m_lock) + { + if (this.m_hasWaitingReceiver) + { + this.m_core.SetException(new ObjectDisposedException(nameof(SerialCore))); + this.m_hasWaitingReceiver = false; + } + } + + // 移除事件处理器(在串口关闭之前) this.m_serialPort.DataReceived -= this.SerialCore_DataReceived; - this.m_serialPort.Close(); - this.m_serialPort.Dispose(); - this.m_core.SetException(new ObjectDisposedException(nameof(SerialCore))); + + // 关闭串口 + if (this.m_serialPort.IsOpen) + { + this.m_serialPort.Close(); + } + + // 释放资源 + this.m_serialPort.SafeDispose(); + this.m_cancellationTokenSource.SafeDispose(); } catch { + // 释放过程中的异常应该被忽略 } } - base.Dispose(disposing); - } - - private void OnReceivePeriod(long value) - { - this.m_receiveBufferSize = Math.Max(TouchSocketCoreUtility.HitBufferLength(value), this.MinBufferSize); - } - - private void OnSendPeriod(long value) - { - this.m_sendBufferSize = Math.Max(TouchSocketCoreUtility.HitBufferLength(value), this.MinBufferSize); } private void SerialCore_DataReceived(object sender, SerialDataReceivedEventArgs e) { - var token = this.m_cancellationTokenSource.Token; - if (token.IsCancellationRequested) + if (this.m_disposed || this.m_cancellationTokenSource.Token.IsCancellationRequested) { return; } try { - var bytesToRead = this.m_serialPort.BytesToRead; - while (bytesToRead > 0) + lock (this.m_lock) { - this.m_receiveLock.Wait(token); + if (this.m_disposed) // 双重检查 + { + return; + } - var eventType = e.EventType; + if (!this.m_hasWaitingReceiver) + { + // 如果没有等待接收,则放入环形缓冲区 + var spinWait = new SpinWait(); + Memory buffer; - var length = Math.Min(bytesToRead, this.m_byteBlock.Capacity); - var bytes = this.m_byteBlock.TotalMemory.GetArray(); - var r = this.m_serialPort.Read(bytes.Array, 0, length); - this.m_receiveCounter.Increment(r); - var serialOperationResult = new SerialOperationResult(r, eventType); - this.m_core.SetResult(serialOperationResult); - bytesToRead -= r; + do + { + if (this.m_disposed || this.m_cancellationTokenSource.Token.IsCancellationRequested) + { + return; + } + buffer = this.m_circularBuffer.GetWriteMemory(); + if (!buffer.IsEmpty) + { + break; + } + spinWait.SpinOnce(); + } + while (true); + + var r = this.m_serialPort.Read(buffer); + this.m_circularBuffer.AdvanceWrite(r); + } + else + { + // 有等待的接收者,直接读取到其缓冲区 + if (!this.m_memory.IsEmpty) + { + var buffer = this.m_memory.GetArray(); + var r = this.m_serialPort.Read(buffer); + var serialOperationResult = new SerialOperationResult(r, e.EventType); + + // 清除等待状态 + this.m_hasWaitingReceiver = false; + this.m_memory = Memory.Empty; + + // 设置结果 + this.m_core.SetResult(serialOperationResult); + } + } } } catch (Exception ex) { - // 设置任务源的异常 - this.m_core.SetException(ex); + lock (this.m_lock) + { + if (this.m_hasWaitingReceiver) + { + this.m_hasWaitingReceiver = false; + this.m_memory = Memory.Empty; + this.m_core.SetException(ex); + } + } } } - private void SetBuffer(ByteBlock byteBlock) + private ValueTask ValueReceiveAsync(Memory memory) { - this.m_byteBlock = byteBlock; - } - - private ValueTask ValueReceiveAsync(ByteBlock byteBlock) - { - this.m_core.Reset(); - this.SetBuffer(byteBlock); - - if (this.m_receiveLock.CurrentCount == 0) + lock (this.m_lock) { - this.m_receiveLock.Release(); - } + this.ThrowIfDisposed(); - return new ValueTask(this, this.m_core.Version); + // 首先检查缓冲区是否有数据 + if (!this.m_circularBuffer.IsEmpty) + { + var r = this.m_circularBuffer.Read(memory.Span); + return new ValueTask(new SerialOperationResult(r, SerialData.Chars)); + } + + // 确保没有其他等待中的接收者 + if (this.m_hasWaitingReceiver) + { + throw new InvalidOperationException("已有一个接收操作正在等待中"); + } + + // 重置任务源并设置等待状态 + this.m_core.Reset(); + this.m_memory = memory; + this.m_hasWaitingReceiver = true; + + return new ValueTask(this, this.m_core.Version); + } } } \ No newline at end of file diff --git a/src/TouchSocket.SerialPorts/Components/SerialPortClient.cs b/src/TouchSocket.SerialPorts/Components/SerialPortClient.cs index 6017d2b47..6c2317d65 100644 --- a/src/TouchSocket.SerialPorts/Components/SerialPortClient.cs +++ b/src/TouchSocket.SerialPorts/Components/SerialPortClient.cs @@ -10,14 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.IO.Ports; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; -using TouchSocket.Sockets; - namespace TouchSocket.SerialPorts; /// @@ -25,9 +17,6 @@ public class SerialPortClient : SerialPortClientBase, ISerialPortClient { #region 属性 - /// - public SerialPort MainSerialPort => this.ProtectedMainSerialPort; - #endregion 属性 #region 事件 @@ -182,28 +171,21 @@ public class SerialPortClient : SerialPortClientBase, ISerialPortClient #region 异步发送 /// - public virtual Task SendAsync(ReadOnlyMemory memory) + public virtual Task SendAsync(ReadOnlyMemory memory, CancellationToken cancellationToken = default) { - return this.ProtectedSendAsync(memory); + return this.ProtectedSendAsync(memory, cancellationToken); } /// - public virtual Task SendAsync(IRequestInfo requestInfo) + public virtual Task SendAsync(IRequestInfo requestInfo, CancellationToken cancellationToken = default) { - return this.ProtectedSendAsync(requestInfo); + return this.ProtectedSendAsync(requestInfo, cancellationToken); } - - /// - public virtual Task SendAsync(IList> transferBytes) - { - return this.ProtectedSendAsync(transferBytes); - } - #endregion 异步发送 /// - public virtual Task ConnectAsync(int millisecondsTimeout, CancellationToken token) + public virtual Task ConnectAsync(CancellationToken cancellationToken) { - return base.SerialPortConnectAsync(millisecondsTimeout, token); + return base.SerialPortConnectAsync(cancellationToken); } } \ No newline at end of file diff --git a/src/TouchSocket.SerialPorts/Components/SerialPortClientBase.cs b/src/TouchSocket.SerialPorts/Components/SerialPortClientBase.cs index 3001b15cf..05849c5d6 100644 --- a/src/TouchSocket.SerialPorts/Components/SerialPortClientBase.cs +++ b/src/TouchSocket.SerialPorts/Components/SerialPortClientBase.cs @@ -10,21 +10,17 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; using System.IO.Ports; using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; -using TouchSocket.Sockets; +using TouchSocket.Resources; namespace TouchSocket.SerialPorts; /// /// 串口客户端基类 /// -public abstract class SerialPortClientBase : SetupConfigObject, ISerialPortSession +[CodeInject.RegionInject(FileName = "TcpClientBase.cs", RegionName = "ReceiveLoopAsync", Placeholders = new[] { "OnTcpReceiving", "OnSerialReceiving" })] +public abstract partial class SerialPortClientBase : SetupConfigObject, ISerialPortSession { /// /// 串口客户端基类 @@ -36,13 +32,12 @@ public abstract class SerialPortClientBase : SetupConfigObject, ISerialPortSessi #region 变量 - private readonly Lock m_lockForAbort = new Lock(); private readonly SemaphoreSlim m_semaphoreForConnect = new SemaphoreSlim(1, 1); private SingleStreamDataHandlingAdapter m_dataHandlingAdapter; private bool m_online; private InternalReceiver m_receiver; - private SerialCore m_serialCore; - private Task m_taskReceive; + private Task m_runTask; + private SerialPortTransport m_transport; #endregion 变量 @@ -102,9 +97,9 @@ public abstract class SerialPortClientBase : SetupConfigObject, ISerialPortSessi /// 如果返回则表示数据已被处理,且不会再向下传递。 /// 返回则表示数据未被处理,可能会继续向下传递。 /// - protected virtual ValueTask OnSerialReceiving(ByteBlock byteBlock) + protected virtual ValueTask OnSerialReceiving(IBytesReader byteBlock) { - return this.PluginManager.RaiseAsync(typeof(ISerialReceivingPlugin), this.Resolver, this, new ByteBlockEventArgs(byteBlock)); + return this.PluginManager.RaiseAsync(typeof(ISerialReceivingPlugin), this.Resolver, this, new BytesReaderEventArgs(byteBlock)); } /// @@ -121,37 +116,28 @@ public abstract class SerialPortClientBase : SetupConfigObject, ISerialPortSessi return this.PluginManager.RaiseAsync(typeof(ISerialSendingPlugin), this.Resolver, this, new SendingEventArgs(memory)); } - private async Task PrivateOnClosing(ClosingEventArgs e) + private async Task PrivateConnected(SerialPortTransport transport) { - await this.OnSerialClosing(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var receiveTask = EasyTask.SafeRun(this.ReceiveLoopAsync, transport); + + var e_connected = new ConnectedEventArgs(); + await this.OnSerialConnected(e_connected).SafeWaitAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await receiveTask.SafeWaitAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + transport.SafeDispose(); + + var e_closed = transport.ClosedEventArgs; + this.m_online = false; + var adapter = this.m_dataHandlingAdapter; + this.m_dataHandlingAdapter = default; + adapter.SafeDispose(); + + await this.OnSerialClosed(e_closed).SafeWaitAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - private async Task PrivateOnSerialClosed(object obj) + private Task PrivateOnClosing(ClosingEventArgs e) { - try - { - var e = (ClosedEventArgs)obj; - var receiver = this.m_receiver; - if (receiver != null) - { - await receiver.Complete(e.Message).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - await this.OnSerialClosed(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - catch - { - } - } - - private async Task PrivateOnSerialConnected(object o) - { - try - { - await this.OnSerialConnected((ConnectedEventArgs)o).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - catch - { - } + return this.OnSerialClosing(e); } private async Task PrivateOnSerialConnecting(ConnectingEventArgs e) @@ -171,17 +157,20 @@ public abstract class SerialPortClientBase : SetupConfigObject, ISerialPortSessi #region 属性 + /// + public CancellationToken ClosedToken => this.m_transport == null ? new CancellationToken(true) : this.m_transport.ClosedToken; + /// public bool IsClient => true; /// - public DateTimeOffset LastReceivedTime => this.m_serialCore.ReceiveCounter.LastIncrement; + public DateTimeOffset LastReceivedTime => this.m_transport?.ReceiveCounter.LastIncrement ?? default; /// - public DateTimeOffset LastSentTime => this.m_serialCore.SendCounter.LastIncrement; + public DateTimeOffset LastSentTime => this.m_transport?.SendCounter.LastIncrement ?? default; /// - public bool Online => this.m_online && this.m_serialCore != null && this.m_serialCore.SerialPort.IsOpen; + public bool Online => this.m_online && this.m_transport != null && this.m_transport.IsOpen; /// public Protocol Protocol { get; protected set; } @@ -189,24 +178,27 @@ public abstract class SerialPortClientBase : SetupConfigObject, ISerialPortSessi /// protected SingleStreamDataHandlingAdapter ProtectedDataHandlingAdapter => this.m_dataHandlingAdapter; - /// - protected SerialPort ProtectedMainSerialPort => this.m_serialCore?.SerialPort; - #endregion 属性 #region 断开操作 /// - public virtual async Task CloseAsync(string msg, CancellationToken token = default) + public virtual async Task CloseAsync(string msg, CancellationToken cancellationToken = default) { try { - if (this.m_online) + if (!this.m_online) { - await this.PrivateOnClosing(new ClosingEventArgs(msg)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.Abort(true, msg); + return Result.Success; } + await this.PrivateOnClosing(new ClosingEventArgs(msg)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var transport = this.m_transport; + if (transport != null) + { + await transport.CloseAsync(msg, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + await this.WaitClearConnect().ConfigureAwait(EasyTask.ContinueOnCapturedContext); return Result.Success; } catch (Exception ex) @@ -216,13 +208,13 @@ public abstract class SerialPortClientBase : SetupConfigObject, ISerialPortSessi } /// - protected override void Dispose(bool disposing) + protected override void SafetyDispose(bool disposing) { if (disposing) { - this.Abort(true, $"{nameof(Dispose)}主动断开"); + _ = EasyTask.SafeRun(async () => await this.CloseAsync(TouchSocketResource.DisposeClose).ConfigureAwait(EasyTask.ContinueOnCapturedContext)); } - base.Dispose(disposing); + base.SafetyDispose(disposing); } #endregion 断开操作 @@ -232,14 +224,13 @@ public abstract class SerialPortClientBase : SetupConfigObject, ISerialPortSessi /// /// 异步连接到串口设备。 /// - /// 连接操作的超时时间(以毫秒为单位)。 - /// 用于取消操作的 。 + /// 用于取消操作的 。 /// 表示异步操作的任务。 - protected async Task SerialPortConnectAsync(int millisecondsTimeout, CancellationToken token) + protected async Task SerialPortConnectAsync(CancellationToken cancellationToken) { this.ThrowIfDisposed(); this.ThrowIfConfigIsNull(); - await this.m_semaphoreForConnect.WaitTimeAsync(millisecondsTimeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.m_semaphoreForConnect.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); try { @@ -247,23 +238,19 @@ public abstract class SerialPortClientBase : SetupConfigObject, ISerialPortSessi { return; } - var serialPortOption = this.Config.GetValue(SerialPortConfigExtension.SerialPortOptionProperty) ?? throw new ArgumentNullException("串口配置不能为空。"); - this.m_serialCore.SafeDispose(); + await this.WaitClearConnect().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - var serialCore = CreateSerial(serialPortOption); + var serialPortOption = this.Config.GetValue(SerialPortConfigExtension.SerialPortOptionProperty); + ThrowHelper.ThrowIfNull(serialPortOption, nameof(serialPortOption)); + + var serialPort = CreateSerial(serialPortOption); await this.PrivateOnSerialConnecting(new ConnectingEventArgs()).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - serialCore.SerialPort.Open(); - + this.m_transport = new SerialPortTransport(serialPort, this.Config.GetValue(TouchSocketConfigExtension.TransportOptionProperty)); this.m_online = true; - - this.m_serialCore = serialCore; - - this.m_taskReceive = EasyTask.Run(this.BeginReceive); - this.m_taskReceive.FireAndForget(); - - _ = EasyTask.Run(this.PrivateOnSerialConnected, new ConnectedEventArgs()); + // 启动新任务,处理连接后的操作 + this.m_runTask = EasyTask.SafeRun(this.PrivateConnected, this.m_transport); } finally { @@ -273,34 +260,6 @@ public abstract class SerialPortClientBase : SetupConfigObject, ISerialPortSessi #endregion Connect - /// - /// 中止连接方法 - /// - /// 是否为手动断开连接 - /// 断开连接的原因 - protected void Abort(bool manual, string msg) - { - // 锁定连接操作,确保线程安全 - lock (this.m_lockForAbort) - { - // 检查是否当前处于在线状态 - if (this.m_online) - { - // 将状态设置为离线 - this.m_online = false; - // 安全释放串口核心资源 - this.m_serialCore.SafeDispose(); - - // 安全释放数据处理适配器资源 - this.m_dataHandlingAdapter.SafeDispose(); - this.m_dataHandlingAdapter = default; - - // 启动一个新的任务,用于处理串口关闭事件 - _ = EasyTask.Run(this.PrivateOnSerialClosed, new ClosedEventArgs(manual, msg)); - } - } - } - /// /// 设置数据处理适配器。 /// @@ -308,91 +267,61 @@ public abstract class SerialPortClientBase : SetupConfigObject, ISerialPortSessi /// 如果提供的适配器实例为,则抛出此异常。 protected void SetAdapter(SingleStreamDataHandlingAdapter adapter) { - // 检查当前实例是否已被释放,如果是,则抛出异常。 + this.ThrowIfDisposed(); - // 检查adapter参数是否为,如果是,则抛出ArgumentNullException异常。 + if (adapter is null) { - throw new ArgumentNullException(nameof(adapter)); + this.m_dataHandlingAdapter = null;//允许Null赋值 + return; } - // 如果当前实例的配置不为空,则将配置应用到适配器上。 if (this.Config != null) { adapter.Config(this.Config); } - // 设置适配器的日志记录器和加载、接收数据的回调方法。 - adapter.Logger = this.Logger; adapter.OnLoaded(this); adapter.ReceivedAsyncCallBack = this.PrivateHandleReceivedData; - //adapter.SendCallBack = this.ProtectedDefaultSend; - adapter.SendAsyncCallBack = this.ProtectedDefaultSendAsync; - - // 将提供的适配器实例设置为当前实例的数据处理适配器。 this.m_dataHandlingAdapter = adapter; } private static SerialCore CreateSerial(SerialPortOption option) { - var serialPort = new SerialCore(option.PortName, option.BaudRate, option.Parity, option.DataBits, option.StopBits, option.StreamAsync); - serialPort.SerialPort.Handshake = option.Handshake; - serialPort.SerialPort.RtsEnable = option.RtsEnable; - serialPort.SerialPort.DtrEnable = option.DtrEnable; - return serialPort; + var serialPort = new SerialPort(option.PortName, option.BaudRate, option.Parity, option.DataBits, option.StopBits) + { + Handshake = option.Handshake, + RtsEnable = option.RtsEnable, + DtrEnable = option.DtrEnable + }; + + serialPort.Open(); + + if (!serialPort.IsOpen) + { + ThrowHelper.ThrowException(TouchSocketCoreResource.UnknownError); + } + return new SerialCore(serialPort, option.StreamAsync); } - private async Task BeginReceive() + private async Task PrivateHandleReceivedData(ReadOnlyMemory memory, IRequestInfo requestInfo) { - while (true) + var receiver = this.m_receiver; + if (receiver != null) { - try - { - using (var byteBlock = new ByteBlock(1024 * 64)) - { - var result = await this.m_serialCore.ReceiveAsync(byteBlock).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - - if (result.BytesTransferred > 0) - { - byteBlock.SetLength(result.BytesTransferred); - await this.HandleReceivingData(byteBlock).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - } - } - catch (Exception ex) - { - this.Abort(false, ex.Message); - break; - } + await receiver.InputReceiveAsync(memory, requestInfo, CancellationToken.None).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return; } + await this.OnSerialReceived(new ReceivedDataEventArgs(memory, requestInfo)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - private async Task HandleReceivingData(ByteBlock byteBlock) + private async Task WaitClearConnect() { - try + // 确保上次接收任务已经结束 + var runTask = this.m_runTask; + if (runTask != null) { - if (this.DisposedValue) - { - return; - } - - if (await this.OnSerialReceiving(byteBlock).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) - { - return; - } - - if (this.m_dataHandlingAdapter == null) - { - await this.PrivateHandleReceivedData(byteBlock, default).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - else - { - await this.m_dataHandlingAdapter.ReceivedInputAsync(byteBlock).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - } - catch (Exception ex) - { - this.Logger?.Log(LogLevel.Error, this, "在处理数据时发生错误", ex); + await runTask.ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } @@ -425,17 +354,6 @@ public abstract class SerialPortClientBase : SetupConfigObject, ISerialPortSessi #endregion Receiver - private async Task PrivateHandleReceivedData(ByteBlock byteBlock, IRequestInfo requestInfo) - { - var receiver = this.m_receiver; - if (receiver != null) - { - await receiver.InputReceiveAsync(byteBlock, requestInfo).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - return; - } - await this.OnSerialReceived(new ReceivedDataEventArgs(byteBlock, requestInfo)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - #region Throw [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -462,43 +380,43 @@ public abstract class SerialPortClientBase : SetupConfigObject, ISerialPortSessi #region 发送 - /// - /// 异步发送数据,保护方法。 - /// - /// 待发送的字节数据内存。 - /// 异步任务。 - protected async Task ProtectedDefaultSendAsync(ReadOnlyMemory memory) - { - // 检查实例是否已被处置,确保资源正确管理。 - this.ThrowIfDisposed(); - // 检查客户端是否已连接,防止在未连接状态下尝试发送数据。 - this.ThrowIfClientNotConnected(); - // 触发序列发送事件之前置处理,为实际数据发送做准备。 - await this.OnSerialSending(memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - // 通过序列核心发送数据,实际的数据发送操作。 - await this.m_serialCore.SendAsync(memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - - #endregion 发送 - - #region 异步发送 /// /// 异步发送数据,通过适配器模式灵活处理数据发送。 /// /// 待发送的只读字节内存块。 + /// 可取消令箭 /// 一个异步任务,表示发送操作。 - protected Task ProtectedSendAsync(in ReadOnlyMemory memory) + protected async Task ProtectedSendAsync(ReadOnlyMemory memory, CancellationToken cancellationToken) { - // 如果数据处理适配器未设置,则使用默认发送方式。 - if (this.m_dataHandlingAdapter == null) + this.ThrowIfDisposed(); + this.ThrowIfClientNotConnected(); + + + await this.OnSerialSending(memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + var transport = this.m_transport; + var adapter = this.m_dataHandlingAdapter; + var locker = transport.WriteLocker; + + await locker.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + try { - return this.ProtectedDefaultSendAsync(memory); + // 如果数据处理适配器未设置,则使用默认发送方式。 + if (adapter == null) + { + await transport.Writer.WriteAsync(memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + else + { + var writer = new PipeBytesWriter(transport.Writer); + adapter.SendInput(ref writer, in memory); + await writer.FlushAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } } - else + finally { - // 否则,使用适配器的发送方法进行数据发送。 - return this.m_dataHandlingAdapter.SendInputAsync(memory); + locker.Release(); } } @@ -509,61 +427,31 @@ public abstract class SerialPortClientBase : SetupConfigObject, ISerialPortSessi /// 如果可以发送,它将使用数据处理适配器来异步发送输入请求。 /// /// 要发送的请求信息。 + /// 可取消令箭 /// 返回一个任务,该任务代表异步操作的结果。 - protected Task ProtectedSendAsync(in IRequestInfo requestInfo) + protected async Task ProtectedSendAsync(IRequestInfo requestInfo, CancellationToken cancellationToken) { // 检查是否具备发送请求的条件,如果不具备则抛出异常 this.ThrowIfCannotSendRequestInfo(); - // 使用数据处理适配器异步发送输入请求 - return this.m_dataHandlingAdapter.SendInputAsync(requestInfo); - } + this.ThrowIfDisposed(); + this.ThrowIfClientNotConnected(); - /// - /// 异步发送数据。 - /// 如果数据处理适配器不存在或无法拼接发送,则将所有传输字节合并到一个连续的内存块中发送。 - /// 如果数据处理适配器存在且支持拼接发送,则直接发送传输字节列表。 - /// - /// 要发送的字节数据列表,每个项代表一个字节片段。 - /// 发送任务。 - protected async Task ProtectedSendAsync(IList> transferBytes) - { - // 检查数据处理适配器是否存在且支持拼接发送 - if (this.m_dataHandlingAdapter == null || !this.m_dataHandlingAdapter.CanSplicingSend) + var transport = this.m_transport; + var adapter = this.m_dataHandlingAdapter; + var locker = transport.WriteLocker; + + await locker.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + try { - // 如果不支持拼接发送,则计算所有字节片段的总长度 - 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(item.Array, item.Offset, item.Count)); - } - // 根据数据处理适配器的存在与否,选择不同的发送方式 - if (this.m_dataHandlingAdapter == null) - { - // 如果没有数据处理适配器,则使用默认方式发送 - await this.ProtectedDefaultSendAsync(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - else - { - // 如果有数据处理适配器,则通过适配器发送 - await this.m_dataHandlingAdapter.SendInputAsync(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - } + var writer = new PipeBytesWriter(transport.Writer); + adapter.SendInput(ref writer, requestInfo); + await writer.FlushAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - else + finally { - // 如果数据处理适配器支持拼接发送,则直接发送字节列表 - await this.m_dataHandlingAdapter.SendInputAsync(transferBytes).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + locker.Release(); } } - - #endregion 异步发送 + #endregion 发送 } \ No newline at end of file diff --git a/src/TouchSocket.SerialPorts/Extension/SerialPortConfigExtension.cs b/src/TouchSocket.SerialPorts/Extension/SerialPortConfigExtension.cs index 5fe2e531a..80f9d8ee3 100644 --- a/src/TouchSocket.SerialPorts/Extension/SerialPortConfigExtension.cs +++ b/src/TouchSocket.SerialPorts/Extension/SerialPortConfigExtension.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using TouchSocket.Core; - namespace TouchSocket.SerialPorts; /// @@ -23,36 +20,14 @@ public static class SerialPortConfigExtension /// /// 设置串口适配器 /// + [GeneratorProperty(TargetType = typeof(TouchSocketConfig))] public static readonly DependencyProperty> SerialDataHandlingAdapterProperty = new("SerialDataHandlingAdapter", null); /// /// 串口属性。 /// + [GeneratorProperty(TargetType = typeof(TouchSocketConfig), ActionMode = true)] public static readonly DependencyProperty SerialPortOptionProperty = - new("SerialPortOption", new SerialPortOption()); - - /// - /// 设置(串口系)数据处理适配器。 - /// - /// - /// - /// - public static TouchSocketConfig SetSerialDataHandlingAdapter(this TouchSocketConfig config, Func value) - { - config.SetValue(SerialDataHandlingAdapterProperty, value); - return config; - } - - /// - /// 设置串口属性。 - /// - /// - /// - /// - public static TouchSocketConfig SetSerialPortOption(this TouchSocketConfig config, SerialPortOption value) - { - config.SetValue(SerialPortOptionProperty, value); - return config; - } + new("SerialPortOption", default); } \ No newline at end of file diff --git a/src/TouchSocket.SerialPorts/Extension/SerialPortExtensions.cs b/src/TouchSocket.SerialPorts/Extension/SerialPortExtensions.cs index a4bbe525d..c78a9a855 100644 --- a/src/TouchSocket.SerialPorts/Extension/SerialPortExtensions.cs +++ b/src/TouchSocket.SerialPorts/Extension/SerialPortExtensions.cs @@ -23,7 +23,7 @@ public static class SerialPortExtensions /// 尝试关闭。不会抛出异常。 /// /// - public static void TryClose(this SerialPort serialPort) + public static Result TryClose(this SerialPort serialPort) { try { @@ -31,9 +31,18 @@ public static class SerialPortExtensions { serialPort.Close(); } + + return Result.Success; } - catch + catch (Exception ex) { + return new Result(ex); } } + + public static int Read(this SerialPort serialPort, Memory memory) + { + var bytes = memory.GetArray(); + return serialPort.Read(bytes.Array, bytes.Offset, bytes.Count); + } } \ No newline at end of file diff --git a/src/TouchSocket.SerialPorts/Extension/SerialPortPluginManagerExtension.cs b/src/TouchSocket.SerialPorts/Extension/SerialPortPluginManagerExtension.cs index 7d424fbe8..afddabb79 100644 --- a/src/TouchSocket.SerialPorts/Extension/SerialPortPluginManagerExtension.cs +++ b/src/TouchSocket.SerialPorts/Extension/SerialPortPluginManagerExtension.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 // ------------------------------------------------------------------------------ -using TouchSocket.Core; -using TouchSocket.Sockets; - namespace TouchSocket.SerialPorts; /// /// 提供扩展方法以支持串口插件管理功能。 @@ -26,8 +23,8 @@ public static class SerialPortPluginManagerExtension /// /// 插件管理器对象,用于管理插件。 /// 返回一个类型的插件实例,用于执行客户端活性检查及清理操作。 - public static CheckClearPlugin UseSerialPortSessionCheckClear(this IPluginManager pluginManager) + public static CheckClearPlugin UseSerialPortSessionCheckClear(this IPluginManager pluginManager, Action> options = null) { - return pluginManager.UseCheckClear(); + return pluginManager.UseCheckClear(options); } } diff --git a/src/TouchSocket.SerialPorts/Extension/WaitingClientExtension.cs b/src/TouchSocket.SerialPorts/Extension/WaitingClientExtension.cs index 17f0440c7..b67b89a43 100644 --- a/src/TouchSocket.SerialPorts/Extension/WaitingClientExtension.cs +++ b/src/TouchSocket.SerialPorts/Extension/WaitingClientExtension.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Sockets; - namespace TouchSocket.SerialPorts; /// diff --git a/src/TouchSocket.SerialPorts/Interface/ISerialPortClient.cs b/src/TouchSocket.SerialPorts/Interface/ISerialPortClient.cs index 490ec5ba5..7edeb1376 100644 --- a/src/TouchSocket.SerialPorts/Interface/ISerialPortClient.cs +++ b/src/TouchSocket.SerialPorts/Interface/ISerialPortClient.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.IO.Ports; -using TouchSocket.Sockets; - namespace TouchSocket.SerialPorts; /// @@ -42,11 +39,6 @@ public interface ISerialPortClient : ISerialPortSession, IConnectableClient, ICl /// ClosingEventHandler Closing { get; set; } - /// - /// 主通信器 - /// - SerialPort MainSerialPort { get; } - /// /// 接收到数据 /// diff --git a/src/TouchSocket.SerialPorts/Interface/ISerialPortSession.cs b/src/TouchSocket.SerialPorts/Interface/ISerialPortSession.cs index c3f7b9be8..8c2f8ddb4 100644 --- a/src/TouchSocket.SerialPorts/Interface/ISerialPortSession.cs +++ b/src/TouchSocket.SerialPorts/Interface/ISerialPortSession.cs @@ -10,14 +10,11 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; -using TouchSocket.Sockets; - namespace TouchSocket.SerialPorts; /// /// 定义了一个串行端口会话接口,继承自多个与客户端、插件、配置、在线状态、连接状态和关闭操作相关的接口。 /// -public interface ISerialPortSession : ISetupConfigObject,IDependencyClient, IOnlineClient, IClosableClient +public interface ISerialPortSession : ISetupConfigObject, IDependencyClient, IOnlineClient, IClosableClient { } \ No newline at end of file diff --git a/src/TouchSocket.SerialPorts/Plugins/ISerialClosedPlugin.cs b/src/TouchSocket.SerialPorts/Plugins/ISerialClosedPlugin.cs index b06c0118a..97ceba989 100644 --- a/src/TouchSocket.SerialPorts/Plugins/ISerialClosedPlugin.cs +++ b/src/TouchSocket.SerialPorts/Plugins/ISerialClosedPlugin.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; -using TouchSocket.Sockets; - namespace TouchSocket.SerialPorts; diff --git a/src/TouchSocket.SerialPorts/Plugins/ISerialClosingPlugin.cs b/src/TouchSocket.SerialPorts/Plugins/ISerialClosingPlugin.cs index 8c9d69b2c..2e3084495 100644 --- a/src/TouchSocket.SerialPorts/Plugins/ISerialClosingPlugin.cs +++ b/src/TouchSocket.SerialPorts/Plugins/ISerialClosingPlugin.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; -using TouchSocket.Sockets; - namespace TouchSocket.SerialPorts; diff --git a/src/TouchSocket.SerialPorts/Plugins/ISerialConnectedPlugin.cs b/src/TouchSocket.SerialPorts/Plugins/ISerialConnectedPlugin.cs index 8df762d3a..4e352cde4 100644 --- a/src/TouchSocket.SerialPorts/Plugins/ISerialConnectedPlugin.cs +++ b/src/TouchSocket.SerialPorts/Plugins/ISerialConnectedPlugin.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; -using TouchSocket.Sockets; - namespace TouchSocket.SerialPorts; /// diff --git a/src/TouchSocket.SerialPorts/Plugins/ISerialConnectingPlugin.cs b/src/TouchSocket.SerialPorts/Plugins/ISerialConnectingPlugin.cs index 3913f7262..908c218fa 100644 --- a/src/TouchSocket.SerialPorts/Plugins/ISerialConnectingPlugin.cs +++ b/src/TouchSocket.SerialPorts/Plugins/ISerialConnectingPlugin.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; -using TouchSocket.Sockets; - namespace TouchSocket.SerialPorts; /// diff --git a/src/TouchSocket.SerialPorts/Plugins/ISerialReceivedPlugin.cs b/src/TouchSocket.SerialPorts/Plugins/ISerialReceivedPlugin.cs index 276b50958..a05154181 100644 --- a/src/TouchSocket.SerialPorts/Plugins/ISerialReceivedPlugin.cs +++ b/src/TouchSocket.SerialPorts/Plugins/ISerialReceivedPlugin.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; -using TouchSocket.Sockets; - namespace TouchSocket.SerialPorts; diff --git a/src/TouchSocket.SerialPorts/Plugins/ISerialReceivingPlugin.cs b/src/TouchSocket.SerialPorts/Plugins/ISerialReceivingPlugin.cs index 5a9598e0d..e40d7fa40 100644 --- a/src/TouchSocket.SerialPorts/Plugins/ISerialReceivingPlugin.cs +++ b/src/TouchSocket.SerialPorts/Plugins/ISerialReceivingPlugin.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; -using TouchSocket.Sockets; - namespace TouchSocket.SerialPorts; @@ -30,5 +26,5 @@ public interface ISerialReceivingPlugin : IPlugin /// 触发事件的串行端口会话对象。 /// 包含接收数据的事件参数对象。 /// 一个Task对象,代表异步操作的结果。 - Task OnSerialReceiving(ISerialPortSession client, ByteBlockEventArgs e); + Task OnSerialReceiving(ISerialPortSession client, BytesReaderEventArgs e); } \ No newline at end of file diff --git a/src/TouchSocket.SerialPorts/Plugins/ISerialSendingPlugin.cs b/src/TouchSocket.SerialPorts/Plugins/ISerialSendingPlugin.cs index da0dd2b4a..87714a06f 100644 --- a/src/TouchSocket.SerialPorts/Plugins/ISerialSendingPlugin.cs +++ b/src/TouchSocket.SerialPorts/Plugins/ISerialSendingPlugin.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; -using TouchSocket.Sockets; - namespace TouchSocket.SerialPorts; diff --git a/src/TouchSocket.SerialPorts/Readme.md b/src/TouchSocket.SerialPorts/Readme.md index 521ef4276..7e2bdfbfa 100644 --- a/src/TouchSocket.SerialPorts/Readme.md +++ b/src/TouchSocket.SerialPorts/Readme.md @@ -12,14 +12,14 @@ TouchSocket.SerialPorts 是一个适用于 .Net(涵盖 C#、VB.Net、F#)的 详细的说明文档请访问:[https://touchsocket.net/](https://touchsocket.net/) ## 支持的目标框架 + - net481 -- net45 - net462 - net472 - netstandard2.0 - netstandard2.1 - net6.0 -- net9.0 +- net10.0 - net8.0 ## 贡献与反馈 diff --git a/src/TouchSocket.SerialPorts/TouchSocket.SerialPorts.csproj b/src/TouchSocket.SerialPorts/TouchSocket.SerialPorts.csproj index 793649e5c..d2c6cafc1 100644 --- a/src/TouchSocket.SerialPorts/TouchSocket.SerialPorts.csproj +++ b/src/TouchSocket.SerialPorts/TouchSocket.SerialPorts.csproj @@ -1,6 +1,6 @@ - net481;net45;net462;net472;netstandard2.0;netstandard2.1;net6.0;net9.0;net8.0 + net462;netstandard2.0;netstandard2.1;net6.0;net10.0;net8.0 Serial;SerialPort;TouchSocket 这是.Net(包括 C# 、VB.Net、F#)的一个整合性的串口通信框架。包含了串口连接、断开等一系列的通信事务。同时能一键式解决数据黏分包问题。使用协议模板,可快速实现「固定包头」、「固定长度」、「区间字符」等一系列的数据报文解析。 @@ -9,11 +9,21 @@ 若汝棋茗;Diego - + + + + + + + + + + + @@ -24,4 +34,13 @@ + + + + + + + + + diff --git a/src/TouchSocket.SerialPorts/Transports/SerialPortTransport.cs b/src/TouchSocket.SerialPorts/Transports/SerialPortTransport.cs new file mode 100644 index 000000000..efd1d41ef --- /dev/null +++ b/src/TouchSocket.SerialPorts/Transports/SerialPortTransport.cs @@ -0,0 +1,125 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.IO.Ports; +using System.Net.Sockets; +using TouchSocket.Resources; + +namespace TouchSocket.SerialPorts; + +internal sealed class SerialPortTransport : BaseTransport +{ + private readonly SerialCore m_serialCore; + + public SerialPortTransport(SerialCore serialCore, TransportOption option) : base(option) + { + this.m_serialCore = serialCore; + + this.Start(); + } + + public bool IsOpen => this.m_serialCore.SerialPort.IsOpen; + + protected override void SafetyDispose(bool disposing) + { + this.m_serialCore.Dispose(); + base.SafetyDispose(disposing); + } + + protected override async Task RunReceive(CancellationToken cancellationToken) + { + try + { + while (!cancellationToken.IsCancellationRequested) + { + var memory = this.m_pipeReceive.Writer.GetMemory(this.ReceiveBufferSize); + var result = await this.m_serialCore.ReceiveAsync(memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + if (result.BytesTransferred == 0) + { + this.m_closedEventArgs ??= new ClosedEventArgs(false, TouchSocketResource.RemoteDisconnects); + await this.m_pipeReceive.Writer.CompleteAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return; + } + + this.m_receiveCounter.Increment(result.BytesTransferred); + + this.m_pipeReceive.Writer.Advance(result.BytesTransferred); + var flushResult = await this.m_pipeReceive.Writer.FlushAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + if (flushResult.IsCompleted) + { + break; + } + } + } + catch (Exception ex) + { + this.m_closedEventArgs ??= new ClosedEventArgs(false, ex.Message); + await this.m_pipeReceive.Writer.CompleteAsync(ex).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + finally + { + // 取消内置接收和发送任务。 + base.m_tokenSource.SafeCancel(); + } + } + + protected override async Task RunSend(CancellationToken cancellationToken) + { + try + { + while (!cancellationToken.IsCancellationRequested) + { + var readResult = await this.m_pipeSend.Reader.ReadAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + if (readResult.IsCanceled) + { + break; + } + + if (readResult.IsCompleted && readResult.Buffer.IsEmpty) + { + break; + } + + try + { + foreach (var item in readResult.Buffer) + { + await this.m_serialCore.SendAsync(item, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + + this.m_pipeSend.Reader.AdvanceTo(readResult.Buffer.End); + + this.m_sentCounter.Increment(readResult.Buffer.Length); + } + catch (Exception ex) + { + // 处理发送失败的情况 + this.m_pipeSend.Reader.Complete(ex); + break; + } + + if (readResult.IsCompleted) + { + break; + } + } + } + catch (Exception ex) when (ex is not OperationCanceledException) + { + this.m_pipeSend.Reader.Complete(ex); + } + finally + { + this.m_pipeSend.Reader.Complete(); + } + } +} diff --git a/src/TouchSocket.Shared/Core/ThrowHelperCore.cs b/src/TouchSocket.Shared/Core/ThrowHelperCore.cs index 120933117..de32f6f3c 100644 --- a/src/TouchSocket.Shared/Core/ThrowHelperCore.cs +++ b/src/TouchSocket.Shared/Core/ThrowHelperCore.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.ComponentModel; using System.Runtime.CompilerServices; using TouchSocket.Resources; @@ -19,70 +18,6 @@ namespace TouchSocket.Core; internal static partial class ThrowHelper { - [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowMessageRegisteredException(string tokenString) - { - throw new MessageRegisteredException(TouchSocketCoreResource.TokenExisted.Format(tokenString)); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowMessageNotFoundException(string tokenString) - { - throw new MessageNotFoundException(TouchSocketCoreResource.MessageNotFound.Format(tokenString)); - } - - #region NotSupportedException - - [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowNotSupportedException(string message) - { - throw CreateNotSupportedException(message); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - public static NotSupportedException CreateNotSupportedException(string message) - { - return new NotSupportedException(message); - } - - #endregion NotSupportedException - - [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowArgumentOutOfRangeException_BetweenAnd(string name, long actualValue, long min, long max) - { - throw new ArgumentOutOfRangeException(name, TouchSocketCoreResource.ValueBetweenAnd.Format(name, actualValue, min, max)); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowArgumentOutOfRangeException_LessThan(string name, long actualValue, long min) - { - throw new ArgumentOutOfRangeException(name, TouchSocketCoreResource.ValueLessThan.Format(name, actualValue, min)); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowArgumentOutOfRangeException_MoreThan(string name, long actualValue, long max) - { - throw new ArgumentOutOfRangeException(name, TouchSocketCoreResource.ValueMoreThan.Format(name, actualValue, max)); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowInvalidOperationException(string message) - { - throw new InvalidOperationException(message); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowTimeoutException() - { - throw new TimeoutException(TouchSocketCoreResource.OperationOvertime); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowOperationCanceledException() - { - throw new OperationCanceledException(TouchSocketCoreResource.OperationCanceled); - } - [MethodImpl(MethodImplOptions.NoInlining)] public static void ThrowArgumentNullException(string name) { @@ -105,15 +40,21 @@ internal static partial class ThrowHelper } [MethodImpl(MethodImplOptions.NoInlining)] - public static T ThrowArgumentNullExceptionIf(T obj, string objectName) where T : class + public static void ThrowArgumentOutOfRangeException_BetweenAnd(string name, long actualValue, long min, long max) { - return obj ?? throw new ArgumentNullException(TouchSocketCoreResource.ArgumentIsNull.Format(objectName)); + throw new ArgumentOutOfRangeException(name, TouchSocketCoreResource.ValueBetweenAnd.Format(name, actualValue, min, max)); } [MethodImpl(MethodImplOptions.NoInlining)] - public static T ThrowArgumentNullExceptionIf(T obj, string objectName, string msg) where T : class + public static void ThrowArgumentOutOfRangeException_LessThan(string name, long actualValue, long min) { - return obj ?? throw new ArgumentNullException(msg.Format(objectName)); + throw new ArgumentOutOfRangeException(name, TouchSocketCoreResource.ValueLessThan.Format(name, actualValue, min)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowArgumentOutOfRangeException_MoreThan(string name, long actualValue, long max) + { + throw new ArgumentOutOfRangeException(name, TouchSocketCoreResource.ValueMoreThan.Format(name, actualValue, max)); } [MethodImpl(MethodImplOptions.NoInlining)] @@ -123,26 +64,56 @@ internal static partial class ThrowHelper } [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowUnknownErrorException() + public static void ThrowIfNull(T obj, string objectName) where T : class { - throw new UnknownErrorException(); - } - - #region InvalidEnumArgumentException - - [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowInvalidEnumArgumentException(Enum @enum) - { - throw CreateInvalidEnumArgumentException(@enum); + if (obj is null) + { + throw new ArgumentNullException(TouchSocketCoreResource.ArgumentIsNull.Format(objectName)); + } } [MethodImpl(MethodImplOptions.NoInlining)] - public static InvalidEnumArgumentException CreateInvalidEnumArgumentException(Enum @enum) + public static void ThrowIfNull(T obj, string objectName, string msg) where T : class { - return new InvalidEnumArgumentException(TouchSocketCoreResource.InvalidEnum.Format(@enum.GetType(), @enum)); + if (obj is null) + { + throw new ArgumentNullException(msg.Format(objectName)); + } } - #endregion InvalidEnumArgumentException + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowInvalidOperationException(string message) + { + throw new InvalidOperationException(message); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowMessageNotFoundException(string tokenString) + { + throw new MessageNotFoundException(TouchSocketCoreResource.MessageNotFound.Format(tokenString)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowMessageRegisteredException(string tokenString) + { + throw new MessageRegisteredException(TouchSocketCoreResource.TokenExisted.Format(tokenString)); + } + + #region NotSupportedException + + [MethodImpl(MethodImplOptions.NoInlining)] + public static NotSupportedException CreateNotSupportedException(string message) + { + return new NotSupportedException(message); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowNotSupportedException(string message) + { + throw CreateNotSupportedException(message); + } + + #endregion NotSupportedException [MethodImpl(MethodImplOptions.NoInlining)] public static void ThrowObjectDisposedException(object obj) @@ -159,7 +130,52 @@ internal static partial class ThrowHelper } } + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowOperationCanceledException() + { + throw new OperationCanceledException(TouchSocketCoreResource.OperationCanceled); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowTimeoutException() + { + throw new TimeoutException(TouchSocketCoreResource.OperationOvertime); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowUnknownErrorException() + { + throw new UnknownErrorException(); + } + + #region InvalidEnumArgumentException + + [MethodImpl(MethodImplOptions.NoInlining)] + public static InvalidEnumArgumentException CreateInvalidEnumArgumentException(TEnum @enum) + where TEnum : Enum + { + return new InvalidEnumArgumentException(TouchSocketCoreResource.InvalidEnum.Format(@enum.GetType(), @enum)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowInvalidEnumArgumentException(TEnum @enum) + where TEnum : Enum + { + throw CreateInvalidEnumArgumentException(@enum); + } + + #endregion InvalidEnumArgumentException + #region Assert + + public static void AssertFalse(bool value, string name) + { + if (value) + { + throw new Exception(TouchSocketCoreResource.AssertFalseFail.Format(name, value)); + } + } + public static void AssertTrue(bool value, string name) { if (value) @@ -170,12 +186,5 @@ internal static partial class ThrowHelper throw new Exception(TouchSocketCoreResource.AssertTrueFail.Format(name, value)); } - public static void AssertFalse(bool value, string name) - { - if (value) - { - throw new Exception(TouchSocketCoreResource.AssertFalseFail.Format(name, value)); - } - } - #endregion + #endregion Assert } \ No newline at end of file diff --git a/src/TouchSocket.Dmtp/Actor/SealedDmtpActor.cs b/src/TouchSocket.Shared/Dmtp/Actor/SealedDmtpActor.cs similarity index 96% rename from src/TouchSocket.Dmtp/Actor/SealedDmtpActor.cs rename to src/TouchSocket.Shared/Dmtp/Actor/SealedDmtpActor.cs index 18ac143f8..183506fa4 100644 --- a/src/TouchSocket.Dmtp/Actor/SealedDmtpActor.cs +++ b/src/TouchSocket.Shared/Dmtp/Actor/SealedDmtpActor.cs @@ -15,7 +15,7 @@ namespace TouchSocket.Dmtp; /// /// 密封的 /// -public sealed class SealedDmtpActor : DmtpActor +internal sealed class SealedDmtpActor : DmtpActor { /// /// 创建一个Dmtp协议的最基础功能件 diff --git a/src/TouchSocket.Shared/SocketIo/EngineIo3.cs b/src/TouchSocket.Shared/SocketIo/EngineIo3.cs index dc2fa0223..2f8606290 100644 --- a/src/TouchSocket.Shared/SocketIo/EngineIo3.cs +++ b/src/TouchSocket.Shared/SocketIo/EngineIo3.cs @@ -29,7 +29,7 @@ namespace TouchSocket.SocketIo public void EncodeToBinary(EngineIoMessage message, ByteBlock byteBlock) { - byteBlock.WriteByte((byte)message.MessageType); + WriterExtension.WriteValue(ref writer,(byte)message.MessageType); byteBlock.Write(message.GetRawData()); } diff --git a/src/TouchSocket.Shared/SocketIo/EngineIo4.cs b/src/TouchSocket.Shared/SocketIo/EngineIo4.cs index eb5dda205..d337fee65 100644 --- a/src/TouchSocket.Shared/SocketIo/EngineIo4.cs +++ b/src/TouchSocket.Shared/SocketIo/EngineIo4.cs @@ -29,7 +29,7 @@ namespace TouchSocket.SocketIo public void EncodeToBinary(EngineIoMessage message, ByteBlock byteBlock) { - byteBlock.WriteByte((byte)message.MessageType); + WriterExtension.WriteValue(ref writer,(byte)message.MessageType); byteBlock.Write(message.GetRawData()); } diff --git a/src/TouchSocket.Shared/Sockets/BaseTransport.cs b/src/TouchSocket.Shared/Sockets/BaseTransport.cs new file mode 100644 index 000000000..f510d47ae --- /dev/null +++ b/src/TouchSocket.Shared/Sockets/BaseTransport.cs @@ -0,0 +1,198 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.IO.Pipelines; +using System.Runtime.CompilerServices; +using TouchSocket.Resources; + +namespace TouchSocket.Sockets; + +internal abstract class BaseTransport : SafetyDisposableObject, ITransport +{ + protected readonly Pipe m_pipeReceive; + protected readonly Pipe m_pipeSend; + protected ClosedEventArgs m_closedEventArgs; + protected ValueCounter m_receiveCounter; + protected ValueCounter m_sentCounter; + private static readonly ClosedEventArgs s_defaultClosedEventArgs = new ClosedEventArgs(false, TouchSocketResource.RemoteDisconnects); + private readonly int m_maxBufferSize; + private readonly int m_minBufferSize; + private readonly SemaphoreSlim m_readLocker = new SemaphoreSlim(1, 1); + protected readonly CancellationTokenSource m_tokenSource = new CancellationTokenSource(); + private readonly SemaphoreSlim m_writeLocker = new SemaphoreSlim(1, 1); + private int m_cachedReceiveBufferSize = 1024 * 2; + + + public BaseTransport(TransportOption option) + { + var maxBufferSize = option.MaxBufferSize; + var minBufferSize = option.MinBufferSize; + var receivePipeOptions = option.ReceivePipeOptions; + var sendPipeOptions = option.SendPipeOptions; + + this.m_pipeReceive = receivePipeOptions == null ? new Pipe() : new Pipe(receivePipeOptions); + this.m_pipeSend = sendPipeOptions == null ? new Pipe() : new Pipe(sendPipeOptions); + this.m_receiveCounter = new ValueCounter(TimeSpan.FromSeconds(1), this.OnReceivePeriod); + + this.m_sentCounter = new ValueCounter(TimeSpan.FromSeconds(1)); + this.m_maxBufferSize = maxBufferSize; + this.m_minBufferSize = minBufferSize; + + // 初始化缓存的缓冲区大小 + // 参考ASP.NET Core的做法,默认使用2KB作为起始值 + // 这是一个在内存占用和性能之间很好的平衡点 + this.m_cachedReceiveBufferSize = this.ClampBufferSize(1024 * 2); + + } + + public ClosedEventArgs ClosedEventArgs => this.m_closedEventArgs ?? s_defaultClosedEventArgs; + + public CancellationToken ClosedToken => this.m_tokenSource.Token; + + /// + /// 获取用于读取数据的管道读取器 + /// + public virtual PipeReader Reader => this.m_pipeReceive.Reader; + + public SemaphoreSlim ReadLocker => this.m_readLocker; + + /// + /// 接收缓存池,运行时的值会根据流速自动调整 + /// + public int ReceiveBufferSize => this.m_cachedReceiveBufferSize; + + /// + /// 接收计数器 + /// + public ValueCounter ReceiveCounter => this.m_receiveCounter; + + + /// + /// 发送计数器 + /// + public ValueCounter SendCounter => this.m_sentCounter; + + public SemaphoreSlim WriteLocker => this.m_writeLocker; + + /// + /// 获取用于写入数据的管道写入器 + /// + public virtual PipeWriter Writer => this.m_pipeSend.Writer; + + + public virtual async Task CloseAsync(string msg, CancellationToken cancellationToken = default) + { + try + { + if (this.m_tokenSource.IsCancellationRequested) + { + return Result.Success; + } + this.m_closedEventArgs ??= new ClosedEventArgs(true, msg); + + // 完成发送管道 + await this.m_pipeSend.Writer.CompleteAsync().SafeWaitAsync(cancellationToken); + + // 等待发送管道读取器完成 + await this.m_pipeSend.Reader.CompleteAsync().SafeWaitAsync(cancellationToken); + + // 完成接收管道 + await this.m_pipeReceive.Writer.CompleteAsync().SafeWaitAsync(cancellationToken); + + // 等待接收管道读取器完成 + await this.m_pipeReceive.Reader.CompleteAsync().SafeWaitAsync(cancellationToken); + return Result.Success; + } + catch (Exception ex) + { + return Result.FromException(ex.Message); + } + finally + { + // 停止所有后台任务 + this.m_tokenSource.SafeCancel(); + } + } + + protected abstract Task RunReceive(CancellationToken cancellationToken); + + protected abstract Task RunSend(CancellationToken cancellationToken); + + protected override void SafetyDispose(bool disposing) + { + if (disposing) + { + this.m_tokenSource.SafeCancel(); + this.m_tokenSource.SafeDispose(); + } + } + + protected void Start() + { + _ = EasyTask.SafeRun(this.RunReceive, this.m_tokenSource.Token); + _ = EasyTask.SafeRun(this.RunSend, this.m_tokenSource.Token); + } + + /// + /// 将缓冲区大小限制在最小值和最大值之间 + /// + /// 原始缓冲区大小 + /// 限制后的缓冲区大小 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int ClampBufferSize(int size) + { + return Math.Min(Math.Max(size, this.m_minBufferSize), this.m_maxBufferSize); + } + + private void OnReceivePeriod(long value) + { + var newSize = CalculateOptimalBufferSize(value); + this.m_cachedReceiveBufferSize = this.ClampBufferSize(newSize); + } + + + /// + /// 根据数据流量计算最优缓冲区大小 + /// + /// 每秒字节数 + /// 建议的缓冲区大小 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int CalculateOptimalBufferSize(long bytesPerSecond) + { + // 定义缓冲区大小常量 + const int KB = 1024; + + return bytesPerSecond switch + { + // <10KB/s: 2KB缓冲区,适用于低流量场景(如聊天、控制命令等) + < 10 * KB => 2 * KB, + + // 10KB/s - 100KB/s: 4KB缓冲区,适用于一般Web请求 + < 100 * KB => 4 * KB, + + // 100KB/s - 1MB/s: 8KB缓冲区,适用于小文件传输 + < KB * KB => 8 * KB, + + // 1MB/s - 10MB/s: 16KB缓冲区,适用于中等流量 + < 10 * KB * KB => 16 * KB, + + // 10MB/s - 50MB/s: 32KB缓冲区,适用于大文件传输 + < 50 * KB * KB => 32 * KB, + + // 50MB/s - 100MB/s: 64KB缓冲区,适用于高速传输 + < 100 * KB * KB => 64 * KB, + + // >=100MB/s: 128KB缓冲区,适用于超高速传输 + _ => 128 * KB + }; + } +} \ No newline at end of file diff --git a/src/TouchSocket.Shared/Sockets/InternalClientCollection.cs b/src/TouchSocket.Shared/Sockets/InternalClientCollection.cs index b8f12891f..c643794de 100644 --- a/src/TouchSocket.Shared/Sockets/InternalClientCollection.cs +++ b/src/TouchSocket.Shared/Sockets/InternalClientCollection.cs @@ -12,7 +12,6 @@ using System.Collections; using System.Collections.Concurrent; -using System.Collections.Generic; using System.Diagnostics; namespace TouchSocket.Sockets; diff --git a/src/TouchSocket.Shared/Sockets/InternalReceiver.cs b/src/TouchSocket.Shared/Sockets/InternalReceiver.cs index d2f530d8d..f444273ec 100644 --- a/src/TouchSocket.Shared/Sockets/InternalReceiver.cs +++ b/src/TouchSocket.Shared/Sockets/InternalReceiver.cs @@ -10,168 +10,82 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Sockets; -/// -/// Receiver -/// -internal sealed class InternalReceiver : BlockSegment, IReceiver +internal class InternalReceiver : SafetyDisposableObject, IReceiver { - #region 字段 - private readonly IReceiverClient m_client; - private readonly AsyncAutoResetEvent m_resetEventForComplateRead = new AsyncAutoResetEvent(false); - private ByteBlock m_byteBlock; - private IRequestInfo m_requestInfo; - private bool m_cacheMode; - private int m_maxCacheSize = 1024 * 64; - private ByteBlock m_cacheByteBlock; - private InternalReceiverResult m_receiverResult; - #endregion 字段 + private readonly AsyncExchange<(ReadOnlyMemory, IRequestInfo)> m_asyncExchange = new(); - public bool CacheMode { get => this.m_cacheMode; set => this.m_cacheMode = value; } + private string m_msg; - public int MaxCacheSize { get => this.m_maxCacheSize; set => this.m_maxCacheSize = value; } - - /// - /// Receiver - /// - /// - public InternalReceiver(IReceiverClient client) : base(true) + public InternalReceiver(IReceiverClient client) { this.m_client = client; } - /// - public ValueTask ReadAsync(CancellationToken token) + public void Complete(string msg) { - if (this.m_receiverResult.IsCompleted) - { - return EasyValueTask.FromResult(this.m_receiverResult); - } - else - { - return base.ProtectedReadAsync(token); - } + this.m_msg = msg; + this.m_asyncExchange.Complete(); } - /// - public async Task InputReceiveAsync(ByteBlock byteBlock, IRequestInfo requestInfo) + public ValueTask InputReceiveAsync(ReadOnlyMemory memory, IRequestInfo requestInfo, CancellationToken cancellationToken) { - if (this.DisposedValue) - { - return; - } - - if (this.m_cacheMode && byteBlock != null) - { - ByteBlock bytes; - if (this.m_cacheByteBlock == null) - { - bytes = new ByteBlock(byteBlock.Length); - bytes.Write(byteBlock.Span); - } - else if (this.m_cacheByteBlock.CanReadLength > 0) - { - bytes = new ByteBlock(byteBlock.Length + this.m_cacheByteBlock.CanReadLength); - bytes.Write(this.m_cacheByteBlock.Span.Slice(this.m_cacheByteBlock.Position, this.m_cacheByteBlock.CanReadLength)); - bytes.Write(byteBlock.Span); - - this.m_cacheByteBlock.Dispose(); - } - else - { - bytes = new ByteBlock(byteBlock.Length); - bytes.Write(byteBlock.Span); - this.m_cacheByteBlock.Dispose(); - } - - bytes.SeekToStart(); - this.m_cacheByteBlock = bytes; - this.m_requestInfo = requestInfo; - } - else - { - this.m_byteBlock = byteBlock; - this.m_requestInfo = requestInfo; - } - - if (this.m_cacheMode) - { - this.m_receiverResult.ByteBlock = this.m_cacheByteBlock; - this.m_receiverResult.RequestInfo = this.m_requestInfo; - } - else - { - this.m_receiverResult.ByteBlock = this.m_byteBlock; - this.m_receiverResult.RequestInfo = this.m_requestInfo; - } - - await base.TriggerAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return this.m_asyncExchange.WriteAsync((memory, requestInfo), cancellationToken); } - public async Task Complete(string msg) + public async ValueTask ReadAsync(CancellationToken cancellationToken) { - try + var readLease = await this.m_asyncExchange.ReadAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var (memory, requestInfo) = readLease.Value; + return new Sockets.InternalReceiverResult(readLease.Dispose) { - this.m_receiverResult.IsCompleted = true; - this.m_receiverResult.Message = msg; - await this.InputReceiveAsync(default, default).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - catch - { - } + IsCompleted = readLease.IsCompleted, + Memory = memory, + RequestInfo = requestInfo, + Message = this.m_msg + }; } - /// - protected override void Dispose(bool disposing) + protected override void SafetyDispose(bool disposing) { - if (this.DisposedValue) - { - return; - } - if (disposing) { this.m_client.ClearReceiver(); - this.m_resetEventForComplateRead.Set(); - this.m_resetEventForComplateRead.SafeDispose(); } - this.m_byteBlock = null; - this.m_requestInfo = null; - base.Dispose(disposing); } - protected override void CompleteRead() + #region Class + + internal class InternalReceiverResult : IReceiverResult { - var byteBlock = this.m_cacheByteBlock; - if (this.m_cacheMode) + private readonly Action m_disAction; + + /// + /// ReceiverResult + /// + /// + public InternalReceiverResult(Action disAction) { - if (byteBlock != null) - { - if (byteBlock.CanReadLength > this.m_maxCacheSize) - { - ThrowHelper.ThrowArgumentOutOfRangeException_MoreThan(nameof(this.m_cacheByteBlock.CanReadLength), this.m_cacheByteBlock.CanReadLength, this.m_maxCacheSize); - } - } + this.m_disAction = disAction; + } + + public bool IsCompleted { get; set; } + + public ReadOnlyMemory Memory { get; set; } + + public string Message { get; set; } + + public IRequestInfo RequestInfo { get; set; } + + /// + public void Dispose() + { + this.m_disAction.Invoke(); } - this.m_byteBlock = default; - this.m_requestInfo = default; - this.m_receiverResult.RequestInfo = default; - this.m_receiverResult.ByteBlock = default; - base.CompleteRead(); } - - protected override IReceiverResult CreateResult(Action actionForDispose) - { - this.m_receiverResult = new InternalReceiverResult(actionForDispose); - return this.m_receiverResult; - } -} + #endregion Class +} \ No newline at end of file diff --git a/src/TouchSocket.Shared/Sockets/InternalReceiverResult.cs b/src/TouchSocket.Shared/Sockets/InternalReceiverResult.cs index e701d76eb..7b02e997f 100644 --- a/src/TouchSocket.Shared/Sockets/InternalReceiverResult.cs +++ b/src/TouchSocket.Shared/Sockets/InternalReceiverResult.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// @@ -34,7 +31,7 @@ internal class InternalReceiverResult : IReceiverResult /// /// 字节块 /// - public ByteBlock ByteBlock { get; set; } + public ReadOnlyMemory Memory { get; set; } /// /// 数据对象 diff --git a/src/TouchSocket.Shared/Sockets/InternalSocketIoCore.cs b/src/TouchSocket.Shared/Sockets/InternalSocketIoCore.cs index 98cb9b0f0..bc8499745 100644 --- a/src/TouchSocket.Shared/Sockets/InternalSocketIoCore.cs +++ b/src/TouchSocket.Shared/Sockets/InternalSocketIoCore.cs @@ -123,12 +123,12 @@ namespace TouchSocket.SocketIo await this.SendAsync(dataItems); } - public async Task EmitWithAckAsync(string eventName, object[] data, int millisecondsTimeout, CancellationToken token) + public async Task EmitWithAckAsync(string eventName, object[] data, int millisecondsTimeout, CancellationToken cancellationToken) { var waitData = this.m_waitHandlePoolForEmit.GetWaitDataAsync(out var sign); var dataItems = this.SerializeEvent(eventName, sign, this.Namespace, data); await this.SendAsync(dataItems); - waitData.SetCancellationToken(token); + waitData.SetCancellationToken(cancellationToken); (await waitData.WaitAsync(millisecondsTimeout)).ThrowIfNotRunning(); return new InternalSocketIoResponse(waitData.WaitResult, this); } diff --git a/src/TouchSocket.Shared/Sockets/StreamTransport.cs b/src/TouchSocket.Shared/Sockets/StreamTransport.cs new file mode 100644 index 000000000..8f9c56d3f --- /dev/null +++ b/src/TouchSocket.Shared/Sockets/StreamTransport.cs @@ -0,0 +1,134 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.Diagnostics; +using TouchSocket.Resources; + +namespace TouchSocket.Sockets; + +internal class StreamTransport : BaseTransport +{ + private readonly Stream m_stream; + + public StreamTransport(Stream stream, TransportOption option) : base(option) + { + this.m_stream = stream; + + this.Start(); + } + + public override async Task CloseAsync(string msg, CancellationToken cancellationToken = default) + { + try + { + await base.CloseAsync(msg, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + this.m_stream.Close(); + return Result.Success; + } + catch (Exception ex) + { + return Result.FromException(ex.Message); + } + } + + protected override async Task RunReceive(CancellationToken cancellationToken) + { + try + { + var pipeWriter = this.m_pipeReceive.Writer; + while (!cancellationToken.IsCancellationRequested) + { + var memory = pipeWriter.GetMemory(this.ReceiveBufferSize); + Debug.WriteLine($"StreamTransport RunReceive GetMemory Size:{memory.Length}"); + var result = await this.m_stream.ReadAsync(memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + if (result == 0) + { + this.m_closedEventArgs ??= new ClosedEventArgs(false, TouchSocketResource.RemoteDisconnects); + await pipeWriter.CompleteAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return; + } + + this.m_receiveCounter.Increment(result); + + pipeWriter.Advance(result); + var flushResult = await pipeWriter.FlushAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + if (flushResult.IsCompleted) + { + break; + } + } + } + catch (Exception ex) + { + this.m_closedEventArgs ??= new ClosedEventArgs(false, ex.Message); + await this.m_pipeReceive.Writer.CompleteAsync(ex).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + } + + protected override async Task RunSend(CancellationToken cancellationToken) + { + try + { + while (!cancellationToken.IsCancellationRequested) + { + var readResult = await this.m_pipeSend.Reader.ReadAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + if (readResult.IsCanceled) + { + break; + } + + if (readResult.IsCompleted && readResult.Buffer.IsEmpty) + { + break; + } + + try + { + foreach (var item in readResult.Buffer) + { + await this.m_stream.WriteAsync(item, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + + this.m_pipeSend.Reader.AdvanceTo(readResult.Buffer.End); + + this.m_sentCounter.Increment(readResult.Buffer.Length); + } + catch (Exception ex) + { + // 处理发送失败的情况 + this.m_pipeSend.Reader.Complete(ex); + break; + } + + if (readResult.IsCompleted) + { + break; + } + } + } + catch (Exception ex) when (ex is not OperationCanceledException) + { + this.m_pipeSend.Reader.Complete(ex); + } + finally + { + this.m_pipeSend.Reader.Complete(); + } + } + + protected override void SafetyDispose(bool disposing) + { + this.m_stream.Dispose(); + base.SafetyDispose(disposing); + } +} \ No newline at end of file diff --git a/src/TouchSocket.Shared/Sockets/ThrowHelperSockets.cs b/src/TouchSocket.Shared/Sockets/ThrowHelperSockets.cs index abb7de969..d8c3834f2 100644 --- a/src/TouchSocket.Shared/Sockets/ThrowHelperSockets.cs +++ b/src/TouchSocket.Shared/Sockets/ThrowHelperSockets.cs @@ -10,6 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ +using System.Net.Sockets; using System.Runtime.CompilerServices; using TouchSocket.Resources; using TouchSocket.Sockets; @@ -29,4 +30,10 @@ internal static partial class ThrowHelper { throw new ClientNotFindException(TouchSocketResource.ClientNotFind.Format(id)); } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowSocketException(int errorCode) + { + throw new SocketException(errorCode); + } } \ No newline at end of file diff --git a/src/TouchSocket.Shared/TouchSocket.Shared.projitems b/src/TouchSocket.Shared/TouchSocket.Shared.projitems index 0db936ba7..3e7ad862b 100644 --- a/src/TouchSocket.Shared/TouchSocket.Shared.projitems +++ b/src/TouchSocket.Shared/TouchSocket.Shared.projitems @@ -9,16 +9,19 @@ TouchSocket.Shared + - + + + diff --git a/src/TouchSocket.SourceGenerator.SharedProject/Core/UtilsCore.cs b/src/TouchSocket.SourceGenerator.SharedProject/Core/UtilsCore.cs new file mode 100644 index 000000000..30cf1ebe2 --- /dev/null +++ b/src/TouchSocket.SourceGenerator.SharedProject/Core/UtilsCore.cs @@ -0,0 +1,40 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace TouchSocket.Core; + +public enum EndianType +{ + /// + /// 小端模式,即DCBA + /// + Little, + + /// + /// 大端模式。即ABCD + /// + Big, + + /// + /// 以交换小端格式。即CDAB + /// + LittleSwap, + + /// + /// 以交换大端,即:BADC + /// + BigSwap +} \ No newline at end of file diff --git a/src/TouchSocket.SourceGenerator.SharedProject/Rpc/RpcClientCodeBuilder.cs b/src/TouchSocket.SourceGenerator.SharedProject/Rpc/RpcClientCodeBuilder.cs index 03e3abbb6..9bf85b8fb 100644 --- a/src/TouchSocket.SourceGenerator.SharedProject/Rpc/RpcClientCodeBuilder.cs +++ b/src/TouchSocket.SourceGenerator.SharedProject/Rpc/RpcClientCodeBuilder.cs @@ -30,10 +30,7 @@ internal enum TaskType internal abstract class RpcClientCodeBuilder : CodeBuilder { - public string RpcAttribute { get; private set; } - protected abstract string RpcAttributeName { get; } private readonly INamedTypeSymbol m_rpcApi; - private readonly Dictionary m_rpcApiNamedArguments; protected RpcClientCodeBuilder(INamedTypeSymbol rpcApi, string rpcAttribute) @@ -58,10 +55,10 @@ internal abstract class RpcClientCodeBuilder : CodeBuilder public override string Id => this.m_rpcApi.ToDisplayString(); public string Prefix { get; set; } - + public string RpcAttribute { get; private set; } public string ServerName { get; set; } - public virtual IEnumerable Usings + public override IEnumerable Usings { get { @@ -74,35 +71,46 @@ internal abstract class RpcClientCodeBuilder : CodeBuilder } } + protected abstract string RpcAttributeName { get; } + public override string GetFileName() { return this.m_rpcApi.ToDisplayString() + "Generator"; } - public override string ToString() + public string ReplacePatterns(Dictionary pairs, string input) { - var codeString = RpcUtils.CreateStringBuilder(); + var sb = new StringBuilder(); - foreach (var item in this.Usings) + for (var i = 0; i < input.Length; i++) { - codeString.AppendLine(item); - } - codeString.AppendLine($"namespace {this.GetNamespace()}"); - codeString.AppendLine("{"); + if (i < input.Length - 1 && input[i] == '{' && char.IsLetter(input[i + 1])) + { + // 检查是否存在下一个字符并且是字母,处理多字符键的情况 + var end = i + 2; + while (end < input.Length && char.IsLetter(input[end - 1])) + { + end++; + } + var key = input.Substring(i + 1, end - i - 2); - if (this.AllowAsync(GeneratorFlag.InterfaceSync) || this.AllowAsync(GeneratorFlag.InterfaceAsync)) - { - this.BuildIntereface(codeString); + if (pairs.TryGetValue(key, out var value)) + { + sb.Append(this.ReplacePattern(key, value)); + i = end - 1; // 跳过"{key}" + } + else + { + sb.Append(input[i]); // 保留原始字符 + } + } + else + { + sb.Append(input[i]); + } } - if (this.AllowAsync(GeneratorFlag.ExtensionSync) || this.AllowAsync(GeneratorFlag.ExtensionAsync)) - { - this.BuildMethod(codeString); - } - codeString.AppendLine("}"); - - // System.Diagnostics.Debugger.Launch(); - return codeString.ToString(); + return sb.ToString(); } protected virtual bool AllowAsync(GeneratorFlag flag, IMethodSymbol method = default, Dictionary namedArguments = default) @@ -147,7 +155,7 @@ internal abstract class RpcClientCodeBuilder : CodeBuilder return true; } - protected virtual void BuildIntereface(StringBuilder codeString) + protected virtual void BuildIntereface(StringBuilder codeBuilder) { var interfaceNames = new List(); if (this.IsInheritedInterface()) @@ -166,38 +174,38 @@ internal abstract class RpcClientCodeBuilder : CodeBuilder if (interfaceNames.Count == 0) { - codeString.AppendLine($"public partial interface I{this.GetClassName()}"); + codeBuilder.AppendLine($"public partial interface I{this.GetClassName()}"); } else { - codeString.AppendLine($"public partial interface I{this.GetClassName()} :{string.Join(",", interfaceNames)}"); + codeBuilder.AppendLine($"public partial interface I{this.GetClassName()} :{string.Join(",", interfaceNames)}"); } - codeString.AppendLine("{"); + codeBuilder.AppendLine("{"); //Debugger.Launch(); foreach (var method in this.FindApiMethods()) { var methodCode = this.BuildMethodInterface(method); - codeString.AppendLine(methodCode); + codeBuilder.AppendLine(methodCode); } - codeString.AppendLine("}"); + codeBuilder.AppendLine("}"); } - protected virtual void BuildMethod(StringBuilder codeString) + protected virtual void BuildMethod(StringBuilder codeBuilder) { - codeString.AppendLine($"public static partial class {this.GetClassName()}Extensions"); - codeString.AppendLine("{"); + codeBuilder.AppendLine($"public static partial class {this.GetClassName()}Extensions"); + codeBuilder.AppendLine("{"); //Debugger.Launch(); foreach (var method in this.FindApiMethods()) { var methodCode = this.BuildMethod(method); - codeString.AppendLine(methodCode); + codeBuilder.AppendLine(methodCode); } - codeString.AppendLine("}"); + codeBuilder.AppendLine("}"); } protected virtual string BuildMethod(IMethodSymbol method) @@ -225,125 +233,100 @@ internal abstract class RpcClientCodeBuilder : CodeBuilder var parameters = method.Parameters.Where(a => (!RpcUtils.IsCallContext(a)) && (!RpcUtils.IsFromServices(a))).ToImmutableArray(); //生成开始 - var codeString = new StringBuilder(); + var codeBuilder = new StringBuilder(); if (allowSync) { - codeString.AppendLine("///"); - codeString.AppendLine($"///{this.GetDescription(method)}"); - codeString.AppendLine("///"); - codeString.Append($"public static {returnType} {methodName}"); - codeString.Append("(");//方法参数 + codeBuilder.AppendLine("///"); + codeBuilder.AppendLine($"///{this.GetDescription(method)}"); + codeBuilder.AppendLine("///"); + codeBuilder.AppendLine("[AsyncToSyncWarning]"); + codeBuilder.Append($"public static {returnType} {methodName}"); + codeBuilder.Append("(");//方法参数 - codeString.Append($"this TClient client"); + codeBuilder.Append($"this TClient client"); - codeString.Append(","); + codeBuilder.Append(","); for (var i = 0; i < parameters.Length; i++) { if (i > 0) { - codeString.Append(","); + codeBuilder.Append(","); } - codeString.Append(this.GetMethodParameterString(parameters[i])); + codeBuilder.Append(this.GetMethodParameterString(parameters[i])); } if (parameters.Length > 0) { - codeString.Append(","); + codeBuilder.Append(","); } - codeString.Append("IInvokeOption invokeOption = default"); - codeString.AppendLine($") where TClient:{string.Join(",", genericConstraintTypes)}"); + codeBuilder.Append("InvokeOption invokeOption = default"); + codeBuilder.AppendLine($") where TClient:{string.Join(",", genericConstraintTypes)}"); - codeString.AppendLine("{");//方法开始 + codeBuilder.AppendLine("{");//方法开始 var parametersString = this.GetParametersString(parameters); if (method.HasReturn()) { - codeString.AppendLine($"return ({returnType}) client.Invoke(\"{invokeKey}\",typeof({returnType}),invokeOption,{parametersString});"); + codeBuilder.AppendLine($"return ({returnType}) client.Invoke(\"{invokeKey}\",typeof({returnType}),invokeOption,{parametersString});"); } else { - codeString.AppendLine($"client.Invoke(\"{invokeKey}\",default,invokeOption,{parametersString});"); + codeBuilder.AppendLine($"client.Invoke(\"{invokeKey}\",default,invokeOption,{parametersString});"); } - codeString.AppendLine("}"); + codeBuilder.AppendLine("}"); } if (allowAsync) { //以下生成异步 - codeString.AppendLine("///"); - codeString.AppendLine($"///{this.GetDescription(method)}"); - codeString.AppendLine("///"); + codeBuilder.AppendLine("///"); + codeBuilder.AppendLine($"///{this.GetDescription(method)}"); + codeBuilder.AppendLine("///"); if (method.HasReturn()) { - codeString.Append($"public static async Task<{returnType}> {methodName}Async"); + codeBuilder.Append($"public static async Task<{returnType}> {methodName}Async"); } else { - codeString.Append($"public static Task {methodName}Async"); + codeBuilder.Append($"public static Task {methodName}Async"); } - codeString.Append("(");//方法参数 + codeBuilder.Append("(");//方法参数 - codeString.Append($"this TClient client"); + codeBuilder.Append($"this TClient client"); - codeString.Append(","); + codeBuilder.Append(","); for (var i = 0; i < parameters.Length; i++) { if (i > 0) { - codeString.Append(","); + codeBuilder.Append(","); } - codeString.Append(this.GetMethodParameterString(parameters[i])); + codeBuilder.Append(this.GetMethodParameterString(parameters[i])); } if (parameters.Length > 0) { - codeString.Append(","); + codeBuilder.Append(","); } - codeString.Append("IInvokeOption invokeOption = default"); - codeString.AppendLine($") where TClient:{string.Join(",", genericConstraintTypes)}"); + codeBuilder.Append("InvokeOption invokeOption = default"); + codeBuilder.AppendLine($") where TClient:{string.Join(",", genericConstraintTypes)}"); - codeString.AppendLine("{");//方法开始 + codeBuilder.AppendLine("{");//方法开始 var parametersString = this.GetParametersString(parameters); if (method.HasReturn()) { - codeString.AppendLine($"return ({returnType}) await client.InvokeAsync(\"{invokeKey}\",typeof({returnType}),invokeOption,{parametersString});"); + codeBuilder.AppendLine($"return ({returnType}) await client.InvokeAsync(\"{invokeKey}\",typeof({returnType}),invokeOption,{parametersString});"); } else { - codeString.AppendLine($"return client.InvokeAsync(\"{invokeKey}\",default,invokeOption,{parametersString});"); + codeBuilder.AppendLine($"return client.InvokeAsync(\"{invokeKey}\",default,invokeOption,{parametersString});"); } - codeString.AppendLine("}"); + codeBuilder.AppendLine("}"); } - return codeString.ToString(); - } - - protected string GetMethodParameterString(IParameterSymbol parameter) - { - //Debugger.Launch(); - if (parameter.HasExplicitDefaultValue) - { - var data = parameter.ExplicitDefaultValue; - if (data == null) - { - return $"{parameter.Type} {parameter.Name}=null"; - } - else if (data.GetType() == typeof(string)) - { - return $"{parameter.Type} {parameter.Name}=\"{parameter.ExplicitDefaultValue}\""; - } - else if (data is bool b) - { - return $"{parameter.Type} {parameter.Name}={b.ToString().ToLower()}"; - } - else - { - return $"{parameter.Type} {parameter.Name}={parameter.ExplicitDefaultValue.ToString()}"; - } - } - return parameter.ToDisplayString(); + return codeBuilder.ToString(); } protected virtual string BuildMethodInterface(IMethodSymbol method) @@ -375,66 +358,67 @@ internal abstract class RpcClientCodeBuilder : CodeBuilder //} //生成开始 - var codeString = new StringBuilder(); + var codeBuilder = new StringBuilder(); if (allowSync) { - codeString.AppendLine("///"); - codeString.AppendLine($"///{this.GetDescription(method)}"); - codeString.AppendLine("///"); - codeString.Append($"{returnType} {methodName}"); - codeString.Append("(");//方法参数 + codeBuilder.AppendLine("///"); + codeBuilder.AppendLine($"///{this.GetDescription(method)}"); + codeBuilder.AppendLine("///"); + codeBuilder.AppendLine("[AsyncToSyncWarning]"); + codeBuilder.Append($"{returnType} {methodName}"); + codeBuilder.Append("(");//方法参数 for (var i = 0; i < parameters.Length; i++) { if (i > 0) { - codeString.Append(","); + codeBuilder.Append(","); } - codeString.Append(this.GetMethodParameterString(parameters[i])); + codeBuilder.Append(this.GetMethodParameterString(parameters[i])); } if (parameters.Length > 0) { - codeString.Append(","); + codeBuilder.Append(","); } - codeString.Append("IInvokeOption invokeOption = default"); - codeString.AppendLine($");"); + codeBuilder.Append("InvokeOption invokeOption = default"); + codeBuilder.AppendLine($");"); } if (allowAsync) { //以下生成异步 - codeString.AppendLine("///"); - codeString.AppendLine($"///{this.GetDescription(method)}"); - codeString.AppendLine("///"); + codeBuilder.AppendLine("///"); + codeBuilder.AppendLine($"///{this.GetDescription(method)}"); + codeBuilder.AppendLine("///"); if (!method.HasReturn()) { - codeString.Append($"Task {methodName}Async"); + codeBuilder.Append($"Task {methodName}Async"); } else { - codeString.Append($"Task<{returnType}> {methodName}Async"); + codeBuilder.Append($"Task<{returnType}> {methodName}Async"); } - codeString.Append("(");//方法参数 + codeBuilder.Append("(");//方法参数 for (var i = 0; i < parameters.Length; i++) { if (i > 0) { - codeString.Append(","); + codeBuilder.Append(","); } - codeString.Append(this.GetMethodParameterString(parameters[i])); + codeBuilder.Append(this.GetMethodParameterString(parameters[i])); } if (parameters.Length > 0) { - codeString.Append(","); + codeBuilder.Append(","); } - codeString.Append("IInvokeOption invokeOption = default"); - codeString.AppendLine($");"); + codeBuilder.Append("InvokeOption invokeOption = default"); + codeBuilder.AppendLine($");"); } - return codeString.ToString(); + return codeBuilder.ToString(); } protected virtual IEnumerable FindApiMethods() @@ -444,7 +428,25 @@ internal abstract class RpcClientCodeBuilder : CodeBuilder .OfType(); } - protected abstract string GetInheritedClassName(INamedTypeSymbol rpcApi); + protected override bool GeneratorCode(StringBuilder codeBuilder) + { + codeBuilder.AppendLine($"namespace {this.GetNamespace()}"); + codeBuilder.AppendLine("{"); + + if (this.AllowAsync(GeneratorFlag.InterfaceSync) || this.AllowAsync(GeneratorFlag.InterfaceAsync)) + { + this.BuildIntereface(codeBuilder); + } + + if (this.AllowAsync(GeneratorFlag.ExtensionSync) || this.AllowAsync(GeneratorFlag.ExtensionAsync)) + { + this.BuildMethod(codeBuilder); + } + codeBuilder.AppendLine("}"); + + // System.Diagnostics.Debugger.Launch(); + return true; + } protected virtual string GetClassName() { @@ -508,6 +510,8 @@ internal abstract class RpcClientCodeBuilder : CodeBuilder } } + protected abstract string GetInheritedClassName(INamedTypeSymbol rpcApi); + protected virtual string GetInvokeKey(IMethodSymbol method, Dictionary namedArguments) { if (namedArguments.TryGetValue("InvokeKey", out var typedConstant)) @@ -554,53 +558,45 @@ internal abstract class RpcClientCodeBuilder : CodeBuilder return name.EndsWith("Async") ? name.Replace("Async", string.Empty) : name; } - public string ReplacePatterns(Dictionary pairs, string input) + protected string GetMethodParameterString(IParameterSymbol parameter) { - var sb = new StringBuilder(); - - for (var i = 0; i < input.Length; i++) + //Debugger.Launch(); + if (parameter.HasExplicitDefaultValue) { - if (i < input.Length - 1 && input[i] == '{' && char.IsLetter(input[i + 1])) + var data = parameter.ExplicitDefaultValue; + if (data == null) { - // 检查是否存在下一个字符并且是字母,处理多字符键的情况 - var end = i + 2; - while (end < input.Length && char.IsLetter(input[end - 1])) - { - end++; - } - var key = input.Substring(i + 1, end - i - 2); - - if (pairs.TryGetValue(key, out var value)) - { - sb.Append(this.ReplacePattern(key, value)); - i = end - 1; // 跳过"{key}" - } - else - { - sb.Append(input[i]); // 保留原始字符 - } + return $"{parameter.Type} {parameter.Name}=null"; + } + else if (data.GetType() == typeof(string)) + { + return $"{parameter.Type} {parameter.Name}=\"{parameter.ExplicitDefaultValue}\""; + } + else if (data is bool b) + { + return $"{parameter.Type} {parameter.Name}={b.ToString().ToLower()}"; } else { - sb.Append(input[i]); + return $"{parameter.Type} {parameter.Name}={parameter.ExplicitDefaultValue.ToString()}"; } } - - return sb.ToString(); - } - - protected virtual string ReplacePattern(string key, TypedConstant typedConstant) - { - return typedConstant.Value?.ToString(); + return parameter.ToDisplayString(); } protected virtual string GetNamespace() { - if (this.m_rpcApiNamedArguments.TryGetValue("Namespace", out var typedConstant)) + var defaultNamespace = $"TouchSocket.Rpc.{this.RpcAttributeName}.Generators"; + if (!this.m_rpcApiNamedArguments.TryGetValue("Namespace", out var typedConstant)) { - return typedConstant.Value?.ToString() ?? "TouchSocket.Rpc.Generators"; + return defaultNamespace; } - return "TouchSocket.Rpc.Generators"; + var namespaceValue = typedConstant.Value?.ToString(); + if (string.IsNullOrEmpty(namespaceValue)) + { + return defaultNamespace; + } + return string.Format(namespaceValue, this.RpcAttributeName); } protected virtual string GetParametersString(ImmutableArray parameters) @@ -609,22 +605,22 @@ internal abstract class RpcClientCodeBuilder : CodeBuilder { return "default"; } - var codeString = new StringBuilder(); + var codeBuilder = new StringBuilder(); - codeString.Append($"new object[]"); - codeString.Append("{"); + codeBuilder.Append($"new object[]"); + codeBuilder.Append("{"); foreach (var parameter in parameters) { - codeString.Append(parameter.Name); - if (!parameter.Equals(parameters[parameters.Length - 1], SymbolEqualityComparer.Default)) + codeBuilder.Append(parameter.Name); + if (!parameter.Equals(parameters[^1], SymbolEqualityComparer.Default)) { - codeString.Append(","); + codeBuilder.Append(","); } } - codeString.AppendLine("}"); + codeBuilder.AppendLine("}"); - return codeString.ToString(); + return codeBuilder.ToString(); } protected virtual string GetReturnType(IMethodSymbol method) @@ -634,6 +630,11 @@ internal abstract class RpcClientCodeBuilder : CodeBuilder return "void"; } + if (method.ReturnType is IArrayTypeSymbol arrayTypeSymbol) + { + return arrayTypeSymbol.ToDisplayString(); + } + if (method.ReturnType is not INamedTypeSymbol returnTypeSymbol) { return "void"; @@ -678,4 +679,9 @@ internal abstract class RpcClientCodeBuilder : CodeBuilder } return true; } + + protected virtual string ReplacePattern(string key, TypedConstant typedConstant) + { + return typedConstant.Value?.ToString(); + } } \ No newline at end of file diff --git a/src/TouchSocket.SourceGenerator.SharedProject/Rpc/RpcUtils.cs b/src/TouchSocket.SourceGenerator.SharedProject/Rpc/RpcUtils.cs index 6f9a86861..af3047f11 100644 --- a/src/TouchSocket.SourceGenerator.SharedProject/Rpc/RpcUtils.cs +++ b/src/TouchSocket.SourceGenerator.SharedProject/Rpc/RpcUtils.cs @@ -23,6 +23,7 @@ internal static class RpcUtils public const string RpcAttributeTypeName = "TouchSocket.Rpc.RpcAttribute"; public const string FromServicesAttributeTypeName = "TouchSocket.Rpc.FromServicesAttribute"; public const string ICallContextTypeName = "TouchSocket.Rpc.ICallContext"; + public const string FromServicesAttributeName = "TouchSocket.Rpc.FromServicesAttribute"; public static StringBuilder CreateStringBuilder() { diff --git a/src/TouchSocket.SourceGenerator.SharedProject/TouchSocket.SourceGenerator.SharedProject.projitems b/src/TouchSocket.SourceGenerator.SharedProject/TouchSocket.SourceGenerator.SharedProject.projitems index 2daa4e6fa..263f8c34a 100644 --- a/src/TouchSocket.SourceGenerator.SharedProject/TouchSocket.SourceGenerator.SharedProject.projitems +++ b/src/TouchSocket.SourceGenerator.SharedProject/TouchSocket.SourceGenerator.SharedProject.projitems @@ -9,6 +9,7 @@ TouchSocket.SourceGenerator.SharedProject + @@ -18,7 +19,4 @@ - - - \ No newline at end of file diff --git a/src/TouchSocket.SourceGenerator.SharedProject/Utils.cs b/src/TouchSocket.SourceGenerator.SharedProject/Utils.cs index 18b6a316c..fa74e22ef 100644 --- a/src/TouchSocket.SourceGenerator.SharedProject/Utils.cs +++ b/src/TouchSocket.SourceGenerator.SharedProject/Utils.cs @@ -14,16 +14,98 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Reflection; +using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; +using System.Xml.Linq; namespace TouchSocket; +public readonly struct CodeSpace : IDisposable +{ + private readonly StringBuilder m_stringBuilder; + + public CodeSpace(StringBuilder stringBuilder) + { + this.m_stringBuilder = stringBuilder; + this.m_stringBuilder.AppendLine("{"); + } + public void Dispose() + { + this.m_stringBuilder?.AppendLine("}"); + } +} + +internal static class SourceProductionContextExtension +{ + public static void AddSource(this SourceProductionContext sourceProductionContext, CodeBuilder builder) + { + if (builder.ToSourceText(out var sourceCode)) + { + sourceProductionContext.AddSource(builder.GetFileName(), sourceCode); + } + } +} + internal static class Utils { + public const string DependencyPropertyBase = "TouchSocket.Core.DependencyPropertyBase"; + public const string GeneratorPackageAttributeTypeName = "TouchSocket.Core.GeneratorPackageAttribute"; + public const string IPackageTypeName = "TouchSocket.Core.IPackage"; public const string Task = "System.Threading.Tasks.Task"; + #region 获取程序集资源 + + /// + /// 获取程序集中的嵌入资源内容 + /// + /// 资源名称,如果为null则返回所有资源名称 + /// 资源内容或资源名称列表 + public static string GetEmbeddedResourceContent(string resourceName) + { + var assembly = Assembly.GetExecutingAssembly(); + var resourceNames = assembly.GetManifestResourceNames(); + + // 查找匹配的资源 + var targetResource = resourceNames.FirstOrDefault(name => + name.Equals(resourceName, StringComparison.OrdinalIgnoreCase) || + name.EndsWith($".{resourceName}", StringComparison.OrdinalIgnoreCase)); + + if (string.IsNullOrEmpty(targetResource)) + { + return null; + } + + try + { + using (var stream = assembly.GetManifestResourceStream(targetResource)) + { + if (stream == null) + { + return null; + } + + using (var reader = new StreamReader(stream, Encoding.UTF8)) + { + return reader.ReadToEnd(); + } + } + } + catch + { + return null; + } + } + + public static string[] GetManifestResourceNames() + { + var assembly = Assembly.GetExecutingAssembly(); + var resourceNames = assembly.GetManifestResourceNames(); + return resourceNames; + } + #endregion public static bool EqualsWithFullName(this ISymbol symbol, string fullName) { @@ -62,97 +144,20 @@ internal static class Utils public static ITypeSymbol GetNullableType(this ITypeSymbol typeSymbol) { - //Debugger.Launch(); - var namedTypeSymbol = (INamedTypeSymbol)typeSymbol; - - if (namedTypeSymbol.SpecialType == SpecialType.System_Nullable_T) + // 如果是可空值类型(Nullable),返回T + if (typeSymbol is INamedTypeSymbol namedTypeSymbol && namedTypeSymbol.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T) { - return namedTypeSymbol.TypeArguments.First(); + return namedTypeSymbol.TypeArguments.Length == 1 ? namedTypeSymbol.TypeArguments[0] : typeSymbol; } + // 如果是可空引用类型(T?),去除可空标注,返回T if (typeSymbol.NullableAnnotation == NullableAnnotation.Annotated) { - if (namedTypeSymbol.TypeArguments.Any()) - { - return namedTypeSymbol.TypeArguments.First(); - } - else - { - return namedTypeSymbol.OriginalDefinition; - } + return typeSymbol.WithNullableAnnotation(NullableAnnotation.None); } - return namedTypeSymbol; - } - - - public static bool HasAttribute(this ISymbol symbol, INamedTypeSymbol attribute) - { - foreach (var attr in symbol.GetAttributes()) - { - var attrClass = attr.AttributeClass; - if (attrClass != null && attrClass.ToDisplayString() == attribute.ToDisplayString()) - { - return true; - } - } - return false; - } - - public static bool HasAttribute(this ISymbol symbol, string attribute) - { - return symbol.HasAttribute(attribute, out _); - } - - public static bool HasAttribute(this ISymbol symbol, string attribute, out AttributeData attributeData) - { - foreach (var attr in symbol.GetAttributes()) - { - var attrClass = attr.AttributeClass; - if (attrClass != null && attrClass.ToDisplayString() == attribute) - { - attributeData = attr; - return true; - } - } - attributeData = default; - return false; - } - - public static bool HasAttributes(this ISymbol symbol, string attribute, out IEnumerable attributeDatas) - { - var list = new List(); - foreach (var attr in symbol.GetAttributes()) - { - var attrClass = attr.AttributeClass; - if (attrClass != null && attrClass.ToDisplayString() == attribute) - { - list.Add(attr); - } - } - - if (list.Count > 0) - { - attributeDatas = list; - return true; - } - attributeDatas = default; - return false; - } - - - public static bool HasFlags(int value, int flag) - { - return (value & flag) == flag; - } - - public static bool HasReturn(this IMethodSymbol method) - { - if (method.ReturnsVoid || method.ReturnType.ToDisplayString() == typeof(Task).FullName) - { - return false; - } - return true; + // 其他情况直接返回原类型 + return typeSymbol; } public static INamedTypeSymbol GetRealReturnType(this IMethodSymbol method) @@ -182,6 +187,109 @@ internal static class Utils } } + // return namedTypeSymbol; + //} + public static string GetTypeofString(this ITypeSymbol typeSymbol) + { + var type = typeSymbol.GetNullableType(); + if (type is IDynamicTypeSymbol) + { + return typeof(object).ToString(); + } + return type.ToDisplayString(); + } + + // if (typeSymbol.NullableAnnotation == NullableAnnotation.Annotated) + // { + // if (namedTypeSymbol.TypeArguments.Any()) + // { + // return namedTypeSymbol.TypeArguments.First(); + // } + // else + // { + // return namedTypeSymbol.OriginalDefinition; + // } + // } + public static bool HasAttribute(this ISymbol symbol, INamedTypeSymbol attribute) + { + foreach (var attr in symbol.GetAttributes()) + { + var attrClass = attr.AttributeClass; + if (attrClass != null && attrClass.ToDisplayString() == attribute.ToDisplayString()) + { + return true; + } + } + return false; + } + + // if (namedTypeSymbol.SpecialType == SpecialType.System_Nullable_T) + // { + // return namedTypeSymbol.TypeArguments.First(); + // } + public static bool HasAttribute(this ISymbol symbol, string attribute) + { + return symbol.HasAttribute(attribute, out _); + } + + // //Debugger.Launch(); + // var namedTypeSymbol = (INamedTypeSymbol)typeSymbol; + public static bool HasAttribute(this ISymbol symbol, string attribute, out AttributeData attributeData) + { + foreach (var attr in symbol.GetAttributes()) + { + var attrClass = attr.AttributeClass; + if (attrClass != null && attrClass.ToDisplayString() == attribute) + { + attributeData = attr; + return true; + } + } + attributeData = default; + return false; + } + + //public static ITypeSymbol GetNullableType(this ITypeSymbol typeSymbol) + //{ + public static bool HasAttributes(this ISymbol symbol, string attribute, out IEnumerable attributeDatas) + { + var list = new List(); + foreach (var attr in symbol.GetAttributes()) + { + var attrClass = attr.AttributeClass; + if (attrClass != null && attrClass.ToDisplayString() == attribute) + { + list.Add(attr); + } + } + + if (list.Count > 0) + { + attributeDatas = list; + return true; + } + attributeDatas = default; + return false; + } + + public static bool HasFlags(int value, int flag) + { + return (value & flag) == flag; + } + + public static bool HasReturn(this IMethodSymbol method) + { + if (method.ReturnsVoid || method.ReturnType.ToDisplayString() == typeof(Task).FullName || method.ReturnType.ToDisplayString() == typeof(ValueTask).FullName) + { + return false; + } + return true; + } + + public static bool IsDependencyProperty(ITypeSymbol typeSymbol) + { + return typeSymbol.IsInheritFrom(Utils.DependencyPropertyBase); + } public static bool IsInheritFrom(this ITypeSymbol typeSymbol, string baseType) { if (typeSymbol.ToDisplayString() == baseType) @@ -210,6 +318,73 @@ internal static class Utils return false; } + public static bool IsReadOnlyMemory(this ITypeSymbol typeSymbol, out ITypeSymbol elementType) + { + elementType = null; + + // 确保是命名类型符号 + if (typeSymbol is not INamedTypeSymbol fieldType) + return false; + + // 检查原始定义的元数据名称和命名空间 + var originalDefinition = fieldType.OriginalDefinition; + if (originalDefinition.MetadataName != "ReadOnlyMemory`1" || + originalDefinition.ContainingNamespace?.ToDisplayString() != "System") + { + return false; + } + + // 验证类型参数数量并获取元素类型 + if (fieldType.TypeArguments.Length == 1) + { + elementType = fieldType.TypeArguments[0]; + return true; + } + + return false; + } + #region 注释 + public static IEnumerable GetXmlSummary(this ISymbol symbol) + { + var xmlDoc = symbol.GetDocumentationCommentXml(); + if (string.IsNullOrWhiteSpace(xmlDoc)) + { + return []; + } + + return ExtractSummaryFromXml(xmlDoc); + } + + private static IEnumerable ExtractSummaryFromXml(string xmlDoc) + { + try + { + var parsed = XElement.Parse(xmlDoc); + var summaryElement = parsed.Element("summary"); + + if (summaryElement != null) + { + return summaryElement.Value + .Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries) + .Select(line => line.Trim()) + .Where(line => !string.IsNullOrWhiteSpace(line)); + } + return []; + } + catch + { + return []; + } + } + + #endregion + public static bool IsUnmanagedType(this ITypeSymbol type) + { + // 使用HashSet防止递归循环引用 + var visited = new HashSet(SymbolEqualityComparer.Default); + return IsUnmanagedTypeCore(type, visited); + } + public static string MakeIdentifier(string input) { if (string.IsNullOrEmpty(input)) @@ -229,6 +404,11 @@ internal static class Utils return result; } + /// + /// 将字符串首字母转换为小写(驼峰命名)。 + /// + /// 要转换的字符串。 + /// 首字母小写的字符串。 public static string RenameCamelCase(this string str) { var firstChar = str[0]; @@ -244,6 +424,32 @@ internal static class Utils return new string(name); } + /// + /// 将字符串首字母转换为大写(帕斯卡命名)。 + /// + /// 要转换的字符串。 + /// 首字母大写的字符串。 + public static string RenamePascalCase(this string str) + { + str = str.ToLower(); + if (string.IsNullOrEmpty(str)) + { + return string.Empty; + } + + var firstChar = str[0]; + + if (firstChar == char.ToUpperInvariant(firstChar)) + { + return str; + } + + var name = str.ToCharArray(); + name[0] = char.ToUpperInvariant(firstChar); + + return new string(name); + } + private static string GenerateKey(IMethodSymbol method) { var parameterTypes = method.Parameters.Select(p => p.Type).ToArray(); // 使用类型名称 @@ -256,9 +462,92 @@ internal static class Utils var parameterTypeNames = string.Join("_", parameterTypes.Select(t => MakeIdentifier(t.Name))); return $"{MakeIdentifier(methodName)}_{parameterTypeNames}"; } + private static bool IsPrimitiveType(this ITypeSymbol type) + { + var specialType = type.SpecialType; + return specialType switch + { + SpecialType.System_SByte => true, + SpecialType.System_Byte => true, + SpecialType.System_Int16 => true, + SpecialType.System_UInt16 => true, + SpecialType.System_Int32 => true, + SpecialType.System_UInt32 => true, + SpecialType.System_Int64 => true, + SpecialType.System_UInt64 => true, + SpecialType.System_Char => true, + SpecialType.System_Single => true, + SpecialType.System_Double => true, + SpecialType.System_Decimal => true, + SpecialType.System_Boolean => true, + SpecialType.System_IntPtr => true, + SpecialType.System_UIntPtr => true, + _ => false + }; + } + private static bool IsUnmanagedTypeCore(ITypeSymbol type, HashSet visited) + { + // 1. 基本值类型 + if (type.IsPrimitiveType()) + return true; + + // 2. 枚举类型 + if (type.TypeKind == TypeKind.Enum) + return true; + + // 3. 指针类型 + if (type is IPointerTypeSymbol) + return true; + + // 4. 类型参数(带unmanaged约束) + if (type is ITypeParameterSymbol typeParam && typeParam.HasUnmanagedTypeConstraint) + return true; + + // 5. 结构体(递归检查字段) + if (type.IsValueType && type.TypeKind == TypeKind.Struct) + { + // 防止递归循环 + if (!visited.Add(type)) + return false; + + try + { + foreach (var member in type.GetMembers()) + { + if (member is IFieldSymbol field && !field.IsStatic) + { + // 5.1 固定缓冲区处理 + if (field.IsFixedSizeBuffer) + { + var bufferType = (IArrayTypeSymbol)field.Type; + if (!IsUnmanagedTypeCore(bufferType.ElementType, visited)) + return false; + } + // 5.2 常规字段处理 + else if (!IsUnmanagedTypeCore(field.Type, visited)) + { + return false; + } + } + } + return true; // 所有字段均满足非托管条件 + } + finally + { + visited.Remove(type); + } + } + + return false; + } #region 类型判断 + public static bool IsArray(this ITypeSymbol namedTypeSymbol) + { + return namedTypeSymbol.TypeKind == TypeKind.Array; + } + public static bool IsDictionary(this INamedTypeSymbol namedTypeSymbol) { //Debugger.Launch(); @@ -272,6 +561,10 @@ internal static class Utils } return false; } + public static bool IsGuid(this ITypeSymbol typeSymbol) + { + return typeSymbol.IsInheritFrom(typeof(Guid).ToString()); + } public static bool IsList(this INamedTypeSymbol namedTypeSymbol) { @@ -287,7 +580,7 @@ internal static class Utils return false; } - public static bool IsPrimitive(this ITypeSymbol typeSymbol) + public static bool IsPrimitiveAndString(this ITypeSymbol typeSymbol) { switch (typeSymbol.SpecialType) { @@ -320,7 +613,7 @@ internal static class Utils return true; case SpecialType.System_Nullable_T: - return typeSymbol.GetNullableType().IsPrimitive(); + return typeSymbol.GetNullableType().IsPrimitiveAndString(); default: return false; @@ -337,28 +630,82 @@ internal static class Utils return typeSymbol.IsInheritFrom(typeof(TimeSpan).ToString()); } - public static bool IsGuid(this ITypeSymbol typeSymbol) + public static bool IsVoid(this ITypeSymbol typeSymbol) { - return typeSymbol.IsInheritFrom(typeof(Guid).ToString()); + return typeSymbol.SpecialType == SpecialType.System_Void; } - #endregion 类型判断 } - internal abstract class CodeBuilder { public abstract string Id { get; } + public virtual IEnumerable Usings + { + get + { + yield return "using System;"; + yield return "using System.Diagnostics;"; + yield return "using TouchSocket.Core;"; + yield return "using System.Threading;"; + yield return "using System.Collections.Generic;"; + yield return "using System.Threading.Tasks;"; + } + } + public static string ReplaceGeneratedCode(string code) + { + return code.Replace("/*GeneratedCode*/", $"[global::System.CodeDom.Compiler.GeneratedCode(\"TouchSocket.SourceGenerator\",\"{Assembly.GetExecutingAssembly().GetName().Version.ToString()}\")]"); + } + + public static StringBuilder ReplaceGeneratedCode(StringBuilder codeBuilder) + { + return codeBuilder.Replace("/*GeneratedCode*/", $"[global::System.CodeDom.Compiler.GeneratedCode(\"TouchSocket.SourceGenerator\",\"{Assembly.GetExecutingAssembly().GetName().Version.ToString()}\")]"); + } + public abstract string GetFileName(); - public string ToSourceText() + public bool ToSourceText(out string sourceCode) { - var code = this.ToString(); - var tree = CSharpSyntaxTree.ParseText(code); - var root = tree.GetRoot().NormalizeWhitespace(); - var ret = root.ToFullString(); - return ret; + var codeBuilder = this.CreateCodeStringBuilder(); + + if (this.GeneratorCode(codeBuilder)) + { + codeBuilder = ReplaceGeneratedCode(codeBuilder); + + var tree = CSharpSyntaxTree.ParseText(codeBuilder.ToString()); + var root = tree.GetRoot().NormalizeWhitespace(); + var ret = root.ToFullString(); + + sourceCode = ret; + return true; + } + + sourceCode = null; + return false; } + + protected CodeSpace CreateCodeSpace(StringBuilder codeBuilder) + { + return new CodeSpace(codeBuilder); + } + + protected StringBuilder CreateCodeStringBuilder() + { + var codeBuilder = new StringBuilder(); + codeBuilder.AppendLine("/*"); + codeBuilder.AppendLine("此代码由工具直接生成,非必要请不要修改此处代码"); + codeBuilder.AppendLine("*/"); + codeBuilder.AppendLine("#pragma warning disable"); + + foreach (var item in this.Usings) + { + codeBuilder.AppendLine(item); + } + + return codeBuilder; + } + + protected abstract bool GeneratorCode(StringBuilder codeBuilder); } internal class CodeBuilderEqualityComparer : IEqualityComparer where T : CodeBuilder @@ -374,4 +721,48 @@ internal class CodeBuilderEqualityComparer : IEqualityComparer where T : C { return obj.Id.GetHashCode(); } -} \ No newline at end of file +} + +internal abstract class TypeCodeBuilder : CodeBuilder where T : class, ITypeSymbol +{ + public TypeCodeBuilder(T typeSymbol) + { + this.TypeSymbol = typeSymbol; + } + public override string Id => this.TypeSymbol.ToDisplayString(); + + public T TypeSymbol { get; } + + public override string GetFileName() + { + return $"{this.Id}.g.cs"; + } + + protected CodeSpace CreateNamespace(StringBuilder codeBuilder) + { + if (this.TypeSymbol.ContainingNamespace.IsGlobalNamespace) + { + return new CodeSpace(); + } + + codeBuilder.AppendLine($"namespace {this.TypeSymbol.ContainingNamespace}"); + return new CodeSpace(codeBuilder); + } + + protected CodeSpace CreateNamespace(StringBuilder codeBuilder, string namespaceString) + { + codeBuilder.AppendLine($"namespace {namespaceString}"); + return new CodeSpace(codeBuilder); + } + + protected CodeSpace CreateNamespaceIfNotGlobalNamespace(StringBuilder codeBuilder, string namespaceString) + { + if (this.TypeSymbol.ContainingNamespace.IsGlobalNamespace) + { + return new CodeSpace(); + } + + codeBuilder.AppendLine($"namespace {namespaceString}"); + return new CodeSpace(codeBuilder); + } +} diff --git a/src/TouchSocket.WebApi.SourceGenerator/TouchSocket.WebApi.SourceGenerator.csproj b/src/TouchSocket.WebApi.SourceGenerator/TouchSocket.WebApi.SourceGenerator.csproj index 21cf0e4da..da0ff4299 100644 --- a/src/TouchSocket.WebApi.SourceGenerator/TouchSocket.WebApi.SourceGenerator.csproj +++ b/src/TouchSocket.WebApi.SourceGenerator/TouchSocket.WebApi.SourceGenerator.csproj @@ -18,6 +18,6 @@ - + diff --git a/src/TouchSocket.WebApi.SourceGenerator/WebApiClientCodeBuilder.cs b/src/TouchSocket.WebApi.SourceGenerator/WebApiClientCodeBuilder.cs index 147f7cc83..0ec8c13b9 100644 --- a/src/TouchSocket.WebApi.SourceGenerator/WebApiClientCodeBuilder.cs +++ b/src/TouchSocket.WebApi.SourceGenerator/WebApiClientCodeBuilder.cs @@ -11,7 +11,6 @@ //------------------------------------------------------------------------------ using Microsoft.CodeAnalysis; -using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -47,6 +46,7 @@ internal sealed class WebApiClientCodeBuilder : RpcClientCodeBuilder yield return "using System.Threading.Tasks;"; yield return "using TouchSocket.Http;"; yield return "using TouchSocket.WebApi;"; + yield return "using System.Collections.Generic;"; } } @@ -90,43 +90,43 @@ internal sealed class WebApiClientCodeBuilder : RpcClientCodeBuilder var parameters = method.Parameters.Where(a => (!RpcUtils.IsCallContext(a)) && (!RpcUtils.IsFromServices(a))).ToImmutableArray(); //生成开始 - var codeString = new StringBuilder(); + var codeBuilder = new StringBuilder(); if (allowSync) { - codeString.AppendLine("///"); - codeString.AppendLine($"///{this.GetDescription(method)}"); - codeString.AppendLine("///"); - codeString.Append($"public static {returnType} {methodName}"); - codeString.Append("(");//方法参数 + codeBuilder.AppendLine("///"); + codeBuilder.AppendLine($"///{this.GetDescription(method)}"); + codeBuilder.AppendLine("///"); + codeBuilder.Append($"public static {returnType} {methodName}"); + codeBuilder.Append("(");//方法参数 - codeString.Append($"this TClient client"); + codeBuilder.Append($"this TClient client"); - codeString.Append(","); + codeBuilder.Append(","); for (var i = 0; i < parameters.Length; i++) { if (i > 0) { - codeString.Append(","); + codeBuilder.Append(","); } - codeString.Append(this.GetMethodParameterString(parameters[i])); + codeBuilder.Append(this.GetMethodParameterString(parameters[i])); } if (parameters.Length > 0) { - codeString.Append(","); + codeBuilder.Append(","); } - codeString.Append("IInvokeOption invokeOption = default"); - codeString.AppendLine($") where TClient:{string.Join(",", genericConstraintTypes)}"); + codeBuilder.Append("InvokeOption invokeOption = default"); + codeBuilder.AppendLine($") where TClient:{string.Join(",", genericConstraintTypes)}"); - codeString.AppendLine("{");//方法开始 + codeBuilder.AppendLine("{");//方法开始 var webApiParameterInfos = parameters.Select(p => new WebApiParameterInfo(p)); - codeString.AppendLine("var _request=new WebApiRequest();"); - codeString.AppendLine($"_request.Method = (HttpMethodType){webApiMethod};"); - codeString.AppendLine($"_request.Headers = {this.GetFromHeaderString(method, webApiParameterInfos)};"); - codeString.AppendLine($"_request.Querys = {this.GetFromQueryString(webApiParameterInfos)};"); - codeString.AppendLine($"_request.Forms = {this.GetFromFormString(webApiParameterInfos)};"); + codeBuilder.AppendLine("var _request=new WebApiRequest();"); + codeBuilder.AppendLine($"_request.Method = \"{webApiMethod}\";"); + codeBuilder.AppendLine($"_request.Headers = {this.GetFromHeaderString(method, webApiParameterInfos)};"); + codeBuilder.AppendLine($"_request.Querys = {this.GetFromQueryString(webApiParameterInfos)};"); + codeBuilder.AppendLine($"_request.Forms = {this.GetFromFormString(webApiParameterInfos)};"); string bodyName = default; @@ -134,7 +134,7 @@ internal sealed class WebApiClientCodeBuilder : RpcClientCodeBuilder foreach (var webApiParameterInfo in webApiParameterInfos) { - if (webApiParameterInfo.Parameter.Type.IsPrimitive()) + if (webApiParameterInfo.Parameter.Type.IsPrimitiveAndString()) { //Debugger.Launch(); if (webApiParameterInfo.IsFromBody) @@ -150,69 +150,69 @@ internal sealed class WebApiClientCodeBuilder : RpcClientCodeBuilder if (bodyName != null) { - codeString.AppendLine($"_request.Body = {bodyName};"); + codeBuilder.AppendLine($"_request.Body = {bodyName};"); } if (method.HasReturn()) { - codeString.AppendLine($"return ({returnType}) client.Invoke(\"{invokeKey}\",typeof({returnType}),invokeOption,_request);"); + codeBuilder.AppendLine($"return ({returnType}) client.Invoke(\"{invokeKey}\",typeof({returnType}),invokeOption,_request);"); } else { - codeString.AppendLine($"client.Invoke(\"{invokeKey}\",default,invokeOption,_request);"); + codeBuilder.AppendLine($"client.Invoke(\"{invokeKey}\",default,invokeOption,_request);"); } - codeString.AppendLine("}"); + codeBuilder.AppendLine("}"); } if (allowAsync) { //以下生成异步 - codeString.AppendLine("///"); - codeString.AppendLine($"///{this.GetDescription(method)}"); - codeString.AppendLine("///"); + codeBuilder.AppendLine("///"); + codeBuilder.AppendLine($"///{this.GetDescription(method)}"); + codeBuilder.AppendLine("///"); if (method.HasReturn()) { - codeString.Append($"public static async Task<{returnType}> {methodName}Async"); + codeBuilder.Append($"public static async Task<{returnType}> {methodName}Async"); } else { - codeString.Append($"public static Task {methodName}Async"); + codeBuilder.Append($"public static Task {methodName}Async"); } - codeString.Append("(");//方法参数 + codeBuilder.Append("(");//方法参数 - codeString.Append($"this TClient client"); + codeBuilder.Append($"this TClient client"); - codeString.Append(","); + codeBuilder.Append(","); for (var i = 0; i < parameters.Length; i++) { if (i > 0) { - codeString.Append(","); + codeBuilder.Append(","); } - codeString.Append(this.GetMethodParameterString(parameters[i])); + codeBuilder.Append(this.GetMethodParameterString(parameters[i])); } if (parameters.Length > 0) { - codeString.Append(","); + codeBuilder.Append(","); } - codeString.Append("IInvokeOption invokeOption = default"); - codeString.AppendLine($") where TClient:{string.Join(",", genericConstraintTypes)}"); + codeBuilder.Append("InvokeOption invokeOption = default"); + codeBuilder.AppendLine($") where TClient:{string.Join(",", genericConstraintTypes)}"); - codeString.AppendLine("{");//方法开始 + codeBuilder.AppendLine("{");//方法开始 var webApiParameterInfos = parameters.Select(p => new WebApiParameterInfo(p)); - codeString.AppendLine("var _request=new WebApiRequest();"); - codeString.AppendLine($"_request.Method = (HttpMethodType){webApiMethod};"); - codeString.AppendLine($"_request.Headers = {this.GetFromHeaderString(method, webApiParameterInfos)};"); - codeString.AppendLine($"_request.Querys = {this.GetFromQueryString(webApiParameterInfos)};"); - codeString.AppendLine($"_request.Forms = {this.GetFromFormString(webApiParameterInfos)};"); + codeBuilder.AppendLine("var _request=new WebApiRequest();"); + codeBuilder.AppendLine($"_request.Method = \"{webApiMethod}\";"); + codeBuilder.AppendLine($"_request.Headers = {this.GetFromHeaderString(method, webApiParameterInfos)};"); + codeBuilder.AppendLine($"_request.Querys = {this.GetFromQueryString(webApiParameterInfos)};"); + codeBuilder.AppendLine($"_request.Forms = {this.GetFromFormString(webApiParameterInfos)};"); string bodyName = default; foreach (var webApiParameterInfo in webApiParameterInfos) { - if (webApiParameterInfo.Parameter.Type.IsPrimitive()) + if (webApiParameterInfo.Parameter.Type.IsPrimitiveAndString()) { if (webApiParameterInfo.IsFromBody) { @@ -227,21 +227,21 @@ internal sealed class WebApiClientCodeBuilder : RpcClientCodeBuilder if (bodyName != null) { - codeString.AppendLine($"_request.Body = {bodyName};"); + codeBuilder.AppendLine($"_request.Body = {bodyName};"); } if (method.HasReturn()) { - codeString.AppendLine($"return ({returnType}) await client.InvokeAsync(\"{invokeKey}\",typeof({returnType}),invokeOption,_request);"); + codeBuilder.AppendLine($"return ({returnType}) await client.InvokeAsync(\"{invokeKey}\",typeof({returnType}),invokeOption,_request);"); } else { - codeString.AppendLine($"return client.InvokeAsync(\"{invokeKey}\",default,invokeOption,_request);"); + codeBuilder.AppendLine($"return client.InvokeAsync(\"{invokeKey}\",default,invokeOption,_request);"); } - codeString.AppendLine("}"); + codeBuilder.AppendLine("}"); } - return codeString.ToString(); + return codeBuilder.ToString(); } protected override string GetInheritedClassName(INamedTypeSymbol rpcApi) @@ -253,9 +253,7 @@ internal sealed class WebApiClientCodeBuilder : RpcClientCodeBuilder { if (key == "Method") { - var value = Convert.ToInt32(typedConstant.Value); - - return ((HttpMethodType)value).ToString(); + return typedConstant.Value.ToString(); } return base.ReplacePattern(key, typedConstant); } @@ -268,12 +266,12 @@ internal sealed class WebApiClientCodeBuilder : RpcClientCodeBuilder return "null"; } - var codeString = new StringBuilder(); - codeString.Append("new KeyValuePair[] {"); - codeString.Append(string.Join(",", parameterInfos.Select(a => $"new KeyValuePair(\"{a.FromFormName}\",{this.GetParameterToString(a.Parameter)})"))); - codeString.Append("}"); + var codeBuilder = new StringBuilder(); + codeBuilder.Append("new KeyValuePair[] {"); + codeBuilder.Append(string.Join(",", parameterInfos.Select(a => $"new KeyValuePair(\"{a.FromFormName}\",{this.GetParameterToString(a.Parameter)})"))); + codeBuilder.Append("}"); - return codeString.ToString(); + return codeBuilder.ToString(); } private string GetFromHeaderString(IMethodSymbol rpcMethod, IEnumerable webApiParameterInfos) @@ -289,11 +287,11 @@ internal sealed class WebApiClientCodeBuilder : RpcClientCodeBuilder if (list.Count > 0) { - var codeString = new StringBuilder(); - codeString.Append("new KeyValuePair[] {"); - codeString.Append(string.Join(",", list)); - codeString.Append("}"); - return codeString.ToString(); + var codeBuilder = new StringBuilder(); + codeBuilder.Append("new KeyValuePair[] {"); + codeBuilder.Append(string.Join(",", list)); + codeBuilder.Append("}"); + return codeBuilder.ToString(); } return "null"; } @@ -302,7 +300,7 @@ internal sealed class WebApiClientCodeBuilder : RpcClientCodeBuilder { var parameterInfos = webApiParameterInfos.Where(a => { - if (a.Parameter.Type.IsPrimitive()) + if (a.Parameter.Type.IsPrimitiveAndString()) { if (a.IsFromBody) { @@ -328,12 +326,12 @@ internal sealed class WebApiClientCodeBuilder : RpcClientCodeBuilder return "null"; } - var codeString = new StringBuilder(); - codeString.Append("new KeyValuePair[] {"); - codeString.Append(string.Join(",", parameterInfos.Select(a => $"new KeyValuePair(\"{a.FromQueryName ?? a.Parameter.Name}\",{this.GetParameterToString(a.Parameter)})"))); - codeString.Append("}"); + var codeBuilder = new StringBuilder(); + codeBuilder.Append("new KeyValuePair[] {"); + codeBuilder.Append(string.Join(",", parameterInfos.Select(a => $"new KeyValuePair(\"{a.FromQueryName ?? a.Parameter.Name}\",{this.GetParameterToString(a.Parameter)})"))); + codeBuilder.Append("}"); - return codeString.ToString(); + return codeBuilder.ToString(); } private string GetMethodType(Dictionary namedArguments) diff --git a/src/TouchSocket.WebApi.SourceGenerator/WebApiClientSourceGenerator.cs b/src/TouchSocket.WebApi.SourceGenerator/WebApiClientSourceGenerator.cs index 1b3c4dad1..a9cb06960 100644 --- a/src/TouchSocket.WebApi.SourceGenerator/WebApiClientSourceGenerator.cs +++ b/src/TouchSocket.WebApi.SourceGenerator/WebApiClientSourceGenerator.cs @@ -12,36 +12,83 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Collections.Generic; using System.Linq; +using TouchSocket.Rpc; namespace TouchSocket; [Generator] -public class WebApiClientSourceGenerator : ISourceGenerator +public class WebApiClientSourceGenerator : IIncrementalGenerator { - public void Initialize(GeneratorInitializationContext context) + public void Initialize(IncrementalGeneratorInitializationContext context) { - context.RegisterForSyntaxNotifications(() => new WebApiClientSyntaxReceiver()); - } + // 第一步:收集所有接口声明语法节点 + var interfaceDeclarations = context.SyntaxProvider + .CreateSyntaxProvider( + predicate: static (s, _) => s is InterfaceDeclarationSyntax, + transform: static (ctx, _) => (InterfaceDeclarationSyntax)ctx.Node); - public void Execute(GeneratorExecutionContext context) - { - var s = context.Compilation.GetMetadataReference(context.Compilation.Assembly); - - if (context.SyntaxReceiver is WebApiClientSyntaxReceiver receiver) - { - var builders = receiver - .GetRpcApiTypes(context.Compilation) - .Select(i => new WebApiClientCodeBuilder(i)) - .Distinct(CodeBuilderEqualityComparer.Default); - //Debugger.Launch(); - foreach (var builder in builders) + // 第二步:将语法节点转换为符号并过滤有效接口 + var interfacesWithSymbol = context.CompilationProvider.Combine(interfaceDeclarations.Collect()) + .SelectMany(static (tuple, _) => { - var tree = CSharpSyntaxTree.ParseText(builder.ToSourceText()); - var root = tree.GetRoot().NormalizeWhitespace(); - var ret = root.ToFullString(); - context.AddSource($"{builder.GetFileName()}.g.cs", ret); - } - } + var (compilation, interfaces) = tuple; + var results = new List(); + var attributeSymbol = RpcUtils.GetGeneratorRpcProxyAttribute(compilation); + + foreach (var interfaceSyntax in interfaces) + { + var model = compilation.GetSemanticModel(interfaceSyntax.SyntaxTree); + var interfaceSymbol = model.GetDeclaredSymbol(interfaceSyntax); + + if (interfaceSymbol != null && + attributeSymbol != null && + RpcUtils.IsRpcApiInterface(interfaceSymbol)) + { + results.Add(interfaceSymbol); + } + } + return results.Distinct(SymbolEqualityComparer.Default); + }); + + // 第三步:生成源代码 + context.RegisterSourceOutput(interfacesWithSymbol, + static (productionContext, interfaceSymbol) => + { + var builder = new WebApiClientCodeBuilder((INamedTypeSymbol)interfaceSymbol); + productionContext.AddSource(builder); + }); } -} \ No newline at end of file +} + +//[Generator] +//public class WebApiClientSourceGenerator : ISourceGenerator +//{ +// public void Initialize(GeneratorInitializationContext context) +// { +// context.RegisterForSyntaxNotifications(() => new WebApiClientSyntaxReceiver()); +// } + +// public void Execute(GeneratorExecutionContext context) +// { +// var s = context.Compilation.GetMetadataReference(context.Compilation.Assembly); + +// if (context.SyntaxReceiver is WebApiClientSyntaxReceiver receiver) +// { +// var builders = receiver +// .GetRpcApiTypes(context.Compilation) +// .Select(i => new WebApiClientCodeBuilder(i)) +// .Distinct(CodeBuilderEqualityComparer.Default); +// //Debugger.Launch(); +// foreach (var builder in builders) +// { +// var tree = CSharpSyntaxTree.ParseText(builder.ToSourceText()); +// var root = tree.GetRoot().NormalizeWhitespace(); +// var ret = root.ToFullString(); +// context.AddSource($"{builder.GetFileName()}.g.cs", ret); +// } +// } +// } +//} \ No newline at end of file diff --git a/src/TouchSocket.WebApi.SourceGenerator/WebApiClientSyntaxReceiver.cs b/src/TouchSocket.WebApi.SourceGenerator/WebApiClientSyntaxReceiver.cs deleted file mode 100644 index eb940381f..000000000 --- a/src/TouchSocket.WebApi.SourceGenerator/WebApiClientSyntaxReceiver.cs +++ /dev/null @@ -1,51 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using System.Collections.Generic; -using TouchSocket.Rpc; - -namespace TouchSocket; - -internal sealed class WebApiClientSyntaxReceiver : ISyntaxReceiver -{ - - private readonly List interfaceSyntaxList = new List(); - - void ISyntaxReceiver.OnVisitSyntaxNode(SyntaxNode syntaxNode) - { - if (syntaxNode is InterfaceDeclarationSyntax syntax) - { - this.interfaceSyntaxList.Add(syntax); - } - } - - public IEnumerable GetRpcApiTypes(Compilation compilation) - { - //Debugger.Launch(); - var generatorRpcProxyAttribute = RpcUtils.GetGeneratorRpcProxyAttribute(compilation); - if (generatorRpcProxyAttribute == null) - { - yield break; - } - foreach (var interfaceSyntax in this.interfaceSyntaxList) - { - var @interface = compilation.GetSemanticModel(interfaceSyntax.SyntaxTree).GetDeclaredSymbol(interfaceSyntax); - if (@interface != null && RpcUtils.IsRpcApiInterface(@interface)) - { - yield return @interface; - } - } - } -} \ No newline at end of file diff --git a/src/TouchSocket.WebApi.Swagger/Attributes/SwaggerDescriptionAttribute.cs b/src/TouchSocket.WebApi.Swagger/Attributes/SwaggerDescriptionAttribute.cs index 0e7f29d73..da9331251 100644 --- a/src/TouchSocket.WebApi.Swagger/Attributes/SwaggerDescriptionAttribute.cs +++ b/src/TouchSocket.WebApi.Swagger/Attributes/SwaggerDescriptionAttribute.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.WebApi.Swagger; /// diff --git a/src/TouchSocket.WebApi.Swagger/Common/OpenApiComponent.cs b/src/TouchSocket.WebApi.Swagger/Common/OpenApiComponent.cs index c9841b9e5..838fa47f7 100644 --- a/src/TouchSocket.WebApi.Swagger/Common/OpenApiComponent.cs +++ b/src/TouchSocket.WebApi.Swagger/Common/OpenApiComponent.cs @@ -11,7 +11,6 @@ //------------------------------------------------------------------------------ using Newtonsoft.Json; -using System.Collections.Generic; namespace TouchSocket.WebApi.Swagger; diff --git a/src/TouchSocket.WebApi.Swagger/Common/OpenApiPath.cs b/src/TouchSocket.WebApi.Swagger/Common/OpenApiPath.cs index 6256f22d6..be58294d7 100644 --- a/src/TouchSocket.WebApi.Swagger/Common/OpenApiPath.cs +++ b/src/TouchSocket.WebApi.Swagger/Common/OpenApiPath.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Collections.Generic; - namespace TouchSocket.WebApi.Swagger; internal class OpenApiPath : Dictionary diff --git a/src/TouchSocket.WebApi.Swagger/Common/OpenApiPathValue.cs b/src/TouchSocket.WebApi.Swagger/Common/OpenApiPathValue.cs index 92d492711..0717fef83 100644 --- a/src/TouchSocket.WebApi.Swagger/Common/OpenApiPathValue.cs +++ b/src/TouchSocket.WebApi.Swagger/Common/OpenApiPathValue.cs @@ -11,7 +11,6 @@ //------------------------------------------------------------------------------ using Newtonsoft.Json; -using System.Collections.Generic; namespace TouchSocket.WebApi.Swagger; diff --git a/src/TouchSocket.WebApi.Swagger/Common/OpenApiRequestBody.cs b/src/TouchSocket.WebApi.Swagger/Common/OpenApiRequestBody.cs index b33c391e5..29e387dd8 100644 --- a/src/TouchSocket.WebApi.Swagger/Common/OpenApiRequestBody.cs +++ b/src/TouchSocket.WebApi.Swagger/Common/OpenApiRequestBody.cs @@ -11,7 +11,6 @@ //------------------------------------------------------------------------------ using Newtonsoft.Json; -using System.Collections.Generic; namespace TouchSocket.WebApi.Swagger; diff --git a/src/TouchSocket.WebApi.Swagger/Common/OpenApiResponse.cs b/src/TouchSocket.WebApi.Swagger/Common/OpenApiResponse.cs index c20dfbd34..109fd97ec 100644 --- a/src/TouchSocket.WebApi.Swagger/Common/OpenApiResponse.cs +++ b/src/TouchSocket.WebApi.Swagger/Common/OpenApiResponse.cs @@ -11,7 +11,6 @@ //------------------------------------------------------------------------------ using Newtonsoft.Json; -using System.Collections.Generic; namespace TouchSocket.WebApi.Swagger; diff --git a/src/TouchSocket.WebApi.Swagger/Common/OpenApiRoot.cs b/src/TouchSocket.WebApi.Swagger/Common/OpenApiRoot.cs index 3bbdaacb5..51b5d2124 100644 --- a/src/TouchSocket.WebApi.Swagger/Common/OpenApiRoot.cs +++ b/src/TouchSocket.WebApi.Swagger/Common/OpenApiRoot.cs @@ -11,7 +11,6 @@ //------------------------------------------------------------------------------ using Newtonsoft.Json; -using System.Collections.Generic; namespace TouchSocket.WebApi.Swagger; diff --git a/src/TouchSocket.WebApi.Swagger/Common/OpenApiSchema.cs b/src/TouchSocket.WebApi.Swagger/Common/OpenApiSchema.cs index 1a4bc87a6..60c96091e 100644 --- a/src/TouchSocket.WebApi.Swagger/Common/OpenApiSchema.cs +++ b/src/TouchSocket.WebApi.Swagger/Common/OpenApiSchema.cs @@ -11,7 +11,6 @@ //------------------------------------------------------------------------------ using Newtonsoft.Json; -using System.Collections.Generic; namespace TouchSocket.WebApi.Swagger; diff --git a/src/TouchSocket.WebApi.Swagger/Extensions/SwaggerPluginsManagerExtension.cs b/src/TouchSocket.WebApi.Swagger/Extensions/SwaggerPluginsManagerExtension.cs index 34b142c84..a67402777 100644 --- a/src/TouchSocket.WebApi.Swagger/Extensions/SwaggerPluginsManagerExtension.cs +++ b/src/TouchSocket.WebApi.Swagger/Extensions/SwaggerPluginsManagerExtension.cs @@ -10,22 +10,34 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.WebApi.Swagger; /// -/// SwaggerPluginManagerExtension +/// /// public static class SwaggerPluginManagerExtension { /// /// 使用插件。 /// - /// - /// - public static SwaggerPlugin UseSwagger(this IPluginManager pluginManager) + /// 插件管理器 + /// Swagger配置选项 + /// Swagger插件实例 + public static void UseSwagger(this IPluginManager pluginManager, Action options) { - return pluginManager.Add(); + SwaggerOption option = new(); + options.Invoke(option); + SwaggerPlugin swaggerPlugin = new(pluginManager.Resolver.Resolve(), option); + pluginManager.Add(swaggerPlugin); + } + + /// + /// 使用插件。 + /// + /// 插件管理器 + /// Swagger插件实例 + public static void UseSwagger(this IPluginManager pluginManager) + { + pluginManager.UseSwagger(options => { }); } } \ No newline at end of file diff --git a/src/TouchSocket.WebApi.Swagger/Options/SwaggerOption.cs b/src/TouchSocket.WebApi.Swagger/Options/SwaggerOption.cs new file mode 100644 index 000000000..061e73a3f --- /dev/null +++ b/src/TouchSocket.WebApi.Swagger/Options/SwaggerOption.cs @@ -0,0 +1,54 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TouchSocket.WebApi.Swagger; + +public class SwaggerOption +{ + + /// + /// 是否在浏览器打开Swagger页面 + /// + public bool LaunchBrowser { get; set; } + + /// + /// 访问Swagger的前缀,默认“swagger” + /// + public string Prefix { get; set; } = "swagger"; + + /// + /// 设置访问Swagger的前缀,默认“swagger” + /// + /// + /// + public void SetPrefix(string value) + { + this.Prefix = value; + } + + /// + /// 在浏览器打开Swagger页面 + /// + /// + public void UseLaunchBrowser() + { + this.LaunchBrowser = true; + } + + +} diff --git a/src/TouchSocket.WebApi.Swagger/Plugins/SwaggerPlugin.cs b/src/TouchSocket.WebApi.Swagger/Plugins/SwaggerPlugin.cs index 4e654fdeb..b433a9874 100644 --- a/src/TouchSocket.WebApi.Swagger/Plugins/SwaggerPlugin.cs +++ b/src/TouchSocket.WebApi.Swagger/Plugins/SwaggerPlugin.cs @@ -11,16 +11,9 @@ //------------------------------------------------------------------------------ using Newtonsoft.Json; -using System; using System.Collections; -using System.Collections.Generic; using System.Diagnostics; -using System.IO; -using System.Linq; using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Http; using TouchSocket.Rpc; using TouchSocket.Sockets; @@ -30,30 +23,32 @@ namespace TouchSocket.WebApi.Swagger; /// /// SwaggerPlugin /// -public sealed class SwaggerPlugin : PluginBase, IServerStartedPlugin, IHttpPlugin +internal sealed class SwaggerPlugin : PluginBase, IServerStartedPlugin, IHttpPlugin { private readonly ILog m_logger; - private readonly IResolver m_resolver; - private readonly Dictionary m_swagger = new Dictionary(); + + private readonly Dictionary> m_swagger = new(); /// /// SwaggerPlugin /// - public SwaggerPlugin(IResolver resolver, ILog logger) + public SwaggerPlugin(ILog logger, SwaggerOption options) { - this.m_resolver = resolver; this.m_logger = logger; + + this.LaunchBrowser = options.LaunchBrowser; + this.Prefix = options.Prefix; } /// /// 是否在浏览器打开Swagger页面 /// - public bool LaunchBrowser { get; set; } + public bool LaunchBrowser { get; } /// /// 访问Swagger的前缀,默认“swagger” /// - public string Prefix { get; set; } = "swagger"; + public string Prefix { get; } /// public async Task OnServerStarted(IServiceBase sender, ServiceStateEventArgs e) @@ -76,7 +71,7 @@ public sealed class SwaggerPlugin : PluginBase, IServerStartedPlugin, IHttpPlugi { using (var stream = assembly.GetManifestResourceStream(item)) { - var bytes = stream.ReadAllToByteArray(); + ReadOnlyMemory bytes = stream.ReadAllToByteArray(); var prefix = this.Prefix.IsNullOrEmpty() ? "/" : (this.Prefix.StartsWith("/") ? this.Prefix : $"/{this.Prefix}"); var name = item.Replace("TouchSocket.WebApi.Swagger.api.", string.Empty); if (name == "openapi.json") @@ -119,27 +114,6 @@ public sealed class SwaggerPlugin : PluginBase, IServerStartedPlugin, IHttpPlugi } } - /// - /// 设置访问Swagger的前缀,默认“swagger” - /// - /// - /// - public SwaggerPlugin SetPrefix(string value) - { - this.Prefix = value; - return this; - } - - /// - /// 在浏览器打开Swagger页面 - /// - /// - public SwaggerPlugin UseLaunchBrowser() - { - this.LaunchBrowser = true; - return this; - } - /// /// 检查类型是否是 IFormFileCollection 类型 /// @@ -213,7 +187,7 @@ public sealed class SwaggerPlugin : PluginBase, IServerStartedPlugin, IHttpPlugi type = type.GetGenericArguments()[0]; } - if (type.IsPrimitive || type == typeof(string)) + if (IsSimpleType(type)) { return; } @@ -234,7 +208,21 @@ public sealed class SwaggerPlugin : PluginBase, IServerStartedPlugin, IHttpPlugi } } - private byte[] BuildOpenApi(WebApiParserPlugin webApiParserPlugin) + private static bool IsSimpleType(Type type) + { + if (type.IsPrimitive || type == TouchSocketCoreUtility.StringType) + { + return true; + } + + if (type.IsNullableType(out var actualType)) + { + return IsSimpleType(actualType); + } + return false; + } + + private ReadOnlyMemory BuildOpenApi(WebApiParserPlugin webApiParserPlugin) { var openApiRoot = new OpenApiRoot(); openApiRoot.Info = new OpenApiInfo(); @@ -536,10 +524,27 @@ public sealed class SwaggerPlugin : PluginBase, IServerStartedPlugin, IHttpPlugi { var schema = this.CreateSchema(type); var properties = new Dictionary(); + + foreach (var propertyInfo in type.GetProperties(BindingFlags.Instance | BindingFlags.Public)) { - properties.Add(propertyInfo.Name, this.CreateProperty(propertyInfo.PropertyType, propertyInfo.GetDescription())); + // 2. 如果已经有同名属性,优先选择当前类的(隐藏/重写 new 的情况) + if (properties.ContainsKey(propertyInfo.Name)) + { + if (propertyInfo.DeclaringType == type) + { + // 覆盖基类的属性 + properties[propertyInfo.Name] = this.CreateProperty(propertyInfo.PropertyType, propertyInfo.GetDescription()); + } + // 否则跳过(基类的被丢弃) + } + else + { + properties.Add(propertyInfo.Name, this.CreateProperty(propertyInfo.PropertyType, propertyInfo.GetDescription())); + } } + + schema.Properties = properties.Count == 0 ? default : properties; components.Schemas.TryAdd(this.GetSchemaName(type), schema); } @@ -549,7 +554,7 @@ public sealed class SwaggerPlugin : PluginBase, IServerStartedPlugin, IHttpPlugi private string GetIn(ParameterInfo parameter) { var type = parameter.ParameterType; - if (!type.IsPrimitive()) + if (!IsSimpleType(type)) { return default; } @@ -674,6 +679,11 @@ public sealed class SwaggerPlugin : PluginBase, IServerStartedPlugin, IHttpPlugi return OpenApiDataTypes.Any; } + if (type.IsNullableType(out var actualType)) + { + return this.ParseDataTypes(actualType); + } + return type switch { // 字符串 diff --git a/src/TouchSocket.WebApi.Swagger/Readme.md b/src/TouchSocket.WebApi.Swagger/Readme.md index 85e85405a..6a28c3ad4 100644 --- a/src/TouchSocket.WebApi.Swagger/Readme.md +++ b/src/TouchSocket.WebApi.Swagger/Readme.md @@ -11,14 +11,14 @@ TouchSocket.WebApi.Swagger 是适用于 TouchSocket.WebApi 的 Swagger 页面组 详细的说明文档请访问:[https://touchsocket.net/](https://touchsocket.net/) ## 支持的目标框架 + - net481 -- net45 - net462 - net472 - netstandard2.0 - netstandard2.1 - net6.0 -- net9.0 +- net10.0 - net8.0 ## 贡献与反馈 diff --git a/src/TouchSocket.WebApi.Swagger/TouchSocket.WebApi.Swagger.csproj b/src/TouchSocket.WebApi.Swagger/TouchSocket.WebApi.Swagger.csproj index 8b8de6926..9adc6d36c 100644 --- a/src/TouchSocket.WebApi.Swagger/TouchSocket.WebApi.Swagger.csproj +++ b/src/TouchSocket.WebApi.Swagger/TouchSocket.WebApi.Swagger.csproj @@ -1,7 +1,7 @@  - net481;net45;net462;net472;netstandard2.0;netstandard2.1;net6.0;net9.0;net8.0 + net462;netstandard2.0;netstandard2.1;net6.0;net10.0;net8.0 Swagger;TouchSocket 这是适用于TouchSocket.WebApi的Swagger页面,可以直接在浏览器调试WebApi。 diff --git a/src/TouchSocket.WebApi/Attribute/EnableCorsAttribute.cs b/src/TouchSocket.WebApi/Attribute/EnableCorsAttribute.cs index 4c947e62e..216ed3b32 100644 --- a/src/TouchSocket.WebApi/Attribute/EnableCorsAttribute.cs +++ b/src/TouchSocket.WebApi/Attribute/EnableCorsAttribute.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Http; using TouchSocket.Rpc; diff --git a/src/TouchSocket.WebApi/Attribute/FromBodyAttribute.cs b/src/TouchSocket.WebApi/Attribute/FromBodyAttribute.cs index 482f5f625..11beb4b51 100644 --- a/src/TouchSocket.WebApi/Attribute/FromBodyAttribute.cs +++ b/src/TouchSocket.WebApi/Attribute/FromBodyAttribute.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.WebApi; /// diff --git a/src/TouchSocket.WebApi/Attribute/FromFormAttribute.cs b/src/TouchSocket.WebApi/Attribute/FromFormAttribute.cs index bcc39b5a3..1c771015b 100644 --- a/src/TouchSocket.WebApi/Attribute/FromFormAttribute.cs +++ b/src/TouchSocket.WebApi/Attribute/FromFormAttribute.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.WebApi; /// diff --git a/src/TouchSocket.WebApi/Attribute/FromHeaderAttribute.cs b/src/TouchSocket.WebApi/Attribute/FromHeaderAttribute.cs index c10d9fa4d..fef0b661c 100644 --- a/src/TouchSocket.WebApi/Attribute/FromHeaderAttribute.cs +++ b/src/TouchSocket.WebApi/Attribute/FromHeaderAttribute.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.WebApi; /// diff --git a/src/TouchSocket.WebApi/Attribute/FromQueryAttribute.cs b/src/TouchSocket.WebApi/Attribute/FromQueryAttribute.cs index f0978f500..482e8f7a7 100644 --- a/src/TouchSocket.WebApi/Attribute/FromQueryAttribute.cs +++ b/src/TouchSocket.WebApi/Attribute/FromQueryAttribute.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.WebApi; /// diff --git a/src/TouchSocket.WebApi/Attribute/RegexRouterAttribute.cs b/src/TouchSocket.WebApi/Attribute/RegexRouterAttribute.cs index a06f8cfce..925fa41e8 100644 --- a/src/TouchSocket.WebApi/Attribute/RegexRouterAttribute.cs +++ b/src/TouchSocket.WebApi/Attribute/RegexRouterAttribute.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.WebApi; /// diff --git a/src/TouchSocket.WebApi/Attribute/RouterAttribute.cs b/src/TouchSocket.WebApi/Attribute/RouterAttribute.cs index 98238f77f..bf0e86ece 100644 --- a/src/TouchSocket.WebApi/Attribute/RouterAttribute.cs +++ b/src/TouchSocket.WebApi/Attribute/RouterAttribute.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.WebApi; /// diff --git a/src/TouchSocket.WebApi/Attribute/WebApiAttribute.cs b/src/TouchSocket.WebApi/Attribute/WebApiAttribute.cs index 8a3013544..a357b00e1 100644 --- a/src/TouchSocket.WebApi/Attribute/WebApiAttribute.cs +++ b/src/TouchSocket.WebApi/Attribute/WebApiAttribute.cs @@ -10,11 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TouchSocket.Core; using TouchSocket.Rpc; namespace TouchSocket.WebApi; @@ -28,17 +23,6 @@ namespace TouchSocket.WebApi; [DynamicMethod] public sealed class WebApiAttribute : RpcAttribute { - /// - /// 构造函数,用于初始化WebApiAttribute对象并设置HTTP方法类型。 - /// - /// 指定HTTP请求的方法类型,如GET、POST等。 - [Obsolete("由于构造函数直接设置参数在源生成时效果不一致,所以取消该方式,如果想要设置参数,请使用属性直接设置,例如:MethodInvoke=true", true)] - public WebApiAttribute(HttpMethodType method) : this() - { - // 设置HTTP方法类型 - this.Method = method; - } - /// /// 使用Get函数的WebApi特性 /// @@ -51,7 +35,7 @@ public sealed class WebApiAttribute : RpcAttribute /// /// 请求函数类型。 /// - public HttpMethodType Method { get; set; } + public string Method { get; set; } /// public override Type[] GetGenericConstraintTypes() @@ -72,7 +56,7 @@ public sealed class WebApiAttribute : RpcAttribute var codeString = new StringBuilder(); codeString.AppendLine("var _request=new WebApiRequest();"); - codeString.AppendLine($"_request.Method = HttpMethodType.{webApiAttribute.Method};"); + codeString.AppendLine($"_request.Method = \"{webApiAttribute.Method}\";"); codeString.AppendLine($"_request.Headers = {this.GetFromHeaderString(rpcMethod, webApiParameterInfos)};"); codeString.AppendLine($"_request.Querys = {this.GetFromQueryString(webApiParameterInfos)};"); codeString.AppendLine($"_request.Forms = {this.GetFromFormString(webApiParameterInfos)};"); @@ -256,7 +240,7 @@ public sealed class WebApiAttribute : RpcAttribute codeString.AppendLine("}"); codeString.AppendLine("var _request=new WebApiRequest();"); - codeString.AppendLine($"_request.Method = HttpMethodType.{webApiAttribute.Method};"); + codeString.AppendLine($"_request.Method = \"{webApiAttribute.Method}\";"); codeString.AppendLine($"_request.Headers = {this.GetFromHeaderString(rpcMethod, webApiParameterInfos)};"); codeString.AppendLine($"_request.Querys = {this.GetFromQueryString(webApiParameterInfos)};"); codeString.AppendLine($"_request.Forms = {this.GetFromFormString(webApiParameterInfos)};"); diff --git a/src/TouchSocket.WebApi/Enum/HttpMethodType.cs b/src/TouchSocket.WebApi/Common/HttpMethodType.cs similarity index 63% rename from src/TouchSocket.WebApi/Enum/HttpMethodType.cs rename to src/TouchSocket.WebApi/Common/HttpMethodType.cs index c490b15b5..bee3fdf0a 100644 --- a/src/TouchSocket.WebApi/Enum/HttpMethodType.cs +++ b/src/TouchSocket.WebApi/Common/HttpMethodType.cs @@ -10,27 +10,23 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.WebApi; /// /// 请求函数类型 /// -public enum HttpMethodType +public static class HttpMethodType { /// - /// 以GET方式。支持调用上下文。 - /// 以该方式时,所有的参数类型必须是基础类型。所有的参数来源均来自url参数。 + /// 以Delete方式。支持调用上下文。 /// - Get = 0, + public const string Delete = "Delete"; /// /// 以GET方式。支持调用上下文。 /// 以该方式时,所有的参数类型必须是基础类型。所有的参数来源均来自url参数。 /// - [Obsolete("此配置已被弃用,请使用Get代替", true)] - GET = 0, + public const string Get = "Get"; /// /// 以Post方式。支持调用上下文。 @@ -40,26 +36,10 @@ public enum HttpMethodType /// 当有多个参数时,最后一个参数可以为任意类型,且参数来源为Body,其余参数均必须是基础类型,且来自url参数。 /// /// - Post = 1, - - /// - /// 以Post方式。支持调用上下文。 - /// 以该方式时,可以应对以下情况: - /// - /// 仅有一个参数时,该参数可以为任意类型,且参数来源为Body - /// 当有多个参数时,最后一个参数可以为任意类型,且参数来源为Body,其余参数均必须是基础类型,且来自url参数。 - /// - /// - [Obsolete("此配置已被弃用,请使用Get代替", true)] - POST = 1, + public const string Post = "Post"; /// /// 以Put方式。支持调用上下文。 /// - Put = 2, - - /// - /// 以Delete方式。支持调用上下文。 - /// - Delete = 3 + public const string Put = "Put"; } \ No newline at end of file diff --git a/src/TouchSocket.WebApi/Common/WebApiCallContext.cs b/src/TouchSocket.WebApi/Common/WebApiCallContext.cs index dba9e535b..1cf5dcac6 100644 --- a/src/TouchSocket.WebApi/Common/WebApiCallContext.cs +++ b/src/TouchSocket.WebApi/Common/WebApiCallContext.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using TouchSocket.Core; using TouchSocket.Http; using TouchSocket.Rpc; diff --git a/src/TouchSocket.WebApi/Common/WebApiRequest.cs b/src/TouchSocket.WebApi/Common/WebApiRequest.cs index 9a29f3237..a29dd8e1c 100644 --- a/src/TouchSocket.WebApi/Common/WebApiRequest.cs +++ b/src/TouchSocket.WebApi/Common/WebApiRequest.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Collections.Generic; - namespace TouchSocket.WebApi; /// @@ -22,7 +20,7 @@ public class WebApiRequest /// /// 获取或设置 HTTP 方法类型。 /// - public HttpMethodType Method { get; set; } + public string Method { get; set; } /// /// 获取或设置请求的主体。 diff --git a/src/TouchSocket.WebApi/Components/WebApiClient.cs b/src/TouchSocket.WebApi/Components/WebApiClient.cs index 5d8f7923e..786d87abb 100644 --- a/src/TouchSocket.WebApi/Components/WebApiClient.cs +++ b/src/TouchSocket.WebApi/Components/WebApiClient.cs @@ -10,11 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Http; using TouchSocket.Rpc; @@ -35,21 +30,31 @@ public class WebApiClient : HttpClientBase, IWebApiClient new JsonStringToClassSerializerFormatter()); } + private readonly SemaphoreSlim m_semaphoreSlim = new SemaphoreSlim(1, 1); + /// /// 字符串转化器 /// public StringSerializerConverter Converter { get; } /// - public Task ConnectAsync(int millisecondsTimeout, CancellationToken token) + public async Task ConnectAsync(CancellationToken cancellationToken) { - return this.TcpConnectAsync(millisecondsTimeout, token); + await this.m_semaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + try + { + await base.HttpConnectAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + finally + { + this.m_semaphoreSlim.Release(); + } } #region Rpc调用 /// - public async Task InvokeAsync(string invokeKey, Type returnType, IInvokeOption invokeOption, params object[] parameters) + public async Task InvokeAsync(string invokeKey, Type returnType, InvokeOption invokeOption, params object[] parameters) { if (parameters.Length != 1 || parameters[0] is not WebApiRequest webApiRequest) { @@ -57,24 +62,8 @@ public class WebApiClient : HttpClientBase, IWebApiClient } var request = new HttpRequest(); - request.URL = (invokeKey); - switch (webApiRequest.Method) - { - case HttpMethodType.Get: - request.Method = HttpMethod.Get; - break; - case HttpMethodType.Post: - request.Method = HttpMethod.Post; - break; - case HttpMethodType.Put: - request.Method = HttpMethod.Put; - break; - case HttpMethodType.Delete: - request.Method = HttpMethod.Delete; - break; - default: - break; - } + request.URL = invokeKey; + request.Method = webApiRequest.Method; request.InitHeaders(); if (webApiRequest.Headers != null) { @@ -105,7 +94,7 @@ public class WebApiClient : HttpClientBase, IWebApiClient await this.PluginManager.RaiseAsync(typeof(IWebApiRequestPlugin), this.Resolver, this, new WebApiEventArgs(request, default)); - using (var responseResult = await this.ProtectedRequestContentAsync(request, invokeOption.Timeout, invokeOption.Token).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) + using (var responseResult = await this.ProtectedRequestAsync(request, invokeOption.Token).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) { var response = responseResult.Response; await this.PluginManager.RaiseAsync(typeof(IWebApiResponsePlugin), this.Resolver, this, new WebApiEventArgs(request, response)); diff --git a/src/TouchSocket.WebApi/Components/WebApiClientSlim.cs b/src/TouchSocket.WebApi/Components/WebApiClientSlim.cs index 9a1a01b3f..844fe48ac 100644 --- a/src/TouchSocket.WebApi/Components/WebApiClientSlim.cs +++ b/src/TouchSocket.WebApi/Components/WebApiClientSlim.cs @@ -10,17 +10,8 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -#if NETSTANDARD2_0_OR_GREATER || NET481_OR_GREATER || NET6_0_OR_GREATER -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; + using System.Net.Http; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; -using TouchSocket.Http; using TouchSocket.Rpc; using TouchSocket.Sockets; using HttpClient = System.Net.Http.HttpClient; @@ -50,7 +41,7 @@ public class WebApiClientSlim : Http.HttpClientSlim, IWebApiClientBase public StringSerializerConverter Converter { get; } /// - public async Task InvokeAsync(string invokeKey, Type returnType, IInvokeOption invokeOption, params object[] parameters) + public async Task InvokeAsync(string invokeKey, Type returnType, InvokeOption invokeOption, params object[] parameters) { if (parameters.Length != 1 || parameters[0] is not WebApiRequest webApiRequest) { @@ -59,24 +50,7 @@ public class WebApiClientSlim : Http.HttpClientSlim, IWebApiClientBase var request = new HttpRequestMessage(); - switch (webApiRequest.Method) - { - case HttpMethodType.Get: - request.Method = HttpMethod.Get; - break; - case HttpMethodType.Post: - request.Method = HttpMethod.Post; - break; - case HttpMethodType.Put: - request.Method = HttpMethod.Put; - break; - case HttpMethodType.Delete: - request.Method = HttpMethod.Delete; - break; - default: - break; - } - + request.Method = new HttpMethod(webApiRequest.Method); if (webApiRequest.Headers != null) { foreach (var item in webApiRequest.Headers) @@ -106,39 +80,30 @@ public class WebApiClientSlim : Http.HttpClientSlim, IWebApiClientBase await this.PluginManager.RaiseAsync(typeof(IWebApiRequestPlugin), this.Resolver, this, new WebApiEventArgs(request, default)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - using (var tokenSource = new CancellationTokenSource(invokeOption.Timeout)) + var response = await this.HttpClient.SendAsync(request, invokeOption.Token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + await this.PluginManager.RaiseAsync(typeof(IWebApiRequestPlugin), this.Resolver, this, new WebApiEventArgs(request, response)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + if (invokeOption.FeedbackType != FeedbackType.WaitInvoke) { - if (invokeOption.Token.CanBeCanceled) - { - invokeOption.Token.Register(tokenSource.Cancel); - } - var response = await this.HttpClient.SendAsync(request, tokenSource.Token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return default; + } - await this.PluginManager.RaiseAsync(typeof(IWebApiRequestPlugin), this.Resolver, this, new WebApiEventArgs(request, response)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - - if (invokeOption.FeedbackType != FeedbackType.WaitInvoke) + if (response.IsSuccessStatusCode) + { + if (returnType != null) { - return default; - } - - if (response.IsSuccessStatusCode) - { - if (returnType != null) - { - return this.Converter.Deserialize(null, await response.Content.ReadAsStringAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext), returnType); - } - return default; - } - else if ((int)response.StatusCode == 422) - { - throw new RpcException(((ActionResult)this.Converter.Deserialize(null, await response.Content.ReadAsStringAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext), typeof(ActionResult))).Message); - } - else - { - throw new RpcException(response.ReasonPhrase); + return this.Converter.Deserialize(null, await response.Content.ReadAsStringAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext), returnType); } + return default; + } + else if ((int)response.StatusCode == 422) + { + throw new RpcException(((ActionResult)this.Converter.Deserialize(null, await response.Content.ReadAsStringAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext), typeof(ActionResult))).Message); + } + else + { + throw new RpcException(response.ReasonPhrase); } } -} - -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/src/TouchSocket.Core/Core/DynamicBuilderType.cs b/src/TouchSocket.WebApi/Enum/RouteMatchStatus.cs similarity index 76% rename from src/TouchSocket.Core/Core/DynamicBuilderType.cs rename to src/TouchSocket.WebApi/Enum/RouteMatchStatus.cs index 9de97b1b8..8efb2e8fd 100644 --- a/src/TouchSocket.Core/Core/DynamicBuilderType.cs +++ b/src/TouchSocket.WebApi/Enum/RouteMatchStatus.cs @@ -10,30 +10,29 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -namespace TouchSocket.Core; +#if NET8_0_OR_GREATER +using System.Collections.Frozen; +#endif + +namespace TouchSocket.WebApi; /// -/// 动态构建类型 +/// 路由匹配状态 /// -public enum DynamicBuilderType +public enum RouteMatchStatus { /// - /// 使用IL构建 + /// 成功匹配 /// - IL, + Success, /// - /// 使用表达式树 + /// 路由未找到 /// - Expression, + NotFound, /// - /// 使用反射 + /// 方法不允许 /// - Reflect, - - /// - /// 使用源生成 - /// - SourceGenerator + MethodNotAllowed } \ No newline at end of file diff --git a/src/TouchSocket.WebApi/EventArgs/WebApiEventArgs.cs b/src/TouchSocket.WebApi/EventArgs/WebApiEventArgs.cs index 123fed5c4..376ff9bf0 100644 --- a/src/TouchSocket.WebApi/EventArgs/WebApiEventArgs.cs +++ b/src/TouchSocket.WebApi/EventArgs/WebApiEventArgs.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; using TouchSocket.Http; namespace TouchSocket.WebApi; @@ -48,7 +47,6 @@ public partial class WebApiEventArgs : PluginEventArgs public HttpResponse Response { get; } } -#if NETSTANDARD2_0_OR_GREATER || NET481_OR_GREATER || NET6_0_OR_GREATER /// /// WebAPI事件参数类,用于封装HTTP请求和响应信息 @@ -76,6 +74,4 @@ public partial class WebApiEventArgs /// Http响应 /// public System.Net.Http.HttpResponseMessage ResponseMessage { get; } -} - -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/src/TouchSocket.WebApi/Extensions/WebApiPluginsManagerExtension.cs b/src/TouchSocket.WebApi/Extensions/WebApiPluginsManagerExtension.cs index edf886a96..465b79e2f 100644 --- a/src/TouchSocket.WebApi/Extensions/WebApiPluginsManagerExtension.cs +++ b/src/TouchSocket.WebApi/Extensions/WebApiPluginsManagerExtension.cs @@ -10,22 +10,43 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ +using TouchSocket.Rpc; using TouchSocket.WebApi; namespace TouchSocket.Core; /// -/// WebApiPluginManagerExtension +/// WebApi插件管理器扩展 /// public static class WebApiPluginManagerExtension { /// /// 使用WebApi的插件。仅服务器可用。 /// - /// - /// + /// 插件管理器 + /// WebApi配置选项 + /// WebApi解析插件 + public static WebApiParserPlugin UseWebApi(this IPluginManager pluginManager, Action options) + { + var resolver = pluginManager.Resolver; + WebApiOption option = new WebApiOption(); + + options.Invoke(option); + + var webApiParserPlugin = new WebApiParserPlugin(resolver.Resolve(), option); + + pluginManager.Add(webApiParserPlugin); + + return webApiParserPlugin; + } + + /// + /// 使用WebApi的插件。仅服务器可用。 + /// + /// 插件管理器 + /// WebApi解析插件 public static WebApiParserPlugin UseWebApi(this IPluginManager pluginManager) { - return pluginManager.Add(); + return UseWebApi(pluginManager, options => { }); } } \ No newline at end of file diff --git a/src/TouchSocket.WebApi/Interface/IWebApiClient.cs b/src/TouchSocket.WebApi/Interface/IWebApiClient.cs index 99d5db95f..9220167e8 100644 --- a/src/TouchSocket.WebApi/Interface/IWebApiClient.cs +++ b/src/TouchSocket.WebApi/Interface/IWebApiClient.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; using TouchSocket.Http; using TouchSocket.Sockets; diff --git a/src/TouchSocket.WebApi/Mapping/IWebApiMapping.cs b/src/TouchSocket.WebApi/Mapping/IWebApiMapping.cs index 093adb28f..90c8e2d48 100644 --- a/src/TouchSocket.WebApi/Mapping/IWebApiMapping.cs +++ b/src/TouchSocket.WebApi/Mapping/IWebApiMapping.cs @@ -10,9 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Collections.Generic; using TouchSocket.Http; -using TouchSocket.Rpc; namespace TouchSocket.WebApi; @@ -26,11 +24,5 @@ public interface IWebApiMapping : IEnumerable /// void MakeReadonly(); - /// - /// 根据指定的 URL 和 HTTP 方法匹配 RPC 方法。 - /// - /// 要匹配的 URL。 - /// 要匹配的 HTTP 方法。 - /// 匹配的 RPC 方法。 - RpcMethod Match(string url, HttpMethod httpMethod); + RouteMatchResult TryMatch(string url, HttpMethod httpMethod); } \ No newline at end of file diff --git a/src/TouchSocket.WebApi/Mapping/InternalWebApiMapping.cs b/src/TouchSocket.WebApi/Mapping/InternalWebApiMapping.cs index ede8cf1dd..704faefad 100644 --- a/src/TouchSocket.WebApi/Mapping/InternalWebApiMapping.cs +++ b/src/TouchSocket.WebApi/Mapping/InternalWebApiMapping.cs @@ -10,52 +10,66 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Collections; -using System.Collections.Generic; using System.Text.RegularExpressions; using TouchSocket.Http; using TouchSocket.Rpc; +#if NET8_0_OR_GREATER +using System.Collections.Frozen; +#endif + namespace TouchSocket.WebApi; internal sealed class InternalWebApiMapping : IWebApiMapping { - private static readonly string[] s_regexString = new string[] { "*", "^", "-", ".", "\\" }; - private readonly Dictionary> m_mappingMethods = new(StringComparer.OrdinalIgnoreCase); - private readonly Dictionary> m_regexMethods = new(StringComparer.OrdinalIgnoreCase); + private Dictionary> m_mappingMethods = new(StringComparer.OrdinalIgnoreCase); + private List m_regexRoutes = new List(); + +#if NET8_0_OR_GREATER + private FrozenDictionary> m_frozenMappingMethods; + private FrozenSet m_frozenRegexRoutes; +#else + private Dictionary> m_frozenMappingMethods; + private RegexRouteEntry[] m_frozenRegexRoutes; +#endif + + private bool m_isReadonly; public void AddRpcMethod(RpcMethod rpcMethod) { + if (this.m_isReadonly) + { + throw new InvalidOperationException("映射已设置为只读,无法添加新的RPC方法"); + } + if (rpcMethod.GetAttribute() is WebApiAttribute attribute) { + var httpMethod = new HttpMethod(attribute.Method.ToString()); + + // 添加普通路由(精确匹配,使用字典) var actionUrls = attribute.GetRouteUrls(rpcMethod); if (actionUrls != null) { - foreach (var item in actionUrls) + foreach (var url in actionUrls) { - if (!this.m_mappingMethods.TryGetValue(item, out var pairs)) + if (!this.m_mappingMethods.TryGetValue(url, out var pairs)) { pairs = new Dictionary(); - this.m_mappingMethods.Add(item, pairs); + this.m_mappingMethods.Add(url, pairs); } - pairs.Add(new HttpMethod(attribute.Method.ToString()), rpcMethod); + pairs.Add(httpMethod, rpcMethod); } } + // 添加正则路由(模式匹配,使用列表) var actionRegexUrls = attribute.GetRegexRouteUrls(rpcMethod); if (actionRegexUrls != null) { - foreach (var item in actionRegexUrls) + foreach (var pattern in actionRegexUrls) { - if (!this.m_regexMethods.TryGetValue(item, out var pairs)) - { - pairs = new Dictionary(); - this.m_regexMethods.Add(item, pairs); - } - - pairs.Add(new HttpMethod(attribute.Method.ToString()), rpcMethod); + this.m_regexRoutes.Add(new RegexRouteEntry(pattern, httpMethod, rpcMethod)); } } } @@ -63,19 +77,36 @@ internal sealed class InternalWebApiMapping : IWebApiMapping public IEnumerator GetEnumerator() { - foreach (var item1 in this.m_mappingMethods) + if (this.m_isReadonly) { - foreach (var item2 in item1.Value) + // 使用冻结的数据 + foreach (var item1 in this.m_frozenMappingMethods) { - yield return new MappingMethod(item1.Key, item2.Key, item2.Value, false); + foreach (var item2 in item1.Value) + { + yield return new MappingMethod(item1.Key, item2.Key, item2.Value, false); + } + } + + foreach (var regexRoute in this.m_frozenRegexRoutes) + { + yield return new MappingMethod(regexRoute.Pattern, regexRoute.HttpMethod, regexRoute.RpcMethod, true); } } - - foreach (var item1 in this.m_regexMethods) + else { - foreach (var item2 in item1.Value) + // 使用可变的数据 + foreach (var item1 in this.m_mappingMethods) { - yield return new MappingMethod(item1.Key, item2.Key, item2.Value, true); + foreach (var item2 in item1.Value) + { + yield return new MappingMethod(item1.Key, item2.Key, item2.Value, false); + } + } + + foreach (var regexRoute in this.m_regexRoutes) + { + yield return new MappingMethod(regexRoute.Pattern, regexRoute.HttpMethod, regexRoute.RpcMethod, true); } } } @@ -85,49 +116,179 @@ internal sealed class InternalWebApiMapping : IWebApiMapping return this.GetEnumerator(); } - public RpcMethod Match(string url, HttpMethod httpMethod) + /// + /// 尝试匹配路由 + /// + /// 请求的URL + /// HTTP方法 + /// 匹配结果 + public RouteMatchResult TryMatch(string url, HttpMethod httpMethod) { - if (!this.m_mappingMethods.TryGetValue(url, out var pairs)) + if (this.m_isReadonly) { - foreach (var item in this.m_regexMethods) - { - var pattern = item.Key; - var isMatch = Regex.IsMatch(url, pattern); - if (isMatch) - { - pairs = item.Value; - break; - } - } + // 使用冻结集合进行查询(性能更优) + return this.TryMatchFrozen(url, httpMethod); } - - if (pairs is null) + else { - return default; + // 使用普通字典进行查询 + return this.TryMatchMutable(url, httpMethod); } - - if (pairs.TryGetValue(httpMethod, out var rpcMethod)) - { - return rpcMethod; - } - - return default; - } - - private bool IsRegexUrl(string url) - { - foreach (var item in s_regexString) - { - if (url.Contains(item)) - { - return true; - } - } - return false; } + /// + /// 将映射设置为只读,并冻结所有集合以提升性能 + /// public void MakeReadonly() { + if (this.m_isReadonly) + { + return; + } +#if NET8_0_OR_GREATER + // .NET 8+: 使用 FrozenDictionary 获得最佳性能 + var frozenBuilder = new Dictionary>(this.m_mappingMethods.Count, StringComparer.OrdinalIgnoreCase); + foreach (var kvp in this.m_mappingMethods) + { + frozenBuilder[kvp.Key] = kvp.Value.ToFrozenDictionary(); + } + this.m_frozenMappingMethods = frozenBuilder.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase); + this.m_frozenRegexRoutes = this.m_regexRoutes.ToFrozenSet(); +#else + // .NET Framework/.NET Standard/.NET 6: 使用普通字典 + this.m_frozenMappingMethods = new Dictionary>(this.m_mappingMethods, StringComparer.OrdinalIgnoreCase); + this.m_frozenRegexRoutes = this.m_regexRoutes.ToArray(); +#endif + + // 释放原始集合以节省内存 + this.m_mappingMethods = null; + this.m_regexRoutes = null; + + this.m_isReadonly = true; } -} \ No newline at end of file + + /// + /// 使用冻结集合进行匹配(只读模式) + /// + private RouteMatchResult TryMatchFrozen(string url, HttpMethod httpMethod) + { + // 首先尝试精确匹配(冻结字典查询,性能最优) + if (this.m_frozenMappingMethods.TryGetValue(url, out var pairs)) + { + // 找到路由,检查HTTP方法是否匹配 + if (pairs.TryGetValue(httpMethod, out var rpcMethod)) + { + return RouteMatchResult.Success(rpcMethod); + } + + // 路由匹配但方法不匹配 + return RouteMatchResult.MethodNotAllowed(pairs.Keys); + } + + // 精确匹配失败,尝试正则路由匹配 + var matchedRegexRoutes = new List(); + + foreach (var regexRoute in this.m_frozenRegexRoutes) + { + if (regexRoute.IsMatch(url)) + { + matchedRegexRoutes.Add(regexRoute); + } + } + + // 如果正则路由有匹配 + if (matchedRegexRoutes.Count > 0) + { + // 查找方法也匹配的路由 + var methodMatchedRoute = matchedRegexRoutes.FirstOrDefault(r => r.HttpMethod == httpMethod); + if (methodMatchedRoute != null) + { + return RouteMatchResult.Success(methodMatchedRoute.RpcMethod); + } + + // 路由匹配但方法不匹配 + var allowedMethods = matchedRegexRoutes.Select(r => r.HttpMethod).Distinct(); + return RouteMatchResult.MethodNotAllowed(allowedMethods); + } + + // 没有找到任何匹配的路由 + return RouteMatchResult.NotFound(); + } + + /// + /// 使用普通字典进行匹配(可变模式) + /// + private RouteMatchResult TryMatchMutable(string url, HttpMethod httpMethod) + { + // 首先尝试精确匹配(字典查询,O(1)性能) + if (this.m_mappingMethods.TryGetValue(url, out var pairs)) + { + // 找到路由,检查HTTP方法是否匹配 + if (pairs.TryGetValue(httpMethod, out var rpcMethod)) + { + return RouteMatchResult.Success(rpcMethod); + } + + // 路由匹配但方法不匹配 + return RouteMatchResult.MethodNotAllowed(pairs.Keys); + } + + // 精确匹配失败,尝试正则路由匹配 + var matchedRegexRoutes = new List(); + + foreach (var regexRoute in this.m_regexRoutes) + { + if (regexRoute.IsMatch(url)) + { + matchedRegexRoutes.Add(regexRoute); + } + } + + // 如果正则路由有匹配 + if (matchedRegexRoutes.Count > 0) + { + // 查找方法也匹配的路由 + var methodMatchedRoute = matchedRegexRoutes.FirstOrDefault(r => r.HttpMethod == httpMethod); + if (methodMatchedRoute != null) + { + return RouteMatchResult.Success(methodMatchedRoute.RpcMethod); + } + + // 路由匹配但方法不匹配 + var allowedMethods = matchedRegexRoutes.Select(r => r.HttpMethod).Distinct(); + return RouteMatchResult.MethodNotAllowed(allowedMethods); + } + + // 没有找到任何匹配的路由 + return RouteMatchResult.NotFound(); + } + + /// + /// 正则路由条目 + /// + private sealed class RegexRouteEntry + { + private readonly Regex m_regex; + + public RegexRouteEntry(string pattern, HttpMethod httpMethod, RpcMethod rpcMethod) + { + this.Pattern = pattern; + this.HttpMethod = httpMethod; + this.RpcMethod = rpcMethod; + this.m_regex = new Regex(pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled); + } + + public string Pattern { get; } + public HttpMethod HttpMethod { get; } + public RpcMethod RpcMethod { get; } + + /// + /// 判断URL是否匹配此正则路由 + /// + public bool IsMatch(string url) + { + return this.m_regex.IsMatch(url); + } + } +} diff --git a/src/TouchSocket.WebApi/Mapping/RouteMatchResult.cs b/src/TouchSocket.WebApi/Mapping/RouteMatchResult.cs new file mode 100644 index 000000000..5d4f9ceeb --- /dev/null +++ b/src/TouchSocket.WebApi/Mapping/RouteMatchResult.cs @@ -0,0 +1,72 @@ +//------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +//------------------------------------------------------------------------------ + +using TouchSocket.Http; +using TouchSocket.Rpc; + +#if NET8_0_OR_GREATER +using System.Collections.Frozen; +#endif + +namespace TouchSocket.WebApi; + +/// +/// 路由匹配结果 +/// +public readonly struct RouteMatchResult +{ + private RouteMatchResult(RouteMatchStatus status, RpcMethod rpcMethod, IEnumerable allowedMethods) + { + this.Status = status; + this.RpcMethod = rpcMethod; + this.AllowedMethods = allowedMethods; + } + + /// + /// 匹配状态 + /// + public RouteMatchStatus Status { get; } + + /// + /// 匹配的RPC方法 + /// + public RpcMethod RpcMethod { get; } + + /// + /// 允许的HTTP方法列表 + /// + public IEnumerable AllowedMethods { get; } + + /// + /// 创建成功的匹配结果 + /// + public static RouteMatchResult Success(RpcMethod rpcMethod) + { + return new RouteMatchResult(RouteMatchStatus.Success, rpcMethod, null); + } + + /// + /// 创建路由未找到的结果 + /// + public static RouteMatchResult NotFound() + { + return new RouteMatchResult(RouteMatchStatus.NotFound, null, null); + } + + /// + /// 创建方法不允许的结果 + /// + public static RouteMatchResult MethodNotAllowed(IEnumerable allowedMethods) + { + return new RouteMatchResult(RouteMatchStatus.MethodNotAllowed, null, allowedMethods); + } +} diff --git a/src/TouchSocket.WebApi/Options/WebApiOption.cs b/src/TouchSocket.WebApi/Options/WebApiOption.cs new file mode 100644 index 000000000..505194130 --- /dev/null +++ b/src/TouchSocket.WebApi/Options/WebApiOption.cs @@ -0,0 +1,42 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +namespace TouchSocket.WebApi; + +/// +/// WebApi配置选项。 +/// +public class WebApiOption +{ + /// + /// 初始化类的新实例。 + /// + public WebApiOption() + { + this.Converter = new WebApiSerializerConverter(); + this.Converter.AddJsonSerializerFormatter(new Newtonsoft.Json.JsonSerializerSettings()); + } + + /// + /// 获取WebApi序列化器转换器。 + /// + public WebApiSerializerConverter Converter { get; } + + /// + /// 配置序列化器转换器。 + /// + /// 配置操作 + public void ConfigureConverter(Action action) + { + action.Invoke(this.Converter); + } +} diff --git a/src/TouchSocket.WebApi/Plugins/Interfaces/IWebApiRequestPlugin.cs b/src/TouchSocket.WebApi/Plugins/Interfaces/IWebApiRequestPlugin.cs index 279f24b08..b93fabbb3 100644 --- a/src/TouchSocket.WebApi/Plugins/Interfaces/IWebApiRequestPlugin.cs +++ b/src/TouchSocket.WebApi/Plugins/Interfaces/IWebApiRequestPlugin.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.WebApi; /// diff --git a/src/TouchSocket.WebApi/Plugins/Interfaces/IWebApiResponsePlugin.cs b/src/TouchSocket.WebApi/Plugins/Interfaces/IWebApiResponsePlugin.cs index 37dc8da7c..3d7d9be26 100644 --- a/src/TouchSocket.WebApi/Plugins/Interfaces/IWebApiResponsePlugin.cs +++ b/src/TouchSocket.WebApi/Plugins/Interfaces/IWebApiResponsePlugin.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.WebApi; /// diff --git a/src/TouchSocket.WebApi/Plugins/WebApiParserPlugin.cs b/src/TouchSocket.WebApi/Plugins/WebApiParserPlugin.cs index 93e7ff648..0d206f43b 100644 --- a/src/TouchSocket.WebApi/Plugins/WebApiParserPlugin.cs +++ b/src/TouchSocket.WebApi/Plugins/WebApiParserPlugin.cs @@ -10,11 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Net.Sockets; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Http; using TouchSocket.Rpc; using TouchSocket.Sockets; @@ -27,116 +22,172 @@ namespace TouchSocket.WebApi; [PluginOption(Singleton = true)] public sealed class WebApiParserPlugin : PluginBase, IHttpPlugin, ITcpClosedPlugin { + private static readonly DependencyProperty s_webApiCallContextProperty = new DependencyProperty("WebApiCallContext", default); private readonly InternalWebApiMapping m_mapping = new InternalWebApiMapping(); private readonly Dictionary m_pairsForParameterInfo = new Dictionary(); private readonly IRpcServerProvider m_rpcServerProvider; - private static readonly DependencyProperty s_webApiCallContextProperty = new DependencyProperty("WebApiCallContext", default); + /// /// 构造函数 /// - public WebApiParserPlugin(IRpcServerProvider rpcServerProvider) + public WebApiParserPlugin(IRpcServerProvider rpcServerProvider, WebApiOption option) { - ThrowHelper.ThrowArgumentNullExceptionIf(rpcServerProvider, nameof(IRpcServerProvider)); + ThrowHelper.ThrowIfNull(rpcServerProvider, nameof(IRpcServerProvider)); this.RegisterServer(rpcServerProvider.GetMethods()); this.m_rpcServerProvider = rpcServerProvider; - this.Converter = new WebApiSerializerConverter(); - this.Converter.AddJsonSerializerFormatter(new Newtonsoft.Json.JsonSerializerSettings()); + this.Converter = option.Converter; } /// /// 转化器 /// - public WebApiSerializerConverter Converter { get; private set; } - - /// - /// 获取Get函数路由映射图 - /// - [Obsolete("此配置已被弃用,请使用Mapping属性代替", true)] - public ActionMap GetRouteMap { get; private set; } + public WebApiSerializerConverter Converter { get; } /// /// 获取WebApi映射 /// public IWebApiMapping Mapping => this.m_mapping; - /// - /// 获取Post函数路由映射图 - /// - [Obsolete("此配置已被弃用,请使用Mapping属性代替", true)] - public ActionMap PostRouteMap { get; private set; } - - /// - /// 配置转换器。可以实现json序列化或者xml序列化。 - /// - /// - /// - public WebApiParserPlugin ConfigureConverter(Action action) - { - action.Invoke(this.Converter); - return this; - } - /// public async Task OnHttpRequest(IHttpSessionClient client, HttpContextEventArgs e) { var httpMethod = e.Context.Request.Method; var url = e.Context.Request.RelativeURL; - var rpcMethod = this.Mapping.Match(url, httpMethod); - if (rpcMethod != null) + + // 尝试匹配路由 + var matchResult = this.m_mapping.TryMatch(url, httpMethod); + + switch (matchResult.Status) { - var callContext = new WebApiCallContext(client, rpcMethod, e.Context, client.Resolver); + case RouteMatchStatus.Success: + // 路由和方法都匹配,执行RPC调用 + await this.ExecuteRpcMethodAsync(client, e, matchResult.RpcMethod).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return; + + case RouteMatchStatus.MethodNotAllowed: + // 路由匹配但方法不允许,返回405 + await this.ResponseMethodNotAllowedAsync(client, e.Context, matchResult.AllowedMethods).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return; + + case RouteMatchStatus.NotFound: + default: + // 路由未找到,继续执行后续插件 + await e.InvokeNext().ConfigureAwait(EasyTask.ContinueOnCapturedContext); + return; + } + } + + private async Task ExecuteRpcMethodAsync(IHttpSessionClient client, HttpContextEventArgs e, RpcMethod rpcMethod) + { + var callContext = new WebApiCallContext(client, rpcMethod, e.Context, client.Resolver); + try + { + client.SetValue(s_webApiCallContextProperty, callContext); + var invokeResult = new InvokeResult(); + var ps = new object[rpcMethod.Parameters.Length]; + try { - client.SetValue(s_webApiCallContextProperty, callContext); - var invokeResult = new InvokeResult(); - var ps = new object[rpcMethod.Parameters.Length]; - try + for (var i = 0; i < rpcMethod.Parameters.Length; i++) { - for (var i = 0; i < rpcMethod.Parameters.Length; i++) - { - var parameter = rpcMethod.Parameters[i]; - ps[i] = await this.ParseParameterAsync(parameter, callContext).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } + var parameter = rpcMethod.Parameters[i]; + ps[i] = await this.ParseParameterAsync(parameter, callContext).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - catch (Exception ex) - { - invokeResult.Status = InvokeStatus.Exception; - invokeResult.Message = ex.Message; - invokeResult.Exception = ex; - } - - callContext.SetParameters(ps); - invokeResult = await this.m_rpcServerProvider.ExecuteAsync(callContext, invokeResult).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - - if (e.Context.Response.Responsed) - { - return; - } - - if (!client.Online) - { - return; - } - - await this.ResponseAsync(client, e.Context, invokeResult).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - finally + catch (Exception ex) { - callContext.Dispose(); - client.RemoveValue(s_webApiCallContextProperty); + invokeResult.Status = InvokeStatus.Exception; + invokeResult.Message = ex.Message; + invokeResult.Exception = ex; } + + callContext.SetParameters(ps); + invokeResult = await this.m_rpcServerProvider.ExecuteAsync(callContext, invokeResult).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + if (e.Context.Response.Responsed) + { + return; + } + + if (!client.Online) + { + return; + } + + await this.ResponseAsync(client, e.Context, invokeResult).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + finally + { + callContext.Dispose(); + client.RemoveValue(s_webApiCallContextProperty); + } + } + + private async Task ResponseMethodNotAllowedAsync(IHttpSessionClient client, HttpContext httpContext, IEnumerable allowedMethods) + { + var httpResponse = httpContext.Response; + + // 设置Allow头,列出允许的方法 + if (allowedMethods != null) + { + var allowHeader = string.Join(", ", allowedMethods.Select(m => m.ToString())); + httpResponse.Headers.TryAdd("Allow", allowHeader); + } + + var jsonString = this.Converter.Serialize(httpContext, new ActionResult() + { + Status = InvokeStatus.UnEnable, + Message = "HTTP方法不被允许" + }); + + await httpResponse + .SetContent(jsonString) + .SetStatus(405, "Method Not Allowed") + .AnswerAsync() + .ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + if (!httpContext.Request.KeepAlive) + { + await client.CloseAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + } + + /// + public async Task OnTcpClosed(ITcpSession client, ClosedEventArgs e) + { + if (client.TryRemoveValue(s_webApiCallContextProperty, out var webApiCallContext)) + { + webApiCallContext.Cancel(); } await e.InvokeNext().ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - private static object PrimitiveParse(string source, Type targetType) + private static bool IsSimpleType(Type type) { - if (targetType.IsPrimitive || targetType == TouchSocketCoreUtility.StringType) + if (type.IsPrimitive || type == TouchSocketCoreUtility.StringType) { - StringExtension.TryParseToType(source, targetType, out var target); + return true; + } + if (type.IsNullableType(out var actualType)) + { + return IsSimpleType(actualType); + } + return false; + } + + private static object ParseSimpleType(string source, Type targetType) + { + if (targetType.IsNullableType(out var actualType)) + { + targetType = actualType; + } + + if (StringExtension.TryParseToType(source, targetType, out var target)) + { return target; } + return default; } @@ -169,7 +220,7 @@ public sealed class WebApiParserPlugin : PluginBase, IHttpPlugin, ITcpClosedPlug { return callContext.Resolver.Resolve(parameter.Type); } - else if (parameter.Type.IsPrimitive || parameter.Type == typeof(string)) + else if (IsSimpleType(parameter.Type)) { var parameterInfo = this.GetParameterInfo(parameter); if (parameterInfo.IsFromBody) @@ -177,7 +228,7 @@ public sealed class WebApiParserPlugin : PluginBase, IHttpPlugin, ITcpClosedPlug var body = await request.GetBodyAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); if (body.HasValue()) { - return WebApiParserPlugin.PrimitiveParse(body, parameter.Type); + return WebApiParserPlugin.ParseSimpleType(body, parameter.Type); } else if (parameter.ParameterInfo.HasDefaultValue) { @@ -191,9 +242,9 @@ public sealed class WebApiParserPlugin : PluginBase, IHttpPlugin, ITcpClosedPlug else if (parameterInfo.IsFromHeader) { var value = request.Headers.Get(parameterInfo.FromHeaderName); - if (value.HasValue()) + if (!value.IsEmpty) { - return WebApiParserPlugin.PrimitiveParse(value, parameter.Type); + return WebApiParserPlugin.ParseSimpleType(value.First, parameter.Type); } else if (parameter.ParameterInfo.HasDefaultValue) { @@ -209,7 +260,7 @@ public sealed class WebApiParserPlugin : PluginBase, IHttpPlugin, ITcpClosedPlug var value = (await request.GetFormCollectionAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext)).Get(parameterInfo.FromFormName); if (value.HasValue()) { - return WebApiParserPlugin.PrimitiveParse(value, parameter.Type); + return WebApiParserPlugin.ParseSimpleType(value, parameter.Type); } else if (parameter.ParameterInfo.HasDefaultValue) { @@ -223,9 +274,9 @@ public sealed class WebApiParserPlugin : PluginBase, IHttpPlugin, ITcpClosedPlug else { var value = request.Query.Get(parameterInfo.FromQueryName ?? parameter.Name); - if (value.HasValue()) + if (!value.IsEmpty) { - return WebApiParserPlugin.PrimitiveParse(value, parameter.Type); + return WebApiParserPlugin.ParseSimpleType(value, parameter.Type); } else if (parameter.ParameterInfo.HasDefaultValue) { @@ -250,6 +301,8 @@ public sealed class WebApiParserPlugin : PluginBase, IHttpPlugin, ITcpClosedPlug { this.m_mapping.AddRpcMethod(rpcMethod); } + + this.m_mapping.MakeReadonly(); } private async Task ResponseAsync(IHttpSessionClient client, HttpContext httpContext, InvokeResult invokeResult) @@ -293,14 +346,14 @@ public sealed class WebApiParserPlugin : PluginBase, IHttpPlugin, ITcpClosedPlug case InvokeStatus.UnEnable: { var jsonString = this.Converter.Serialize(httpContext, new ActionResult() { Status = invokeResult.Status, Message = invokeResult.Message }); - httpResponse.SetContent(jsonString).SetStatus(405, "UnEnable"); + httpResponse.SetContent(jsonString).SetStatus(405, "Method Not Allowed"); break; } case InvokeStatus.InvocationException: case InvokeStatus.Exception: { var jsonString = this.Converter.Serialize(httpContext, new ActionResult() { Status = invokeResult.Status, Message = invokeResult.Message }); - httpResponse.SetContent(jsonString).SetStatus(422, "Exception"); + httpResponse.SetContent(jsonString).SetStatus(422, "Unprocessable Entity"); break; } } @@ -309,17 +362,7 @@ public sealed class WebApiParserPlugin : PluginBase, IHttpPlugin, ITcpClosedPlug if (!httpContext.Request.KeepAlive) { - await client.ShutdownAsync(SocketShutdown.Both).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await client.CloseAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } - - /// - public async Task OnTcpClosed(ITcpSession client, ClosedEventArgs e) - { - if (client.TryRemoveValue(s_webApiCallContextProperty, out var webApiCallContext)) - { - webApiCallContext.Cancel(); - } - await e.InvokeNext().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } } \ No newline at end of file diff --git a/src/TouchSocket.WebApi/Proxy/WebApiDispatchProxy.cs b/src/TouchSocket.WebApi/Proxy/WebApiDispatchProxy.cs index 90b37d550..db33450b7 100644 --- a/src/TouchSocket.WebApi/Proxy/WebApiDispatchProxy.cs +++ b/src/TouchSocket.WebApi/Proxy/WebApiDispatchProxy.cs @@ -10,26 +10,28 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -#if NET6_0_OR_GREATER || NET481_OR_GREATER +using System.Diagnostics.CodeAnalysis; using TouchSocket.Rpc; namespace TouchSocket.WebApi; + /// -/// WebApiDispatchProxy +/// WebApi动态代理。 +/// /// -/// +[RequiresUnreferencedCode("动态代理不支持AOT环境")] public abstract class WebApiDispatchProxy : RpcDispatchProxy where TClient : IWebApiClientBase { } /// -/// WebApiDispatchProxy +/// WebApi动态代理。 +/// /// +[RequiresUnreferencedCode("动态代理不支持AOT环境")] public abstract class WebApiDispatchProxy : WebApiDispatchProxy { } - -#endif \ No newline at end of file diff --git a/src/TouchSocket.WebApi/Proxy/WebApiRealityProxy.cs b/src/TouchSocket.WebApi/Proxy/WebApiRealityProxy.cs deleted file mode 100644 index 5d21aba3c..000000000 --- a/src/TouchSocket.WebApi/Proxy/WebApiRealityProxy.cs +++ /dev/null @@ -1,42 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -#if NET45_OR_GREATER -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using TouchSocket.Rpc; - -namespace TouchSocket.WebApi; - -/// -/// WebApiRealityProxy -/// -/// -/// -public abstract class WebApiRealityProxy : RpcRealityProxy where TClient : IWebApiClientBase -{ - -} - -/// -/// WebApiRealityProxy -/// -/// -public abstract class WebApiRealityProxy : WebApiRealityProxy -{ - -} - -#endif \ No newline at end of file diff --git a/src/TouchSocket.WebApi/Readme.md b/src/TouchSocket.WebApi/Readme.md index f0995c4ff..cd4efa91e 100644 --- a/src/TouchSocket.WebApi/Readme.md +++ b/src/TouchSocket.WebApi/Readme.md @@ -12,14 +12,14 @@ TouchSocket.WebApi 是一个提供 WebApi 服务器和客户端的组件库。 详细的说明文档请访问:[https://touchsocket.net/](https://touchsocket.net/) ## 支持的目标框架 + - net481 -- net45 - net462 - net472 - netstandard2.0 - netstandard2.1 - net6.0 -- net9.0 +- net10.0 - net8.0 ## 贡献与反馈 diff --git a/src/TouchSocket.WebApi/SerializerFormatter/WebApiJsonSerializerFormatter.cs b/src/TouchSocket.WebApi/SerializerFormatter/WebApiJsonSerializerFormatter.cs index 6607b70a4..4fa85e089 100644 --- a/src/TouchSocket.WebApi/SerializerFormatter/WebApiJsonSerializerFormatter.cs +++ b/src/TouchSocket.WebApi/SerializerFormatter/WebApiJsonSerializerFormatter.cs @@ -10,14 +10,13 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; using TouchSocket.Http; namespace TouchSocket.WebApi; internal sealed class WebApiJsonSerializerFormatter : JsonStringToClassSerializerFormatter { - public override bool TrySerialize(HttpContext state, in object target, out string source) + public override bool TrySerialize(HttpContext state, in TTarget target, out string source) { switch (state.Request.Accept) { diff --git a/src/TouchSocket.WebApi/SerializerFormatter/WebApiSerializerConverter.cs b/src/TouchSocket.WebApi/SerializerFormatter/WebApiSerializerConverter.cs index 5db3812bb..0ac52966f 100644 --- a/src/TouchSocket.WebApi/SerializerFormatter/WebApiSerializerConverter.cs +++ b/src/TouchSocket.WebApi/SerializerFormatter/WebApiSerializerConverter.cs @@ -11,8 +11,7 @@ //------------------------------------------------------------------------------ using Newtonsoft.Json; -using System; -using TouchSocket.Core; +using System.Diagnostics.CodeAnalysis; using TouchSocket.Http; namespace TouchSocket.WebApi; @@ -26,7 +25,7 @@ public class WebApiSerializerConverter : TouchSocketSerializerConverter /// 添加Xml序列化器 /// + [RequiresUnreferencedCode("Members from deserialized types may be trimmed if not referenced directly")] public void AddXmlSerializerFormatter() { this.Add(new WebApiXmlSerializerFormatter()); } -#if SystemTextJson /// /// 添加System.Text.Json序列化器 /// @@ -70,5 +69,4 @@ public class WebApiSerializerConverter : TouchSocketSerializerConverter +[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "使用该序列化时,会和源生成配合使用")] +[UnconditionalSuppressMessage("AOT", "IL3050:", Justification = "使用该序列化时,会和源生成配合使用")] +internal sealed class WebApiSystemTextJsonSerializerFormatter : SystemTextJsonSerializerFormatter { - private readonly JsonSerializerOptions m_jsonSerializerOptions; - public WebApiSystemTextJsonSerializerFormatter(JsonSerializerOptions jsonSerializerOptions) { - this.m_jsonSerializerOptions = jsonSerializerOptions; + this.JsonSettings = jsonSerializerOptions; } - public int Order { get; set; } - - public bool TryDeserialize(HttpContext state, in string source, Type targetType, out object target) + public override bool TryDeserialize(HttpContext state, in string source, Type targetType, out object target) { try { - target = System.Text.Json.JsonSerializer.Deserialize(source, targetType, this.m_jsonSerializerOptions); + target = System.Text.Json.JsonSerializer.Deserialize(source, targetType, this.JsonSettings); return true; } catch @@ -48,7 +38,7 @@ internal sealed class WebApiSystemTextJsonSerializerFormatter : ISerializerForma } } - public bool TrySerialize(HttpContext state, in object target, out string source) + public override bool TrySerialize(HttpContext state, in TTarget target, out string source) { switch (state.Request.Accept) { @@ -65,7 +55,7 @@ internal sealed class WebApiSystemTextJsonSerializerFormatter : ISerializerForma { try { - source = System.Text.Json.JsonSerializer.Serialize(target, target.GetType(), this.m_jsonSerializerOptions); + source = System.Text.Json.JsonSerializer.Serialize(target, target.GetType(), this.JsonSettings); return true; } catch @@ -77,5 +67,3 @@ internal sealed class WebApiSystemTextJsonSerializerFormatter : ISerializerForma } } } - -#endif \ No newline at end of file diff --git a/src/TouchSocket.WebApi/SerializerFormatter/WebApiXmlSerializerFormatter.cs b/src/TouchSocket.WebApi/SerializerFormatter/WebApiXmlSerializerFormatter.cs index 8fbbbeae7..909961b1e 100644 --- a/src/TouchSocket.WebApi/SerializerFormatter/WebApiXmlSerializerFormatter.cs +++ b/src/TouchSocket.WebApi/SerializerFormatter/WebApiXmlSerializerFormatter.cs @@ -10,14 +10,15 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; +using System.Diagnostics.CodeAnalysis; using TouchSocket.Http; namespace TouchSocket.WebApi; +[RequiresUnreferencedCode("Members from deserialized types may be trimmed if not referenced directly")] internal sealed class WebApiXmlSerializerFormatter : XmlStringToClassSerializerFormatter { - public override bool TrySerialize(HttpContext state, in object target, out string source) + public override bool TrySerialize(HttpContext state, in TTarget target, out string source) { switch (state.Request.Accept) { diff --git a/src/TouchSocket.WebApi/TouchSocket.WebApi.csproj b/src/TouchSocket.WebApi/TouchSocket.WebApi.csproj index 2fad319d5..49502dad7 100644 --- a/src/TouchSocket.WebApi/TouchSocket.WebApi.csproj +++ b/src/TouchSocket.WebApi/TouchSocket.WebApi.csproj @@ -1,6 +1,6 @@ - net481;net45;net462;net472;netstandard2.0;netstandard2.1;net6.0;net9.0;net8.0 + net462;netstandard2.0;netstandard2.1;net6.0;net10.0;net8.0 WebApi;TouchSocket 这是一个提供WebApi服务器和客户端的组件库。可以通过该组件创建WebApi服务解析器和客户端,让桌面端、Web端、移动端可以跨语言调用Rpc函数。功能支持自定义路由、Get传参、Post传参等。 @@ -21,11 +21,6 @@ - - - - - diff --git a/src/TouchSocket.WebApi/WebApiNameAttribute.cs b/src/TouchSocket.WebApi/WebApiNameAttribute.cs index 9f3fee4c5..e40167b0f 100644 --- a/src/TouchSocket.WebApi/WebApiNameAttribute.cs +++ b/src/TouchSocket.WebApi/WebApiNameAttribute.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.WebApi; /// diff --git a/src/TouchSocket.XmlRpc.SourceGenerator/TouchSocket.XmlRpc.SourceGenerator.csproj b/src/TouchSocket.XmlRpc.SourceGenerator/TouchSocket.XmlRpc.SourceGenerator.csproj index 21cf0e4da..da0ff4299 100644 --- a/src/TouchSocket.XmlRpc.SourceGenerator/TouchSocket.XmlRpc.SourceGenerator.csproj +++ b/src/TouchSocket.XmlRpc.SourceGenerator/TouchSocket.XmlRpc.SourceGenerator.csproj @@ -18,6 +18,6 @@ - + diff --git a/src/TouchSocket.XmlRpc.SourceGenerator/XmlRpcClientSourceGenerator.cs b/src/TouchSocket.XmlRpc.SourceGenerator/XmlRpcClientSourceGenerator.cs index b7f9b2b83..02f4a9aed 100644 --- a/src/TouchSocket.XmlRpc.SourceGenerator/XmlRpcClientSourceGenerator.cs +++ b/src/TouchSocket.XmlRpc.SourceGenerator/XmlRpcClientSourceGenerator.cs @@ -12,36 +12,83 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Collections.Generic; using System.Linq; +using TouchSocket.Rpc; namespace TouchSocket; [Generator] -public class XmlRpcClientSourceGenerator : ISourceGenerator +public class XmlRpcClientSourceGenerator : IIncrementalGenerator { - public void Initialize(GeneratorInitializationContext context) + public void Initialize(IncrementalGeneratorInitializationContext context) { - context.RegisterForSyntaxNotifications(() => new XmlRpcClientSyntaxReceiver()); - } + // 第一步:收集所有接口声明语法节点 + var interfaceDeclarations = context.SyntaxProvider + .CreateSyntaxProvider( + predicate: static (s, _) => s is InterfaceDeclarationSyntax, + transform: static (ctx, _) => (InterfaceDeclarationSyntax)ctx.Node); - public void Execute(GeneratorExecutionContext context) - { - var s = context.Compilation.GetMetadataReference(context.Compilation.Assembly); - - if (context.SyntaxReceiver is XmlRpcClientSyntaxReceiver receiver) - { - var builders = receiver - .GetRpcApiTypes(context.Compilation) - .Select(i => new XmlRpcClientCodeBuilder(i)) - .Distinct(CodeBuilderEqualityComparer.Default); - //Debugger.Launch(); - foreach (var builder in builders) + // 第二步:将语法节点转换为符号并过滤有效接口 + var interfacesWithSymbol = context.CompilationProvider.Combine(interfaceDeclarations.Collect()) + .SelectMany(static (tuple, _) => { - var tree = CSharpSyntaxTree.ParseText(builder.ToSourceText()); - var root = tree.GetRoot().NormalizeWhitespace(); - var ret = root.ToFullString(); - context.AddSource($"{builder.GetFileName()}.g.cs", ret); - } - } + var (compilation, interfaces) = tuple; + var results = new List(); + var attributeSymbol = RpcUtils.GetGeneratorRpcProxyAttribute(compilation); + + foreach (var interfaceSyntax in interfaces) + { + var model = compilation.GetSemanticModel(interfaceSyntax.SyntaxTree); + var interfaceSymbol = model.GetDeclaredSymbol(interfaceSyntax); + + if (interfaceSymbol != null && + attributeSymbol != null && + RpcUtils.IsRpcApiInterface(interfaceSymbol)) + { + results.Add(interfaceSymbol); + } + } + return results.Distinct(SymbolEqualityComparer.Default); + }); + + // 第三步:生成源代码 + context.RegisterSourceOutput(interfacesWithSymbol, + static (productionContext, interfaceSymbol) => + { + var builder = new XmlRpcClientCodeBuilder((INamedTypeSymbol)interfaceSymbol); + productionContext.AddSource(builder); + }); } -} \ No newline at end of file +} + +//[Generator] +//public class XmlRpcClientSourceGenerator : ISourceGenerator +//{ +// public void Initialize(GeneratorInitializationContext context) +// { +// context.RegisterForSyntaxNotifications(() => new XmlRpcClientSyntaxReceiver()); +// } + +// public void Execute(GeneratorExecutionContext context) +// { +// var s = context.Compilation.GetMetadataReference(context.Compilation.Assembly); + +// if (context.SyntaxReceiver is XmlRpcClientSyntaxReceiver receiver) +// { +// var builders = receiver +// .GetRpcApiTypes(context.Compilation) +// .Select(i => new XmlRpcClientCodeBuilder(i)) +// .Distinct(CodeBuilderEqualityComparer.Default); +// //Debugger.Launch(); +// foreach (var builder in builders) +// { +// var tree = CSharpSyntaxTree.ParseText(builder.ToSourceText()); +// var root = tree.GetRoot().NormalizeWhitespace(); +// var ret = root.ToFullString(); +// context.AddSource($"{builder.GetFileName()}.g.cs", ret); +// } +// } +// } +//} \ No newline at end of file diff --git a/src/TouchSocket.XmlRpc/Attribute/XmlRpcAttribute.cs b/src/TouchSocket.XmlRpc/Attribute/XmlRpcAttribute.cs index 4d0324c23..226995060 100644 --- a/src/TouchSocket.XmlRpc/Attribute/XmlRpcAttribute.cs +++ b/src/TouchSocket.XmlRpc/Attribute/XmlRpcAttribute.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using TouchSocket.Core; using TouchSocket.Rpc; namespace TouchSocket.XmlRpc; diff --git a/src/TouchSocket.XmlRpc/Common/XmlDataTool.cs b/src/TouchSocket.XmlRpc/Common/XmlDataTool.cs index f4013a249..ea56bee53 100644 --- a/src/TouchSocket.XmlRpc/Common/XmlDataTool.cs +++ b/src/TouchSocket.XmlRpc/Common/XmlDataTool.cs @@ -10,11 +10,8 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Collections; -using System.Text; using System.Xml; -using TouchSocket.Core; using TouchSocket.Http; namespace TouchSocket.XmlRpc; diff --git a/src/TouchSocket.XmlRpc/Common/XmlRpcCallContext.cs b/src/TouchSocket.XmlRpc/Common/XmlRpcCallContext.cs index a3d96d7db..a9e77685f 100644 --- a/src/TouchSocket.XmlRpc/Common/XmlRpcCallContext.cs +++ b/src/TouchSocket.XmlRpc/Common/XmlRpcCallContext.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; using TouchSocket.Http; using TouchSocket.Rpc; diff --git a/src/TouchSocket.XmlRpc/Components/XmlRpcClient.cs b/src/TouchSocket.XmlRpc/Components/XmlRpcClient.cs index f457b7ed3..d518e52f7 100644 --- a/src/TouchSocket.XmlRpc/Components/XmlRpcClient.cs +++ b/src/TouchSocket.XmlRpc/Components/XmlRpcClient.cs @@ -10,11 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading; -using System.Threading.Tasks; using System.Xml; -using TouchSocket.Core; using TouchSocket.Http; using TouchSocket.Rpc; @@ -25,14 +21,23 @@ namespace TouchSocket.XmlRpc; /// public class XmlRpcClient : HttpClientBase, IXmlRpcClient { + private readonly SemaphoreSlim m_semaphoreSlim = new SemaphoreSlim(1, 1); /// - public Task ConnectAsync(int millisecondsTimeout, CancellationToken token) + public async Task ConnectAsync(CancellationToken cancellationToken) { - return this.TcpConnectAsync(millisecondsTimeout, token); + await this.m_semaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + try + { + await base.HttpConnectAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + finally + { + this.m_semaphoreSlim.Release(); + } } /// - public async Task InvokeAsync(string invokeKey, Type returnType, IInvokeOption invokeOption, params object[] parameters) + public async Task InvokeAsync(string invokeKey, Type returnType, InvokeOption invokeOption, params object[] parameters) { invokeOption ??= InvokeOption.WaitInvoke; @@ -40,7 +45,7 @@ public class XmlRpcClient : HttpClientBase, IXmlRpcClient { var request = XmlDataTool.CreateRequest(this, invokeKey, parameters); - using (var responseResult = await this.ProtectedRequestContentAsync(request, invokeOption.Timeout, invokeOption.Token).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) + using (var responseResult = await this.ProtectedRequestAsync(request, invokeOption.Token).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) { var response = responseResult.Response; diff --git a/src/TouchSocket.XmlRpc/Extensions/XmlRpcPluginsManagerExtension.cs b/src/TouchSocket.XmlRpc/Extensions/XmlRpcPluginsManagerExtension.cs index 732931b46..f584b8676 100644 --- a/src/TouchSocket.XmlRpc/Extensions/XmlRpcPluginsManagerExtension.cs +++ b/src/TouchSocket.XmlRpc/Extensions/XmlRpcPluginsManagerExtension.cs @@ -10,22 +10,40 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ +using TouchSocket.Rpc; using TouchSocket.XmlRpc; namespace TouchSocket.Core; /// -/// XmlRpcPluginManagerExtension +/// XmlRpc插件管理器扩展 /// public static class XmlRpcPluginManagerExtension { /// /// 使用XmlRpc的插件。仅服务器可用。 /// - /// - /// - public static XmlRpcParserPlugin UseXmlRpc(this IPluginManager pluginManager) + /// 插件管理器 + /// XmlRpc配置选项 + /// XmlRpc解析插件 + public static XmlRpcParserPlugin UseXmlRpc(this IPluginManager pluginManager, Action options) { - return pluginManager.Add(); + var option = new XmlRpcOption(); + + options.Invoke(option); + var plugin = new XmlRpcParserPlugin(pluginManager.Resolver.Resolve(), option); + pluginManager.Add(plugin); + return plugin; + } + + /// + /// 使用XmlRpc的插件。仅服务器可用。 + /// + /// 插件管理器 + /// XmlRpc的Url,默认为/xmlrpc + /// XmlRpc解析插件 + public static XmlRpcParserPlugin UseXmlRpc(this IPluginManager pluginManager, string url = "/xmlrpc") + { + return UseXmlRpc(pluginManager, (option) => option.SetAllowXmlRpc(url)); } } \ No newline at end of file diff --git a/src/TouchSocket.XmlRpc/Interface/IXmlRpcClient.cs b/src/TouchSocket.XmlRpc/Interface/IXmlRpcClient.cs index 205327838..0c881a51f 100644 --- a/src/TouchSocket.XmlRpc/Interface/IXmlRpcClient.cs +++ b/src/TouchSocket.XmlRpc/Interface/IXmlRpcClient.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; using TouchSocket.Http; using TouchSocket.Rpc; using TouchSocket.Sockets; diff --git a/src/TouchSocket.XmlRpc/Options/XmlRpcOption.cs b/src/TouchSocket.XmlRpc/Options/XmlRpcOption.cs new file mode 100644 index 000000000..13e6a8790 --- /dev/null +++ b/src/TouchSocket.XmlRpc/Options/XmlRpcOption.cs @@ -0,0 +1,56 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TouchSocket.Http; + +namespace TouchSocket.XmlRpc; + +public class XmlRpcOption +{ + public XmlRpcOption() + { + this.SetAllowXmlRpc(); + } + public Func> AllowXmlRpc { get; set; } + + /// + /// 设置允许XmlRpc的委托。 + /// + /// 允许XmlRpc的委托 + public void SetAllowXmlRpc(Func> allowXmlRpc) + { + this.AllowXmlRpc = allowXmlRpc; + } + + /// + /// 设置允许XmlRpc的Url。 + /// + /// 允许的Url,默认为"/XmlRpc"。 + public void SetAllowXmlRpc(string url = "/XmlRpc") + { + this.AllowXmlRpc = (client, context) => Task.FromResult(context.Request.UrlEquals(url)); + } + + /// + /// 设置允许XmlRpc的委托。 + /// + /// 允许XmlRpc的委托 + public void SetAllowXmlRpc(Func allowXmlRpc) + { + this.AllowXmlRpc = (client, context) => Task.FromResult(allowXmlRpc(client, context)); + } +} diff --git a/src/TouchSocket.XmlRpc/Plugins/XmlRpcParserPlugin.cs b/src/TouchSocket.XmlRpc/Plugins/XmlRpcParserPlugin.cs index d299ad5f9..33b72a9f5 100644 --- a/src/TouchSocket.XmlRpc/Plugins/XmlRpcParserPlugin.cs +++ b/src/TouchSocket.XmlRpc/Plugins/XmlRpcParserPlugin.cs @@ -10,13 +10,10 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Net.Sockets; -using System.Threading.Tasks; using System.Xml; -using TouchSocket.Core; using TouchSocket.Http; using TouchSocket.Rpc; +using TouchSocket.Sockets; namespace TouchSocket.XmlRpc; @@ -27,16 +24,16 @@ namespace TouchSocket.XmlRpc; public class XmlRpcParserPlugin : PluginBase, IHttpPlugin { private readonly IRpcServerProvider m_rpcServerProvider; - private string m_xmlRpcUrl = "/xmlrpc"; /// /// 构造函数 /// - public XmlRpcParserPlugin(IRpcServerProvider rpcServerProvider) + public XmlRpcParserPlugin(IRpcServerProvider rpcServerProvider, XmlRpcOption option) { this.ActionMap = new ActionMap(true); this.RegisterServer(rpcServerProvider.GetMethods()); this.m_rpcServerProvider = rpcServerProvider; + this.m_allowXmlRpc = option.AllowXmlRpc; } /// @@ -49,21 +46,15 @@ public class XmlRpcParserPlugin : PluginBase, IHttpPlugin /// public RpcStore RpcStore { get; private set; } - /// - /// 当挂载在时,匹配Url然后响应。当设置为或空时,会全部响应。 - /// - public string XmlRpcUrl - { - get => this.m_xmlRpcUrl; - set => this.m_xmlRpcUrl = string.IsNullOrEmpty(value) ? "/" : value; - } + private readonly Func> m_allowXmlRpc; /// public async Task OnHttpRequest(IHttpSessionClient client, HttpContextEventArgs e) { if (e.Context.Request.Method == HttpMethod.Post) { - if (this.m_xmlRpcUrl == "/" || e.Context.Request.UrlEquals(this.m_xmlRpcUrl)) + var allowXmlRpc = await this.m_allowXmlRpc.Invoke(client, e.Context); + if (allowXmlRpc) { e.Handled = true; XmlRpcCallContext callContext = null; @@ -152,7 +143,7 @@ public class XmlRpcParserPlugin : PluginBase, IHttpPlugin if (!e.Context.Request.KeepAlive) { - await client.ShutdownAsync(SocketShutdown.Both).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await client.CloseAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } finally @@ -166,17 +157,6 @@ public class XmlRpcParserPlugin : PluginBase, IHttpPlugin await e.InvokeNext().ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - /// - /// 当挂载在时,匹配Url然后响应。当设置为或空时,会全部响应。 - /// - /// - /// - public XmlRpcParserPlugin SetXmlRpcUrl(string xmlRpcUrl) - { - this.XmlRpcUrl = xmlRpcUrl; - return this; - } - private void RegisterServer(RpcMethod[] rpcMethods) { foreach (var rpcMethod in rpcMethods) diff --git a/src/TouchSocket.XmlRpc/Proxy/XmlRpcDispatchProxy.cs b/src/TouchSocket.XmlRpc/Proxy/XmlRpcDispatchProxy.cs index 6831da17c..169f12bf4 100644 --- a/src/TouchSocket.XmlRpc/Proxy/XmlRpcDispatchProxy.cs +++ b/src/TouchSocket.XmlRpc/Proxy/XmlRpcDispatchProxy.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -#if NET6_0_OR_GREATER || NET481_OR_GREATER using TouchSocket.Rpc; namespace TouchSocket.XmlRpc; @@ -31,6 +30,3 @@ public abstract class XmlRpcDispatchProxy : XmlRpcDispatchProxy { } - - -#endif \ No newline at end of file diff --git a/src/TouchSocket.XmlRpc/Proxy/XmlRpcRealityProxy.cs b/src/TouchSocket.XmlRpc/Proxy/XmlRpcRealityProxy.cs deleted file mode 100644 index 07138f05b..000000000 --- a/src/TouchSocket.XmlRpc/Proxy/XmlRpcRealityProxy.cs +++ /dev/null @@ -1,42 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -#if NET45_OR_GREATER -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using TouchSocket.Rpc; - -namespace TouchSocket.XmlRpc; - -/// -/// XmlRpcRealityProxy -/// -/// -/// -public abstract class XmlRpcRealityProxy : RpcRealityProxy where TClient : IXmlRpcClient -{ - -} - -/// -/// XmlRpcRealityProxy -/// -/// -public abstract class XmlRpcRealityProxy : XmlRpcRealityProxy -{ - -} - -#endif \ No newline at end of file diff --git a/src/TouchSocket.XmlRpc/Readme.md b/src/TouchSocket.XmlRpc/Readme.md index aca1b40d1..fd7d14351 100644 --- a/src/TouchSocket.XmlRpc/Readme.md +++ b/src/TouchSocket.XmlRpc/Readme.md @@ -12,14 +12,14 @@ TouchSocket.XmlRpc 是一个提供 XmlRpc 服务器和客户端的组件库。 详细的说明文档请访问:[https://touchsocket.net/](https://touchsocket.net/) ## 支持的目标框架 + - net481 -- net45 - net462 - net472 - netstandard2.0 - netstandard2.1 - net6.0 -- net9.0 +- net10.0 - net8.0 ## 贡献与反馈 diff --git a/src/TouchSocket.XmlRpc/TouchSocket.XmlRpc.csproj b/src/TouchSocket.XmlRpc/TouchSocket.XmlRpc.csproj index 41b563a51..2e6d278c5 100644 --- a/src/TouchSocket.XmlRpc/TouchSocket.XmlRpc.csproj +++ b/src/TouchSocket.XmlRpc/TouchSocket.XmlRpc.csproj @@ -1,6 +1,6 @@ - net481;net45;net462;net472;netstandard2.0;netstandard2.1;net6.0;net9.0;net8.0 + net462;netstandard2.0;netstandard2.1;net6.0;net10.0;net8.0 XmlRpc;TouchSocket 这是一个提供XmlRpc服务器和客户端的组件库。可以通过该组件创建XmlRpc服务解析器,完美支持XmlRpc数据类型,类型嵌套,Array等。也能与CookComputing.XmlRpcV2完美对接。不限Web,Android等平台。 @@ -12,10 +12,6 @@ - - - - diff --git a/src/TouchSocket/Common/IPHost.cs b/src/TouchSocket/Common/IPHost.cs index 018b56b2b..bffb9fdcc 100644 --- a/src/TouchSocket/Common/IPHost.cs +++ b/src/TouchSocket/Common/IPHost.cs @@ -10,11 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Linq; using System.Net; -using TouchSocket.Core; using TouchSocket.Resources; namespace TouchSocket.Sockets; @@ -91,6 +87,10 @@ public class IPHost : Uri { this.m_endPoint = new DnsEndPoint(this.DnsSafeHost, this.Port); } + else if (this.HostNameType == UriHostNameType.IPv4 || this.HostNameType == UriHostNameType.IPv6) + { + this.m_endPoint = new IPEndPoint(IPAddress.Parse(this.Host), this.Port); + } else { this.m_endPoint = new IPEndPoint(IPAddress.Parse(this.DnsSafeHost), this.Port); @@ -201,6 +201,21 @@ public class IPHost : Uri return iPs.ToArray(); } + /// + /// 指示是否为安全连接(SSL/TLS) + /// + public bool IsSsl => this.Scheme.ToLowerInvariant() switch + { + "https" => true, // HTTP over SSL/TLS + "wss" => true, // WebSocket Secure + "ftps" => true, // FTP over SSL/TLS + "tcps" => true, // TCP over SSL/TLS + "ssl" => true, // Generic SSL/TLS + "tls" => true, // Generic TLS + "mqtts" => true, // MQTT over SSL/TLS + _ => false + }; + private static string VerifyUri(string uriString) { return TouchSocketCoreUtility.IsUrl(uriString) ? uriString : $"tcp://{uriString}"; diff --git a/src/TouchSocket/Common/KeepAliveValue.cs b/src/TouchSocket/Common/KeepAliveValue.cs index a452e359b..af8f71995 100644 --- a/src/TouchSocket/Common/KeepAliveValue.cs +++ b/src/TouchSocket/Common/KeepAliveValue.cs @@ -10,7 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; +using System.Net.Sockets; using System.Runtime.InteropServices; namespace TouchSocket.Sockets; @@ -45,4 +45,19 @@ public class KeepAliveValue /// 确认间隔,默认2*1000ms /// public uint AckInterval { get; set; } = 2 * 1000; + + internal void Config(Socket socket) + { +#if NET462_OR_GREATER + + socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); + socket.IOControl(IOControlCode.KeepAliveValues, this.KeepAliveTime, null); +#else + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); + socket.IOControl(IOControlCode.KeepAliveValues, this.KeepAliveTime, null); + } +#endif + } } \ No newline at end of file diff --git a/src/TouchSocket/Common/Protocol.cs b/src/TouchSocket/Common/Protocol.cs index a88fd6017..a2db18642 100644 --- a/src/TouchSocket/Common/Protocol.cs +++ b/src/TouchSocket/Common/Protocol.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Sockets; /// diff --git a/src/TouchSocket/Common/SocketOperationResult.cs b/src/TouchSocket/Common/SocketOperationResult.cs deleted file mode 100644 index 29615c899..000000000 --- a/src/TouchSocket/Common/SocketOperationResult.cs +++ /dev/null @@ -1,24 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System.Net; -using System.Net.Sockets; - -namespace TouchSocket.Sockets; - -internal class SocketOperationResult -{ - public int BytesTransferred; - public EndPoint RemoteEndPoint; - public SocketException SocketError; - public IPPacketInformation ReceiveMessageFromPacketInfo; -} \ No newline at end of file diff --git a/src/TouchSocket/Common/TcpNetworkMonitor.cs b/src/TouchSocket/Common/TcpNetworkMonitor.cs index df5d4bb45..200289101 100644 --- a/src/TouchSocket/Common/TcpNetworkMonitor.cs +++ b/src/TouchSocket/Common/TcpNetworkMonitor.cs @@ -10,9 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Net.Sockets; -using TouchSocket.Core; namespace TouchSocket.Sockets; @@ -30,8 +28,10 @@ public class TcpNetworkMonitor /// 如果option或socket为,则抛出此异常 public TcpNetworkMonitor(TcpListenOption option, Socket socket, SocketAsyncEventArgs e) { - this.Option = ThrowHelper.ThrowArgumentNullExceptionIf(option, nameof(option)); - this.Socket = ThrowHelper.ThrowArgumentNullExceptionIf(socket, nameof(socket)); + ThrowHelper.ThrowIfNull(option, nameof(option)); + ThrowHelper.ThrowIfNull(socket, nameof(socket)); + this.Option = option; + this.Socket = socket; this.SocketAsyncEvent = e; } diff --git a/src/TouchSocket/Common/TcpOperationResult.cs b/src/TouchSocket/Common/TcpOperationResult.cs new file mode 100644 index 000000000..3e63a6b10 --- /dev/null +++ b/src/TouchSocket/Common/TcpOperationResult.cs @@ -0,0 +1,28 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.Net.Sockets; + +namespace TouchSocket.Sockets; + +internal readonly struct TcpOperationResult +{ + public TcpOperationResult(int bytesTransferred, SocketError socketError) + { + this.BytesTransferred = bytesTransferred; + this.SocketError = socketError; + } + + public int BytesTransferred { get; } + + public SocketError SocketError { get; } +} \ No newline at end of file diff --git a/src/TouchSocket/Common/TouchSocketUtility.cs b/src/TouchSocket/Common/TouchSocketUtility.cs index 2a8f20972..8da49ee34 100644 --- a/src/TouchSocket/Common/TouchSocketUtility.cs +++ b/src/TouchSocket/Common/TouchSocketUtility.cs @@ -13,9 +13,8 @@ namespace TouchSocket.Sockets; /// -/// TouchSocketUtility +/// TouchSocketUtility 提供实用方法。 /// -public class TouchSocketUtility +public static class TouchSocketUtility { - } \ No newline at end of file diff --git a/src/TouchSocket/Common/UdpNetworkMonitor.cs b/src/TouchSocket/Common/UdpNetworkMonitor.cs index c62d01bfc..f2f7e1cf8 100644 --- a/src/TouchSocket/Common/UdpNetworkMonitor.cs +++ b/src/TouchSocket/Common/UdpNetworkMonitor.cs @@ -10,9 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Net.Sockets; -using TouchSocket.Core; namespace TouchSocket.Sockets; @@ -30,8 +28,8 @@ public class UdpNetworkMonitor public UdpNetworkMonitor(IPHost iPHost, Socket socket) { this.IPHost = iPHost; - // 检查socket参数是否为空,为空则抛出ArgumentNullException - this.Socket = ThrowHelper.ThrowArgumentNullExceptionIf(socket, nameof(socket)); + ThrowHelper.ThrowIfNull(socket, nameof(socket)); + this.Socket = socket; } /// diff --git a/src/TouchSocket/Common/UdpOperationResult.cs b/src/TouchSocket/Common/UdpOperationResult.cs new file mode 100644 index 000000000..d0ad4d08c --- /dev/null +++ b/src/TouchSocket/Common/UdpOperationResult.cs @@ -0,0 +1,32 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.Net; +using System.Net.Sockets; + +namespace TouchSocket.Sockets; + +internal readonly struct UdpOperationResult +{ + public UdpOperationResult(int bytesTransferred, EndPoint remoteEndPoint, SocketError socketError, IPPacketInformation receiveMessageFromPacketInfo) + { + this.BytesTransferred = bytesTransferred; + this.RemoteEndPoint = remoteEndPoint; + this.SocketError = socketError; + this.ReceiveMessageFromPacketInfo = receiveMessageFromPacketInfo; + } + + public int BytesTransferred { get; } + public EndPoint RemoteEndPoint { get; } + public SocketError SocketError { get; } + public IPPacketInformation ReceiveMessageFromPacketInfo { get; } +} diff --git a/src/TouchSocket/Components/Base/ConnectableService.cs b/src/TouchSocket/Components/Base/ConnectableService.cs index 678c0a29d..f0ee67f72 100644 --- a/src/TouchSocket/Components/Base/ConnectableService.cs +++ b/src/TouchSocket/Components/Base/ConnectableService.cs @@ -10,12 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// @@ -23,7 +17,7 @@ namespace TouchSocket.Sockets; /// public abstract class ConnectableService : ServiceBase, IConnectableService { - private Func m_getDefaultNewId; + private Func m_getDefaultNewId; private int m_maxCount; private int m_nextId; @@ -57,7 +51,7 @@ public abstract class ConnectableService : ServiceBase, IConnectableService public abstract IEnumerable GetIds(); /// - public abstract Task ResetIdAsync(string sourceId, string targetId); + public abstract Task ResetIdAsync(string sourceId, string targetId, CancellationToken cancellationToken = default); /// protected abstract IEnumerable GetClients(); @@ -67,12 +61,12 @@ public abstract class ConnectableService : ServiceBase, IConnectableService /// 尝试获取下一个新的标识符。 /// /// 返回新的标识符,如果内部方法调用失败,则返回默认的新标识符。 - protected virtual string GetNextNewId() + protected virtual string GetNextNewId(IClient client) { try { // 尝试调用内部方法获取新的标识符。 - return this.m_getDefaultNewId.Invoke(); + return this.m_getDefaultNewId.Invoke(client); } catch (Exception ex) { @@ -80,22 +74,22 @@ public abstract class ConnectableService : ServiceBase, IConnectableService this.Logger?.Exception(this, ex); } // 如果调用内部方法失败,则使用此方法作为回退,返回一个默认的新标识符。 - return this.GetDefaultNewId(); + return this.GetDefaultNewId(client); } /// protected override void LoadConfig(TouchSocketConfig config) { - if (config.GetValue(TouchSocketConfigExtension.GetDefaultNewIdProperty) is Func fun) + if (config.TryGetValue(TouchSocketConfigExtension.GetDefaultNewIdProperty,out var func)) { - this.m_getDefaultNewId = fun; + this.m_getDefaultNewId = func; } this.m_maxCount = config.GetValue(TouchSocketConfigExtension.MaxCountProperty); base.LoadConfig(config); } - private string GetDefaultNewId() + private string GetDefaultNewId(IClient client) { return BitConverter.ToString(BitConverter.GetBytes(Interlocked.Increment(ref this.m_nextId))); } diff --git a/src/TouchSocket/Components/Base/ConnectableServiceT.cs b/src/TouchSocket/Components/Base/ConnectableServiceT.cs index 2ea62fdc7..e7a169ed6 100644 --- a/src/TouchSocket/Components/Base/ConnectableServiceT.cs +++ b/src/TouchSocket/Components/Base/ConnectableServiceT.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Collections.Generic; -using System.Linq; - namespace TouchSocket.Sockets; /// diff --git a/src/TouchSocket/Components/Base/ServiceBase.cs b/src/TouchSocket/Components/Base/ServiceBase.cs index b88e7792a..53a979548 100644 --- a/src/TouchSocket/Components/Base/ServiceBase.cs +++ b/src/TouchSocket/Components/Base/ServiceBase.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// @@ -31,5 +27,5 @@ public abstract class ServiceBase : SetupConfigObject, IServiceBase public abstract Task StartAsync(); /// - public abstract Task StopAsync(CancellationToken token = default); + public abstract Task StopAsync(CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/TouchSocket/Components/Core/SocketAwaitableEventArgs.cs b/src/TouchSocket/Components/Core/SocketAwaitableEventArgs.cs index 7059f978f..0fa1b8257 100644 --- a/src/TouchSocket/Components/Core/SocketAwaitableEventArgs.cs +++ b/src/TouchSocket/Components/Core/SocketAwaitableEventArgs.cs @@ -10,112 +10,69 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Net.Sockets; -using System.Runtime.CompilerServices; -using System.Threading; using System.Threading.Tasks.Sources; namespace TouchSocket.Sockets; -internal abstract class SocketAwaitableEventArgs : SocketAsyncEventArgs, IValueTaskSource +internal abstract class SocketAwaitableEventArgs : SocketAsyncEventArgs, IValueTaskSource + where TResult : struct { - private static readonly Action s_continuationCompleted = _ => { }; - - private volatile Action m_continuation; - private readonly SocketOperationResult m_socketOperationResult = new SocketOperationResult(); - - protected override void OnCompleted(SocketAsyncEventArgs _) - { - var c = this.m_continuation; - - if (c != null || (c = Interlocked.CompareExchange(ref this.m_continuation, s_continuationCompleted, null)) != null) - { - var continuationState = this.UserToken; - this.UserToken = null; - this.m_continuation = s_continuationCompleted; // in case someone's polling IsCompleted #if NET6_0_OR_GREATER - ThreadPool.UnsafeQueueUserWorkItem(c, continuationState, true); + public SocketAwaitableEventArgs() + : base(true) + { + + } #else - void Run(object o) - { - c.Invoke(o); - } + public SocketAwaitableEventArgs() + { - ThreadPool.UnsafeQueueUserWorkItem(Run, continuationState); + } #endif - } + + + + protected ManualResetValueTaskSourceCore m_core = new ManualResetValueTaskSourceCore(); + + public bool RunContinuationsAsynchronously { get => this.m_core.RunContinuationsAsynchronously; set => this.m_core.RunContinuationsAsynchronously = value; } + + TResult IValueTaskSource.GetResult(short cancellationToken) + { + return this.m_core.GetResult(cancellationToken); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected SocketOperationResult GetSocketOperationResult() + ValueTaskSourceStatus IValueTaskSource.GetStatus(short cancellationToken) { - if (this.SocketError != SocketError.Success) - { - this.m_socketOperationResult.SocketError = CreateException(this.SocketError); - this.m_socketOperationResult.BytesTransferred = default; - this.m_socketOperationResult.RemoteEndPoint = default; - this.m_socketOperationResult.ReceiveMessageFromPacketInfo = default; - } - else - { - this.m_socketOperationResult.SocketError = null; - this.m_socketOperationResult.BytesTransferred = this.BytesTransferred; - this.m_socketOperationResult.RemoteEndPoint = this.RemoteEndPoint; - this.m_socketOperationResult.ReceiveMessageFromPacketInfo = this.ReceiveMessageFromPacketInfo; - } - - return this.m_socketOperationResult; + return this.m_core.GetStatus(cancellationToken); } - #region IValueTaskSource - - public SocketOperationResult GetResult(short token) + void IValueTaskSource.OnCompleted(Action continuation, object state, short cancellationToken, ValueTaskSourceOnCompletedFlags flags) { - this.m_continuation = null; - - return this.GetSocketOperationResult(); - } - - public ValueTaskSourceStatus GetStatus(short token) - { - if (!ReferenceEquals(this.m_continuation, s_continuationCompleted)) + try { - return ValueTaskSourceStatus.Pending; + this.m_core.OnCompleted(continuation, state, cancellationToken, flags); } - else + catch (Exception ex) { - return this.SocketError == SocketError.Success ? ValueTaskSourceStatus.Succeeded : - ValueTaskSourceStatus.Faulted; - } - } - - public void OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) - { - this.UserToken = state; - var prevContinuation = Interlocked.CompareExchange(ref this.m_continuation, continuation, null); - if (ReferenceEquals(prevContinuation, s_continuationCompleted)) - { - this.UserToken = null; - -#if NET6_0_OR_GREATER - ThreadPool.UnsafeQueueUserWorkItem(continuation, state, true); -#else - void Run(object o) + // 如果可能,尝试通知异常 + try { - continuation.Invoke(o); + this.m_core.SetException(ex); + } + catch + { + // 忽略SetException的异常,避免递归异常 } - - ThreadPool.UnsafeQueueUserWorkItem(Run, state); -#endif } } - #endregion IValueTaskSource - - protected static SocketException CreateException(SocketError e) + protected override void OnCompleted(SocketAsyncEventArgs e) { - return new SocketException((int)e); + this.m_core.SetResult(this.GetResult()); + base.OnCompleted(e); } + + protected abstract TResult GetResult(); } \ No newline at end of file diff --git a/src/TouchSocket/Components/Core/SocketReceiver.cs b/src/TouchSocket/Components/Core/SocketReceiver.cs index d67b99c8f..306e11f30 100644 --- a/src/TouchSocket/Components/Core/SocketReceiver.cs +++ b/src/TouchSocket/Components/Core/SocketReceiver.cs @@ -10,34 +10,36 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Net.Sockets; -using System.Threading.Tasks; -using TouchSocket.Core; namespace TouchSocket.Sockets; -internal sealed class SocketReceiver : SocketAwaitableEventArgs +internal sealed class SocketReceiver : SocketAwaitableEventArgs { - public ValueTask WaitForDataAsync(Socket socket) + + public ValueTask WaitForDataAsync(Socket socket) { + this.m_core.Reset(); #if NET6_0_OR_GREATER this.SetBuffer(Memory.Empty); #else - var empty = new byte[0]; - this.SetBuffer(empty, 0, 0); + this.SetBuffer([], 0, 0); #endif if (socket.ReceiveAsync(this)) { - return new ValueTask(this, 0); + return new ValueTask(this, this.m_core.Version); } - return new ValueTask(this.GetSocketOperationResult()); + var bytesTransferred = this.BytesTransferred; + var error = this.SocketError; + + return new ValueTask(new TcpOperationResult(bytesTransferred, error)); } - public ValueTask ReceiveAsync(Socket socket, Memory buffer) + public ValueTask ReceiveAsync(Socket socket, Memory buffer) { + this.m_core.Reset(); #if NET6_0_OR_GREATER this.SetBuffer(buffer); #else @@ -48,9 +50,14 @@ internal sealed class SocketReceiver : SocketAwaitableEventArgs if (socket.ReceiveAsync(this)) { - return new ValueTask(this, 0); + return new ValueTask(this, this.m_core.Version); } - return new ValueTask(this.GetSocketOperationResult()); + return new ValueTask(this.GetResult()); + } + + protected sealed override TcpOperationResult GetResult() + { + return new TcpOperationResult(this.BytesTransferred, this.SocketError); } } \ No newline at end of file diff --git a/src/TouchSocket/Components/Core/SocketSender.cs b/src/TouchSocket/Components/Core/SocketSender.cs index 42d212d52..28267eada 100644 --- a/src/TouchSocket/Components/Core/SocketSender.cs +++ b/src/TouchSocket/Components/Core/SocketSender.cs @@ -10,35 +10,46 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Buffers; -using System.Collections.Generic; using System.Net.Sockets; using System.Runtime.InteropServices; -using System.Threading.Tasks; -using TouchSocket.Core; namespace TouchSocket.Sockets; -internal sealed class SocketSender : SocketAwaitableEventArgs +internal sealed class SocketSender : SocketAwaitableEventArgs { private List> m_bufferList; - public ValueTask SendAsync(Socket socket, in ReadOnlySequence buffers) + public ValueTask SendAsync(Socket socket, in ReadOnlySequence buffers) { if (buffers.IsSingleSegment) { return this.SendAsync(socket, buffers.First); } + this.m_core.Reset(); + this.SetBufferList(buffers); if (socket.SendAsync(this)) { - return new ValueTask(this, 0); + return new ValueTask(this, this.m_core.Version); } - return new ValueTask(this.GetSocketOperationResult()); + return new ValueTask(this.GetResult()); + } + + public ValueTask SendAsync(Socket socket, List> buffers) + { + this.m_core.Reset(); + this.SetBufferList(buffers); + + if (socket.SendAsync(this)) + { + return new ValueTask(this, this.m_core.Version); + } + + return new ValueTask(this.GetResult()); } public void Reset() @@ -55,8 +66,9 @@ internal sealed class SocketSender : SocketAwaitableEventArgs } } - public ValueTask SendAsync(Socket socket, in ReadOnlyMemory memory) + public ValueTask SendAsync(Socket socket, in ReadOnlyMemory memory) { + this.m_core.Reset(); #if NET6_0_OR_GREATER this.SetBuffer(MemoryMarshal.AsMemory(memory)); #else @@ -66,10 +78,10 @@ internal sealed class SocketSender : SocketAwaitableEventArgs #endif if (socket.SendAsync(this)) { - return new ValueTask(this, 0); + return new ValueTask(this, this.m_core.Version); } - return new ValueTask(this.GetSocketOperationResult()); + return new ValueTask(this.GetResult()); } private void SetBufferList(in ReadOnlySequence buffer) @@ -82,4 +94,15 @@ internal sealed class SocketSender : SocketAwaitableEventArgs } this.BufferList = this.m_bufferList; } + + private void SetBufferList(List> buffer) + { + this.BufferList = buffer; + } + + + protected override TcpOperationResult GetResult() + { + return new TcpOperationResult(this.BytesTransferred, this.SocketError); + } } \ No newline at end of file diff --git a/src/TouchSocket/Components/Core/TcpCore.cs b/src/TouchSocket/Components/Core/TcpCore.cs index 805506535..6103d3cc1 100644 --- a/src/TouchSocket/Components/Core/TcpCore.cs +++ b/src/TouchSocket/Components/Core/TcpCore.cs @@ -10,15 +10,8 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Buffers; -using System.Collections.Concurrent; -using System.Net.Security; using System.Net.Sockets; -using System.Runtime.ExceptionServices; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Resources; namespace TouchSocket.Sockets; @@ -26,66 +19,8 @@ namespace TouchSocket.Sockets; /// /// Tcp核心 /// -internal sealed class TcpCore : DisposableObject +internal sealed class TcpCore : SafetyDisposableObject { - /// - /// 最小缓存尺寸 - /// - public int MinBufferSize { get; set; } = 1024 * 10; - - /// - /// 最大缓存尺寸 - /// - public int MaxBufferSize { get; set; } = 1024 * 512; - - #region 字段 - - private const int BatchSize = 100; - private const int MaxMemoryLength = 1024; - private readonly Task m_sendTask; - private int m_receiveBufferSize = 1024 * 10; - private ValueCounter m_receiveCounter; - private int m_sendBufferSize = 1024 * 10; - private ValueCounter m_sentCounter; - private Socket m_socket; - private SslStream m_sslStream; - private bool m_useSsl; - private readonly SemaphoreSlim m_semaphoreForSend = new SemaphoreSlim(1, 1); - private readonly SocketReceiver m_socketReceiver = new SocketReceiver(); - private readonly SocketSender m_socketSender = new SocketSender(); - private readonly AsyncResetEvent m_asyncResetEventForTask = new AsyncResetEvent(false, true); - private readonly AsyncResetEvent m_asyncResetEventForSend = new AsyncResetEvent(true, false); - private readonly ConcurrentQueue m_sendingBytes = new ConcurrentQueue(); - private readonly SemaphoreSlim m_semaphoreSlimForMax = new SemaphoreSlim(BatchSize); - private ExceptionDispatchInfo m_exceptionDispatchInfo; - private short m_version; - private bool m_noDelay; - private readonly SocketOperationResult m_result = new SocketOperationResult(); - private readonly CancellationTokenSource m_cancellationTokenSource = new(); - private readonly CancellationToken m_cancellationToken; - #endregion 字段 - - /// - /// Tcp核心 - /// - public TcpCore() - { - this.m_receiveCounter = new ValueCounter - { - Period = TimeSpan.FromSeconds(1), - OnPeriod = this.OnReceivePeriod - }; - - this.m_sentCounter = new ValueCounter - { - Period = TimeSpan.FromSeconds(1), - OnPeriod = this.OnSendPeriod - }; - this.m_cancellationToken = this.m_cancellationTokenSource.Token; - this.m_sendTask = Task.Run(this.TaskSend); - this.m_sendTask.FireAndForget(); - } - /// /// 析构函数 /// @@ -94,151 +29,33 @@ internal sealed class TcpCore : DisposableObject this.Dispose(disposing: false); } - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - if (disposing) - { - this.m_socketReceiver.SafeDispose(); - this.m_socketSender.SafeDispose(); - this.m_semaphoreForSend.SafeDispose(); - this.m_asyncResetEventForTask.SafeDispose(); - this.m_asyncResetEventForSend.SafeDispose(); - this.m_semaphoreSlimForMax.SafeDispose(); - this.m_cancellationTokenSource.Cancel(); - this.m_cancellationTokenSource.SafeDispose(); - } - } + #region 字段 - /// - /// 接收缓存池,运行时的值会根据流速自动调整 - /// - public int ReceiveBufferSize => Math.Min(Math.Max(this.m_receiveBufferSize, this.MinBufferSize), this.MaxBufferSize); + //private readonly SocketReceiver2 m_socketReceiver = new(System.IO.Pipelines.PipeScheduler.ThreadPool); + private readonly SocketReceiver m_socketReceiver = new(); + private readonly SocketSender m_socketSender = new(); + private Socket m_socket; - /// - /// 接收计数器 - /// - public ValueCounter ReceiveCounter => this.m_receiveCounter; + #endregion 字段 - /// - /// 发送缓存池,运行时的值会根据流速自动调整 - /// - public int SendBufferSize => Math.Min(Math.Max(this.m_sendBufferSize, this.MinBufferSize), this.MaxBufferSize); - - public bool NoDelay => this.m_noDelay; - - /// - /// 发送计数器 - /// - public ValueCounter SendCounter => this.m_sentCounter; - - /// - /// Socket - /// + public bool ReceiveRunContinuationsAsynchronously { get => this.m_socketReceiver.RunContinuationsAsynchronously; set => this.m_socketReceiver.RunContinuationsAsynchronously = value; } + public bool SendRunContinuationsAsynchronously { get => this.m_socketSender.RunContinuationsAsynchronously; set => this.m_socketSender.RunContinuationsAsynchronously = value; } public Socket Socket => this.m_socket; - /// - /// 提供一个用于客户端-服务器通信的流,该流使用安全套接字层 (SSL) 安全协议对服务器和(可选)客户端进行身份验证。 - /// - public SslStream SslStream => this.m_sslStream; - /// - /// 是否启用了Ssl - /// - public bool UseSsl => this.m_useSsl; + #region Receive - public async Task ShutdownAsync(SocketShutdown how) + public ValueTask ReceiveAsync(in Memory memory) { - //主要等待发送队列任务完成 - await this.m_semaphoreForSend.WaitAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - try - { - await this.m_asyncResetEventForSend.WaitOneAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.m_socket.Shutdown(how); - - return Result.Success; - } - catch (Exception ex) - { - return Result.FromException(ex); - } - finally - { - this.m_semaphoreForSend.Release(); - } + return this.m_socketReceiver.ReceiveAsync(this.m_socket, memory); } - /// - /// 以Ssl服务器模式授权 - /// - /// - /// - public async Task AuthenticateAsync(ServiceSslOption sslOption) + public ValueTask WaitForDataAsync() { - if (this.m_useSsl) - { - return; - } - var sslStream = (sslOption.CertificateValidationCallback != null) ? new SslStream(new NetworkStream(this.m_socket, false), false, sslOption.CertificateValidationCallback) : new SslStream(new NetworkStream(this.m_socket, false), false); - - await sslStream.AuthenticateAsServerAsync(sslOption.Certificate, sslOption.ClientCertificateRequired - , sslOption.SslProtocols - , sslOption.CheckCertificateRevocation).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - - //await sslStream.AuthenticateAsServerAsync(sslOption.Certificate).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - - this.m_sslStream = sslStream; - this.m_useSsl = true; + return this.m_socketReceiver.WaitForDataAsync(this.m_socket); } - /// - /// 以Ssl客户端模式授权 - /// - /// - /// - public async Task AuthenticateAsync(ClientSslOption sslOption) - { - if (this.m_useSsl) - { - return; - } - - var sslStream = (sslOption.CertificateValidationCallback != null) ? new SslStream(new NetworkStream(this.m_socket, false), false, sslOption.CertificateValidationCallback) : new SslStream(new NetworkStream(this.m_socket, false), false); - if (sslOption.ClientCertificates == null) - { - await sslStream.AuthenticateAsClientAsync(sslOption.TargetHost).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - else - { - await sslStream.AuthenticateAsClientAsync(sslOption.TargetHost, sslOption.ClientCertificates, sslOption.SslProtocols, sslOption.CheckCertificateRevocation).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - this.m_sslStream = sslStream; - this.m_useSsl = true; - } - - public async Task ReadAsync(Memory memory) - { - if (this.m_useSsl) - { -#if NET6_0_OR_GREATER - - var r = await this.m_sslStream.ReadAsync(memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - -#else - var bytes = memory.GetArray(); - var r = await Task.Factory.FromAsync(this.m_sslStream.BeginRead, this.m_sslStream.EndRead, bytes.Array, 0, bytes.Count, default).ConfigureAwait(EasyTask.ContinueOnCapturedContext); -#endif - this.m_result.BytesTransferred = r; - this.m_receiveCounter.Increment(r); - return this.m_result; - } - else - { - var result = await this.m_socketReceiver.ReceiveAsync(this.m_socket, memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.m_receiveCounter.Increment(result.BytesTransferred); - return result; - } - } + #endregion Receive /// /// 重置环境,并设置新的。 @@ -246,7 +63,7 @@ internal sealed class TcpCore : DisposableObject /// public void Reset(Socket socket) { - ThrowHelper.ThrowArgumentNullExceptionIf(socket, nameof(socket)); + ThrowHelper.ThrowIfNull(socket, nameof(socket)); if (!socket.Connected) { @@ -254,7 +71,6 @@ internal sealed class TcpCore : DisposableObject } this.Reset(); this.m_socket = socket; - this.m_noDelay = socket.NoDelay; } /// @@ -262,203 +78,37 @@ internal sealed class TcpCore : DisposableObject /// public void Reset() { - this.m_version++; - this.m_exceptionDispatchInfo = null; - this.m_useSsl = false; - this.m_socketSender.Reset(); - this.m_receiveCounter.Reset(); - this.m_sentCounter.Reset(); - this.m_sslStream?.Dispose(); - this.m_sslStream = null; this.m_socket = null; - this.m_receiveBufferSize = this.MinBufferSize; - this.m_sendBufferSize = this.MinBufferSize; } - #region Send - - /// - /// 异步发送数据。 - /// - /// 内部会根据是否启用Ssl,进行直接发送,还是Ssl发送。 - /// - /// - /// - /// - /// - public async Task SendAsync(ReadOnlyMemory memory) + public async Task SendAsync(ReadOnlySequence buffer) { - await this.m_semaphoreForSend.WaitAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var length = buffer.Length; try { - // 如果内存长度小于最大长度,且不是无延迟模式 - if (memory.Length < MaxMemoryLength && !this.NoDelay) + var result = await this.m_socketSender.SendAsync(this.m_socket, buffer).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + if (result.SocketError != SocketError.Success) { - var dispatchInfo = this.m_exceptionDispatchInfo; - dispatchInfo?.Throw(); - - await this.m_semaphoreSlimForMax.WaitAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - - var sendSegment = ArrayPool.Shared.Rent(memory.Length); - //if (!this.m_stores.TryPop(out var sendSegment)) - //{ - // sendSegment = new SendSegment() - // { - // Date = new byte[MaxMemoryLength] - // }; - //} - - var segment = memory.GetArray(); - - Array.Copy(segment.Array, segment.Offset, sendSegment, 0, segment.Count); - - - this.m_sendingBytes.Enqueue(new SendSegment(sendSegment, memory.Length, this.m_version)); - //var byteBlock = new ValueByteBlock(memory.Length); - //byteBlock.Write(memory.Span); - //this.m_ints.Enqueue(byteBlock); - - this.m_asyncResetEventForSend.Reset(); - - this.m_asyncResetEventForTask.Set(); - return; + ThrowHelper.ThrowSocketException((int)result.SocketError); } - await this.m_asyncResetEventForSend.WaitOneAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - await this.PrivateSendAsync(memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + if (result.BytesTransferred != length) + { + ThrowHelper.ThrowException(TouchSocketResource.IncompleteDataTransmission); + } } finally { - this.m_semaphoreForSend.Release(); + this.m_socketSender.Reset(); } } - private async Task TaskSend() + protected override void SafetyDispose(bool disposing) { - var valuesToProcess = new SendSegment[BatchSize]; - - while (!this.m_cancellationToken.IsCancellationRequested) + if (disposing) { - // 重置计数器和数组内容 - var count = 0; - - // 尝试填充数组 - while (count < BatchSize && this.m_sendingBytes.TryDequeue(out var value)) - { - if (value.version == this.m_version) - { - valuesToProcess[count++] = value; - } - this.m_semaphoreSlimForMax.Release(); - } - - if (this.m_cancellationToken.IsCancellationRequested) - { - return; - } - - // 如果有元素需要处理,并且没有异常 - if (count > 0) - { - //Debug.WriteLine(count.ToString()); - if (this.m_exceptionDispatchInfo == null) - { - using (var byteBlock = new ValueByteBlock(count * MaxMemoryLength)) - { - for (var i = 0; i < count; i++) - { - var value = valuesToProcess[i]; - - byteBlock.Write(new ReadOnlySpan(value.Data, 0, value.Length)); - - ArrayPool.Shared.Return(value.Data); - //value.Dispose(); - } - - try - { - await this.PrivateSendAsync(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - catch (Exception ex) - { - this.m_exceptionDispatchInfo = ExceptionDispatchInfo.Capture(ex); - this.m_sendingBytes.Clear(); - - } - } - } - - Array.Clear(valuesToProcess, 0, count); - } - else - { - //Debug.WriteLine("Pause"); - // 队列为空,设置事件并等待 - this.m_asyncResetEventForSend.Set(); - await this.m_asyncResetEventForTask.WaitOneAsync(this.m_cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - } - } - - private async Task PrivateSendAsync(ReadOnlyMemory memory) - { - var length = memory.Length; -#if NET6_0_OR_GREATER - if (this.UseSsl) - { - await this.SslStream.WriteAsync(memory, this.m_cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } -#else - if (this.m_useSsl) - { - var segment = memory.GetArray(); - await this.SslStream.WriteAsync(segment.Array, segment.Offset, segment.Count, this.m_cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } -#endif - else - { - var sentLength = 0; - while (sentLength < length && !this.m_cancellationToken.IsCancellationRequested) - { - var result = await this.m_socketSender.SendAsync(this.m_socket, memory.Slice(sentLength)) - .ConfigureAwait(EasyTask.ContinueOnCapturedContext); - if (result.SocketError != null) - { - throw result.SocketError; - } - if (result.BytesTransferred == 0 && memory.Length > 0) - { - throw new Exception(TouchSocketResource.IncompleteDataTransmission); - } - sentLength += result.BytesTransferred; - } - } - this.m_sentCounter.Increment(length); - } - - #endregion Send - - private void OnReceivePeriod(long value) - { - this.m_receiveBufferSize = Math.Max(TouchSocketCoreUtility.HitBufferLength(value), this.MinBufferSize); - } - - private void OnSendPeriod(long value) - { - this.m_sendBufferSize = Math.Max(TouchSocketCoreUtility.HitBufferLength(value), this.MinBufferSize); - } - - private struct SendSegment - { - public byte[] Data; - public int Length; - public short version; - - public SendSegment(byte[] bytes, int length, short version) - { - this.Data = bytes; - this.Length = length; - this.version = version; + this.m_socketReceiver.SafeDispose(); + this.m_socketSender.SafeDispose(); } } } \ No newline at end of file diff --git a/src/TouchSocket/Components/Core/TcpCorePool.cs b/src/TouchSocket/Components/Core/TcpCorePool.cs index fdb28ba4e..b088dd165 100644 --- a/src/TouchSocket/Components/Core/TcpCorePool.cs +++ b/src/TouchSocket/Components/Core/TcpCorePool.cs @@ -11,8 +11,6 @@ //------------------------------------------------------------------------------ using System.Collections.Concurrent; -using System.Threading; -using TouchSocket.Core; namespace TouchSocket.Sockets; diff --git a/src/TouchSocket/Components/Core/TransportStream.cs b/src/TouchSocket/Components/Core/TransportStream.cs new file mode 100644 index 000000000..ff281416c --- /dev/null +++ b/src/TouchSocket/Components/Core/TransportStream.cs @@ -0,0 +1,269 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.Buffers; +using System.IO.Pipelines; + +namespace TouchSocket.Sockets; + +/// +/// 基于管道的流实现 +/// +public class TransportStream : Stream +{ + + private bool m_disposed; + private readonly PipeWriter m_writer; + private readonly PipeReader m_reader; + + /// + /// 初始化 类的新实例 + /// + /// 传输层对象 + public TransportStream(ITransport transport) + { + ThrowHelper.ThrowIfNull(transport, nameof(transport)); + this.m_writer = transport.Writer; + this.m_reader = transport.Reader; + + } + + /// + public override bool CanRead => !this.m_disposed; + + /// + public override bool CanSeek => false; + + /// + public override bool CanWrite => !this.m_disposed; + + /// + public override long Length => throw new NotSupportedException(); + + /// + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + /// + public override void Flush() + { + this.ThrowIfDisposed(); + this.FlushAsync().GetAwaiter().GetResult(); + } + + /// + public override async Task FlushAsync(CancellationToken cancellationToken) + { + this.ThrowIfDisposed(); + + var result = await this.m_writer.FlushAsync(cancellationToken).ConfigureAwait(false); + if (result.IsCanceled) + { + throw new OperationCanceledException(); + } + } + + /// + public override int Read(byte[] buffer, int offset, int count) + { + return this.ReadAsync(buffer, offset, count).GetAwaiter().GetResult(); + } + + /// + public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + this.ThrowIfDisposed(); + + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); + if (offset < 0 || offset > buffer.Length) + throw new ArgumentOutOfRangeException(nameof(offset)); + if (count < 0 || offset + count > buffer.Length) + throw new ArgumentOutOfRangeException(nameof(count)); + + if (count == 0) + return 0; + + var result = await this.m_reader.ReadAsync(cancellationToken).ConfigureAwait(false); + + if (result.IsCanceled) + { + throw new OperationCanceledException(); + } + + var sequence = result.Buffer; + var bytesToRead = (int)Math.Min(count, sequence.Length); + + if (bytesToRead == 0) + { + this.m_reader.AdvanceTo(sequence.Start); + return result.IsCompleted ? 0 : await this.ReadAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false); + } + + var sliced = sequence.Slice(0, bytesToRead); + sliced.CopyTo(new Span(buffer, offset, bytesToRead)); + + this.m_reader.AdvanceTo(sliced.End); + + return bytesToRead; + } + +#if !NETFRAMEWORK && !NETSTANDARD2_0 + /// + public override async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) + { + this.ThrowIfDisposed(); + + if (buffer.Length == 0) + return 0; + + var result = await this.m_reader.ReadAsync(cancellationToken).ConfigureAwait(false); + + if (result.IsCanceled) + { + throw new OperationCanceledException(); + } + + var sequence = result.Buffer; + var bytesToRead = (int)Math.Min(buffer.Length, sequence.Length); + + if (bytesToRead == 0) + { + this.m_reader.AdvanceTo(sequence.Start); + return result.IsCompleted ? 0 : await this.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); + } + + var sliced = sequence.Slice(0, bytesToRead); + sliced.CopyTo(buffer.Span); + + this.m_reader.AdvanceTo(sliced.End); + + return bytesToRead; + } +#endif + + /// + public override int ReadByte() + { + var buffer = new byte[1]; + var bytesRead = this.Read(buffer, 0, 1); + return bytesRead == 0 ? -1 : buffer[0]; + } + + /// + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + /// + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + /// + public override void Write(byte[] buffer, int offset, int count) + { + this.WriteAsync(buffer, offset, count).GetAwaiter().GetResult(); + } + + /// + public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + this.ThrowIfDisposed(); + + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); + if (offset < 0 || offset > buffer.Length) + throw new ArgumentOutOfRangeException(nameof(offset)); + if (count < 0 || offset + count > buffer.Length) + throw new ArgumentOutOfRangeException(nameof(count)); + + if (count == 0) + return; + + var memory = this.m_writer.GetMemory(count); + var source = new ReadOnlySpan(buffer, offset, count); + source.CopyTo(memory.Span); + this.m_writer.Advance(count); + + var result = await this.m_writer.FlushAsync(cancellationToken).ConfigureAwait(false); + if (result.IsCanceled) + { + throw new OperationCanceledException(); + } + } + +#if !NETFRAMEWORK && !NETSTANDARD2_0 + /// + public override async ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + { + this.ThrowIfDisposed(); + + if (buffer.Length == 0) + return; + + var result = await this.m_writer.WriteAsync(buffer, cancellationToken).ConfigureAwait(false); + if (result.IsCanceled) + { + throw new OperationCanceledException(); + } + } +#endif + + /// + public override void WriteByte(byte value) + { + var buffer = new byte[] { value }; + this.Write(buffer, 0, 1); + } + + /// + protected override void Dispose(bool disposing) + { + if (!this.m_disposed && disposing) + { + this.m_disposed = true; + } + + base.Dispose(disposing); + } + +#if !NETFRAMEWORK && !NETSTANDARD2_0 + /// + public override async ValueTask DisposeAsync() + { + if (!this.m_disposed) + { + this.m_disposed = true; + } + + await base.DisposeAsync().ConfigureAwait(false); + } +#endif + + /// + /// 检查对象是否已被释放,如果已释放则抛出异常 + /// + /// 对象已被释放 + private void ThrowIfDisposed() + { + if (this.m_disposed) + { + throw new ObjectDisposedException(this.GetType().Name); + } + } +} diff --git a/src/TouchSocket/Components/Core/UdpSocketReceiver.cs b/src/TouchSocket/Components/Core/UdpSocketReceiver.cs index 5ccdc73b6..9068c274a 100644 --- a/src/TouchSocket/Components/Core/UdpSocketReceiver.cs +++ b/src/TouchSocket/Components/Core/UdpSocketReceiver.cs @@ -10,35 +10,16 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Net; using System.Net.Sockets; -using System.Threading.Tasks; -using TouchSocket.Core; namespace TouchSocket.Sockets; -internal sealed class UdpSocketReceiver : SocketAwaitableEventArgs +internal sealed class UdpSocketReceiver : SocketAwaitableEventArgs { - public ValueTask WaitForDataAsync(Socket socket, EndPoint endPoint) - { -#if NET6_0_OR_GREATER - this.SetBuffer(Memory.Empty); -#else - var empty = new byte[0]; - this.SetBuffer(empty, 0, 0); -#endif - this.RemoteEndPoint = endPoint; - if (socket.ReceiveFromAsync(this)) - { - return new ValueTask(this, 0); - } - - return new ValueTask(this.GetSocketOperationResult()); - } - - public ValueTask ReceiveAsync(Socket socket, EndPoint endPoint, Memory buffer) + public ValueTask ReceiveAsync(Socket socket, EndPoint endPoint, Memory buffer) { + this.m_core.Reset(); #if NET6_0_OR_GREATER this.SetBuffer(buffer); #else @@ -49,9 +30,14 @@ internal sealed class UdpSocketReceiver : SocketAwaitableEventArgs this.RemoteEndPoint = endPoint; if (socket.ReceiveFromAsync(this)) { - return new ValueTask(this, 0); + return new ValueTask(this, this.m_core.Version); } - return new ValueTask(this.GetSocketOperationResult()); + return new ValueTask(this.GetResult()); + } + + protected override UdpOperationResult GetResult() + { + return new UdpOperationResult(this.BytesTransferred, this.RemoteEndPoint, this.SocketError, this.ReceiveMessageFromPacketInfo); } } \ No newline at end of file diff --git a/src/TouchSocket/Components/Factory/ClientFactory.cs b/src/TouchSocket/Components/Factory/ClientFactory.cs index 986b1cf3a..a8449dd06 100644 --- a/src/TouchSocket/Components/Factory/ClientFactory.cs +++ b/src/TouchSocket/Components/Factory/ClientFactory.cs @@ -10,12 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; namespace TouchSocket.Sockets; @@ -84,25 +79,25 @@ public abstract class ClientFactory : DependencyObject where TClient : /// 创建客户端 /// /// 返回一个异步任务,该任务结果为创建的客户端对象。 - protected abstract Task CreateClient(); + protected abstract Task CreateClient(CancellationToken cancellationToken); /// - protected override void Dispose(bool disposing) + protected override void SafetyDispose(bool disposing) { if (disposing) { this.m_cts.Cancel(); this.Clear(); } - base.Dispose(disposing); + base.SafetyDispose(disposing); } /// /// 租赁客户端 /// - /// 等待时间 + /// /// 客户端实例 - protected virtual async ValueTask RentClient(TimeSpan waitTime) + protected virtual async ValueTask RentClient(CancellationToken cancellationToken) { // 从空闲客户端队列中尝试取出一个客户端 while (this.FreeClients.TryDequeue(out var client)) @@ -117,15 +112,27 @@ public abstract class ClientFactory : DependencyObject where TClient : // 如果已创建的客户端数量超过最大数量 if (this.CreatedClients.Count > this.MaxCount) { - // 等待指定时间,然后递归尝试租赁客户端 - if (SpinWait.SpinUntil(this.Wait, waitTime)) + var startTime = DateTimeOffset.UtcNow; + while (true) { - return await this.RentClient(waitTime).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + // 检查是否超过最大等待时间 + if (DateTimeOffset.UtcNow - startTime > this.MaxWaitTime) + { + // 如果超过最大等待时间,创建一个新的客户端 + break; + } + cancellationToken.ThrowIfCancellationRequested(); + if (!this.FreeClients.IsEmpty) + { + return await this.RentClient(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + + await Task.Delay(10, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } // 创建一个新的客户端 - var clientRes = await this.CreateClient().ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var clientRes = await this.CreateClient(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); // 将新创建的客户端添加到已创建的客户端列表中 this.m_createdClients.Add(clientRes); // 返回新创建的客户端 @@ -176,7 +183,7 @@ public abstract class ClientFactory : DependencyObject where TClient : var clients = new List(); while (this.CreatedCount < this.MinCount) { - var client = await this.RentClient(TimeSpan.FromSeconds(1)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var client = await this.RentClient(CancellationToken.None).ConfigureAwait(EasyTask.ContinueOnCapturedContext); clients.Add(client); } @@ -212,6 +219,11 @@ public abstract class ClientFactory : DependencyObject where TClient : /// public int AvailableCount => Math.Max(0, this.MaxCount - this.CreatedClients.Count) + this.FreeClients.Count; + /// + /// 获取或设置客户端池等待可用客户端的最大时间。默认1秒。 + /// + public TimeSpan MaxWaitTime { get; set; } = TimeSpan.FromSeconds(1); + /// /// 获取已经创建的客户端数量。 /// @@ -247,30 +259,14 @@ public abstract class ClientFactory : DependencyObject where TClient : #region GetClient /// - /// 获取用于传输的客户端结果。可以支持。 + /// 获取一个客户端实例,并在使用完成后自动归还到客户端池。 /// - /// 等待时间,超过此时间则取消获取客户端的操作。 - /// 返回一个对象,包含租用的客户端和归还客户端的方法。 - public virtual async ValueTask> GetClient(TimeSpan waitTime) + /// 用于取消操作的 。 + /// 包含客户端实例和归还方法的 + public virtual async ValueTask> GetClient(CancellationToken cancellationToken = default) { // 租用客户端,并配置不等待主线程 - return new ClientFactoryResult(await this.RentClient(waitTime).ConfigureAwait(EasyTask.ContinueOnCapturedContext), this.ReturnClient); + return new ClientFactoryResult(await this.RentClient(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext), this.ReturnClient); } - - /// - /// 获取一个指定客户端,默认情况下等待1秒。 - /// - /// 返回一个对象,包含租用的客户端和归还客户端的方法。 - public ValueTask> GetClient() - { - // 使用默认等待时间1秒来获取客户端 - return this.GetClient(TimeSpan.FromSeconds(1)); - } - #endregion GetClient - - private bool Wait() - { - return !this.FreeClients.IsEmpty; - } } \ No newline at end of file diff --git a/src/TouchSocket/Components/Factory/ClientFactoryResult.cs b/src/TouchSocket/Components/Factory/ClientFactoryResult.cs index 190111af7..505055afb 100644 --- a/src/TouchSocket/Components/Factory/ClientFactoryResult.cs +++ b/src/TouchSocket/Components/Factory/ClientFactoryResult.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Sockets; /// diff --git a/src/TouchSocket/Components/Factory/ConnectableClientFactory.cs b/src/TouchSocket/Components/Factory/ConnectableClientFactory.cs index a3f8873bb..ba9aec07a 100644 --- a/src/TouchSocket/Components/Factory/ConnectableClientFactory.cs +++ b/src/TouchSocket/Components/Factory/ConnectableClientFactory.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// @@ -22,11 +18,6 @@ namespace TouchSocket.Sockets; /// 客户端类型,必须实现IClient和IConnectableClient接口。 public abstract class ConnectableClientFactory : ClientFactory where TClient : class, IClient, IConnectableClient, IOnlineClient { - /// - /// 连接超时设定 - /// - public TimeSpan ConnectTimeout { get; set; } = TimeSpan.FromSeconds(5); - /// /// 获取传输的客户端配置 /// @@ -39,17 +30,18 @@ public abstract class ConnectableClientFactory : ClientFactory } /// - protected sealed override Task CreateClient() + protected sealed override Task CreateClient(CancellationToken cancellationToken) { - return this.CreateClient(this.OnGetConfig()); + return this.CreateClient(this.OnGetConfig(), cancellationToken); } /// /// 创建客户端。 /// /// 传输客户端配置。 + /// /// 返回创建的客户端任务。 - protected abstract Task CreateClient(TouchSocketConfig config); + protected abstract Task CreateClient(TouchSocketConfig config, CancellationToken cancellationToken); /// /// 获取配置。 diff --git a/src/TouchSocket/Components/Factory/TcpClientFactory.cs b/src/TouchSocket/Components/Factory/TcpClientFactory.cs index 07da5351e..5e4eca4be 100644 --- a/src/TouchSocket/Components/Factory/TcpClientFactory.cs +++ b/src/TouchSocket/Components/Factory/TcpClientFactory.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// @@ -22,15 +18,6 @@ namespace TouchSocket.Sockets; /// 表示Tcp客户端的类型参数,必须实现ITcpClient接口。 public abstract class TcpClientFactory : ConnectableClientFactory where TClient : class, ITcpClient { - /// - /// 处理Tcp客户端的释放操作。 - /// - /// 要释放的Tcp客户端。 - public override void DisposeClient(TClient client) - { - client.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both).GetFalseAwaitResult(); - base.DisposeClient(client); - } } /// @@ -39,11 +26,11 @@ public abstract class TcpClientFactory : ConnectableClientFactory { /// - protected override async Task CreateClient(TouchSocketConfig config) + protected override async Task CreateClient(TouchSocketConfig config, CancellationToken cancellationToken) { var client = new TcpClient(); await client.SetupAsync(config).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - await client.ConnectAsync((int)this.ConnectTimeout.TotalMilliseconds, CancellationToken.None).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await client.ConnectAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return client; } } \ No newline at end of file diff --git a/src/TouchSocket/Components/Nat/Interfaces/INatSessionClient.cs b/src/TouchSocket/Components/Nat/Interfaces/INatSessionClient.cs index 67348168f..f9eff5932 100644 --- a/src/TouchSocket/Components/Nat/Interfaces/INatSessionClient.cs +++ b/src/TouchSocket/Components/Nat/Interfaces/INatSessionClient.cs @@ -10,11 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// diff --git a/src/TouchSocket/Components/Nat/NatSessionClient.cs b/src/TouchSocket/Components/Nat/NatSessionClient.cs index 7631f0e5f..a87ca8a7e 100644 --- a/src/TouchSocket/Components/Nat/NatSessionClient.cs +++ b/src/TouchSocket/Components/Nat/NatSessionClient.cs @@ -10,11 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// @@ -75,7 +70,6 @@ public abstract class NatSessionClient : TcpSessionClientBase, INatSessionClient { if (!client.StandBy) { - client.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both).GetFalseAwaitResult(); client.SafeDispose(); } @@ -127,13 +121,13 @@ public abstract class NatSessionClient : TcpSessionClientBase, INatSessionClient continue; } - if (e.ByteBlock != null) + if (!e.Memory.IsEmpty) { // 转发数据到目标客户端 try { - await client.SendAsync(e.ByteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await client.SendAsync(e.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } catch (Exception ex) { @@ -177,13 +171,13 @@ public abstract class NatSessionClient : TcpSessionClientBase, INatSessionClient return; } - if (e.ByteBlock != null) + if (!e.Memory.IsEmpty) { // 转发数据到当前客户端 try { - await this.ProtectedSendAsync(e.ByteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.ProtectedSendAsync(e.Memory, CancellationToken.None).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } catch (Exception ex) { @@ -196,7 +190,7 @@ public abstract class NatSessionClient : TcpSessionClientBase, INatSessionClient // 转发数据到当前客户端 try { - await this.ProtectedSendAsync(e.RequestInfo).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.ProtectedSendAsync(e.RequestInfo, CancellationToken.None).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } catch (Exception ex) { diff --git a/src/TouchSocket/Components/Nat/NatTargetClient.cs b/src/TouchSocket/Components/Nat/NatTargetClient.cs index c2c4246e0..8b4f6b50b 100644 --- a/src/TouchSocket/Components/Nat/NatTargetClient.cs +++ b/src/TouchSocket/Components/Nat/NatTargetClient.cs @@ -10,12 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// @@ -53,27 +47,21 @@ public sealed class NatTargetClient : TcpClientBase, ITcpConnectableClient, ICli public bool StandBy { get; } /// - public Task ConnectAsync(int millisecondsTimeout, CancellationToken token) + public Task ConnectAsync(CancellationToken cancellationToken) { - return base.TcpConnectAsync(millisecondsTimeout, token); + return base.TcpConnectAsync(cancellationToken); } /// - public Task SendAsync(IList> transferBytes) + public Task SendAsync(ReadOnlyMemory memory, CancellationToken cancellationToken = default) { - return base.ProtectedSendAsync(transferBytes); + return base.ProtectedSendAsync(memory, cancellationToken); } /// - public Task SendAsync(ReadOnlyMemory memory) + public Task SendAsync(IRequestInfo requestInfo, CancellationToken cancellationToken = default) { - return base.ProtectedSendAsync(memory); - } - - /// - public Task SendAsync(IRequestInfo requestInfo) - { - return this.ProtectedSendAsync(requestInfo); + return this.ProtectedSendAsync(requestInfo, cancellationToken); } /// diff --git a/src/TouchSocket/Components/Tcp/PipeTcpClient.cs b/src/TouchSocket/Components/Tcp/PipeTcpClient.cs new file mode 100644 index 000000000..6c1d66d28 --- /dev/null +++ b/src/TouchSocket/Components/Tcp/PipeTcpClient.cs @@ -0,0 +1,40 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.IO.Pipelines; + +namespace TouchSocket.Sockets; + +/// +/// 管道式Tcp客户端。 +/// +public class PipeTcpClient : TcpClientBase, IPipeTcpClient +{ + /// + public PipeReader Input => base.Transport.Reader; + + /// + public PipeWriter Output => base.Transport.Writer; + + /// + public Task ConnectAsync(CancellationToken cancellationToken) + { + return this.TcpConnectAsync(cancellationToken); + } + + /// + protected sealed override async Task ReceiveLoopAsync(ITransport transport) + { + var cancellationToken = transport.ClosedToken; + await Task.Delay(-1, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } +} \ No newline at end of file diff --git a/src/TouchSocket/Components/Tcp/TcpClient.cs b/src/TouchSocket/Components/Tcp/TcpClient.cs index 90bd9cb95..7355c85e8 100644 --- a/src/TouchSocket/Components/Tcp/TcpClient.cs +++ b/src/TouchSocket/Components/Tcp/TcpClient.cs @@ -10,12 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Sockets; @@ -111,9 +105,9 @@ public class TcpClient : TcpClientBase, ITcpClient #region Connect /// - public virtual Task ConnectAsync(int millisecondsTimeout, CancellationToken token) + public virtual Task ConnectAsync(CancellationToken cancellationToken) { - return this.TcpConnectAsync(millisecondsTimeout, token); + return this.TcpConnectAsync(cancellationToken); } #endregion Connect @@ -155,22 +149,15 @@ public class TcpClient : TcpClientBase, ITcpClient #region 异步发送 /// - public virtual Task SendAsync(ReadOnlyMemory memory) + public virtual Task SendAsync(ReadOnlyMemory memory, CancellationToken cancellationToken = default) { - return this.ProtectedSendAsync(memory); + return this.ProtectedSendAsync(memory, cancellationToken); } /// - public virtual Task SendAsync(IRequestInfo requestInfo) + public virtual Task SendAsync(IRequestInfo requestInfo, CancellationToken cancellationToken = default) { - return this.ProtectedSendAsync(requestInfo); + return this.ProtectedSendAsync(requestInfo, cancellationToken); } - - /// - public virtual Task SendAsync(IList> transferBytes) - { - return this.ProtectedSendAsync(transferBytes); - } - #endregion 异步发送 } \ No newline at end of file diff --git a/src/TouchSocket/Components/Tcp/TcpClientBase.cs b/src/TouchSocket/Components/Tcp/TcpClientBase.cs index 30bf3966c..ba30699b4 100644 --- a/src/TouchSocket/Components/Tcp/TcpClientBase.cs +++ b/src/TouchSocket/Components/Tcp/TcpClientBase.cs @@ -10,20 +10,15 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; using System.Net.Sockets; using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Resources; namespace TouchSocket.Sockets; /// -/// TcpClientBase类是作为一个抽象基类设计的,它继承自SetupConfigObject,并实现了ITcpSession接口。 -/// 这个类的主要目的是为TCP会话相关的操作提供一个基础框架,同时整合了配置设定的功能。 +/// TcpClientBase 抽象基类,封装了TCP客户端的核心功能,包括连接、断开、数据收发、适配器设置、事件触发等。 +/// 该类实现了 ITcpSession 接口,并继承自 SetupConfigObject。 /// public abstract partial class TcpClientBase : SetupConfigObject, ITcpSession { @@ -38,17 +33,15 @@ public abstract partial class TcpClientBase : SetupConfigObject, ITcpSession #region 变量 - private readonly Lock m_lockForAbort = new Lock(); - private readonly SemaphoreSlim m_semaphoreForConnect = new SemaphoreSlim(1, 1); private readonly TcpCore m_tcpCore = new TcpCore(); - private Task m_receiveTask; + private readonly SemaphoreSlim m_semaphoreForConnectAndClose = new SemaphoreSlim(1, 1); private SingleStreamDataHandlingAdapter m_dataHandlingAdapter; private string m_iP; - private Socket m_mainSocket; private volatile bool m_online; private int m_port; private InternalReceiver m_receiver; - private CancellationTokenSource m_tokenSourceForReceive; + private Task m_runTask; + private TcpTransport m_transport; #endregion 变量 @@ -106,19 +99,22 @@ public abstract partial class TcpClientBase : SetupConfigObject, ITcpSession await this.PluginManager.RaiseAsync(typeof(ITcpConnectingPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - private async Task PrivateOnTcpClosed((Task receiveTask, ClosedEventArgs e, InternalReceiver receiver) tuple) + private async Task PrivateOnConnected(TcpTransport transport) { - var receiveTask = tuple.receiveTask; - var e = tuple.e; - var receiver = tuple.receiver; + var receiveTask = EasyTask.SafeRun(this.ReceiveLoopAsync, transport); + var e_connected = new ConnectedEventArgs(); + await this.OnTcpConnected(e_connected).SafeWaitAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await receiveTask.SafeWaitAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - await EasyTask.SafeWaitAsync(receiveTask).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + transport.SafeDispose(); - if (receiver != null) - { - await receiver.Complete(e.Message).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - await this.OnTcpClosed(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var e_closed = transport.ClosedEventArgs; + this.m_online = false; + var adapter = this.m_dataHandlingAdapter; + this.m_dataHandlingAdapter = default; + adapter.SafeDispose(); + + await this.OnTcpClosed(e_closed).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } private Task PrivateOnTcpClosing(ClosingEventArgs e) @@ -126,12 +122,6 @@ public abstract partial class TcpClientBase : SetupConfigObject, ITcpSession return this.OnTcpClosing(e); } - private async Task PrivateOnTcpConnected(ConnectedEventArgs e, CancellationToken token) - { - this.m_receiveTask = EasyTask.SafeRun(this.BeginReceive, token); - await this.OnTcpConnected(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - private async Task PrivateOnTcpConnecting(ConnectingEventArgs e) { await this.OnTcpConnecting(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); @@ -149,6 +139,9 @@ public abstract partial class TcpClientBase : SetupConfigObject, ITcpSession #region 属性 + /// + public CancellationToken ClosedToken => this.m_transport == null ? new CancellationToken(true) : this.m_transport.ClosedToken; + /// public SingleStreamDataHandlingAdapter DataHandlingAdapter => this.m_dataHandlingAdapter; @@ -159,13 +152,10 @@ public abstract partial class TcpClientBase : SetupConfigObject, ITcpSession public bool IsClient => true; /// - public DateTimeOffset LastReceivedTime => this.m_tcpCore.ReceiveCounter.LastIncrement; + public DateTimeOffset LastReceivedTime => this.m_transport == null ? default : this.m_transport.ReceiveCounter.LastIncrement; /// - public DateTimeOffset LastSentTime => this.m_tcpCore.SendCounter.LastIncrement; - - /// - public Socket MainSocket => this.m_mainSocket; + public DateTimeOffset LastSentTime => this.m_transport == null ? default : this.m_transport.SendCounter.LastIncrement; /// public virtual bool Online => this.m_online; @@ -182,94 +172,65 @@ public abstract partial class TcpClientBase : SetupConfigObject, ITcpSession public IPHost RemoteIPHost => this.Config.GetValue(TouchSocketConfigExtension.RemoteIPHostProperty); /// - public bool UseSsl => this.m_tcpCore.UseSsl; + public bool UseSsl => this.m_transport.UseSsl; + + /// + /// 获取当前TCP传输层对象。 + /// + protected ITransport Transport => this.m_transport; #endregion 属性 #region 断开操作 /// - public virtual async Task CloseAsync(string msg, CancellationToken token = default) + public virtual async Task CloseAsync(string msg, CancellationToken cancellationToken = default) { + await this.m_semaphoreForConnectAndClose.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); try { - if (this.m_online) + if (!this.m_online) { - await this.PrivateOnTcpClosing(new ClosingEventArgs(msg)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - lock (this.m_lockForAbort) - { - //https://gitee.com/RRQM_Home/TouchSocket/issues/IASH1A - this.MainSocket.TryClose(); - this.Abort(true, msg); - } + return Result.Success; } + await this.PrivateOnTcpClosing(new ClosingEventArgs(msg)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var transport = this.m_transport; + if (transport != null) + { + await transport.CloseAsync(msg, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + await this.WaitClearConnect().ConfigureAwait(EasyTask.ContinueOnCapturedContext); return Result.Success; } catch (Exception ex) { return Result.FromException(ex); } + finally + { + this.m_semaphoreForConnectAndClose.Release(); + } } /// - protected override void Dispose(bool disposing) + protected override void SafetyDispose(bool disposing) { if (disposing) { - this.Abort(true, TouchSocketResource.DisposeClose); + _ = EasyTask.SafeRun(async () => await this.CloseAsync(TouchSocketResource.DisposeClose).ConfigureAwait(EasyTask.ContinueOnCapturedContext)); this.m_tcpCore.SafeDispose(); + this.m_semaphoreForConnectAndClose.SafeDispose(); } - base.Dispose(disposing); + base.SafetyDispose(disposing); } #endregion 断开操作 - /// - public async Task ShutdownAsync(SocketShutdown how) + protected Task AuthenticateAsync(ClientSslOption sslOption) { - if (!this.m_online) - { - return Result.FromFail(TouchSocketResource.ClientNotConnected); - } - - var tcpCore = this.m_tcpCore; - if (tcpCore == null) - { - return Result.FromFail(TouchSocketCoreResource.ArgumentIsNull.Format(nameof(tcpCore))); - } - - return await tcpCore.ShutdownAsync(how).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - - /// - /// 中止连接。 - /// - /// 是否为手动中止。 - /// 中止的消息。 - protected void Abort(bool manual, string msg) - { - // 锁定对象以确保线程安全 - lock (this.m_lockForAbort) - { - // 检查是否在线状态 - if (this.m_online) - { - // 将在线状态设置为false - this.m_online = false; - // 安全地释放主套接字资源 - this.MainSocket.SafeDispose(); - - // 安全地释放数据处理适配器资源 - var adapter = this.m_dataHandlingAdapter; - this.m_dataHandlingAdapter = default; - adapter.SafeDispose(); - this.CancelReceive(); - // 启动一个新任务来处理连接关闭事件 - _ = EasyTask.SafeRun(this.PrivateOnTcpClosed, (this.m_receiveTask, new ClosedEventArgs(manual, msg), this.m_receiver)); - } - } + return this.m_transport.AuthenticateAsync(sslOption); } /// @@ -278,21 +239,19 @@ public abstract partial class TcpClientBase : SetupConfigObject, ITcpSession /// 包含接收到的数据事件的相关信息。 protected virtual async Task OnTcpReceived(ReceivedDataEventArgs e) { - // 提高插件管理器,让所有实现ITcpReceivedPlugin接口的插件处理接收到的数据。 - await this.PluginManager.RaiseAsync(typeof(ITcpReceivedPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.PluginManager.RaiseITcpReceivedPluginAsync(this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } /// /// 当收到原始数据时,触发相关插件进行处理。 /// - /// 包含收到的原始数据的字节块。 + /// 包含收到的原始数据的字节块。 /// /// 如果返回,则表示数据已被处理,且不会再向下传递。 /// - protected virtual ValueTask OnTcpReceiving(ByteBlock byteBlock) + protected virtual ValueTask OnTcpReceiving(IBytesReader reader) { - // 提交异步事件,通知所有实现了ITcpReceivingPlugin接口的插件进行数据处理。 - return this.PluginManager.RaiseAsync(typeof(ITcpReceivingPlugin), this.Resolver, this, new ByteBlockEventArgs(byteBlock)); + return this.PluginManager.RaiseITcpReceivingPluginAsync(this.Resolver, this, new BytesReaderEventArgs(reader)); } /// @@ -302,10 +261,111 @@ public abstract partial class TcpClientBase : SetupConfigObject, ITcpSession /// 返回值意义:表示是否继续发送数据的指示,true为继续,false为取消发送。 protected virtual ValueTask OnTcpSending(ReadOnlyMemory memory) { - // 提高插件管理器,让所有实现ITcpSendingPlugin接口的插件决定是否继续发送数据。 - return this.PluginManager.RaiseAsync(typeof(ITcpSendingPlugin), this.Resolver, this, new SendingEventArgs(memory)); + return this.PluginManager.RaiseITcpSendingPluginAsync(this.Resolver, this, new SendingEventArgs(memory)); } + #region ReceiveLoopAsync + /// + /// 数据接收主循环,负责从传输层读取数据并分发给适配器或插件。 + /// + /// 传输层对象 + protected virtual async Task ReceiveLoopAsync(ITransport transport) + { + using var reader = new PooledBytesReader(); + var cancellationToken = transport.ClosedToken; + + await transport.ReadLocker.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + try + { + while (true) + { + if (this.DisposedValue || cancellationToken.IsCancellationRequested) + { + return; + } + + //不使用取消令箭进行读取,安全退出 + var readTask = transport.Reader.ReadAsync(CancellationToken.None); + + System.IO.Pipelines.ReadResult result; + + // 快速路径:如果读取同步完成 + if (readTask.IsCompleted) + { + result = readTask.Result; + } + else + { + // 慢速路径:异步等待 + result = await readTask.ConfigureAwait(false); + } + + if (result.Buffer.Length == 0) + { + break; + } + + try + { + reader.Reset(result.Buffer); + if (!await this.OnTcpReceiving(reader).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) + { + if (this.m_dataHandlingAdapter == null) + { + foreach (var item in reader.Sequence) + { + await this.PrivateHandleReceivedData(item, default).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + reader.Advance(item.Length); + } + } + else + { + await this.m_dataHandlingAdapter.ReceivedInputAsync(reader).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + } + + try + { + var position = result.Buffer.GetPosition(reader.BytesRead); + transport.Reader.AdvanceTo(position, result.Buffer.End); + + if (result.IsCompleted || result.IsCanceled) + { + return; + } + } + catch + { + //此处捕获由于管道完成,或关闭后,继续操作位置等,错误引发的异常 + //不做任何处理,直接退出循环即可。 + } + + reader.Clear(); + } + catch (Exception ex) + { + this.Logger?.Exception(this, ex); + + // 处理数据出现异常时,关闭连接并退出循环 + await transport.CloseAsync(ex.Message).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + break; // 关闭连接后退出循环 + } + } + } + catch (Exception ex) + { + this.Logger?.Debug(this, ex); + } + finally + { + var receiver = this.m_receiver; + var e_closed = transport.ClosedEventArgs; + receiver?.Complete(e_closed.Message); + transport.ReadLocker.Release(); + } + } + #endregion + /// /// 设置适配器。 /// @@ -314,149 +374,31 @@ public abstract partial class TcpClientBase : SetupConfigObject, ITcpSession { // 检查当前实例是否已被释放 this.ThrowIfDisposed(); + if (adapter is null) + { + this.m_dataHandlingAdapter = null;//允许Null赋值 + return; + } - // 如果适配器参数为,抛出ArgumentNullException异常 - ThrowHelper.ThrowArgumentNullExceptionIf(adapter, nameof(adapter)); - - // 如果当前实例的配置对象不为空,则将配置应用到适配器上 if (this.Config != null) { adapter.Config(this.Config); } - // 设置适配器的日志记录器为当前实例的日志记录器 - adapter.Logger = this.Logger; - // 通知适配器当前实例已加载 adapter.OnLoaded(this); - // 设置适配器接收到数据时的回调方法 adapter.ReceivedAsyncCallBack = this.PrivateHandleReceivedData; - // 设置适配器发送数据时的异步回调方法 - adapter.SendAsyncCallBack = this.ProtectedDefaultSendAsync; - // 将当前适配器设置为实例的适配器 this.m_dataHandlingAdapter = adapter; } - private async Task AuthenticateAsync() - { - if (this.Config.TryGetValue(TouchSocketConfigExtension.ClientSslOptionProperty, out var sslOption)) - { - await this.m_tcpCore.AuthenticateAsync(sslOption).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - } - - private void CancelReceive() - { - var tokenSourceForReceive = this.m_tokenSourceForReceive; - if (tokenSourceForReceive != null) - { - tokenSourceForReceive.Cancel(); - tokenSourceForReceive.Dispose(); - } - this.m_tokenSourceForReceive = null; - } - - private async Task BeginReceive(CancellationToken token) - { - var byteBlock = new ByteBlock(this.m_tcpCore.ReceiveBufferSize); - while (true) - { - try - { - var result = await this.m_tcpCore.ReadAsync(byteBlock.TotalMemory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - - if (this.DisposedValue || token.IsCancellationRequested) - { - byteBlock.Dispose(); - return; - } - - if (result.BytesTransferred > 0) - { - byteBlock.SetLength(result.BytesTransferred); - try - { - if (await this.OnTcpReceiving(byteBlock).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) - { - continue; - } - - if (this.m_dataHandlingAdapter == null) - { - await this.PrivateHandleReceivedData(byteBlock, default).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - else - { - await this.m_dataHandlingAdapter.ReceivedInputAsync(byteBlock).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - } - catch (Exception ex) - { - this.Logger?.Exception(this, ex); - } - finally - { - if (byteBlock.Holding || byteBlock.DisposedValue) - { - byteBlock.Dispose();//释放上个内存 - byteBlock = new ByteBlock(this.m_tcpCore.ReceiveBufferSize); - } - else - { - byteBlock.Reset(); - if (this.m_tcpCore.ReceiveBufferSize > byteBlock.Capacity) - { - byteBlock.SetCapacity(this.m_tcpCore.ReceiveBufferSize); - } - } - } - } - else if (result.SocketError != null) - { - byteBlock.Dispose(); - this.Abort(false, result.SocketError.Message); - return; - } - else - { - byteBlock.Dispose(); - this.Abort(false, TouchSocketResource.RemoteDisconnects); - return; - } - } - catch (Exception ex) - { - byteBlock.Dispose(); - this.Abort(false, ex.Message); - return; - } - } - } - - #region Receiver - - /// - protected void ProtectedClearReceiver() - { - this.m_receiver = null; - } - - /// - protected IReceiver ProtectedCreateReceiver(IReceiverClient receiverObject) - { - return this.m_receiver ??= new InternalReceiver(receiverObject); - } - - #endregion Receiver - - private async Task PrivateHandleReceivedData(ByteBlock byteBlock, IRequestInfo requestInfo) + private async Task PrivateHandleReceivedData(ReadOnlyMemory memory, IRequestInfo requestInfo) { var receiver = this.m_receiver; if (receiver != null) { - await receiver.InputReceiveAsync(byteBlock, requestInfo).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await receiver.InputReceiveAsync(memory, requestInfo, CancellationToken.None).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return; } - await this.OnTcpReceived(new ReceivedDataEventArgs(byteBlock, requestInfo)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.OnTcpReceived(new ReceivedDataEventArgs(memory, requestInfo)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } private void SetSocket(Socket socket) @@ -470,28 +412,55 @@ public abstract partial class TcpClientBase : SetupConfigObject, ITcpSession this.m_iP = socket.RemoteEndPoint.GetIP(); this.m_port = socket.RemoteEndPoint.GetPort(); - this.m_mainSocket = socket; this.m_tcpCore.Reset(socket); - //this.m_tcpCore.OnReceived = this.HandleReceived; - //this.m_tcpCore.OnAbort = this.TcpCoreAbort; - if (this.Config.GetValue(TouchSocketConfigExtension.MinBufferSizeProperty) is int minValue) - { - this.m_tcpCore.MinBufferSize = minValue; - } - - if (this.Config.GetValue(TouchSocketConfigExtension.MaxBufferSizeProperty) is int maxValue) - { - this.m_tcpCore.MaxBufferSize = maxValue; - } } + private async Task TryAuthenticateAsync(IPHost iPHost) + { + if (!this.Config.TryGetValue(TouchSocketConfigExtension.ClientSslOptionProperty, out var sslOption)) + { + if (!iPHost.IsSsl) + { + return; + } + + sslOption = new ClientSslOption() + { + TargetHost = iPHost.Host + }; + } + await this.AuthenticateAsync(sslOption).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + + #region Receiver + + /// + /// 清除内部接收器。 + /// + protected void ProtectedClearReceiver() + { + this.m_receiver = null; + } + + /// + /// 创建内部接收器。 + /// + /// 接收器客户端对象 + /// 接收器实例 + protected IReceiver ProtectedCreateReceiver(IReceiverClient receiverObject) + { + return this.m_receiver ??= new InternalReceiver(receiverObject); + } + + #endregion Receiver + #region Throw /// /// 如果TCP客户端未连接,则抛出异常。 /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected void ThrowIfTcpClientNotConnected() + protected void ThrowIfClientNotConnected() { if (this.m_online) { @@ -512,51 +481,43 @@ public abstract partial class TcpClientBase : SetupConfigObject, ITcpSession #endregion Throw - #region 直接发送 - - /// - /// 异步发送数据,保护方法。 - /// 此方法用于在已建立的TCP连接上异步发送数据。 - /// 它首先检查当前实例是否已被弃用,然后检查客户端是否已连接。 - /// 如果这些检查通过,它将调用OnTcpSending事件处理程序进行预发送处理, - /// 最后通过TCP核心发送数据。 - /// - /// 要发送的数据,存储在只读内存区中。 - /// 一个Task对象,表示异步操作的结果。 - /// 如果当前实例已被弃用,则抛出此异常。 - /// 如果客户端未连接,则抛出此异常。 - protected async Task ProtectedDefaultSendAsync(ReadOnlyMemory memory) - { - // 检查当前实例是否已被弃用 - this.ThrowIfDisposed(); - // 检查客户端是否已连接 - this.ThrowIfTcpClientNotConnected(); - // 调用OnTcpSending事件处理程序进行预发送处理 - await this.OnTcpSending(memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - // 通过TCP核心发送数据 - await this.m_tcpCore.SendAsync(memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - - #endregion 直接发送 - - #region 异步发送 + #region 发送 /// /// 异步发送数据,通过适配器模式灵活处理数据发送。 /// /// 待发送的只读字节内存块。 + /// 可取消令箭 /// 一个异步任务,表示发送操作。 - protected Task ProtectedSendAsync(in ReadOnlyMemory memory) + protected async Task ProtectedSendAsync(ReadOnlyMemory memory, CancellationToken cancellationToken) { - // 如果数据处理适配器未设置,则使用默认发送方式。 - if (this.m_dataHandlingAdapter == null) + this.ThrowIfDisposed(); + this.ThrowIfClientNotConnected(); + + await this.OnTcpSending(memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + var transport = this.m_transport; + var adapter = this.m_dataHandlingAdapter; + var locker = transport.WriteLocker; + + await locker.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + try { - return this.ProtectedDefaultSendAsync(memory); + // 如果数据处理适配器未设置,则使用默认发送方式。 + if (adapter == null) + { + await transport.Writer.WriteAsync(memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + else + { + var writer = new PipeBytesWriter(transport.Writer); + adapter.SendInput(ref writer, in memory); + await writer.FlushAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } } - else + finally { - // 否则,使用适配器的发送方法进行数据发送。 - return this.m_dataHandlingAdapter.SendInputAsync(memory); + locker.Release(); } } @@ -567,61 +528,32 @@ public abstract partial class TcpClientBase : SetupConfigObject, ITcpSession /// 如果可以发送,它将使用数据处理适配器来异步发送输入请求。 /// /// 要发送的请求信息。 + /// 可取消令箭 /// 返回一个任务,该任务代表异步操作的结果。 - protected Task ProtectedSendAsync(in IRequestInfo requestInfo) + protected async Task ProtectedSendAsync(IRequestInfo requestInfo, CancellationToken cancellationToken) { // 检查是否具备发送请求的条件,如果不具备则抛出异常 this.ThrowIfCannotSendRequestInfo(); - // 使用数据处理适配器异步发送输入请求 - return this.m_dataHandlingAdapter.SendInputAsync(requestInfo); - } + this.ThrowIfDisposed(); + this.ThrowIfClientNotConnected(); - /// - /// 异步发送数据。 - /// 如果数据处理适配器不存在或无法拼接发送,则将所有传输字节合并到一个连续的内存块中发送。 - /// 如果数据处理适配器存在且支持拼接发送,则直接发送传输字节列表。 - /// - /// 要发送的字节数据列表,每个项代表一个字节片段。 - /// 发送任务。 - protected async Task ProtectedSendAsync(IList> transferBytes) - { - // 检查数据处理适配器是否存在且支持拼接发送 - if (this.m_dataHandlingAdapter == null || !this.m_dataHandlingAdapter.CanSplicingSend) + var transport = this.m_transport; + var adapter = this.m_dataHandlingAdapter; + var locker = transport.WriteLocker; + + await locker.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + try { - // 如果不支持拼接发送,则计算所有字节片段的总长度 - 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(item.Array, item.Offset, item.Count)); - } - // 根据数据处理适配器的存在与否,选择不同的发送方式 - if (this.m_dataHandlingAdapter == null) - { - // 如果没有数据处理适配器,则使用默认方式发送 - await this.ProtectedDefaultSendAsync(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - else - { - // 如果有数据处理适配器,则通过适配器发送 - await this.m_dataHandlingAdapter.SendInputAsync(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - } + var writer = new PipeBytesWriter(transport.Writer); + adapter.SendInput(ref writer, requestInfo); + await writer.FlushAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - else + finally { - // 如果数据处理适配器支持拼接发送,则直接发送字节列表 - await this.m_dataHandlingAdapter.SendInputAsync(transferBytes).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + locker.Release(); } } - #endregion 异步发送 + #endregion 发送 } \ No newline at end of file diff --git a/src/TouchSocket/Components/Tcp/TcpClientBase_Precompile.cs b/src/TouchSocket/Components/Tcp/TcpClientBase_Precompile.cs index a62c50c72..cf7eb3e13 100644 --- a/src/TouchSocket/Components/Tcp/TcpClientBase_Precompile.cs +++ b/src/TouchSocket/Components/Tcp/TcpClientBase_Precompile.cs @@ -10,12 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Net.Sockets; -using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; namespace TouchSocket.Sockets; @@ -23,216 +18,100 @@ public partial class TcpClientBase { #region Connect - -#if NET6_0_OR_GREATER - /// /// 异步连接服务器 /// - /// 连接超时时间,单位为毫秒 - /// 用于取消操作的令牌 + /// 用于取消操作的令牌 /// 返回一个异步任务 /// 如果对象已被处置,则抛出此异常 /// 如果必要参数为空,则抛出此异常 /// 如果连接超时,则抛出此异常 - protected async Task TcpConnectAsync(int millisecondsTimeout, CancellationToken token) + protected virtual async Task TcpConnectAsync(CancellationToken cancellationToken) { - // 检查对象是否已被处置 this.ThrowIfDisposed(); - // 检查配置是否为空 - this.ThrowIfConfigIsNull(); - // 等待信号量,以控制并发连接 - await this.m_semaphoreForConnect.WaitTimeAsync(millisecondsTimeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - Socket socket = null; + this.ThrowIfConfigIsNull(); + + await this.m_semaphoreForConnectAndClose.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); try { - // 如果已经在线,则无需再次连接 if (this.m_online) { return; } - // 确保远程IP和端口已设置 - var iPHost = ThrowHelper.ThrowArgumentNullExceptionIf(this.RemoteIPHost, nameof(this.RemoteIPHost)); - // 释放之前的Socket资源 - this.MainSocket.SafeDispose(); - // 创建新的Socket连接 - socket = this.CreateSocket(iPHost); - // 触发连接前的事件 + + var iPHost = this.RemoteIPHost; + ThrowHelper.ThrowIfNull(iPHost, nameof(this.RemoteIPHost)); + + var socket = this.CreateSocket(iPHost); + var args = new ConnectingEventArgs(); await this.PrivateOnTcpConnecting(args).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - // 根据取消令牌决定是否支持异步取消 - if (token.CanBeCanceled) + try { - await socket.ConnectAsync(iPHost.Host, iPHost.Port, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - else - { - // 使用信号量控制超时 - using (var tokenSource = new CancellationTokenSource(millisecondsTimeout)) - { - try - { - // 尝试连接 - await socket.ConnectAsync(iPHost.Host, iPHost.Port, tokenSource.Token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - catch (OperationCanceledException) - { - // 超时处理 - throw new TimeoutException(); - } - } - } - - // 更新在线状态 - this.m_online = true; - // 设置当前Socket - this.SetSocket(socket); - // 进行身份验证 - await this.AuthenticateAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - - this.m_tokenSourceForReceive = new CancellationTokenSource(); - - // 确保上次接收任务已经结束 - var receiveTask = this.m_receiveTask; - if (receiveTask != null) - { - await receiveTask.ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - - // 触发连接成功的事件 - _ = EasyTask.SafeRun(this.PrivateOnTcpConnected, new ConnectedEventArgs(), this.m_tokenSourceForReceive.Token); - } - catch - { - // 连接失败时确保Socket被释放 - socket?.SafeDispose(); - // 重置在线状态 - this.m_online = false; - throw; - } - finally - { - // 释放信号量 - this.m_semaphoreForConnect.Release(); - } - } +#if NET6_0_OR_GREATER + await socket.ConnectAsync(iPHost.Host, iPHost.Port, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); #else + var task = Task.Factory.FromAsync(socket.BeginConnect, socket.EndConnect, iPHost.Host, iPHost.Port, null); - /// - /// 异步连接服务器 - /// - /// 连接超时时间,单位为毫秒 - /// 取消令牌 - /// 返回任务 - protected async Task TcpConnectAsync(int millisecondsTimeout, CancellationToken token) - { - // 检查对象是否已释放 - this.ThrowIfDisposed(); - // 检查配置是否为空 - this.ThrowIfConfigIsNull(); - // 等待信号量,以确保同时只有一个连接操作 - await this.m_semaphoreForConnect.WaitTimeAsync(millisecondsTimeout, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - - Socket socket = null; - try - { - // 如果已经在线,则无需进行连接操作 - if (this.m_online) - { - return; + await task.WithCancellation(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); +#endif } - // 获取远程IP和端口信息 - var iPHost = ThrowHelper.ThrowArgumentNullExceptionIf(this.RemoteIPHost, nameof(this.RemoteIPHost)); - // 释放之前的Socket资源 - this.MainSocket.SafeDispose(); - // 创建新的Socket连接 - socket = this.CreateSocket(iPHost); - // 触发连接前的事件 - await this.PrivateOnTcpConnecting(new ConnectingEventArgs()).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - // 异步开始连接 - var task = Task.Factory.FromAsync(socket.BeginConnect, socket.EndConnect, iPHost.Host, iPHost.Port, null); - // 等待连接完成,设置超时时间 - await task.WaitAsync(TimeSpan.FromMilliseconds(millisecondsTimeout)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - // 更新在线状态 + catch + { + socket.Dispose(); + throw; + } + this.m_online = true; - // 设置新的Socket为当前使用 + this.SetSocket(socket); - // 进行身份验证 - await this.AuthenticateAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.m_tokenSourceForReceive = new CancellationTokenSource(); + await this.WaitClearConnect().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - // 确保上次接收任务已经结束 - var receiveTask = this.m_receiveTask; - if (receiveTask != null) - { - await receiveTask.ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - - // 启动新任务,处理连接后的操作 - _ = EasyTask.SafeRun(this.PrivateOnTcpConnected, new ConnectedEventArgs(), this.m_tokenSourceForReceive.Token); - } - catch - { - // 连接失败时确保Socket被释放 - socket?.SafeDispose(); - // 重置在线状态 - this.m_online = false; - throw; + this.m_transport = new TcpTransport(this.m_tcpCore, this.Config.GetValue(TouchSocketConfigExtension.TransportOptionProperty)); + await this.TryAuthenticateAsync(iPHost).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + this.m_runTask = EasyTask.SafeRun(this.PrivateOnConnected, this.m_transport); } finally { - // 释放信号量,允许其他连接操作 - this.m_semaphoreForConnect.Release(); + this.m_semaphoreForConnectAndClose.Release(); } } -#endif - #endregion Connect + private async Task WaitClearConnect() + { + // 确保上次接收任务已经结束 + var runTask = this.m_runTask; + if (runTask != null) + { + await runTask.ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + } + private Socket CreateSocket(IPHost iPHost) { Socket socket; if (iPHost.HostNameType == UriHostNameType.Dns) { - socket = new Socket(SocketType.Stream, ProtocolType.Tcp) - { - SendTimeout = this.Config.GetValue(TouchSocketConfigExtension.SendTimeoutProperty) - }; + socket = new Socket(SocketType.Stream, ProtocolType.Tcp); } else { - socket = new Socket(iPHost.EndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp) - { - SendTimeout = this.Config.GetValue(TouchSocketConfigExtension.SendTimeoutProperty) - }; + socket = new Socket(iPHost.EndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); } if (this.Config.GetValue(TouchSocketConfigExtension.KeepAliveValueProperty) is KeepAliveValue keepAliveValue) { -#if NET45_OR_GREATER - - socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); - socket.IOControl(IOControlCode.KeepAliveValues, keepAliveValue.KeepAliveTime, null); -#else - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); - socket.IOControl(IOControlCode.KeepAliveValues, keepAliveValue.KeepAliveTime, null); - } -#endif + keepAliveValue.Config(socket); } - var noDelay = this.Config.GetValue(TouchSocketConfigExtension.NoDelayProperty); - if (noDelay.HasValue) - { - socket.NoDelay = noDelay.Value; - } + socket.NoDelay = this.Config.GetValue(TouchSocketConfigExtension.NoDelayProperty); if (this.Config.GetValue(TouchSocketConfigExtension.BindIPHostProperty) != null) { diff --git a/src/TouchSocket/Components/Tcp/TcpServiceBaseT.cs b/src/TouchSocket/Components/Tcp/TcpServiceBaseT.cs index a94cf84f2..d30c42e2a 100644 --- a/src/TouchSocket/Components/Tcp/TcpServiceBaseT.cs +++ b/src/TouchSocket/Components/Tcp/TcpServiceBaseT.cs @@ -10,12 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; using System.Net.Sockets; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Resources; namespace TouchSocket.Sockets; @@ -54,10 +49,10 @@ public abstract class TcpServiceBase : ConnectableService, ITc /// public void AddListen(TcpListenOption option) { - ThrowHelper.ThrowArgumentNullExceptionIf(option, nameof(option)); + ThrowHelper.ThrowIfNull(option, nameof(option)); this.ThrowIfDisposed(); - ThrowHelper.ThrowArgumentNullExceptionIf(option.IpHost, nameof(option.IpHost)); + ThrowHelper.ThrowIfNull(option.IpHost, nameof(option.IpHost)); var socket = new Socket(option.IpHost.EndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); @@ -111,7 +106,7 @@ public abstract class TcpServiceBase : ConnectableService, ITc /// public bool RemoveListen(TcpNetworkMonitor monitor) { - ThrowHelper.ThrowArgumentNullExceptionIf(monitor, nameof(monitor)); + ThrowHelper.ThrowIfNull(monitor, nameof(monitor)); if (this.m_monitors.Remove(monitor)) { @@ -123,7 +118,7 @@ public abstract class TcpServiceBase : ConnectableService, ITc } /// - public override async Task ResetIdAsync(string sourceId, string targetId) + public override async Task ResetIdAsync(string sourceId, string targetId, CancellationToken cancellationToken = default) { ThrowHelper.ThrowArgumentNullExceptionIfStringIsNullOrEmpty(sourceId, nameof(sourceId)); ThrowHelper.ThrowArgumentNullExceptionIfStringIsNullOrEmpty(targetId, nameof(targetId)); @@ -135,7 +130,7 @@ public abstract class TcpServiceBase : ConnectableService, ITc if (this.m_clients.TryGetClient(sourceId, out var client)) { - await client.ResetIdAsync(targetId).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await client.ResetIdAsync(targetId, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } else { @@ -151,9 +146,9 @@ public abstract class TcpServiceBase : ConnectableService, ITc try { var optionList = new List(); - if (this.Config.GetValue(TouchSocketConfigExtension.ListenOptionsProperty) is Action> action) + if (this.Config.GetValue(TouchSocketConfigExtension.ListenOptionsProperty) is List list) { - action.Invoke(optionList); + optionList.AddRange(list); } var iPHosts = this.Config.GetValue(TouchSocketConfigExtension.ListenIPHostsProperty); @@ -170,8 +165,6 @@ public abstract class TcpServiceBase : ConnectableService, ITc Adapter = this.Config.GetValue(TouchSocketConfigExtension.TcpDataHandlingAdapterProperty), }; option.Backlog = this.Config.GetValue(TouchSocketConfigExtension.BacklogProperty) ?? option.Backlog; - option.SendTimeout = this.Config.GetValue(TouchSocketConfigExtension.SendTimeoutProperty); - optionList.Add(option); } } @@ -208,7 +201,7 @@ public abstract class TcpServiceBase : ConnectableService, ITc } /// - public override async Task StopAsync(CancellationToken token = default) + public override async Task StopAsync(CancellationToken cancellationToken = default) { this.ThrowIfDisposed(); @@ -236,7 +229,7 @@ public abstract class TcpServiceBase : ConnectableService, ITc } /// - protected override void Dispose(bool disposing) + protected override void SafetyDispose(bool disposing) { if (this.DisposedValue) { @@ -248,7 +241,7 @@ public abstract class TcpServiceBase : ConnectableService, ITc this.StopAsync().GetFalseAwaitResult(); this.m_tcpCorePool.SafeDispose(); } - base.Dispose(disposing); + base.SafetyDispose(disposing); } /// @@ -320,7 +313,6 @@ public abstract class TcpServiceBase : ConnectableService, ITc var socket = e.AcceptSocket; if (this.Count < this.MaxCount) { - //this.OnClientSocketInit(Tuple.Create(socket, (TcpNetworkMonitor)e.UserToken)).GetFalseAwaitResult(); _ = EasyTask.SafeRun(this.OnClientInit, Tuple.Create(socket, (TcpNetworkMonitor)e.UserToken)); } else @@ -362,36 +354,39 @@ public abstract class TcpServiceBase : ConnectableService, ITc var socket = tuple.Item1; var monitor = tuple.Item2; + var tcpCore = this.RentTcpCore(); + + TClient client = default; + try { - if (monitor.Option.NoDelay.HasValue) + client = this.NewClient(); + socket.NoDelay = monitor.Option.NoDelay; + + if (this.Config.GetValue(TouchSocketConfigExtension.KeepAliveValueProperty) is KeepAliveValue keepAliveValue) { - socket.NoDelay = monitor.Option.NoDelay.Value; + keepAliveValue.Config(socket); } - socket.SendTimeout = monitor.Option.SendTimeout; - - var tcpCore = this.RentTcpCore(); - tcpCore.Reset(socket); - var client = this.NewClient(); - - client.InternalSetService(this); - client.InternalSetResolver(this.Resolver); - client.InternalSetListenOption(monitor.Option); - client.InternalSetTcpCore(tcpCore); - client.InternalSetPluginManager(this.PluginManager); - client.InternalSetReturnTcpCore(this.ReturnTcpCore); - client.InternalSetAction(this.TryAdd, this.TryRemove, this.TryGet); - this.ClientInitialized(client); - await client.InternalInitialized().ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await client.InternalInitialized( + this, + this.Resolver, + monitor.Option, + tcpCore, + this.PluginManager, + this.TryAdd, + this.TryRemove, + this.TryGet + ) + .ConfigureAwait(EasyTask.ContinueOnCapturedContext); var args = new ConnectingEventArgs() { - Id = this.GetNextNewId() + Id = this.GetNextNewId(client) }; await client.InternalConnecting(args).ConfigureAwait(EasyTask.ContinueOnCapturedContext);//Connecting if (args.IsPermitOperation) @@ -403,11 +398,14 @@ public abstract class TcpServiceBase : ConnectableService, ITc return; } + var transport = new TcpTransport(tcpCore, this.Config.GetValue(TouchSocketConfigExtension.TransportOptionProperty)); + if (monitor.Option.UseSsl) { try { - await tcpCore.AuthenticateAsync(monitor.Option.ServiceSslOption).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await transport.AuthenticateAsync(monitor.Option.ServiceSslOption) + .ConfigureAwait(EasyTask.ContinueOnCapturedContext); } catch (Exception ex) { @@ -415,26 +413,27 @@ public abstract class TcpServiceBase : ConnectableService, ITc throw; } } - if (this.m_clients.TryAdd(client)) { - await client.InternalConnected(new ConnectedEventArgs()).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await client.InternalConnected(transport).SafeWaitAsync() + .ConfigureAwait(EasyTask.ContinueOnCapturedContext); } else { ThrowHelper.ThrowException(TouchSocketResource.IdAlreadyExists.Format(args.Id)); } } - else - { - socket.SafeDispose(); - } } catch (Exception ex) { - socket.SafeDispose(); this.Logger?.Exception(this, ex); } + finally + { + socket.SafeDispose(); + client.SafeDispose(); + this.ReturnTcpCore(tcpCore); + } } private async Task ReleaseAllAsync() diff --git a/src/TouchSocket/Components/Tcp/TcpServiceT.cs b/src/TouchSocket/Components/Tcp/TcpServiceT.cs index 53b016470..17a2110c8 100644 --- a/src/TouchSocket/Components/Tcp/TcpServiceT.cs +++ b/src/TouchSocket/Components/Tcp/TcpServiceT.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// @@ -158,13 +154,13 @@ public abstract class TcpService : TcpServiceBase, ITcpService #region 发送 /// - public Task SendAsync(string id, ReadOnlyMemory memory) + public Task SendAsync(string id, ReadOnlyMemory memory, CancellationToken cancellationToken = default) { return this.GetClientOrThrow(id).SendAsync(memory); } /// - public Task SendAsync(string id, IRequestInfo requestInfo) + public Task SendAsync(string id, IRequestInfo requestInfo, CancellationToken cancellationToken = default) { return this.GetClientOrThrow(id).SendAsync(requestInfo); } diff --git a/src/TouchSocket/Components/Tcp/TcpSessionClient.cs b/src/TouchSocket/Components/Tcp/TcpSessionClient.cs index 5d50aa743..bc2c6f6ec 100644 --- a/src/TouchSocket/Components/Tcp/TcpSessionClient.cs +++ b/src/TouchSocket/Components/Tcp/TcpSessionClient.cs @@ -10,11 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Threading.Tasks; -using TouchSocket.Core; namespace TouchSocket.Sockets; @@ -193,37 +189,30 @@ public abstract class TcpSessionClient : TcpSessionClientBase, ITcpSessionClient #region 异步发送 /// - public virtual Task SendAsync(ReadOnlyMemory memory) + public virtual Task SendAsync(ReadOnlyMemory memory, CancellationToken cancellationToken = default) { - return this.ProtectedSendAsync(memory); + return this.ProtectedSendAsync(memory, cancellationToken); } /// - public virtual Task SendAsync(IRequestInfo requestInfo) + public virtual Task SendAsync(IRequestInfo requestInfo, CancellationToken cancellationToken = default) { - return this.ProtectedSendAsync(requestInfo); + return this.ProtectedSendAsync(requestInfo, cancellationToken); } - - /// - public virtual Task SendAsync(IList> transferBytes) - { - return this.ProtectedSendAsync(transferBytes); - } - #endregion 异步发送 #region Id发送 /// - public Task SendAsync(string id, ReadOnlyMemory memory) + public Task SendAsync(string id, ReadOnlyMemory memory, CancellationToken cancellationToken = default) { - return this.GetClientOrThrow(id).ProtectedSendAsync(memory); + return this.GetClientOrThrow(id).ProtectedSendAsync(memory, cancellationToken); } /// - public Task SendAsync(string id, IRequestInfo requestInfo) + public Task SendAsync(string id, IRequestInfo requestInfo, CancellationToken cancellationToken = default) { - return this.GetClientOrThrow(id).ProtectedSendAsync(requestInfo); + return this.GetClientOrThrow(id).ProtectedSendAsync(requestInfo, cancellationToken); } private TcpSessionClient GetClientOrThrow(string id) diff --git a/src/TouchSocket/Components/Tcp/TcpSessionClientBase.cs b/src/TouchSocket/Components/Tcp/TcpSessionClientBase.cs index 1add19d15..7dea54792 100644 --- a/src/TouchSocket/Components/Tcp/TcpSessionClientBase.cs +++ b/src/TouchSocket/Components/Tcp/TcpSessionClientBase.cs @@ -10,14 +10,8 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Net.Sockets; using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Resources; namespace TouchSocket.Sockets; @@ -35,7 +29,8 @@ namespace TouchSocket.Sockets; /// /// [DebuggerDisplay("Id={Id},IP={IP},Port={Port}")] -public abstract class TcpSessionClientBase : ResolverConfigObject, ITcpSession, ITcpListenableClient, IIdClient +[CodeInject.RegionInject(FileName = "TcpClientBase.cs", RegionName = "ReceiveLoopAsync")] +public abstract partial class TcpSessionClientBase : ResolverConfigObject, ITcpSession, ITcpListenableClient, IIdClient { /// /// TcpSessionClientBase 类的构造函数。 @@ -51,26 +46,21 @@ public abstract class TcpSessionClientBase : ResolverConfigObject, ITcpSession, #region 变量 - private readonly Lock m_lockForAbort = new Lock(); - private Task m_beginReceiveTask; private SingleStreamDataHandlingAdapter m_dataHandlingAdapter; private string m_id; private string m_iP; private TcpListenOption m_listenOption; - private Socket m_mainSocket; private volatile bool m_online; private IPluginManager m_pluginManager; private int m_port; private InternalReceiver m_receiver; - - //private IResolver m_resolver; - private Action m_returnTcpCore; - + private Task m_runTask; private IScopedResolver m_scopedResolver; private ITcpServiceBase m_service; private string m_serviceIp; private int m_servicePort; private TcpCore m_tcpCore; + private TcpTransport m_transport; private Func m_tryAddAction; private TryOutEventHandler m_tryGet; private TryOutEventHandler m_tryRemoveAction; @@ -80,7 +70,10 @@ public abstract class TcpSessionClientBase : ResolverConfigObject, ITcpSession, #region 属性 /// - public override sealed TouchSocketConfig Config => this.Service?.Config; + public CancellationToken ClosedToken => this.m_transport == null ? new CancellationToken(true) : this.m_transport.ClosedToken; + + /// + public sealed override TouchSocketConfig Config => this.Service?.Config; /// public SingleStreamDataHandlingAdapter DataHandlingAdapter => this.m_dataHandlingAdapter; @@ -95,17 +88,14 @@ public abstract class TcpSessionClientBase : ResolverConfigObject, ITcpSession, public bool IsClient => false; /// - public DateTimeOffset LastReceivedTime => this.m_tcpCore.ReceiveCounter.LastIncrement; + public DateTimeOffset LastReceivedTime => this.m_transport == null ? default : this.m_transport.ReceiveCounter.LastIncrement; /// - public DateTimeOffset LastSentTime => this.m_tcpCore.SendCounter.LastIncrement; + public DateTimeOffset LastSentTime => this.m_transport == null ? default : this.m_transport.SendCounter.LastIncrement; /// public TcpListenOption ListenOption => this.m_listenOption; - /// - public Socket MainSocket => this.m_mainSocket; - /// public virtual bool Online => this.m_online; @@ -131,21 +121,23 @@ public abstract class TcpSessionClientBase : ResolverConfigObject, ITcpSession, public int ServicePort => this.m_servicePort; /// - public bool UseSsl => this.m_tcpCore.UseSsl; + public bool UseSsl => this.m_transport.UseSsl; + + /// + /// 获取底层传输对象 + /// + protected ITransport Transport => this.m_transport; #endregion 属性 #region Internal - internal async Task InternalConnected(ConnectedEventArgs e) + internal async Task InternalConnected(TcpTransport transport) { this.m_online = true; - - this.m_beginReceiveTask = Task.Run(this.BeginReceive); - - this.m_beginReceiveTask.FireAndForget(); - - await this.OnTcpConnected(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + this.m_transport = transport; + this.m_runTask = EasyTask.SafeRun(this.PrivateConnected, transport); + await this.m_runTask.ConfigureAwait(EasyTask.ContinueOnCapturedContext); } internal async Task InternalConnecting(ConnectingEventArgs e) @@ -161,16 +153,37 @@ public abstract class TcpSessionClientBase : ResolverConfigObject, ITcpSession, } } - internal Task InternalInitialized() + internal async Task InternalInitialized( + ITcpServiceBase serviceBase, + IResolver resolver, + TcpListenOption option, + TcpCore tcpCore, + IPluginManager pluginManager, + Func tryAddAction, + TryOutEventHandler tryRemoveAction, + TryOutEventHandler tryGet) { - return this.OnInitialized(); - } + this.m_service = serviceBase; + + var scopedResolver = resolver.CreateScopedResolver(); + this.m_scopedResolver = scopedResolver; + this.Logger ??= scopedResolver.Resolver.Resolve(); + + this.m_listenOption = option; + + var socket = tcpCore.Socket; + this.m_iP = socket.RemoteEndPoint.GetIP(); + this.m_port = socket.RemoteEndPoint.GetPort(); + this.m_serviceIp = socket.LocalEndPoint.GetIP(); + this.m_servicePort = socket.LocalEndPoint.GetPort(); + this.m_tcpCore = tcpCore; + + this.m_pluginManager = pluginManager; - internal void InternalSetAction(Func tryAddAction, TryOutEventHandler tryRemoveAction, TryOutEventHandler tryGet) - { this.m_tryAddAction = tryAddAction; this.m_tryRemoveAction = tryRemoveAction; this.m_tryGet = tryGet; + await this.OnInitialized().ConfigureAwait(EasyTask.ContinueOnCapturedContext); } internal void InternalSetId(string id) @@ -179,176 +192,24 @@ public abstract class TcpSessionClientBase : ResolverConfigObject, ITcpSession, this.m_id = id; } - internal void InternalSetListenOption(TcpListenOption option) + private async Task PrivateConnected(TcpTransport transport) { - this.m_listenOption = option; - } + var receiveTask = EasyTask.SafeRun(this.ReceiveLoopAsync, transport); - internal void InternalSetPluginManager(IPluginManager pluginManager) - { - this.m_pluginManager = pluginManager; - } + var e_connected = new ConnectedEventArgs(); + await this.OnTcpConnected(e_connected).SafeWaitAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await receiveTask.SafeWaitAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - internal void InternalSetResolver(IResolver resolver) - { - var scopedResolver = resolver.CreateScopedResolver(); - //this.m_resolver = resolver; - this.m_scopedResolver = scopedResolver; - this.Logger ??= scopedResolver.Resolver.Resolve(); - } + transport.SafeDispose(); - internal void InternalSetReturnTcpCore(Action returnTcpCore) - { - this.m_returnTcpCore = returnTcpCore; - } + var e_closed = transport.ClosedEventArgs; + this.m_online = false; + var adapter = this.m_dataHandlingAdapter; + this.m_dataHandlingAdapter = default; + adapter.SafeDispose(); - internal void InternalSetService(ITcpServiceBase serviceBase) - { - this.m_service = serviceBase; - } - - internal void InternalSetTcpCore(TcpCore tcpCore) - { - var socket = tcpCore.Socket; - this.m_mainSocket = socket; - this.m_iP = socket.RemoteEndPoint.GetIP(); - this.m_port = socket.RemoteEndPoint.GetPort(); - this.m_serviceIp = socket.LocalEndPoint.GetIP(); - this.m_servicePort = socket.LocalEndPoint.GetPort(); - - //tcpCore.OnReceived = this.HandleReceived; - //tcpCore.OnAbort = this.TcpCoreBreakOut; - if (this.Config.GetValue(TouchSocketConfigExtension.MinBufferSizeProperty) is int minValue) - { - tcpCore.MinBufferSize = minValue; - } - - if (this.Config.GetValue(TouchSocketConfigExtension.MaxBufferSizeProperty) is int maxValue) - { - tcpCore.MaxBufferSize = maxValue; - } - this.m_tcpCore = tcpCore; - } - - /// - /// 中止当前操作。 - /// - /// 是否为手动中止。 - /// 中止的消息。 - protected void Abort(bool manual, string msg) - { - lock (this.m_lockForAbort) - { - // 如果当前实例没有有效的ID,则无需执行中止操作 - - var id = this.m_id; - if (!this.m_online) - { - return; - } - - //此处的设计是为了防止多次调用Abort方法。 - //一般情况下,由于m_tryRemoveAction是线程安全,所以移除客户端只会被调用一次, - //但是在ResetId的某些情况下,可能会被多次调用。 - //所以这里加了一个判断,如果已经中止,则直接返回。 - //https://gitee.com/RRQM_Home/TouchSocket/issues/IBCUHW - this.m_online = false; - - // 尝试移除与ID关联的操作 - if (this.m_tryRemoveAction(id, out var outClient)) - { - if (!ReferenceEquals(this, outClient)) - { - //如果移除的客户端和当前实例不相等,则抛出异常。 - //由于m_online的设计,一般这里不会被执行。 - ThrowHelper.ThrowInvalidOperationException("移除的客户端和当前实例不相等,可能Id已经被修改。"); - } - - // 安全地释放MainSocket资源 - this.MainSocket.SafeDispose(); - // 安全地释放数据处理适配器资源 - this.m_dataHandlingAdapter.SafeDispose(); - // 启动一个新的任务,用于处理TCP关闭事件 - _ = EasyTask.Run(this.PrivateOnTcpClosed, new ClosedEventArgs(manual, msg)); - } - } - } - - private async Task BeginReceive() - { - var byteBlock = new ByteBlock(this.m_tcpCore.ReceiveBufferSize); - while (true) - { - try - { - var result = await this.m_tcpCore.ReadAsync(byteBlock.TotalMemory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - - if (this.DisposedValue) - { - byteBlock.Dispose(); - return; - } - - if (result.BytesTransferred > 0) - { - byteBlock.SetLength(result.BytesTransferred); - try - { - if (await this.OnTcpReceiving(byteBlock).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) - { - continue; - } - - if (this.m_dataHandlingAdapter == null) - { - await this.PrivateHandleReceivedData(byteBlock, default).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - else - { - await this.m_dataHandlingAdapter.ReceivedInputAsync(byteBlock).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - } - catch (Exception ex) - { - this.Logger?.Exception(this, ex); - } - finally - { - if (byteBlock.Holding || byteBlock.DisposedValue) - { - byteBlock.Dispose();//释放上个内存 - byteBlock = new ByteBlock(this.m_tcpCore.ReceiveBufferSize); - } - else - { - byteBlock.Reset(); - if (this.m_tcpCore.ReceiveBufferSize > byteBlock.Capacity) - { - byteBlock.SetCapacity(this.m_tcpCore.ReceiveBufferSize); - } - } - } - } - else if (result.SocketError != null) - { - byteBlock.Dispose(); - this.Abort(false, result.SocketError.Message); - return; - } - else - { - byteBlock.Dispose(); - this.Abort(false, TouchSocketResource.RemoteDisconnects); - return; - } - } - catch (Exception ex) - { - byteBlock.Dispose(); - this.Abort(false, ex.Message); - return; - } - } + await this.OnTcpClosed(e_closed).SafeWaitAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); + this.m_tryRemoveAction.Invoke(this.m_id, out var _); } #endregion Internal @@ -415,48 +276,15 @@ public abstract class TcpSessionClientBase : ResolverConfigObject, ITcpSession, await this.PluginManager.RaiseAsync(typeof(ITcpConnectingPlugin), this.Resolver, this, e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - private Task PrivateOnClosing(ClosingEventArgs e) + private Task PrivateOnTcpClosing(ClosingEventArgs e) { return this.OnTcpClosing(e); } - private async Task PrivateOnTcpClosed(object obj) - { - try - { - var beginReceiveTask = this.m_beginReceiveTask; - if (beginReceiveTask != null) - { - await beginReceiveTask.ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - - var e = (ClosedEventArgs)obj; - - var receiver = this.m_receiver; - if (receiver != null) - { - await receiver.Complete(e.Message).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - - await this.OnTcpClosed(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - catch - { - // ignored - } - finally - { - var tcp = this.m_tcpCore; - this.m_tcpCore = null; - this.m_returnTcpCore.Invoke(tcp); - this.Dispose(); - } - } - #endregion 事件&委托 /// - public virtual async Task CloseAsync(string msg, CancellationToken token = default) + public virtual async Task CloseAsync(string msg, CancellationToken cancellationToken = default) { try { @@ -465,14 +293,13 @@ public abstract class TcpSessionClientBase : ResolverConfigObject, ITcpSession, return Result.Success; } - await this.PrivateOnClosing(new ClosingEventArgs(msg)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - lock (this.m_lockForAbort) + await this.PrivateOnTcpClosing(new ClosingEventArgs(msg)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var transport = this.m_transport; + if (transport != null) { - //https://gitee.com/RRQM_Home/TouchSocket/issues/IASH1A - this.MainSocket.TryClose(); - this.Abort(true, msg); + await transport.CloseAsync(msg, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - + await this.WaitClearConnect().ConfigureAwait(EasyTask.ContinueOnCapturedContext); return Result.Success; } catch (Exception ex) @@ -482,44 +309,11 @@ public abstract class TcpSessionClientBase : ResolverConfigObject, ITcpSession, } /// - public virtual Task ResetIdAsync(string newId) + public virtual Task ResetIdAsync(string newId, CancellationToken cancellationToken = default) { return this.ProtectedResetIdAsync(newId); } - /// - public async Task ShutdownAsync(SocketShutdown how) - { - if (!this.m_online) - { - return Result.FromFail(TouchSocketResource.ClientNotConnected); - } - - var tcpCore = this.m_tcpCore; - if (tcpCore == null) - { - return Result.FromFail(TouchSocketCoreResource.ArgumentIsNull.Format(nameof(tcpCore))); - } - - return await tcpCore.ShutdownAsync(how).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - - /// - protected override void Dispose(bool disposing) - { - if (this.DisposedValue) - { - return; - } - - base.Dispose(disposing); - if (disposing) - { - this.m_scopedResolver.SafeDispose(); - this.Abort(true, TouchSocketResource.DisposeClose); - } - } - /// /// 当Id更新的时候触发 /// @@ -545,12 +339,12 @@ public abstract class TcpSessionClientBase : ResolverConfigObject, ITcpSession, /// /// 当收到原始数据 /// - /// 包含收到的原始数据。 + /// 包含收到的原始数据。 /// 如果返回则表示数据已被处理,且不会再向下传递。 - protected virtual ValueTask OnTcpReceiving(ByteBlock byteBlock) + protected virtual ValueTask OnTcpReceiving(IBytesReader reader) { // 将原始数据传递给所有相关的预处理插件,以进行初步的数据处理 - return this.PluginManager.RaiseAsync(typeof(ITcpReceivingPlugin), this.Resolver, this, new ByteBlockEventArgs(byteBlock)); + return this.PluginManager.RaiseAsync(typeof(ITcpReceivingPlugin), this.Resolver, this, new BytesReaderEventArgs(reader)); } /// @@ -626,6 +420,16 @@ public abstract class TcpSessionClientBase : ResolverConfigObject, ITcpSession, return this.m_tryGet(id, out sessionClient); } + /// + protected override void SafetyDispose(bool disposing) + { + if (disposing) + { + _ = EasyTask.SafeRun(async () => await this.CloseAsync(TouchSocketResource.DisposeClose).ConfigureAwait(EasyTask.ContinueOnCapturedContext)); + } + base.SafetyDispose(disposing); + } + /// /// 设置数据处理适配器。 /// @@ -633,10 +437,12 @@ public abstract class TcpSessionClientBase : ResolverConfigObject, ITcpSession, /// 如果提供的适配器为,则抛出此异常。 protected void SetAdapter(SingleStreamDataHandlingAdapter adapter) { - // 检查适配器是否为,如果是则抛出异常 + this.ThrowIfDisposed(); + if (adapter is null) { - throw new ArgumentNullException(nameof(adapter)); + this.m_dataHandlingAdapter = null;//允许Null赋值 + return; } // 如果当前实例的配置不为空,则将配置应用到适配器 @@ -645,28 +451,32 @@ public abstract class TcpSessionClientBase : ResolverConfigObject, ITcpSession, adapter.Config(this.Config); } - // 将当前实例的日志记录器和加载回调设置到适配器中 - adapter.Logger = this.Logger; adapter.OnLoaded(this); // 将接收到数据时的异步回调和发送数据时的异步回调设置到适配器中 adapter.ReceivedAsyncCallBack = this.PrivateHandleReceivedData; - //adapter.SendCallBack = this.ProtectedDefaultSend; - adapter.SendAsyncCallBack = this.ProtectedDefaultSendAsync; - - // 将当前的数据处理适配器设置为传入的适配器实例 this.m_dataHandlingAdapter = adapter; } - private async Task PrivateHandleReceivedData(ByteBlock byteBlock, IRequestInfo requestInfo) + private async Task PrivateHandleReceivedData(ReadOnlyMemory memory, IRequestInfo requestInfo) { var receiver = this.m_receiver; if (receiver != null) { - await receiver.InputReceiveAsync(byteBlock, requestInfo).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await receiver.InputReceiveAsync(memory, requestInfo, CancellationToken.None).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return; } - await this.OnTcpReceived(new ReceivedDataEventArgs(byteBlock, requestInfo)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.OnTcpReceived(new ReceivedDataEventArgs(memory, requestInfo)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + + private async Task WaitClearConnect() + { + // 确保上次接收任务已经结束 + var runTask = this.m_runTask; + if (runTask != null) + { + await runTask.ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } } #region Throw @@ -693,109 +503,81 @@ public abstract class TcpSessionClientBase : ResolverConfigObject, ITcpSession, #endregion Throw - #region 直接发送 + #region 发送 /// - /// 异步发送数据,保护方法。 - /// 此方法用于在已建立的TCP连接上异步发送数据。 - /// 它首先检查当前实例是否已被处置,然后检查客户端是否已连接。 - /// 如果这些检查通过,它将调用OnTcpSending事件处理程序进行预发送处理, - /// 最后通过TCP核心组件实际发送数据。 + /// 异步发送数据,通过适配器模式灵活处理数据发送。 /// - /// 要发送的数据,存储在内存中。 - /// 一个Task对象,表示异步操作的结果。 - /// 如果调用此方法的实例已被处置。 - /// 如果客户端未连接时抛出此异常。 - protected async Task ProtectedDefaultSendAsync(ReadOnlyMemory memory) + /// 待发送的只读字节内存块。 + /// 可取消令箭 + /// 一个异步任务,表示发送操作。 + protected async Task ProtectedSendAsync(ReadOnlyMemory memory, CancellationToken cancellationToken) { - // 检查实例是否已被处置 this.ThrowIfDisposed(); - // 检查客户端是否已连接 this.ThrowIfClientNotConnected(); - // 调用OnTcpSending事件处理程序进行预发送处理 + await this.OnTcpSending(memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - // 通过TCP核心组件实际发送数据 - await this.m_tcpCore.SendAsync(memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - #endregion 直接发送 + var transport = this.m_transport; + var adapter = this.m_dataHandlingAdapter; + var locker = transport.WriteLocker; - #region 异步发送 - - /// - /// 异步发送只读内存数据。 - /// - /// 要发送的只读内存块。 - /// 异步操作任务。 - protected Task ProtectedSendAsync(in ReadOnlyMemory memory) - { - // 如果数据处理适配器未初始化,则调用默认发送方法。 - if (this.m_dataHandlingAdapter == null) + await locker.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + try { - return this.ProtectedDefaultSendAsync(memory); + // 如果数据处理适配器未设置,则使用默认发送方式。 + if (adapter == null) + { + await transport.Writer.WriteAsync(memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + else + { + var writer = new PipeBytesWriter(transport.Writer); + adapter.SendInput(ref writer, in memory); + await writer.FlushAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } } - else + finally { - // 否则,使用数据处理适配器发送数据。 - return this.m_dataHandlingAdapter.SendInputAsync(memory); + locker.Release(); } } /// - /// 异步发送请求信息。 + /// 异步发送请求信息的受保护方法。 + /// + /// 此方法首先检查当前对象是否能够发送请求信息,如果不能,则抛出异常。 + /// 如果可以发送,它将使用数据处理适配器来异步发送输入请求。 /// /// 要发送的请求信息。 - /// 异步操作任务。 - protected Task ProtectedSendAsync(in IRequestInfo requestInfo) + /// 可取消令箭 + /// 返回一个任务,该任务代表异步操作的结果。 + protected async Task ProtectedSendAsync(IRequestInfo requestInfo, CancellationToken cancellationToken) { - // 在发送前验证是否能够发送请求信息。 + // 检查是否具备发送请求的条件,如果不具备则抛出异常 this.ThrowIfCannotSendRequestInfo(); - // 使用数据处理适配器发送请求信息。 - return this.m_dataHandlingAdapter.SendInputAsync(requestInfo); - } - /// - /// 异步发送字节传输列表。 - /// - /// 包含字节数据的传输列表。 - /// 异步操作任务。 - protected async Task ProtectedSendAsync(IList> transferBytes) - { - // 检查数据处理适配器是否初始化,或者是否支持拼接发送。 - if (this.m_dataHandlingAdapter == null || !this.m_dataHandlingAdapter.CanSplicingSend) + this.ThrowIfDisposed(); + this.ThrowIfClientNotConnected(); + + var transport = this.m_transport; + var adapter = this.m_dataHandlingAdapter; + var locker = transport.WriteLocker; + + await locker.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + try { - // 计算所有传输字节的总长度。 - 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(item.Array, item.Offset, item.Count)); - } - // 根据数据处理适配器是否初始化,调用默认发送方法或适配器的发送方法。 - if (this.m_dataHandlingAdapter == null) - { - await this.ProtectedDefaultSendAsync(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - else - { - await this.m_dataHandlingAdapter.SendInputAsync(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - } + var writer = new PipeBytesWriter(transport.Writer); + adapter.SendInput(ref writer, requestInfo); + await writer.FlushAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - else + finally { - // 如果数据处理适配器支持拼接发送,则直接使用适配器发送传输列表。 - await this.m_dataHandlingAdapter.SendInputAsync(transferBytes).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + locker.Release(); } } - #endregion 异步发送 + #endregion 发送 #region Receiver diff --git a/src/TouchSocket/Components/Udp/UdpSession.cs b/src/TouchSocket/Components/Udp/UdpSession.cs index af3ab3e2f..3b70d1655 100644 --- a/src/TouchSocket/Components/Udp/UdpSession.cs +++ b/src/TouchSocket/Components/Udp/UdpSession.cs @@ -10,11 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; using System.Net; -using System.Threading.Tasks; -using TouchSocket.Core; namespace TouchSocket.Sockets; @@ -48,15 +44,15 @@ public class UdpSession : UdpSessionBase, IUdpSession #region 向默认远程异步发送 /// - public virtual Task SendAsync(ReadOnlyMemory memory) + public virtual Task SendAsync(ReadOnlyMemory memory, CancellationToken cancellationToken = default) { - return this.ProtectedSendAsync(memory); + return this.ProtectedSendAsync(memory, cancellationToken); } /// - public virtual Task SendAsync(IRequestInfo requestInfo) + public virtual Task SendAsync(IRequestInfo requestInfo, CancellationToken cancellationToken = default) { - return this.ProtectedSendAsync(requestInfo); + return this.ProtectedSendAsync(requestInfo, cancellationToken); } #endregion 向默认远程异步发送 @@ -64,35 +60,19 @@ public class UdpSession : UdpSessionBase, IUdpSession #region 向设置的远程异步发送 /// - public virtual Task SendAsync(EndPoint endPoint, ReadOnlyMemory memory) + public virtual Task SendAsync(EndPoint endPoint, ReadOnlyMemory memory, CancellationToken cancellationToken = default) { - return this.ProtectedSendAsync(endPoint, memory); + return this.ProtectedSendAsync(endPoint, memory, cancellationToken); } /// - public virtual Task SendAsync(EndPoint endPoint, IRequestInfo requestInfo) + public virtual Task SendAsync(EndPoint endPoint, IRequestInfo requestInfo, CancellationToken cancellationToken = default) { - return this.ProtectedSendAsync(endPoint, requestInfo); + return this.ProtectedSendAsync(endPoint, requestInfo, cancellationToken); } #endregion 向设置的远程异步发送 - #region 组合发送 - - /// - public Task SendAsync(IList> transferBytes) - { - return this.ProtectedSendAsync(transferBytes); - } - - /// - public Task SendAsync(EndPoint endPoint, IList> transferBytes) - { - return this.ProtectedSendAsync(endPoint, transferBytes); - } - - #endregion 组合发送 - #region Receiver /// diff --git a/src/TouchSocket/Components/Udp/UdpSessionBase.cs b/src/TouchSocket/Components/Udp/UdpSessionBase.cs index b6180475f..8499b6d68 100644 --- a/src/TouchSocket/Components/Udp/UdpSessionBase.cs +++ b/src/TouchSocket/Components/Udp/UdpSessionBase.cs @@ -10,15 +10,10 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Resources; namespace TouchSocket.Sockets; @@ -79,7 +74,7 @@ public abstract class UdpSessionBase : ServiceBase, IUdpSessionBase public Protocol Protocol { get; protected set; } /// - public IPHost RemoteIPHost => this.Config?.GetValue(TouchSocketConfigExtension.RemoteIPHostProperty); + public IPHost RemoteIPHost => this.Config?.RemoteIPHost; /// public override ServerState ServerState => this.m_serverState; @@ -99,7 +94,7 @@ public abstract class UdpSessionBase : ServiceBase, IUdpSessionBase { this.ThrowIfDisposed(); - ThrowHelper.ThrowArgumentNullExceptionIf(multicastAddr, nameof(multicastAddr)); + ThrowHelper.ThrowIfNull(multicastAddr, nameof(multicastAddr)); // 根据Socket的地址族类型,执行相应的退出组播组操作 if (this.m_monitor.Socket.AddressFamily == AddressFamily.InterNetwork) @@ -127,7 +122,7 @@ public abstract class UdpSessionBase : ServiceBase, IUdpSessionBase { this.ThrowIfDisposed(); - ThrowHelper.ThrowArgumentNullExceptionIf(multicastAddr, nameof(multicastAddr)); + ThrowHelper.ThrowIfNull(multicastAddr, nameof(multicastAddr)); // 根据不同的地址族设置组播成员资格 if (this.m_monitor.Socket.AddressFamily == AddressFamily.InterNetwork) @@ -153,25 +148,17 @@ public abstract class UdpSessionBase : ServiceBase, IUdpSessionBase switch (this.m_serverState) { case ServerState.None: + case ServerState.Stopped: { + this.m_serverState = ServerState.Running; if (this.Config.GetValue(TouchSocketConfigExtension.BindIPHostProperty) is IPHost iPHost) { this.BeginReceive(iPHost); } - break; } case ServerState.Running: return; - - case ServerState.Stopped: - { - if (this.Config.GetValue(TouchSocketConfigExtension.BindIPHostProperty) is IPHost iPHost) - { - this.BeginReceive(iPHost); - } - break; - } default: { ThrowHelper.ThrowInvalidEnumArgumentException(this.m_serverState); @@ -179,20 +166,21 @@ public abstract class UdpSessionBase : ServiceBase, IUdpSessionBase } } - this.m_serverState = ServerState.Running; - - await this.PluginManager.RaiseAsync(typeof(IServerStartedPlugin), this.Resolver, this, new ServiceStateEventArgs(this.m_serverState, default)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.PluginManager.RaiseIServerStartedPluginAsync(this.Resolver, this, new ServiceStateEventArgs(this.m_serverState, default)) + .ConfigureAwait(EasyTask.ContinueOnCapturedContext); } catch (Exception ex) { this.m_serverState = ServerState.Exception; - await this.PluginManager.RaiseAsync(typeof(IServerStartedPlugin), this.Resolver, this, new ServiceStateEventArgs(this.m_serverState, ex) { Message = ex.Message }).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + await this.PluginManager.RaiseIServerStartedPluginAsync(this.Resolver, this, new ServiceStateEventArgs(this.m_serverState, ex) { Message = ex.Message }) + .ConfigureAwait(EasyTask.ContinueOnCapturedContext); throw; } } /// - public override async Task StopAsync(CancellationToken token = default) + public override async Task StopAsync(CancellationToken cancellationToken = default) { try { @@ -202,10 +190,7 @@ public abstract class UdpSessionBase : ServiceBase, IUdpSessionBase await Task.WhenAll(this.m_receiveTasks.ToArray()).ConfigureAwait(EasyTask.ContinueOnCapturedContext); this.m_receiveTasks.Clear(); - if (this.m_receiver != null) - { - await this.m_receiver.Complete(default).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } + this.m_receiver?.Complete(default); await this.PluginManager.RaiseAsync(typeof(IServerStartedPlugin), this.Resolver, this, new ServiceStateEventArgs(this.m_serverState, default)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return Result.Success; @@ -217,7 +202,7 @@ public abstract class UdpSessionBase : ServiceBase, IUdpSessionBase } /// - protected override void Dispose(bool disposing) + protected override void SafetyDispose(bool disposing) { if (!this.DisposedValue) { @@ -226,7 +211,7 @@ public abstract class UdpSessionBase : ServiceBase, IUdpSessionBase this.StopAsync().GetFalseAwaitResult(); } } - base.Dispose(disposing); + base.SafetyDispose(disposing); } /// @@ -275,23 +260,25 @@ public abstract class UdpSessionBase : ServiceBase, IUdpSessionBase { // 检查当前实例是否已被释放 this.ThrowIfDisposed(); - // 如果适配器参数为空,则抛出ArgumentNullException异常 - ThrowHelper.ThrowArgumentNullExceptionIf(adapter, nameof(adapter)); + + + if (adapter is null) + { + this.m_dataHandlingAdapter = null;//允许Null赋值 + return; + } // 如果当前实例的配置信息不为空,则将配置信息应用到适配器上 if (this.Config != null) { adapter.Config(this.Config); } - // 设置适配器的日志记录器 - adapter.Logger = this.Logger; // 通知适配器当前实例的状态 adapter.OnLoaded(this); // 设置适配器接收到数据时的回调方法 adapter.ReceivedCallBack = this.PrivateHandleReceivedData; // 设置适配器发送数据时的异步回调方法 adapter.SendCallBackAsync = this.ProtectedDefaultSendAsync; - // 将提供的适配器设置为当前实例的数据处理适配器 this.m_dataHandlingAdapter = adapter; } @@ -311,8 +298,8 @@ public abstract class UdpSessionBase : ServiceBase, IUdpSessionBase private void BeginReceive(IPHost iPHost) { - var threadCount = this.Config.GetValue(TouchSocketConfigExtension.ThreadCountProperty); - threadCount = threadCount < 0 ? 1 : threadCount; + var overlappedCount = this.Config.GetValue(TouchSocketConfigExtension.UdpOverlappedCountProperty); + var socket = new Socket(iPHost.EndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp) { EnableBroadcast = this.Config.GetValue(TouchSocketConfigExtension.EnableBroadcastProperty) @@ -324,7 +311,7 @@ public abstract class UdpSessionBase : ServiceBase, IUdpSessionBase #region Windows下UDP连接被重置错误10054 -#if NET45_OR_GREATER +#if NET462_OR_GREATER if (this.Config.GetValue(TouchSocketConfigExtension.UdpConnResetProperty)) { const int SIP_UDP_CONNRESET = -1744830452; @@ -349,10 +336,9 @@ public abstract class UdpSessionBase : ServiceBase, IUdpSessionBase this.m_monitor = new UdpNetworkMonitor(iPHost, socket); - for (var i = 0; i < threadCount; i++) + for (var i = 0; i < overlappedCount; i++) { - var task = Task.Run(this.RunReceive); - task.FireAndForget(); + var task = EasyTask.SafeRun(this.RunReceive); this.m_receiveTasks.Add(task); } } @@ -363,43 +349,43 @@ public abstract class UdpSessionBase : ServiceBase, IUdpSessionBase { while (true) { - using (var byteBlock = new ByteBlock(1024 * 64)) - { - try - { - if (this.m_serverState != ServerState.Running) - { - return; - } - var result = await udpSocketReceiver.ReceiveAsync(this.m_monitor.Socket, this.m_monitor.IPHost.EndPoint, byteBlock.TotalMemory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + //UDP单次接收最大64k数据,所以此处直接申请64k内存。 + var memory = new Memory(new byte[1024 * 64]); - if (result.BytesTransferred > 0) - { - byteBlock.SetLength(result.BytesTransferred); - await this.HandleReceivingData(byteBlock, result.RemoteEndPoint).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - else if (result.SocketError != null) - { - this.Logger?.Debug(this, result.SocketError.Message); - return; - } - else - { - this.Logger?.Debug(this, TouchSocketCoreResource.UnknownError); - return; - } - } - catch (Exception ex) + try + { + if (this.m_serverState != ServerState.Running) { - this.Logger?.Exception(this, ex); return; } + var result = await udpSocketReceiver.ReceiveAsync(this.m_monitor.Socket, this.m_monitor.IPHost.EndPoint, memory) + .ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + if (result.BytesTransferred > 0) + { + await this.HandleReceivingData(memory.Slice(0, result.BytesTransferred), result.RemoteEndPoint).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + else if (result.SocketError != SocketError.Success) + { + this.Logger?.Debug(this, result.SocketError.ToString()); + return; + } + else + { + this.Logger?.Debug(this, TouchSocketCoreResource.UnknownError); + return; + } + } + catch (Exception ex) + { + this.Logger?.Exception(this, ex); + return; } } } } - private async Task HandleReceivingData(ByteBlock byteBlock, EndPoint remoteEndPoint) + private async Task HandleReceivingData(ReadOnlyMemory memory, EndPoint remoteEndPoint) { try { @@ -410,18 +396,18 @@ public abstract class UdpSessionBase : ServiceBase, IUdpSessionBase this.m_lastReceivedTime = DateTimeOffset.UtcNow; - if (await this.OnUdpReceiving(new UdpReceiveingEventArgs(remoteEndPoint, byteBlock)).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) + if (await this.OnUdpReceiving(new UdpReceiveingEventArgs(remoteEndPoint, memory)).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) { return; } if (this.m_dataHandlingAdapter == null) { - await this.PrivateHandleReceivedData(remoteEndPoint, byteBlock, default).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.PrivateHandleReceivedData(remoteEndPoint, memory, default).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } else { - await this.m_dataHandlingAdapter.ReceivedInput(remoteEndPoint, byteBlock).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.m_dataHandlingAdapter.ReceivedInputAsync(remoteEndPoint, memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } catch (Exception ex) @@ -444,14 +430,14 @@ public abstract class UdpSessionBase : ServiceBase, IUdpSessionBase return this.PluginManager.RaiseAsync(typeof(IUdpReceivingPlugin), this.Resolver, this, e); } - private async Task PrivateHandleReceivedData(EndPoint remoteEndPoint, ByteBlock byteBlock, IRequestInfo requestInfo) + private async Task PrivateHandleReceivedData(EndPoint remoteEndPoint, ReadOnlyMemory memory, IRequestInfo requestInfo) { if (this.m_receiver != null) { await this.m_semaphoreSlimForReceiver.WaitAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); try { - await this.m_receiver.InputReceive(remoteEndPoint, byteBlock, requestInfo).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.m_receiver.InputReceive(remoteEndPoint, memory, requestInfo, CancellationToken.None).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return; } finally @@ -459,7 +445,7 @@ public abstract class UdpSessionBase : ServiceBase, IUdpSessionBase this.m_semaphoreSlimForReceiver.Release(); } } - await this.OnUdpReceived(new UdpReceivedDataEventArgs(remoteEndPoint, byteBlock, requestInfo)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.OnUdpReceived(new UdpReceivedDataEventArgs(remoteEndPoint, memory, requestInfo)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } #region Throw @@ -467,7 +453,7 @@ public abstract class UdpSessionBase : ServiceBase, IUdpSessionBase [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ThrowIfRemoteIPHostNull() { - ThrowHelper.ThrowArgumentNullExceptionIf(this.RemoteIPHost, nameof(this.RemoteIPHost)); + ThrowHelper.ThrowIfNull(this.RemoteIPHost, nameof(this.RemoteIPHost)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -496,26 +482,25 @@ public abstract class UdpSessionBase : ServiceBase, IUdpSessionBase /// 异步发送数据,使用提供的内存数据。 /// /// 要发送的字节数据的内存段。 + /// 可取消令箭 /// 返回一个任务,表示发送操作的异步执行。 - protected virtual Task ProtectedSendAsync(ReadOnlyMemory memory) + protected virtual Task ProtectedSendAsync(ReadOnlyMemory memory, CancellationToken cancellationToken) { - // 确保RemoteIPHost不为,因为发送操作需要它。 this.ThrowIfRemoteIPHostNull(); // 调用重载的ProtectedSendAsync方法,传递目的端点和内存数据。 - return this.ProtectedSendAsync(this.RemoteIPHost.EndPoint, memory); + return this.ProtectedSendAsync(this.RemoteIPHost.EndPoint, memory, cancellationToken); } /// /// 异步发送数据,使用提供的请求信息。 /// /// 包含要发送数据的请求信息的对象。 + /// 可取消令箭 /// 返回一个任务,表示发送操作的异步执行。 - protected virtual Task ProtectedSendAsync(IRequestInfo requestInfo) + protected virtual Task ProtectedSendAsync(IRequestInfo requestInfo, CancellationToken cancellationToken) { - // 确保RemoteIPHost不为,因为发送操作需要它。 this.ThrowIfRemoteIPHostNull(); - // 调用重载的ProtectedSendAsync方法,传递目的端点和请求信息。 - return this.ProtectedSendAsync(this.RemoteIPHost.EndPoint, requestInfo); + return this.ProtectedSendAsync(this.RemoteIPHost.EndPoint, requestInfo, cancellationToken); } #endregion 向默认远程异步发送 @@ -528,13 +513,14 @@ public abstract class UdpSessionBase : ServiceBase, IUdpSessionBase /// /// 要发送数据到的目标端点。 /// 待发送的字节数据。 + /// 可取消令箭 /// 返回一个任务,表示异步操作的结果。 - protected virtual Task ProtectedSendAsync(EndPoint endPoint, ReadOnlyMemory memory) + protected virtual Task ProtectedSendAsync(EndPoint endPoint, ReadOnlyMemory memory, CancellationToken cancellationToken) { // 根据m_dataHandlingAdapter是否已设置,选择不同的发送方式 return this.m_dataHandlingAdapter == null - ? this.ProtectedDefaultSendAsync(endPoint, memory) // 使用默认发送方式 - : this.m_dataHandlingAdapter.SendInputAsync(endPoint, memory); // 通过适配器发送数据 + ? this.ProtectedDefaultSendAsync(endPoint, memory, cancellationToken) // 使用默认发送方式 + : this.m_dataHandlingAdapter.SendInputAsync(endPoint, memory, cancellationToken); // 通过适配器发送数据 } /// @@ -543,13 +529,14 @@ public abstract class UdpSessionBase : ServiceBase, IUdpSessionBase /// /// 要发送数据到的目标端点。 /// 待发送的请求信息。 + /// 可取消令箭 /// 返回一个任务,表示异步操作的结果。 - protected virtual Task ProtectedSendAsync(EndPoint endPoint, IRequestInfo requestInfo) + protected virtual Task ProtectedSendAsync(EndPoint endPoint, IRequestInfo requestInfo, CancellationToken cancellationToken) { // 检查是否具备发送请求信息的能力 this.ThrowIfCannotSendRequestInfo(); // 通过适配器发送请求信息 - return this.m_dataHandlingAdapter.SendInputAsync(endPoint, requestInfo); + return this.m_dataHandlingAdapter.SendInputAsync(endPoint, requestInfo, cancellationToken); } #endregion 向设置的远程异步发送 @@ -561,15 +548,16 @@ public abstract class UdpSessionBase : ServiceBase, IUdpSessionBase /// 此方法提供了一种默认的发送方式,确保只有在可以发送且远程IP主机不为空时才尝试发送数据。 /// /// 要发送的只读字节内存。 + /// 可取消令箭 /// 一个等待任务,表示异步操作。 - protected async Task ProtectedDefaultSendAsync(ReadOnlyMemory memory) + protected async Task ProtectedDefaultSendAsync(ReadOnlyMemory memory, CancellationToken cancellationToken = default) { // 如果不能发送数据,则抛出异常。 this.ThrowIfCannotSend(); // 如果远程IP主机为空,则抛出异常。 this.ThrowIfRemoteIPHostNull(); // 异步调用实际的发送方法,并传入远程主机的端点和要发送的数据。 - await this.ProtectedDefaultSendAsync(this.RemoteIPHost.EndPoint, memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.ProtectedDefaultSendAsync(this.RemoteIPHost.EndPoint, memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } #if NET6_0_OR_GREATER @@ -580,6 +568,7 @@ public abstract class UdpSessionBase : ServiceBase, IUdpSessionBase /// /// 要发送数据到的端点。 /// 待发送的数据,以只读内存块的形式。 + /// /// /// 在执行实际的数据发送之前,方法会: /// @@ -590,12 +579,12 @@ public abstract class UdpSessionBase : ServiceBase, IUdpSessionBase /// 之后,使用方法异步地将数据发送到指定的端点。 /// 发送完成后,更新最后一次发送时间()为当前的UTC时间。 /// - protected async Task ProtectedDefaultSendAsync(EndPoint endPoint, ReadOnlyMemory memory) + protected async Task ProtectedDefaultSendAsync(EndPoint endPoint, ReadOnlyMemory memory, CancellationToken cancellationToken) { this.ThrowIfDisposed(); this.ThrowIfCannotSend(); await this.OnUdpSending(endPoint, memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - await this.Monitor.Socket.SendToAsync(memory, SocketFlags.None, endPoint); + await this.Monitor.Socket.SendToAsync(memory, SocketFlags.None, endPoint, cancellationToken); this.m_lastSendTime = DateTimeOffset.UtcNow; } #else @@ -605,12 +594,14 @@ public abstract class UdpSessionBase : ServiceBase, IUdpSessionBase /// /// 要发送数据到的端点。 /// 待发送的数据,以只读内存形式提供。 + /// 可取消令箭 /// /// 此方法为异步发送操作提供保护措施,确保数据在发送前进行必要的检查, /// 并通过UDP协议进行发送。 /// - protected async Task ProtectedDefaultSendAsync(EndPoint endPoint, ReadOnlyMemory memory) + protected async Task ProtectedDefaultSendAsync(EndPoint endPoint, ReadOnlyMemory memory, CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested(); // 检查是否具备发送条件,如果不具备则抛出异常。 this.ThrowIfCannotSend(); // 检查对象是否已被释放,如果已被释放则抛出异常。 @@ -640,74 +631,6 @@ public abstract class UdpSessionBase : ServiceBase, IUdpSessionBase #endregion DefaultSendAsync - #region 组合发送 - - /// - /// 异步安全发送数据方法。 - /// - /// 本方法提供了一种安全的异步数据发送方式,确保在发送过程中, - /// 使用了端点信息并且避免了潜在的空引用错误。 - /// - /// 要发送的字节数据集合。 - /// 返回一个任务,表示异步操作的完成。 - protected Task ProtectedSendAsync(IList> transferBytes) - { - // 检查RemoteIPHost是否为,因为发送数据前需要确保目标端点已设置。 - this.ThrowIfRemoteIPHostNull(); - // 调用重载的ProtectedSendAsync方法,传入远端端点和要传输的字节数据。 - return this.ProtectedSendAsync(this.RemoteIPHost.EndPoint, transferBytes); - } - - /// - /// 异步发送数据到指定的端点。 - /// - /// 要发送数据的端点。 - /// 待发送的字节数据列表,每个项包含要传输的字节片段。 - /// 异步操作任务。 - protected async Task ProtectedSendAsync(EndPoint endPoint, IList> transferBytes) - { - // 确保对象未被释放 - this.ThrowIfDisposed(); - - // 检查是否需要拼接发送数据 - if (this.m_dataHandlingAdapter == null || !this.m_dataHandlingAdapter.CanSplicingSend) - { - // 计算所有数据片段的总长度 - 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(item.Array, item.Offset, item.Count)); - } - - // 根据数据处理适配器的存在与否,选择不同的发送方法 - if (this.m_dataHandlingAdapter == null) - { - await this.ProtectedDefaultSendAsync(endPoint, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - else - { - await this.m_dataHandlingAdapter.SendInputAsync(endPoint, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - } - } - else - { - // 如果数据处理适配器支持拼接发送,则直接使用适配器发送数据列表 - await this.m_dataHandlingAdapter.SendInputAsync(endPoint, transferBytes).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - } - - #endregion 组合发送 - #region Receiver /// diff --git a/src/TouchSocket/DataAdapter/Test/UdpDataAdapterTester.cs b/src/TouchSocket/DataAdapter/Test/UdpDataAdapterTester.cs deleted file mode 100644 index 2091e9cd8..000000000 --- a/src/TouchSocket/DataAdapter/Test/UdpDataAdapterTester.cs +++ /dev/null @@ -1,158 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; - -namespace TouchSocket.Sockets; - -/// -/// Udp数据处理适配器测试 -/// -public class UdpDataAdapterTester : IDisposable -{ - private readonly IntelligentDataQueue m_asyncBytes; - private UdpDataHandlingAdapter m_adapter; - private int m_count; - private bool m_dispose; - private int m_expectedCount; - private Func m_receivedCallBack; - private Stopwatch m_stopwatch; - private int m_millisecondsTimeout; - - private UdpDataAdapterTester(int multiThread) - { - this.m_asyncBytes = new IntelligentDataQueue(1024 * 1024 * 10); - for (var i = 0; i < multiThread; i++) - { - Task.Run(this.BeginSend); - } - } - - /// - /// 获取测试器 - /// - /// 待测试适配器 - /// 并发多线程数量 - /// 收到数据回调 - /// - public static UdpDataAdapterTester CreateTester(UdpDataHandlingAdapter adapter, int multiThread, Func receivedCallBack = default) - { - var tester = new UdpDataAdapterTester(multiThread); - tester.m_adapter = adapter; - adapter.SendCallBackAsync = tester.SendCallback; - adapter.ReceivedCallBack = tester.OnReceived; - tester.m_receivedCallBack = receivedCallBack; - return tester; - } - - /// - /// 释放 - /// - public void Dispose() - { - this.m_dispose = true; - } - - /// - /// 模拟测试运行发送 - /// - /// 待测试的内存块 - /// 测试次数 - /// 期待测试次数 - /// 超时时间(毫秒) - /// 测试运行的时间差 - public TimeSpan Run(ReadOnlyMemory memory, int testCount, int expectedCount, int millisecondsTimeout) - { - this.m_count = 0; - this.m_expectedCount = expectedCount; - this.m_millisecondsTimeout = millisecondsTimeout; - this.m_stopwatch = new Stopwatch(); - this.m_stopwatch.Start(); - Task.Run(async () => - { - for (var i = 0; i < testCount; i++) - { - await this.m_adapter.SendInputAsync(null, memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - }); - if (SpinWait.SpinUntil(() => this.m_count == this.m_expectedCount, this.m_millisecondsTimeout)) - { - this.m_stopwatch.Stop(); - return this.m_stopwatch.Elapsed; - } - - throw new TimeoutException(); - } - - - private async Task BeginSend() - { - while (!this.m_dispose) - { - if (this.TryGet(out var byteBlocks)) - { - foreach (var block in byteBlocks) - { - try - { - await this.m_adapter.ReceivedInput(null, block).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - finally - { - block.Dispose(); - } - } - } - else - { - Thread.Sleep(1); - } - } - } - - private async Task OnReceived(EndPoint endPoint, ByteBlock byteBlock, IRequestInfo requestInfo) - { - if (this.m_receivedCallBack != null) - { - await this.m_receivedCallBack(byteBlock, requestInfo).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - Interlocked.Increment(ref this.m_count); - } - - private Task SendCallback(EndPoint endPoint, ReadOnlyMemory memory) - { - var array = memory.ToArray(); - var asyncByte = new QueueDataBytes(array, 0, array.Length); - //Array.Copy(buffer, offset, asyncByte.Buffer, 0, length); - this.m_asyncBytes.Enqueue(asyncByte); - return EasyTask.CompletedTask; - } - - private bool TryGet(out List byteBlocks) - { - byteBlocks = new List(); - - while (this.m_asyncBytes.TryDequeue(out var asyncByte)) - { - var block = new ByteBlock(asyncByte.Length); - block.Write(new ReadOnlySpan(asyncByte.Buffer, asyncByte.Offset, asyncByte.Length)); - byteBlocks.Add(block); - } - return byteBlocks.Count > 0; - } -} \ No newline at end of file diff --git a/src/TouchSocket/DataAdapter/Udp/NormalUdpDataHandlingAdapter.cs b/src/TouchSocket/DataAdapter/Udp/NormalUdpDataHandlingAdapter.cs deleted file mode 100644 index 72efa4bb3..000000000 --- a/src/TouchSocket/DataAdapter/Udp/NormalUdpDataHandlingAdapter.cs +++ /dev/null @@ -1,93 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; -using System.Collections.Generic; -using System.Net; -using System.Threading.Tasks; -using TouchSocket.Core; - -namespace TouchSocket.Sockets; - -/// -/// 常规UDP数据处理适配器 -/// -public class NormalUdpDataHandlingAdapter : UdpDataHandlingAdapter -{ - /// - public override bool CanSplicingSend => true; - - /// - public override bool CanSendRequestInfo => false; - - /// - /// - /// - protected override Task PreviewReceived(EndPoint remoteEndPoint, ByteBlock byteBlock) - { - return this.GoReceived(remoteEndPoint, byteBlock, null); - } - - ///// - ///// - ///// - ///// - ///// - ///// - ///// - //protected override void PreviewSend(EndPoint endPoint, byte[] buffer, int offset, int length) - //{ - // this.GoSend(endPoint, buffer, offset, length); - //} - - ///// - //protected override void PreviewSend(EndPoint endPoint, IList> transferBytes) - //{ - // var length = 0; - // foreach (var item in transferBytes) - // { - // length += item.Count; - // } - - // this.ThrowIfMoreThanMaxPackageSize(length); - - // using (var byteBlock = new ByteBlock(length)) - // { - // foreach (var item in transferBytes) - // { - // byteBlock.Write(item.Array, item.Offset, item.Count); - // } - // this.GoSend(endPoint, byteBlock.Buffer, 0, byteBlock.Len); - // } - //} - - /// - protected override async Task PreviewSendAsync(EndPoint endPoint, IList> transferBytes) - { - var length = 0; - foreach (var item in transferBytes) - { - length += item.Count; - } - - this.ThrowIfMoreThanMaxPackageSize(length); - - using (var byteBlock = new ByteBlock(length)) - { - foreach (var item in transferBytes) - { - byteBlock.Write(new ReadOnlySpan(item.Array, item.Offset, item.Count)); - } - await this.GoSendAsync(endPoint, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - } -} \ No newline at end of file diff --git a/src/TouchSocket/DataAdapter/Udp/UdpPackage/UdpPackageAdapter.cs b/src/TouchSocket/DataAdapter/Udp/UdpPackage/UdpPackageAdapter.cs deleted file mode 100644 index fb6cf50df..000000000 --- a/src/TouchSocket/DataAdapter/Udp/UdpPackage/UdpPackageAdapter.cs +++ /dev/null @@ -1,267 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Net; -using System.Threading.Tasks; -using TouchSocket.Core; -using TouchSocket.Resources; - -namespace TouchSocket.Sockets; - -/// -/// UDP数据包的适配器 -/// -public class UdpPackageAdapter : UdpDataHandlingAdapter -{ - private readonly ConcurrentDictionary m_revStore; - private int m_mtu = 1472; - - /// - /// 构造函数 - /// - public UdpPackageAdapter() - { - this.m_revStore = new ConcurrentDictionary(); - } - - /// - public override bool CanSendRequestInfo => false; - - /// - public override bool CanSplicingSend => true; - - /// - /// 最大传输单元 - /// - public int MTU - { - get => this.m_mtu + 11; - set => this.m_mtu = value > 11 ? value : 1472; - } - - /// - /// 接收超时时间,默认5000ms - /// - public int Timeout { get; set; } = 5000; - - /// - protected override async Task PreviewReceived(EndPoint remoteEndPoint, ByteBlock byteBlock) - { - var udpFrame = new UdpFrame(); - if (udpFrame.Parse(byteBlock.Span)) - { - var udpPackage = this.m_revStore.GetOrAdd(udpFrame.Id, (i) => new UdpPackage(i, this.Timeout, this.m_revStore)); - udpPackage.Add(udpFrame); - if (udpPackage.Length > this.MaxPackageSize) - { - this.m_revStore.TryRemove(udpPackage.Id, out _); - - this.OnError(default, TouchSocketCoreResource.ValueMoreThan.Format(nameof(udpPackage.Length), udpPackage.Length, this.MaxPackageSize), true, true); - return; - } - if (udpPackage.IsComplated) - { - if (this.m_revStore.TryRemove(udpPackage.Id, out _)) - { - using (var block = new ByteBlock(udpPackage.Length)) - { - if (udpPackage.TryGetData(block)) - { - await this.GoReceived(remoteEndPoint, block, null); - } - } - } - } - } - } - - /// - protected override async Task PreviewSendAsync(EndPoint endPoint, ReadOnlyMemory memory) - { - this.ThrowIfMoreThanMaxPackageSize(memory.Length); - - var id = TouchSocketCoreUtility.GenerateRandomInt64(); - var off = 0; - var surLen = memory.Length; - var freeRoom = this.m_mtu - 11; - ushort sn = 0; - /*|********|**|*|n|*/ - /*|********|**|*|**|*/ - while (surLen > 0) - { - using (var byteBlock = new ByteBlock(this.m_mtu)) - { - //var data = new byte[this.m_mtu]; - byteBlock.WriteInt64(id);//8 - byteBlock.WriteUInt16(sn++);//2 - //Buffer.BlockCopy(TouchSocketBitConverter.Default.GetBytes(id), 0, data, 0, 8); - //Buffer.BlockCopy(TouchSocketBitConverter.Default.GetBytes(sn++), 0, data, 8, 2); - if (surLen > freeRoom)//有余 - { - byteBlock.WriteByte(0); - byteBlock.Write(memory.Span.Slice(off, freeRoom)); - //Buffer.BlockCopy(buffer, off, data, 11, freeRoom); - off += freeRoom; - surLen -= freeRoom; - await this.GoSendAsync(endPoint, byteBlock.Memory); - } - else if (surLen + 2 <= freeRoom)//结束且能容纳Crc - { - - var flag = ((byte)0).SetBit(7, true);//设置终结帧 - byteBlock.WriteByte(flag); - - byteBlock.Write(memory.Span.Slice(off, surLen)); - //Buffer.BlockCopy(buffer, off, data, 11, surLen); - - byteBlock.WriteUInt16(Crc.Crc16Value(memory.Span), EndianType.Big); - - //Buffer.BlockCopy(Crc.Crc16(buffer, offset, length), 0, data, 11 + surLen, 2); - - //Buffer.BlockCopy(Crc.Crc16(memory.Span), 0, data, 11 + surLen, 2); - - //await this.GoSendAsync(endPoint, data, 0, surLen + 11 + 2); - await this.GoSendAsync(endPoint, byteBlock.Memory); - - off += surLen; - surLen -= surLen; - } - else//结束但不能容纳Crc - { - byteBlock.WriteByte(0); - byteBlock.Write(memory.Span.Slice(off, surLen)); - //Buffer.BlockCopy(buffer, off, data, 11, surLen); - await this.GoSendAsync(endPoint, byteBlock.Memory); - - byteBlock.Reset(); - - off += surLen; - surLen -= surLen; - - //var finData = new byte[13]; - - byteBlock.WriteInt64(id); - byteBlock.WriteUInt16(sn++); - byteBlock.WriteByte(((byte)0).SetBit(7, true)); - byteBlock.WriteUInt16(Crc.Crc16Value(memory.Span), EndianType.Big); - - //Buffer.BlockCopy(TouchSocketBitConverter.Default.GetBytes(id), 0, finData, 0, 8); - //Buffer.BlockCopy(TouchSocketBitConverter.Default.GetBytes(sn++), 0, finData, 8, 2); - //byte flag = 0; - //finData[10] = flag.SetBit(7, 1); - //Buffer.BlockCopy(Crc.Crc16(buffer, offset, length), 0, finData, 11, 2); - await this.GoSendAsync(endPoint, byteBlock.Memory); - } - } - } - } - - ///// - //protected override void PreviewSend(EndPoint endPoint, byte[] buffer, int offset, int length) - //{ - // this.ThrowIfMoreThanMaxPackageSize(length); - - // var id = TouchSocketCoreUtility.GenerateRandomInt64(); - // var off = 0; - // var surLen = length; - // var freeRoom = this.m_mtu - 11; - // ushort sn = 0; - // /*|********|**|*|n|*/ - // /*|********|**|*|**|*/ - // while (surLen > 0) - // { - // var data = new byte[this.m_mtu]; - // Buffer.BlockCopy(TouchSocketBitConverter.Default.GetBytes(id), 0, data, 0, 8); - // Buffer.BlockCopy(TouchSocketBitConverter.Default.GetBytes(sn++), 0, data, 8, 2); - // if (surLen > freeRoom)//有余 - // { - // Buffer.BlockCopy(buffer, off, data, 11, freeRoom); - // off += freeRoom; - // surLen -= freeRoom; - // this.GoSend(endPoint, data, 0, this.m_mtu); - // } - // else if (surLen + 2 <= freeRoom)//结束且能容纳Crc - // { - // byte flag = 0; - // data[10] = flag.SetBit(7, 1);//设置终结帧 - - // Buffer.BlockCopy(buffer, off, data, 11, surLen); - // Buffer.BlockCopy(Crc.Crc16(buffer, offset, length), 0, data, 11 + surLen, 2); - - // this.GoSend(endPoint, data, 0, surLen + 11 + 2); - - // off += surLen; - // surLen -= surLen; - // } - // else//结束但不能容纳Crc - // { - // Buffer.BlockCopy(buffer, off, data, 11, surLen); - // this.GoSend(endPoint, data, 0, surLen + 11); - // off += surLen; - // surLen -= surLen; - - // var finData = new byte[13]; - // Buffer.BlockCopy(TouchSocketBitConverter.Default.GetBytes(id), 0, finData, 0, 8); - // Buffer.BlockCopy(TouchSocketBitConverter.Default.GetBytes(sn++), 0, finData, 8, 2); - // byte flag = 0; - // finData[10] = flag.SetBit(7, 1); - // Buffer.BlockCopy(Crc.Crc16(buffer, offset, length), 0, finData, 11, 2); - // this.GoSend(endPoint, finData, 0, finData.Length); - // } - // } - //} - - ///// - //protected override void PreviewSend(EndPoint endPoint, IList> transferBytes) - //{ - // var length = 0; - // foreach (var item in transferBytes) - // { - // length += item.Count; - // } - - // this.ThrowIfMoreThanMaxPackageSize(length); - - // using (var byteBlock = new ByteBlock(length)) - // { - // foreach (var item in transferBytes) - // { - // byteBlock.Write(item.Array, item.Offset, item.Count); - // } - // this.PreviewSend(endPoint, byteBlock.Buffer, 0, byteBlock.Len); - // } - //} - - /// - protected override async Task PreviewSendAsync(EndPoint endPoint, IList> transferBytes) - { - var length = 0; - foreach (var item in transferBytes) - { - length += item.Count; - } - - this.ThrowIfMoreThanMaxPackageSize(length); - - using (var byteBlock = new ByteBlock(length)) - { - foreach (var item in transferBytes) - { - byteBlock.Write(new ReadOnlySpan(item.Array, item.Offset, item.Count)); - } - await this.PreviewSendAsync(endPoint, byteBlock.Memory); - } - } -} \ No newline at end of file diff --git a/src/TouchSocket/DelegateCollection.cs b/src/TouchSocket/DelegateCollection.cs index 1536109e6..46e8d4b20 100644 --- a/src/TouchSocket/DelegateCollection.cs +++ b/src/TouchSocket/DelegateCollection.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; - namespace TouchSocket.Sockets; /// diff --git a/src/TouchSocket.Core/Container/DependencyType.cs b/src/TouchSocket/Enum/ConnectionCheckResult.cs similarity index 80% rename from src/TouchSocket.Core/Container/DependencyType.cs rename to src/TouchSocket/Enum/ConnectionCheckResult.cs index 78dbd7e48..73511c009 100644 --- a/src/TouchSocket.Core/Container/DependencyType.cs +++ b/src/TouchSocket/Enum/ConnectionCheckResult.cs @@ -10,29 +10,25 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - -namespace TouchSocket.Core; +namespace TouchSocket.Sockets; /// -/// 依赖注入类型。 +/// 连接检查结果枚举 /// -[Flags] -public enum DependencyType +public enum ConnectionCheckResult : byte { /// - /// 构造函数 + /// 跳过此次检查 /// - - Constructor = 0, + Skip, /// - /// 属性 + /// 连接存活 /// - Property = 1, + Alive, /// - /// 方法 + /// 连接失活,需要重连 /// - Method = 2 -} \ No newline at end of file + Dead +} diff --git a/src/TouchSocket/Enum/ReconnectionStrategy.cs b/src/TouchSocket/Enum/ReconnectionStrategy.cs new file mode 100644 index 000000000..64185bcce --- /dev/null +++ b/src/TouchSocket/Enum/ReconnectionStrategy.cs @@ -0,0 +1,36 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +namespace TouchSocket.Sockets; + +/// +/// 重连策略类型 +/// +public enum ReconnectionStrategy : byte +{ + /// + /// 简单重连 - 固定间隔重连 + /// + Simple, + /// + /// 指数退避重连 - 每次失败后延迟时间指数增长 + /// + ExponentialBackoff, + /// + /// 线性增长重连 - 每次失败后延迟时间线性增长 + /// + LinearBackoff, + /// + /// 自定义重连策略 + /// + Custom +} diff --git a/src/TouchSocket/EventArgs/ByteBlockEventArgs.cs b/src/TouchSocket/EventArgs/BytesReaderEventArgs.cs similarity index 78% rename from src/TouchSocket/EventArgs/ByteBlockEventArgs.cs rename to src/TouchSocket/EventArgs/BytesReaderEventArgs.cs index b86f6587b..bb7d53e4d 100644 --- a/src/TouchSocket/EventArgs/ByteBlockEventArgs.cs +++ b/src/TouchSocket/EventArgs/BytesReaderEventArgs.cs @@ -10,26 +10,24 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// /// 字节事件参数类,用于在插件之间传递字节块数据 /// -public class ByteBlockEventArgs : PluginEventArgs +public class BytesReaderEventArgs : PluginEventArgs { /// /// 初始化字节事件参数对象 /// - /// 需要传递的字节块数据 - public ByteBlockEventArgs(ByteBlock byteBlock) + /// 需要传递的字节块数据 + public BytesReaderEventArgs(IBytesReader reader) { - this.ByteBlock = byteBlock; + this.Reader = reader; } /// /// 获取字节块数据 /// - public ByteBlock ByteBlock { get; private set; } + public IBytesReader Reader { get; } } \ No newline at end of file diff --git a/src/TouchSocket/EventArgs/ClosingEventArgs.cs b/src/TouchSocket/EventArgs/ClosingEventArgs.cs index b4e78d86d..7fb211473 100644 --- a/src/TouchSocket/EventArgs/ClosingEventArgs.cs +++ b/src/TouchSocket/EventArgs/ClosingEventArgs.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// diff --git a/src/TouchSocket/EventArgs/ConnectedEventArgs.cs b/src/TouchSocket/EventArgs/ConnectedEventArgs.cs index 033873e6d..847ece4f3 100644 --- a/src/TouchSocket/EventArgs/ConnectedEventArgs.cs +++ b/src/TouchSocket/EventArgs/ConnectedEventArgs.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// diff --git a/src/TouchSocket/EventArgs/ConnectingEventArgs.cs b/src/TouchSocket/EventArgs/ConnectingEventArgs.cs index 65b89aae6..2930959cf 100644 --- a/src/TouchSocket/EventArgs/ConnectingEventArgs.cs +++ b/src/TouchSocket/EventArgs/ConnectingEventArgs.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// @@ -24,7 +22,7 @@ public class ConnectingEventArgs : MsgPermitEventArgs /// /// 构造函数 - /// 初始化IsPermitOperation属性为true,表示默认允许操作。 + /// 初始化IsPermitOperation属性为,表示默认允许操作。 /// public ConnectingEventArgs() { diff --git a/src/TouchSocket/EventArgs/IdChangedEventArgs.cs b/src/TouchSocket/EventArgs/IdChangedEventArgs.cs index 8f829e4f9..b95b8e3fb 100644 --- a/src/TouchSocket/EventArgs/IdChangedEventArgs.cs +++ b/src/TouchSocket/EventArgs/IdChangedEventArgs.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// diff --git a/src/TouchSocket/EventArgs/MemoryEventArgs.cs b/src/TouchSocket/EventArgs/MemoryEventArgs.cs new file mode 100644 index 000000000..3f48eaf3e --- /dev/null +++ b/src/TouchSocket/EventArgs/MemoryEventArgs.cs @@ -0,0 +1,35 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +namespace TouchSocket.Sockets; +/// +/// 表示包含内存数据的事件参数。 +/// +public class MemoryEventArgs : PluginEventArgs +{ + /// + /// 使用指定的内存数据初始化 类的新实例。 + /// + /// 事件中包含的只读内存数据。 + public MemoryEventArgs(ReadOnlyMemory memory) + { + this.Memory = memory; + } + + /// + /// 获取事件中包含的只读内存数据。 + /// + /// + /// 在使用时,不要脱离 的生命周期。 + /// + public ReadOnlyMemory Memory { get; } +} diff --git a/src/TouchSocket/EventArgs/ReceivedDataEventArgs.cs b/src/TouchSocket/EventArgs/ReceivedDataEventArgs.cs index 10a64f58b..414e7fb36 100644 --- a/src/TouchSocket/EventArgs/ReceivedDataEventArgs.cs +++ b/src/TouchSocket/EventArgs/ReceivedDataEventArgs.cs @@ -10,23 +10,21 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// /// 接收数据事件参数类,继承自ByteBlockEventArgs /// 用于封装接收到的数据和相关的请求信息 /// -public class ReceivedDataEventArgs : ByteBlockEventArgs +public class ReceivedDataEventArgs : MemoryEventArgs { /// /// 构造函数,初始化接收到的数据和请求信息 /// - /// 接收到的数据块 + /// 接收到的数据块 /// 请求信息,描述了数据接收的上下文 - public ReceivedDataEventArgs(ByteBlock byteBlock, IRequestInfo requestInfo) : base(byteBlock) + public ReceivedDataEventArgs(ReadOnlyMemory memory, IRequestInfo requestInfo) : base(memory) { this.RequestInfo = requestInfo; } diff --git a/src/TouchSocket/EventArgs/SendingEventArgs.cs b/src/TouchSocket/EventArgs/SendingEventArgs.cs index b76533392..a255e3a97 100644 --- a/src/TouchSocket/EventArgs/SendingEventArgs.cs +++ b/src/TouchSocket/EventArgs/SendingEventArgs.cs @@ -10,27 +10,19 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// /// SendingEventArgs 类,继承自 PluginEventArgs,用于封装待发送数据的内存块。 /// -public class SendingEventArgs : PluginEventArgs +public class SendingEventArgs : MemoryEventArgs { /// /// 初始化 SendingEventArgs 类的新实例。 /// /// 待发送数据的只读内存块。 - public SendingEventArgs(in ReadOnlyMemory memory) + public SendingEventArgs(in ReadOnlyMemory memory) : base(memory) { - this.Memory = memory; - } - /// - /// 数据缓存区,该属性可能获取来自于内存池,所以最好不要引用该对象,可以同步使用该对象 - /// - public ReadOnlyMemory Memory { get; } + } } \ No newline at end of file diff --git a/src/TouchSocket/EventArgs/ServiceStateEventArgs.cs b/src/TouchSocket/EventArgs/ServiceStateEventArgs.cs index c1849d25a..9348b07ea 100644 --- a/src/TouchSocket/EventArgs/ServiceStateEventArgs.cs +++ b/src/TouchSocket/EventArgs/ServiceStateEventArgs.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// diff --git a/src/TouchSocket/EventArgs/UdpReceivedDataEventArgs.cs b/src/TouchSocket/EventArgs/UdpReceivedDataEventArgs.cs index c6ccc439c..73da50a30 100644 --- a/src/TouchSocket/EventArgs/UdpReceivedDataEventArgs.cs +++ b/src/TouchSocket/EventArgs/UdpReceivedDataEventArgs.cs @@ -11,7 +11,6 @@ //------------------------------------------------------------------------------ using System.Net; -using TouchSocket.Core; namespace TouchSocket.Sockets; @@ -26,9 +25,9 @@ public class UdpReceivedDataEventArgs : ReceivedDataEventArgs /// 初始化 UdpReceivedDataEventArgs 对象 /// /// 接收数据的终结点 - /// 接收到的数据块 + /// 接收到的内存数据 /// 请求信息,提供关于此次接收请求的元数据 - public UdpReceivedDataEventArgs(EndPoint endPoint, ByteBlock byteBlock, IRequestInfo requestInfo) : base(byteBlock, requestInfo) + public UdpReceivedDataEventArgs(EndPoint endPoint, ReadOnlyMemory memory, IRequestInfo requestInfo) : base(memory, requestInfo) { this.EndPoint = endPoint; } diff --git a/src/TouchSocket/EventArgs/UdpReceiveingEventArgs.cs b/src/TouchSocket/EventArgs/UdpReceiveingEventArgs.cs index 08236e6cc..5bbd107ef 100644 --- a/src/TouchSocket/EventArgs/UdpReceiveingEventArgs.cs +++ b/src/TouchSocket/EventArgs/UdpReceiveingEventArgs.cs @@ -11,7 +11,6 @@ //------------------------------------------------------------------------------ using System.Net; -using TouchSocket.Core; namespace TouchSocket.Sockets; @@ -19,15 +18,15 @@ namespace TouchSocket.Sockets; /// UdpReceiveingEventArgs 类,继承自 ByteBlockEventArgs 类 /// 用于封装 UDP 接收到的数据及相关信息 /// -public class UdpReceiveingEventArgs : ByteBlockEventArgs +public class UdpReceiveingEventArgs : MemoryEventArgs { /// /// 构造函数 /// 初始化 UdpReceivedDataEventArgs 对象 /// /// 接收数据的终结点 - /// 接收到的数据块 - public UdpReceiveingEventArgs(EndPoint endPoint, ByteBlock byteBlock) : base(byteBlock) + /// 接收到的内存块 + public UdpReceiveingEventArgs(EndPoint endPoint, ReadOnlyMemory memory) : base(memory) { this.EndPoint = endPoint; } diff --git a/src/TouchSocket/EventArgs/UdpSendingEventArgs.cs b/src/TouchSocket/EventArgs/UdpSendingEventArgs.cs index e73c93515..ace44d8b0 100644 --- a/src/TouchSocket/EventArgs/UdpSendingEventArgs.cs +++ b/src/TouchSocket/EventArgs/UdpSendingEventArgs.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Net; namespace TouchSocket.Sockets; diff --git a/src/TouchSocket/Exceptions/ClientNotConnectedException.cs b/src/TouchSocket/Exceptions/ClientNotConnectedException.cs index 9b33e4fdc..f5d04af4e 100644 --- a/src/TouchSocket/Exceptions/ClientNotConnectedException.cs +++ b/src/TouchSocket/Exceptions/ClientNotConnectedException.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using TouchSocket.Resources; namespace TouchSocket.Sockets; diff --git a/src/TouchSocket/Exceptions/ClientNotFindException.cs b/src/TouchSocket/Exceptions/ClientNotFindException.cs index 590a829c8..8349e6d93 100644 --- a/src/TouchSocket/Exceptions/ClientNotFindException.cs +++ b/src/TouchSocket/Exceptions/ClientNotFindException.cs @@ -10,7 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using TouchSocket.Resources; namespace TouchSocket.Sockets; diff --git a/src/TouchSocket/Exceptions/CloseException.cs b/src/TouchSocket/Exceptions/CloseException.cs new file mode 100644 index 000000000..0df0ed310 --- /dev/null +++ b/src/TouchSocket/Exceptions/CloseException.cs @@ -0,0 +1,25 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; +//using System.Threading.Tasks; + +//namespace TouchSocket.Sockets; +//public sealed class CloseException:Exception +//{ +// public CloseException(string msg):base(msg) +// { +// } +//} diff --git a/src/TouchSocket/Extensions/ClientExtension.cs b/src/TouchSocket/Extensions/ClientExtension.cs index f5a1a12f1..a18f5490b 100644 --- a/src/TouchSocket/Extensions/ClientExtension.cs +++ b/src/TouchSocket/Extensions/ClientExtension.cs @@ -10,13 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Sockets; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Resources; namespace TouchSocket.Sockets; @@ -72,45 +65,6 @@ public static class ClientExtension return client.Service.GetIds().Where(id => id != client.Id); } - /// - /// 安全性发送关闭报文 - /// - /// 客户端类型,必须是ITcpSession接口的实现 - /// 要关闭的客户端对象 - /// 关闭的方式,默认值为SocketShutdown.Both,即读写都关闭 - /// 操作是否成功 - [Obsolete("此操作已被弃用,请使用ShutdownAsync代替", true)] - public static bool TryShutdown(this TClient client, SocketShutdown how = SocketShutdown.Both) where TClient : class, ITcpSession - { - // 检查客户端对象是否为或默认值 - if (client == default) - { - return false; - } - try - { - // 获取客户端的主要Socket对象 - var socket = client.MainSocket; - // 检查Socket对象是否为空 - if (socket == null) - { - return false; - } - // 检查Socket连接是否已经建立 - if (!socket.Connected) - { - return false; - } - // 发送关闭报文 - socket.Shutdown(how); - return true; - } - catch - { - return false; - } - } - #region CloseAsync /// @@ -147,7 +101,7 @@ public static class ClientExtension } else { - // 异步调用CloseAsync方法关闭客户端,传递关闭消息,并指定ConfigureAwait为false以避免同步上下文。 + // 异步调用CloseAsync方法关闭客户端,传递关闭消息,并指定ConfigureAwait为以避免同步上下文。 await client.CloseAsync(msg).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } } @@ -260,14 +214,25 @@ public static class ClientExtension #region ConnectAsync - /// + /// public static async Task ConnectAsync(this IConnectableClient client, int millisecondsTimeout = 5000) { - await client.ConnectAsync(millisecondsTimeout, CancellationToken.None).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + using (var cancellationTokenSource = new CancellationTokenSource(millisecondsTimeout)) + { + try + { + await client.ConnectAsync(cancellationTokenSource.Token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + catch (OperationCanceledException) + { + ThrowHelper.ThrowTimeoutException(); + } + } + } - /// - public static async Task ConnectAsync(this TClient client, IPHost ipHost, int millisecondsTimeout = 5000) where TClient : ISetupConfigObject, IConnectableClient + /// + public static async Task ConnectAsync(this TClient client, IPHost ipHost, int millisecondsTimeout = 5000) where TClient : ISetupConfigObject, ITcpConnectableClient { TouchSocketConfig config; if (client.Config == null) @@ -335,31 +300,30 @@ public static class ClientExtension /// 同步执行连接操作。 /// /// - /// 注意,本同步操作是直接等待的,所以请谨慎使用。 + /// 注意,本同步操作是直接等待的,所以请谨慎使用。 /// /// 要连接的客户端对象。 - /// 连接超时时间,以毫秒为单位。默认为5000毫秒。 /// 用于取消操作的令牌。 [AsyncToSyncWarning] - public static void Connect(this IConnectableClient client, int millisecondsTimeout = 5000, CancellationToken cancellationToken = default) + public static void Connect(this IConnectableClient client, CancellationToken cancellationToken = default) { // 使用GetFalseAwaitResult()方法直接等待异步操作完成,以实现同步执行。 // 这种方法适用于不需要处理异步操作结果的场景。 - client.ConnectAsync(millisecondsTimeout, cancellationToken).GetFalseAwaitResult(); + client.ConnectAsync(cancellationToken).GetFalseAwaitResult(); } /// /// 同步执行连接操作。 /// /// - /// 注意,本同步操作是直接等待的,所以请谨慎使用。 + /// 注意,本同步操作是直接等待的,所以请谨慎使用。 /// /// 要连接的客户端类型,必须实现接口。 /// 要进行连接的客户端实例。 /// 连接的目标IP地址和端口信息。 /// 连接超时时间,单位为毫秒,默认为5000毫秒。 [AsyncToSyncWarning] - public static void Connect(this TClient client, IPHost ipHost, int millisecondsTimeout = 5000) where TClient : ISetupConfigObject, IConnectableClient + public static void Connect(this TClient client, IPHost ipHost, int millisecondsTimeout = 5000) where TClient : ISetupConfigObject, ITcpConnectableClient { ConnectAsync(client, ipHost, millisecondsTimeout).GetFalseAwaitResult(); } @@ -368,7 +332,7 @@ public static class ClientExtension /// 同步执行连接操作。不会抛出异常。 /// /// - /// 注意,本同步操作是直接等待的,所以请谨慎使用。 + /// 注意,本同步操作是直接等待的,所以请谨慎使用。 /// /// 要执行连接操作的客户端对象。 /// 连接超时时间,以毫秒为单位。默认值为5000毫秒。 @@ -394,7 +358,7 @@ public static class ClientExtension /// 同步执行连接操作。不会抛出异常。 /// /// - /// 注意,本同步操作是直接等待的,所以请谨慎使用。 + /// 注意,本同步操作是直接等待的,所以请谨慎使用。 /// /// 要连接的客户端类型,必须实现. /// 要执行连接操作的客户端实例。 @@ -418,4 +382,13 @@ public static class ClientExtension } #endregion Connect + + #region PauseReconnection + + /// + /// 指示是否暂停重新连接。 + /// + [GeneratorProperty(TargetType = typeof(IDependencyClient))] + public static readonly DependencyProperty PauseReconnectionProperty = new DependencyProperty("PauseReconnection", false); + #endregion } \ No newline at end of file diff --git a/src/TouchSocket/Extensions/HeartbeatPluginExtension.cs b/src/TouchSocket/Extensions/HeartbeatPluginExtension.cs deleted file mode 100644 index 2880b56d3..000000000 --- a/src/TouchSocket/Extensions/HeartbeatPluginExtension.cs +++ /dev/null @@ -1,54 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; - -namespace TouchSocket.Sockets; - - -/// -/// 心跳插件扩展类 -/// -public static class HeartbeatPluginExtension -{ - /// - /// 设置心跳间隔。默认3秒。 - /// - /// 心跳插件类型,必须是HeartbeatPlugin的派生类型。 - /// 将要设置心跳间隔的心跳插件实例。 - /// 心跳间隔时间,包括小时、分钟和秒等。 - /// 返回设置后的心跳插件实例,支持链式调用。 - public static THeartbeatPlugin SetTick(this THeartbeatPlugin heartbeatPlugin, TimeSpan timeSpan) - where THeartbeatPlugin : HeartbeatPlugin - { - // 设置心跳插件的Tick属性为指定的时间间隔 - heartbeatPlugin.Tick = timeSpan; - // 返回设置后的心跳插件实例 - return heartbeatPlugin; - } - - /// - /// 设置最大失败次数,默认3。 - /// - /// 心跳插件类型 - /// 具体的心跳插件实例 - /// 设置的最大失败次数 - /// 返回设置后的心跳插件实例 - public static THeartbeatPlugin SetMaxFailCount(this THeartbeatPlugin heartbeatPlugin, int value) - where THeartbeatPlugin : HeartbeatPlugin - { - // 设置插件的最大失败次数 - heartbeatPlugin.MaxFailCount = value; - // 返回设置后的插件实例 - return heartbeatPlugin; - } -} \ No newline at end of file diff --git a/src/TouchSocket/Extensions/PluginRaiseExtension.cs b/src/TouchSocket/Extensions/PluginRaiseExtension.cs new file mode 100644 index 000000000..d395387ae --- /dev/null +++ b/src/TouchSocket/Extensions/PluginRaiseExtension.cs @@ -0,0 +1,30 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +namespace TouchSocket.Sockets; + +[PluginRaise(typeof(IIdChangedPlugin))] +[PluginRaise(typeof(IServerStartedPlugin))] +[PluginRaise(typeof(IServerStoppedPlugin))] +[PluginRaise(typeof(ITcpClosedPlugin))] +[PluginRaise(typeof(ITcpClosingPlugin))] +[PluginRaise(typeof(ITcpConnectedPlugin))] +[PluginRaise(typeof(ITcpConnectingPlugin))] +[PluginRaise(typeof(ITcpReceivedPlugin))] +[PluginRaise(typeof(ITcpReceivingPlugin))] +[PluginRaise(typeof(ITcpSendingPlugin))] +[PluginRaise(typeof(IUdpReceivedPlugin))] +[PluginRaise(typeof(IUdpReceivingPlugin))] +[PluginRaise(typeof(IUdpSendingPlugin))] +internal static partial class PluginRaiseExtension +{ +} diff --git a/src/TouchSocket/Extensions/SenderExtension.cs b/src/TouchSocket/Extensions/SenderExtension.cs index cdd81ce06..7c5831278 100644 --- a/src/TouchSocket/Extensions/SenderExtension.cs +++ b/src/TouchSocket/Extensions/SenderExtension.cs @@ -10,12 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; using System.Net; -using System.Text; -using System.Threading.Tasks; -using TouchSocket.Core; namespace TouchSocket.Sockets; @@ -60,32 +55,21 @@ public static class SenderExtension /// 返回一个Task对象,表示异步操作。 public static async Task SendAsync(this TClient client, string value) where TClient : ISender { - using (var byteBlock = new ByteBlock(1024)) + var byteBlock = new ByteBlock(1024); + + try { - byteBlock.WriteNormalString(value, Encoding.UTF8); + WriterExtension.WriteNormalString(ref byteBlock, value, Encoding.UTF8); await client.SendAsync(byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } + finally + { + byteBlock.Dispose(); + } } #endregion ISend - #region IClientSender - - /// - /// 同步发送数据。 - /// - /// 发送数据的客户端对象。 - /// 待发送的字节数据列表。 - /// 客户端对象类型,必须实现接口。 - [AsyncToSyncWarning] - public static void Send(this TClient client, IList> bytesList) where TClient : IClientSender - { - // 调用客户端对象的SendAsync方法发送数据,并忽略返回结果。 - client.SendAsync(bytesList).GetFalseAwaitResult(); - } - - #endregion IClientSender - #region IRequestInfoSender /// @@ -204,11 +188,17 @@ public static class SenderExtension /// 返回一个Task对象,代表异步操作的完成状态。 public static async Task SendAsync(this TClient client, EndPoint endPoint, string value) where TClient : IUdpClientSender { - using (var byteBlock = new ByteBlock(1024)) + var byteBlock = new ByteBlock(1024); + + try { - byteBlock.WriteNormalString(value, Encoding.UTF8); + WriterExtension.WriteNormalString(ref byteBlock, value, Encoding.UTF8); await client.SendAsync(endPoint, byteBlock.Memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } + finally + { + byteBlock.Dispose(); + } } #endregion IUdpClientSender diff --git a/src/TouchSocket/Extensions/ServiceExtension.cs b/src/TouchSocket/Extensions/ServiceExtension.cs index e351af864..d795c8a4b 100644 --- a/src/TouchSocket/Extensions/ServiceExtension.cs +++ b/src/TouchSocket/Extensions/ServiceExtension.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// diff --git a/src/TouchSocket/Extensions/SocketExtension.cs b/src/TouchSocket/Extensions/SocketExtension.cs index e293abaa3..59894fa36 100644 --- a/src/TouchSocket/Extensions/SocketExtension.cs +++ b/src/TouchSocket/Extensions/SocketExtension.cs @@ -10,9 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Net.Sockets; -using TouchSocket.Core; using TouchSocket.Resources; namespace TouchSocket.Sockets; @@ -58,7 +56,7 @@ public static class SocketExtension /// 尝试关闭。不会抛出异常。 /// /// - public static void TryClose(this Socket socket) + public static Result SafeClose(this Socket socket) { try { @@ -66,9 +64,11 @@ public static class SocketExtension { socket.Close(); } + return Result.Success; } - catch + catch (Exception ex) { + return Result.FromException(ex.Message); } } } \ No newline at end of file diff --git a/src/TouchSocket/Extensions/SocketPluginsManagerExtension.cs b/src/TouchSocket/Extensions/SocketPluginsManagerExtension.cs index 116283ddc..eff0f1199 100644 --- a/src/TouchSocket/Extensions/SocketPluginsManagerExtension.cs +++ b/src/TouchSocket/Extensions/SocketPluginsManagerExtension.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// @@ -20,125 +17,60 @@ namespace TouchSocket.Sockets; /// public static class SocketPluginManagerExtension { - /// - /// 检查连接客户端活性插件。 - /// 当在设置的周期内,没有接收/发送任何数据,则判定该客户端掉线。执行清理。默认配置:60秒为一个周期,同时检测发送和接收。 - /// 服务器、客户端均适用。 - /// - /// 插件管理器对象,用于管理插件。 - /// 返回一个类型的插件实例,用于执行客户端活性检查及清理操作。 - [Obsolete("此方法由于不能很好的描述CheckClear的应用对象,已被弃用,请直接使用UseTcpSessionCheckClear代替")] - public static CheckClearPlugin UseCheckClear(this IPluginManager pluginManager) - { - // 使用插件管理器添加一个检查和清理不活跃客户端的插件 - return pluginManager.Add>(); - } - /// /// 使用检查连接客户端活性插件。 - /// 当在设置的周期内,没有接收/发送任何数据,则判定该客户端掉线。执行清理。默认配置:60秒为一个周期,同时检测发送和接收。 + /// 当在设置的周期内,没有接收/发送任何数据,则判定该客户端掉线。执行清理。默认配置:60秒为一个周期,同时检测发送和接收。 /// 服务器、客户端均适用。 /// - /// 插件管理器对象,用于管理插件。 - /// 返回一个类型的插件实例,用于执行客户端活性检查及清理操作。 - public static CheckClearPlugin UseTcpSessionCheckClear(this IPluginManager pluginManager) + /// 插件管理器对象,用于管理插件。 + /// 配置选项的委托 + /// 返回一个类型的插件实例,用于执行客户端活性检查及清理操作。 + public static CheckClearPlugin UseTcpSessionCheckClear(this IPluginManager pluginManager, Action> options = null) { - return pluginManager.UseCheckClear(); + return pluginManager.UseCheckClear(options); } /// /// 检查连接客户端活性插件。 - /// 当在设置的周期内,没有接收/发送任何数据,则判定该客户端掉线。执行清理。默认配置:60秒为一个周期,同时检测发送和接收。 + /// 当在设置的周期内,没有接收/发送任何数据,则判定该客户端掉线。执行清理。默认配置:60秒为一个周期,同时检测发送和接收。 /// 服务器、客户端均适用。 /// /// 插件管理器 + /// 配置选项的委托 /// 返回一个用于检查和清理不活跃客户端的插件实例 - public static CheckClearPlugin UseCheckClear(this IPluginManager pluginManager) where TClient : class, IDependencyClient, IClosableClient, IOnlineClient + public static CheckClearPlugin UseCheckClear(this IPluginManager pluginManager, Action> options = null) + where TClient : class, IDependencyClient, IClosableClient { - // 添加并返回一个新的检查和清理插件实例 - return pluginManager.Add>(); + var option = new CheckClearOption(); + options?.Invoke(option); + + var logger = pluginManager.Resolver.Resolve(); + var checkClearPlugin = new CheckClearPlugin(logger, option); + pluginManager.Add(checkClearPlugin); + return checkClearPlugin; } #region Reconnection /// - /// 使用断线重连。 + /// 使用断线重连 /// - /// - /// - /// - [Obsolete("此配置已被弃用,请使用UseTcpReconnection代替", true)] - public static ReconnectionPlugin UseReconnection(this IPluginManager pluginManager) where TClient : class, ITcpClient + /// 客户端类型 + /// 插件管理器实例 + /// 配置选项的委托 + /// 返回创建的重连插件实例 + public static ReconnectionPlugin UseReconnection( + this IPluginManager pluginManager, + Action> configureOptions = null) + where TClient : IConnectableClient, IOnlineClient, IDependencyClient, IClosableClient { - throw new NotImplementedException(); - } + var options = new ReconnectionOption(); + configureOptions?.Invoke(options); - /// - /// 使用断线重连。 - /// 该效果仅客户端在完成首次连接,且为被动断开时有效。 - /// - /// - /// 成功回调函数 - /// 尝试重连次数,设为-1时则永远尝试连接 - /// 是否输出日志。 - /// 失败时,停留时间 - /// - [Obsolete("此配置已被弃用,请使用UseTcpReconnection代替", true)] - public static ReconnectionPlugin UseReconnection(this IPluginManager pluginManager, int tryCount = 10, bool printLog = false, int sleepTime = 1000, Action successCallback = null) - { - throw new NotImplementedException(); - } - - /// - /// 使用断线重连。 - /// 该效果仅客户端在完成首次连接,且为被动断开时有效。 - /// - /// - /// 失败时间隔时间 - /// 失败时回调(参数依次为:客户端,本轮尝试重连次数,异常信息)。如果回调为或者返回,则终止尝试下次连接。 - /// 成功连接时回调。 - /// - [Obsolete("此配置已被弃用,请使用UseTcpReconnection代替", true)] - public static ReconnectionPlugin UseReconnection(this IPluginManager pluginManager, TimeSpan sleepTime, - Func failCallback = default, - Action successCallback = default) - { - throw new NotImplementedException(); + var reconnectionPlugin = new ReconnectionPlugin(options); + pluginManager.Add(reconnectionPlugin); + return reconnectionPlugin; } #endregion Reconnection - - #region TcpReconnection - - /// - /// 使用断线重连。 - /// - /// 指定的客户端类型,必须继承自ITcpClient。 - /// 插件管理器实例,用于添加断线重连插件。 - /// 返回创建的重连实例。 - public static ReconnectionPlugin UseTcpReconnection(this IPluginManager pluginManager) where TClient : ITcpClient - { - // 创建并初始化断线重连插件实例 - var reconnectionPlugin = new TcpReconnectionPlugin(); - // 将断线重连插件添加到插件管理器中 - pluginManager.Add(reconnectionPlugin); - // 返回断线重连插件实例 - return reconnectionPlugin; - } - - /// - /// 为插件管理器添加TCP重新连接插件。 - /// - /// 要添加插件的插件管理器。 - /// 返回新创建的TCP重新连接插件实例。 - public static ReconnectionPlugin UseTcpReconnection(this IPluginManager pluginManager) - { - // 创建TCP重新连接插件实例 - var reconnectionPlugin = new TcpReconnectionPlugin(); - // 将插件添加到插件管理器中 - pluginManager.Add(reconnectionPlugin); - // 返回新创建的插件实例 - return reconnectionPlugin; - } - #endregion TcpReconnection } \ No newline at end of file diff --git a/src/TouchSocket/Extensions/TouchSocketConfigExtension.cs b/src/TouchSocket/Extensions/TouchSocketConfigExtension.cs index 20546fbdc..40e16bd65 100644 --- a/src/TouchSocket/Extensions/TouchSocketConfigExtension.cs +++ b/src/TouchSocket/Extensions/TouchSocketConfigExtension.cs @@ -10,14 +10,10 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Security.Authentication; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Sockets; + + /// /// 触摸套接字配置扩展类 /// @@ -26,109 +22,26 @@ public static class TouchSocketConfigExtension #region 数据 /// - /// 发送超时设定,默认为0。 - /// 所需类型 + /// 传输选项配置属性,类型为。 /// - public static readonly DependencyProperty SendTimeoutProperty = - new("SendTimeout", 0); + [GeneratorProperty(TargetType = typeof(TouchSocketConfig), ActionMode = true)] + public static readonly DependencyProperty TransportOptionProperty = new("TransportOption", new TransportOption()); + /// /// 数据处理适配器 /// 所需类型 /// + [GeneratorProperty(TargetType = typeof(TouchSocketConfig))] public static readonly DependencyProperty> TcpDataHandlingAdapterProperty = new("TcpDataHandlingAdapter", null); /// /// 数据处理适配器 /// 所需类型 /// + [GeneratorProperty(TargetType = typeof(TouchSocketConfig))] public static readonly DependencyProperty> UdpDataHandlingAdapterProperty = new("UdpDataHandlingAdapter", null); - /// - /// 最小缓存池尺寸 - /// 所需类型 - /// - public static readonly DependencyProperty MinBufferSizeProperty = new("MinBufferSize", default); - - /// - /// 最大缓存池尺寸 - /// 所需类型 - /// - public static readonly DependencyProperty MaxBufferSizeProperty = new("MaxBufferSize", default); - - /// - /// 最小缓存容量,默认缺省。 - /// - /// - /// - /// - /// - /// - public static TouchSocketConfig SetMinBufferSize(this TouchSocketConfig config, int value) - { - if (value < 1024) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(value), value, 1024); - } - config.SetValue(MinBufferSizeProperty, value); - return config; - } - - /// - /// 最大缓存容量,默认缺省。 - /// - /// - /// - /// - /// - /// - public static TouchSocketConfig SetMaxBufferSize(this TouchSocketConfig config, int value) - { - if (value < 1024) - { - ThrowHelper.ThrowArgumentOutOfRangeException_LessThan(nameof(value), value, 1024); - } - - config.SetValue(MaxBufferSizeProperty, value); - return config; - } - - /// - /// 发送超时设定,单位毫秒,默认为0。意为禁用该配置。 - /// - /// - /// - /// - public static TouchSocketConfig SetSendTimeout(this TouchSocketConfig config, int value) - { - config.SetValue(SendTimeoutProperty, value); - return config; - } - - /// - /// 设置(Tcp系)数据处理适配器。 - /// - /// - /// - /// - public static TouchSocketConfig SetTcpDataHandlingAdapter(this TouchSocketConfig config, Func value) - { - config.SetValue(TcpDataHandlingAdapterProperty, value); - return config; - } - - /// - /// 设置(Udp系)数据处理适配器。 - /// - /// - /// - /// - public static TouchSocketConfig SetUdpDataHandlingAdapter(this TouchSocketConfig config, Func value) - { - config.SetValue(UdpDataHandlingAdapterProperty, value); - return config; - } - #endregion 数据 #region ServiceBase @@ -136,39 +49,16 @@ public static class TouchSocketConfigExtension /// /// 服务名称,用于标识,无实际意义,所需类型 /// + [GeneratorProperty(TargetType = typeof(TouchSocketConfig))] public static readonly DependencyProperty ServerNameProperty = new("ServerName", "TouchSocketServer"); /// - /// 多线程数量。默认-1缺省。 + /// 重叠IO并发数。默认1。 /// UDP模式中,该值为重叠IO并发数 /// 所需类型 /// - public static readonly DependencyProperty ThreadCountProperty = new("ThreadCount", -1); - - /// - /// 服务名称,用于标识,无实际意义 - /// - /// - /// - /// - public static TouchSocketConfig SetServerName(this TouchSocketConfig config, string value) - { - config.SetValue(ServerNameProperty, value); - return config; - } - - /// - /// 多线程数量,默认为-1缺省,实际上在udp中相当于1。 - /// UDP模式中,该值为重叠IO并发数 - /// - /// - /// - /// - public static TouchSocketConfig SetThreadCount(this TouchSocketConfig config, int value) - { - config.SetValue(ThreadCountProperty, value); - return config; - } + [GeneratorProperty(TargetType = typeof(TouchSocketConfig))] + public static readonly DependencyProperty UdpOverlappedCountProperty = new("UdpOverlappedCount", 1); #endregion ServiceBase @@ -178,111 +68,43 @@ public static class TouchSocketConfigExtension /// Tcp固定端口绑定, /// 所需类型 /// + [GeneratorProperty(TargetType = typeof(TouchSocketConfig))] public static readonly DependencyProperty BindIPHostProperty = new("BindIPHost", null); /// /// 在Socket配置KeepAlive属性,这个是操作tcp底层的,如果你对底层不了解,建议不要动。 /// 所需类型 /// + [GeneratorProperty(TargetType = typeof(TouchSocketConfig), ActionMode = true)] public static readonly DependencyProperty KeepAliveValueProperty = new("KeepAliveValue", default); /// /// 设置Socket不使用Delay算法, /// 所需类型 /// - public static readonly DependencyProperty NoDelayProperty = new("NoDelay", null); + [GeneratorProperty(TargetType = typeof(TouchSocketConfig))] + public static readonly DependencyProperty NoDelayProperty = new("NoDelay", true); /// /// 远程目标地址,所需类型 /// + [GeneratorProperty(TargetType = typeof(TouchSocketConfig))] public static readonly DependencyProperty RemoteIPHostProperty = new("RemoteIPHost", null); /// /// ClientSslOption配置,为Null时则不启用 /// 所需类型 /// + [GeneratorProperty(TargetType = typeof(TouchSocketConfig), ActionMode = true)] public static readonly DependencyProperty ClientSslOptionProperty = new("ClientSslOption", null); /// /// ServiceSslOption配置,为Null时则不启用 /// 所需类型 /// + [GeneratorProperty(TargetType = typeof(TouchSocketConfig), ActionMode = true)] public static readonly DependencyProperty ServiceSslOptionProperty = new("ServiceSslOption", null); - /// - /// 固定端口绑定。 - /// 中表示本地监听地址 - /// 中表示固定客户端端口号。 - /// - /// - /// - /// - public static TouchSocketConfig SetBindIPHost(this TouchSocketConfig config, IPHost value) - { - config.SetValue(BindIPHostProperty, value); - return config; - } - - /// - /// 设置客户端Ssl配置,为Null时则不启用。 - /// - /// - /// - /// - public static TouchSocketConfig SetClientSslOption(this TouchSocketConfig config, ClientSslOption value) - { - config.SetValue(ClientSslOptionProperty, value); - return config; - } - - /// - /// 在Socket的KeepAlive属性。 - /// 注意:这个是操作tcp底层的,如果你对底层不了解,建议不要动。 - /// - /// - /// - /// - public static TouchSocketConfig SetKeepAliveValue(this TouchSocketConfig config, KeepAliveValue value) - { - config.SetValue(KeepAliveValueProperty, value); - return config; - } - - /// - /// 设置远程目标地址。在中,表示默认发送时的目标地址。 - /// - /// - /// - /// - public static TouchSocketConfig SetRemoteIPHost(this TouchSocketConfig config, IPHost value) - { - config.SetValue(RemoteIPHostProperty, value); - if (value.Scheme.Equals("https", StringComparison.CurrentCultureIgnoreCase) - || value.Scheme.Equals("wss", StringComparison.CurrentCultureIgnoreCase) - || value.Scheme.Equals("ssl", StringComparison.CurrentCultureIgnoreCase) - || value.Scheme.Equals("tls", StringComparison.CurrentCultureIgnoreCase)) - { - config.SetClientSslOption(new ClientSslOption() - { - TargetHost = value.Host, - SslProtocols= SslProtocols.None - }); - } - return config; - } - - /// - /// 设置Socket的NoDelay属性,默认不做处理。 - /// - /// - /// - /// - public static TouchSocketConfig SetNoDelay(this TouchSocketConfig config, bool value) - { - config.SetValue(NoDelayProperty, value); - return config; - } - #endregion TcpClient #region TcpService @@ -290,117 +112,39 @@ public static class TouchSocketConfigExtension /// /// 挂起连接队列的最大长度,所需类型 /// + [GeneratorProperty(TargetType = typeof(TouchSocketConfig))] public static readonly DependencyProperty BacklogProperty = new("Backlog", null); /// /// 设置默认Id的获取方式,所需类型 /// - public static readonly DependencyProperty> GetDefaultNewIdProperty = new("GetDefaultNewId", null); + [GeneratorProperty(TargetType = typeof(TouchSocketConfig))] + public static readonly DependencyProperty> GetDefaultNewIdProperty = new("GetDefaultNewId", null); /// /// 服务器负责监听的地址组。所需类型数组 /// + [GeneratorProperty(TargetType = typeof(TouchSocketConfig))] public static readonly DependencyProperty ListenIPHostsProperty = new("ListenIPHosts", null); /// /// 直接单个配置服务器监听的地址组。所需类型 /// - public static readonly DependencyProperty>> ListenOptionsProperty = new("ListenOptions", null); + [GeneratorProperty(TargetType = typeof(TouchSocketConfig), ActionMode = true)] + public static readonly DependencyProperty> ListenOptionsProperty = new("ListenOptions", null); /// /// 最大可连接数,默认为10000,所需类型 /// + [GeneratorProperty(TargetType = typeof(TouchSocketConfig))] public static readonly DependencyProperty MaxCountProperty = new("MaxCount", 10000); /// - /// 端口复用,默认为false,所需类型 + /// 端口复用,默认为,所需类型 /// + [GeneratorProperty(TargetType = typeof(TouchSocketConfig))] public static readonly DependencyProperty ReuseAddressProperty = new("ReuseAddress", false); - /// - /// 挂起连接队列的最大长度,默认不设置值。 - /// - /// - /// - /// - public static TouchSocketConfig SetBacklog(this TouchSocketConfig config, int value) - { - config.SetValue(BacklogProperty, value); - return config; - } - - /// - /// 设置Tcp服务器默认Id的获取方式。仅服务器生效。 - /// - /// - /// - /// - public static TouchSocketConfig SetGetDefaultNewId(this TouchSocketConfig config, Func value) - { - config.SetValue(GetDefaultNewIdProperty, value); - return config; - } - - /// - /// 服务器负责监听的地址组。 - /// - /// - /// - /// - public static TouchSocketConfig SetListenIPHosts(this TouchSocketConfig config, params IPHost[] values) - { - config.SetValue(ListenIPHostsProperty, values); - return config; - } - - /// - /// 直接单个配置服务器监听的地址组。 - /// - /// - /// - /// - public static TouchSocketConfig SetListenOptions(this TouchSocketConfig config, Action> value) - { - config.SetValue(ListenOptionsProperty, value); - return config; - } - - /// - /// 最大可连接数,默认为10000。 - /// - /// - /// - /// - public static TouchSocketConfig SetMaxCount(this TouchSocketConfig config, int value) - { - config.SetValue(MaxCountProperty, value); - return config; - } - - /// - /// 设置客户端Ssl配置,为Null时则不启用。 - /// - /// - /// - /// - public static TouchSocketConfig SetServiceSslOption(this TouchSocketConfig config, ServiceSslOption value) - { - config.SetValue(ServiceSslOptionProperty, value); - return config; - } - - /// - /// 启用端口复用。 - /// 该配置可在服务器、或客户端在监听端口时,运行监听同一个端口。可以一定程度缓解端口来不及释放的问题 - /// - /// - /// - public static TouchSocketConfig UseReuseAddress(this TouchSocketConfig config) - { - config.SetValue(ReuseAddressProperty, true); - return config; - } - #endregion TcpService #region UDP @@ -408,27 +152,17 @@ public static class TouchSocketConfigExtension /// /// 该值指定 System.Net.Sockets.Socket可以发送或接收广播数据包。 /// + [GeneratorProperty(TargetType = typeof(TouchSocketConfig))] public static readonly DependencyProperty EnableBroadcastProperty = new("EnableBroadcast", false); /// - /// 该值指定 System.Net.Sockets.Socket可以发送或接收广播数据包。 - /// - /// - /// - public static TouchSocketConfig UseBroadcast(this TouchSocketConfig config) - { - config.SetValue(EnableBroadcastProperty, true); - return config; - } - - /// - /// 当udp作为客户端时,开始接收数据。起作用相当于随机端口。 + /// 当udp作为客户端时,开始接收数据。起作用相当于0端口。 /// /// /// public static TouchSocketConfig UseUdpReceive(this TouchSocketConfig config) { - return SetBindIPHost(config, 0); + return config.SetBindIPHost(0); } /// @@ -437,23 +171,9 @@ public static class TouchSocketConfigExtension #if NET6_0_OR_GREATER [System.Runtime.Versioning.SupportedOSPlatform("windows")] #endif + [GeneratorProperty(TargetType = typeof(TouchSocketConfig))] public static readonly DependencyProperty UdpConnResetProperty = new("UdpConnReset", false); - /// - /// 解决Windows下UDP连接被重置错误10054。 - /// - /// - /// -#if NET6_0_OR_GREATER - [System.Runtime.Versioning.SupportedOSPlatform("windows")] -#endif - - public static TouchSocketConfig UseUdpConnReset(this TouchSocketConfig config) - { - config.SetValue(UdpConnResetProperty, true); - return config; - } - #endregion UDP #region 创建 diff --git a/src/TouchSocket/Interfaces/IClient.cs b/src/TouchSocket/Interfaces/IClient.cs index 151e384bb..8e91518bd 100644 --- a/src/TouchSocket/Interfaces/IClient.cs +++ b/src/TouchSocket/Interfaces/IClient.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// diff --git a/src/TouchSocket/Interfaces/IClientCollection.cs b/src/TouchSocket/Interfaces/IClientCollection.cs index 21516ccdf..65e2b2e67 100644 --- a/src/TouchSocket/Interfaces/IClientCollection.cs +++ b/src/TouchSocket/Interfaces/IClientCollection.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Collections.Generic; - namespace TouchSocket.Sockets; /// diff --git a/src/TouchSocket/Interfaces/IClosableClient.cs b/src/TouchSocket/Interfaces/IClosableClient.cs index a2655fdbd..dbf2d079b 100644 --- a/src/TouchSocket/Interfaces/IClosableClient.cs +++ b/src/TouchSocket/Interfaces/IClosableClient.cs @@ -10,21 +10,23 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Sockets; -/// -/// 具有关闭动作的对象。 -/// +/// +/// 具有关闭动作的对象。 +/// public interface IClosableClient { - /// - /// 关闭客户端。 - /// - /// 关闭时的提示信息。 - /// 可取消令箭 - Task CloseAsync(string msg, CancellationToken token = default); + /// + /// 获取一个 ,用于指示客户端是否已关闭。 + /// + CancellationToken ClosedToken { get; } + + /// + /// 关闭客户端。 + /// + /// 关闭时的提示信息。 + /// 可取消令箭。 + /// 表示异步操作结果的 + Task CloseAsync(string msg, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/TouchSocket/Interfaces/IConnectableClient.cs b/src/TouchSocket/Interfaces/IConnectableClient.cs index 7631df36f..2f06b49ca 100644 --- a/src/TouchSocket/Interfaces/IConnectableClient.cs +++ b/src/TouchSocket/Interfaces/IConnectableClient.cs @@ -10,13 +10,8 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading; -using System.Threading.Tasks; - namespace TouchSocket.Sockets; - /// /// 定义可连接客户端的行为。 /// @@ -25,9 +20,8 @@ public interface IConnectableClient /// /// 异步连接 /// - /// 最大等待时间 - /// 可取消令箭 + /// 可取消令箭 /// 当连接超时时抛出 /// 当连接过程中发生错误时抛出 - Task ConnectAsync(int millisecondsTimeout, CancellationToken token); + Task ConnectAsync(CancellationToken cancellationToken); } \ No newline at end of file diff --git a/src/TouchSocket/Interfaces/IConnectableService.cs b/src/TouchSocket/Interfaces/IConnectableService.cs index 58296948d..023b67b00 100644 --- a/src/TouchSocket/Interfaces/IConnectableService.cs +++ b/src/TouchSocket/Interfaces/IConnectableService.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Collections.Generic; -using System.Threading.Tasks; - namespace TouchSocket.Sockets; /// @@ -46,7 +43,8 @@ public interface IConnectableService : IServiceBase /// /// 源Id /// 目标Id - Task ResetIdAsync(string sourceId, string targetId); + /// + Task ResetIdAsync(string sourceId, string targetId, CancellationToken cancellationToken = default); /// /// 根据Id判断对应的客户端是否存在 diff --git a/src/TouchSocket/Interfaces/IDependencyClient.cs b/src/TouchSocket/Interfaces/IDependencyClient.cs index 2c62a074b..528e59cb5 100644 --- a/src/TouchSocket/Interfaces/IDependencyClient.cs +++ b/src/TouchSocket/Interfaces/IDependencyClient.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 // ------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// /// 表示一个依赖客户端接口。 diff --git a/src/TouchSocket/Interfaces/IIdClient.cs b/src/TouchSocket/Interfaces/IIdClient.cs index baf36311d..586cdd351 100644 --- a/src/TouchSocket/Interfaces/IIdClient.cs +++ b/src/TouchSocket/Interfaces/IIdClient.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; - namespace TouchSocket.Sockets; /// @@ -19,14 +17,15 @@ namespace TouchSocket.Sockets; /// public interface IIdClient { - /// - /// 重新设置Id - /// - /// 新的Id值 - Task ResetIdAsync(string newId); - /// /// 用于索引的Id /// string Id { get; } + + /// + /// 重新设置Id + /// + /// 新的Id值 + /// + Task ResetIdAsync(string newId, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/TouchSocket/Interfaces/IPipeTcpClient.cs b/src/TouchSocket/Interfaces/IPipeTcpClient.cs new file mode 100644 index 000000000..b086035fb --- /dev/null +++ b/src/TouchSocket/Interfaces/IPipeTcpClient.cs @@ -0,0 +1,22 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.IO.Pipelines; + +namespace TouchSocket.Sockets; + +/// +/// 管道式Tcp客户端。 +/// +public interface IPipeTcpClient : ISetupConfigObject, ITcpSession, IDuplexPipe, ITcpConnectableClient +{ +} diff --git a/src/TouchSocket/Interfaces/ISender/IClientSender.cs b/src/TouchSocket/Interfaces/ISender/IClientSender.cs index 50bd050b7..22801aa52 100644 --- a/src/TouchSocket/Interfaces/ISender/IClientSender.cs +++ b/src/TouchSocket/Interfaces/ISender/IClientSender.cs @@ -10,11 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// @@ -22,14 +17,4 @@ namespace TouchSocket.Sockets; /// public interface IClientSender : ISender, IRequestInfoSender { - - /// - /// 异步组合发送数据。 - /// 该发送会经过适配器封装,具体封装内容由适配器决定。 - /// - /// 组合数据 - /// 客户端没有连接 - /// 发送数据超长 - /// 其他异常 - Task SendAsync(IList> transferBytes); } \ No newline at end of file diff --git a/src/TouchSocket/Interfaces/ISender/IIdRequestInfoSender.cs b/src/TouchSocket/Interfaces/ISender/IIdRequestInfoSender.cs index 8b5130cd7..60f6e5eb1 100644 --- a/src/TouchSocket/Interfaces/ISender/IIdRequestInfoSender.cs +++ b/src/TouchSocket/Interfaces/ISender/IIdRequestInfoSender.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// @@ -26,6 +23,7 @@ public interface IIdRequestInfoSender /// /// 要发送的标识符 /// 请求信息对象,包含发送的具体内容 + /// 可取消令箭 /// 返回一个任务,表示异步操作的完成 - Task SendAsync(string id, IRequestInfo requestInfo); + Task SendAsync(string id, IRequestInfo requestInfo, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/TouchSocket/Interfaces/ISender/IIdSender.cs b/src/TouchSocket/Interfaces/ISender/IIdSender.cs index 3e85e1c81..9480ac42b 100644 --- a/src/TouchSocket/Interfaces/ISender/IIdSender.cs +++ b/src/TouchSocket/Interfaces/ISender/IIdSender.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; - namespace TouchSocket.Sockets; /// @@ -25,8 +22,9 @@ public interface IIdSender /// /// 目标客户端的唯一标识符 /// 要发送的数据,以字节形式存储在内存中 + /// 可取消令箭 /// 如果目标客户端未连接,则抛出此异常 /// 如果无法根据Id找到对应的客户端,则抛出此异常 /// 如果发生其他异常情况 - Task SendAsync(string id, ReadOnlyMemory memory); + Task SendAsync(string id, ReadOnlyMemory memory, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/TouchSocket/Interfaces/ISender/IRequestInfoSender.cs b/src/TouchSocket/Interfaces/ISender/IRequestInfoSender.cs index 10b270fde..2e48d40c6 100644 --- a/src/TouchSocket/Interfaces/ISender/IRequestInfoSender.cs +++ b/src/TouchSocket/Interfaces/ISender/IRequestInfoSender.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// @@ -26,8 +22,9 @@ public interface IRequestInfoSender /// 该发送会经过适配器封装,具体封装内容由适配器决定。 /// /// 解析对象 + /// 可取消令箭 /// 客户端没有连接 /// 发送数据超长 /// 其他异常 - Task SendAsync(IRequestInfo requestInfo); + Task SendAsync(IRequestInfo requestInfo, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/TouchSocket/Interfaces/ISender/ISender.cs b/src/TouchSocket/Interfaces/ISender/ISender.cs index d82c62c43..383741e19 100644 --- a/src/TouchSocket/Interfaces/ISender/ISender.cs +++ b/src/TouchSocket/Interfaces/ISender/ISender.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// @@ -26,8 +22,9 @@ public interface ISender /// 该发送会经过适配器封装,具体封装内容由适配器决定。 /// /// 要发送的数据,以字节的只读内存形式提供。 + /// 可取消令箭 /// 客户端没有连接 /// 发送数据超长 /// 其他异常 - Task SendAsync(ReadOnlyMemory memory); + Task SendAsync(ReadOnlyMemory memory, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/TouchSocket/Interfaces/ISender/IUdpClientSender.cs b/src/TouchSocket/Interfaces/ISender/IUdpClientSender.cs index 13bf97b5f..feac96149 100644 --- a/src/TouchSocket/Interfaces/ISender/IUdpClientSender.cs +++ b/src/TouchSocket/Interfaces/ISender/IUdpClientSender.cs @@ -10,11 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; using System.Net; -using System.Threading.Tasks; -using TouchSocket.Core; namespace TouchSocket.Sockets; @@ -23,25 +19,15 @@ namespace TouchSocket.Sockets; /// public interface IUdpClientSender : ISender, IUdpRequestInfoSender { - /// - /// 异步组合发送数据。 - /// 该发送会经过适配器封装,具体封装内容由适配器决定。 - /// - /// 远程终结点 - /// 组合数据 - /// 客户端没有连接 - /// 发送数据超长 - /// 其他异常 - Task SendAsync(EndPoint endPoint, IList> transferBytes); - /// /// 异步组合发送数据。 /// 该发送会经过适配器封装,具体封装内容由适配器决定。 /// /// 远程终结点 /// 只读内存块,包含待发送的数据 + /// 可取消令箭 /// 发送数据超长 /// 其他异常 /// 一个表示异步操作的Task对象 - Task SendAsync(EndPoint endPoint, ReadOnlyMemory memory); + Task SendAsync(EndPoint endPoint, ReadOnlyMemory memory, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/TouchSocket/Interfaces/ISender/IUdpRequestInfoSender.cs b/src/TouchSocket/Interfaces/ISender/IUdpRequestInfoSender.cs index 680224692..4c0a26f04 100644 --- a/src/TouchSocket/Interfaces/ISender/IUdpRequestInfoSender.cs +++ b/src/TouchSocket/Interfaces/ISender/IUdpRequestInfoSender.cs @@ -10,10 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Net; -using System.Threading.Tasks; -using TouchSocket.Core; namespace TouchSocket.Sockets; @@ -28,11 +25,12 @@ public interface IUdpRequestInfoSender /// /// 发送数据的目标端点。 /// 要发送的请求信息,包含具体的请求数据和元信息。 + /// 可取消令箭 /// 客户端没有连接 /// 发送数据超长 /// 其他异常 /// /// 此方法为异步非阻塞方式,调用后立即返回,不保证数据发送成功。 /// - Task SendAsync(EndPoint endPoint, IRequestInfo requestInfo); + Task SendAsync(EndPoint endPoint, IRequestInfo requestInfo, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/TouchSocket/Interfaces/ISender/IWaitSender.cs b/src/TouchSocket/Interfaces/ISender/IWaitSender.cs deleted file mode 100644 index f4fb4da9d..000000000 --- a/src/TouchSocket/Interfaces/ISender/IWaitSender.cs +++ /dev/null @@ -1,30 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -namespace TouchSocket.Sockets; - -///// -///// 发送等待接口 -///// -//public interface IWaitSender -//{ -// /// -// /// 异步发送 -// /// -// /// 要发送的内存数据 -// /// 取消令箭 -// /// 客户端没有连接 -// /// 发送数据超长 -// /// 其他异常 -// /// 返回的数据 -// Task SendThenReturnAsync(ReadOnlyMemory memory, CancellationToken token); -//} \ No newline at end of file diff --git a/src/TouchSocket/Interfaces/IServiceBase.cs b/src/TouchSocket/Interfaces/IServiceBase.cs index 274040186..d7972c5e2 100644 --- a/src/TouchSocket/Interfaces/IServiceBase.cs +++ b/src/TouchSocket/Interfaces/IServiceBase.cs @@ -10,11 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// @@ -41,7 +36,7 @@ public interface IServiceBase : ISetupConfigObject /// /// 异步停止服务器 /// - /// 用于取消操作的 。 + /// 用于取消操作的 。 /// 表示操作结果的 - Task StopAsync(CancellationToken token = default); + Task StopAsync(CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/TouchSocket/Interfaces/ITcpClient.cs b/src/TouchSocket/Interfaces/ITcpClient.cs index 11c0d1d8c..8cfeb2287 100644 --- a/src/TouchSocket/Interfaces/ITcpClient.cs +++ b/src/TouchSocket/Interfaces/ITcpClient.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Sockets; diff --git a/src/TouchSocket/Interfaces/ITcpServiceBase.cs b/src/TouchSocket/Interfaces/ITcpServiceBase.cs index 24d6aa3e6..6d18a76aa 100644 --- a/src/TouchSocket/Interfaces/ITcpServiceBase.cs +++ b/src/TouchSocket/Interfaces/ITcpServiceBase.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Collections.Generic; - namespace TouchSocket.Sockets; /// diff --git a/src/TouchSocket/Interfaces/ITcpSession.cs b/src/TouchSocket/Interfaces/ITcpSession.cs index 738ad84a9..b5f3758ca 100644 --- a/src/TouchSocket/Interfaces/ITcpSession.cs +++ b/src/TouchSocket/Interfaces/ITcpSession.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Net.Sockets; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// @@ -32,11 +28,6 @@ public interface ITcpSession : IDependencyClient, IResolverConfigObject, IOnline /// string IP { get; } - /// - /// 主通信器 - /// - Socket MainSocket { get; } - /// /// 端口号 /// @@ -46,11 +37,4 @@ public interface ITcpSession : IDependencyClient, IResolverConfigObject, IOnline /// 使用Ssl加密 /// bool UseSsl { get; } - - /// - /// 异步关闭TCP会话。此操作相比于,会等待缓存中的数据发送完成后再关闭会话。 - /// - /// 指定如何关闭套接字。 - /// 表示异步操作的任务。 - Task ShutdownAsync(SocketShutdown how); } diff --git a/src/TouchSocket/ObsoleteClass.cs b/src/TouchSocket/ObsoleteClass.cs index 1c93c2c9a..06f88ecec 100644 --- a/src/TouchSocket/ObsoleteClass.cs +++ b/src/TouchSocket/ObsoleteClass.cs @@ -10,81 +10,4 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; - namespace TouchSocket.Sockets; - -/// -[Obsolete($"此接口由于表述不清,已被弃用,请使用{nameof(ITcpSession)}代替。", true)] -public interface ITcpClientBase -{ -} - -/// -[Obsolete($"此接口由于表述不清,已被弃用,请使用{nameof(ITcpSessionClient)}代替。", true)] -public interface ISocketClient -{ -} - -/// -[Obsolete($"此接口由于表述不清,已被弃用,请使用{nameof(TcpSessionClient)}代替。", true)] -public class SocketClient -{ -} - -/// -/// 轻量级Tcp客户端 -/// -[Obsolete("此组件已被弃用,请使用TcpClient替代", true)] -public class TcpClientSlim -{ -} - -/// -/// ITcpDisconnectedPlugin -/// -[Obsolete($"此接口已被弃用,请使用{nameof(ITcpClosedPlugin)}代替", true)] -public interface ITcpDisconnectedPlugin -{ -} - -/// -/// ITcpDisconnectedPlugin -/// -[Obsolete($"此接口已被弃用,请使用{nameof(ITcpClosedPlugin)}代替", true)] -public interface ITcpDisconnectedPlugin : ITcpDisconnectedPlugin -{ -} - -/// -/// ITcpDisconnectingPlugin -/// -[Obsolete($"此接口已被弃用,请使用{nameof(ITcpClosingPlugin)}代替", true)] -public interface ITcpDisconnectingPlugin -{ -} - -/// -/// ITcpDisconnectingPlugin -/// -[Obsolete($"此接口已被弃用,请使用{nameof(ITcpClosingPlugin)}代替", true)] -public interface ITcpDisconnectingPlugin : ITcpDisconnectingPlugin -{ -} - -/// -/// DisconnectEventArgs -/// -[Obsolete($"此类型已被弃用,请使用{nameof(ClosingEventArgs)}或者{nameof(ClosedEventArgs)}代替", true)] -public class DisconnectEventArgs -{ -} - -/// -/// DisconnectEventArgs -/// -[Obsolete($"此接口因为拼写错误,请使用{nameof(IServerStoppedPlugin)}代替", true)] -public interface IServerStopedPlugin -{ - -} \ No newline at end of file diff --git a/src/TouchSocket/Options/CheckClearOption.cs b/src/TouchSocket/Options/CheckClearOption.cs new file mode 100644 index 000000000..ab10e22b8 --- /dev/null +++ b/src/TouchSocket/Options/CheckClearOption.cs @@ -0,0 +1,37 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +namespace TouchSocket.Sockets; + +/// +/// 检查清理连接插件配置选项 +/// +/// 客户端类型 +public class CheckClearOption + where TClient : class, IDependencyClient, IClosableClient +{ + /// + /// 清理统计类型。默认为:。当设置为时, + /// 则只检验发送方向是否有数据流动。没有的话则会断开连接。 + /// + public CheckClearType CheckClearType { get; set; } = CheckClearType.All; + + /// + /// 清理无数据交互的Client,默认60秒。 + /// + public TimeSpan Tick { get; set; } = TimeSpan.FromSeconds(60); + + /// + /// 当因为超出时间限定而关闭。 + /// + public Func OnClose { get; set; } +} diff --git a/src/TouchSocket/Common/Options/ClientSslOption.cs b/src/TouchSocket/Options/ClientSslOption.cs similarity index 99% rename from src/TouchSocket/Common/Options/ClientSslOption.cs rename to src/TouchSocket/Options/ClientSslOption.cs index 232131057..cc923d03b 100644 --- a/src/TouchSocket/Common/Options/ClientSslOption.cs +++ b/src/TouchSocket/Options/ClientSslOption.cs @@ -11,7 +11,6 @@ //------------------------------------------------------------------------------ using System.Security.Cryptography.X509Certificates; - namespace TouchSocket.Sockets; /// diff --git a/src/TouchSocket/Options/ReconnectionOption.cs b/src/TouchSocket/Options/ReconnectionOption.cs new file mode 100644 index 000000000..2cb9131ae --- /dev/null +++ b/src/TouchSocket/Options/ReconnectionOption.cs @@ -0,0 +1,258 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using TouchSocket.Resources; + +namespace TouchSocket.Sockets; + +/// +/// 重连插件配置选项 +/// +/// 客户端类型 +public class ReconnectionOption + where TClient : IConnectableClient, IOnlineClient, IDependencyClient +{ + /// + /// 检查客户端活性的委托。 + /// + /// + /// 注意,当返回值为时,请确保已经清理现有异常的在线状态(例如:tcp的断网假死),不然重连可能无法触发。 + /// + public Func> CheckAction { get; set; } + + /// + /// 尝试连接的委托 + /// + public Func ConnectAction { get; set; } + + /// + /// 轮询时间间隔 + /// + public TimeSpan PollingInterval { get; set; } = TimeSpan.FromSeconds(1); + + /// + /// 重连策略 + /// + public ReconnectionStrategy Strategy { get; set; } = ReconnectionStrategy.Simple; + + /// + /// 最大重连次数,-1表示无限重连 + /// + public int MaxRetryCount { get; set; } = -1; + + /// + /// 基础重连间隔 + /// + public TimeSpan BaseInterval { get; set; } = TimeSpan.FromSeconds(1); + + /// + /// 最大重连间隔 + /// + public TimeSpan MaxInterval { get; set; } = TimeSpan.FromMinutes(5); + + /// + /// 退避倍数(用于指数和线性退避) + /// + public double BackoffMultiplier { get; set; } = 2.0; + + /// + /// 是否记录重连日志 + /// + public bool LogReconnection { get; set; } = true; + + /// + /// 重连成功回调 + /// 当重连操作成功完成时触发此回调 + /// 触发时机: + /// 1. 检测到客户端已经在线时 + /// 2. 执行ConnectAsync成功后 + /// 第1参数:重连成功的客户端实例 + /// + public Action OnSuccessed { get; set; } + + /// + /// 重连失败回调 + /// 当单次重连尝试失败时触发此回调,每次重连失败都会调用 + /// 触发时机:在ConnectAsync抛出异常时立即触发 + /// 第1参数:重连失败的客户端实例 + /// 第2参数:当前重连尝试次数(从1开始计数) + /// 第3参数:导致重连失败的异常信息 + /// + public Action OnFailed { get; set; } + + /// + /// 重连放弃回调 + /// 当达到最大重连次数限制,决定放弃继续重连时触发此回调 + /// 触发时机:当MaxRetryCount大于0且重连尝试次数达到该限制时 + /// 注意:如果MaxRetryCount设置为-1(无限重连),此回调永远不会被触发 + /// 第1参数:放弃重连的客户端实例 + /// 第2参数:总共尝试的重连次数 + /// + public Action OnGiveUp { get; set; } + + /// + /// 重连插件配置选项 + /// + public ReconnectionOption() + { + this.CheckAction = (client) => + { + var result = client.Online ? ConnectionCheckResult.Alive : ConnectionCheckResult.Dead; + return Task.FromResult(result); + }; + + this.ConnectAction = async (client, cancellationToken) => + { + var attempts = 0; + var currentInterval = this.BaseInterval; + + while (this.MaxRetryCount < 0 || attempts < this.MaxRetryCount) + { + if (client.PauseReconnection) + { + if (this.LogReconnection) + { + client.Logger?.Debug(this, TouchSocketResource.PauseReconnection); + } + continue; + } + + attempts++; + + try + { + if (client.Online) + { + this.OnSuccessed?.Invoke(client); + return; + } + + await client.ConnectAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + this.OnSuccessed?.Invoke(client); + + if (this.LogReconnection) + { + client.Logger?.Info(this, $"重连成功,尝试次数: {attempts}"); + } + return; + } + catch (Exception ex) + { + this.OnFailed?.Invoke(client, attempts, ex); + + if (this.LogReconnection) + { + client.Logger?.Warning(this, $"重连失败,尝试次数: {attempts},错误: {ex.Message}"); + } + + if (this.MaxRetryCount > 0 && attempts >= this.MaxRetryCount) + { + this.OnGiveUp?.Invoke(client, attempts); + if (this.LogReconnection) + { + client.Logger?.Error(this, $"达到最大重连次数 {this.MaxRetryCount},放弃重连"); + } + return; + } + + // 计算下次重连间隔 + currentInterval = this.CalculateNextInterval(attempts, currentInterval); + + await Task.Delay(currentInterval, CancellationToken.None).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + } + }; + } + + /// + /// 使用简单重连策略 - 固定间隔重连 + /// + /// 重连间隔,默认1秒 + /// 最大重连次数,-1表示无限重连 + public void UseSimple(TimeSpan? interval = null, int maxRetryCount = -1) + { + this.Strategy = ReconnectionStrategy.Simple; + this.BaseInterval = interval ?? TimeSpan.FromSeconds(1); + this.MaxRetryCount = maxRetryCount; + } + + /// + /// 使用指数退避重连策略 - 每次失败后延迟时间指数增长 + /// + /// 基础间隔,默认1秒 + /// 最大间隔,默认5分钟 + /// 退避倍数,默认2.0 + /// 最大重连次数,-1表示无限重连 + public void UseExponentialBackoff( + TimeSpan? baseInterval = null, + TimeSpan? maxInterval = null, + double multiplier = 2.0, + int maxRetryCount = -1) + { + this.Strategy = ReconnectionStrategy.ExponentialBackoff; + this.BaseInterval = baseInterval ?? TimeSpan.FromSeconds(1); + this.MaxInterval = maxInterval ?? TimeSpan.FromMinutes(5); + this.BackoffMultiplier = multiplier; + this.MaxRetryCount = maxRetryCount; + } + + /// + /// 使用线性退避重连策略 - 每次失败后延迟时间线性增长 + /// + /// 基础间隔,默认1秒 + /// 最大间隔,默认5分钟 + /// 每次增加的时间,默认1秒 + /// 最大重连次数,-1表示无限重连 + public void UseLinearBackoff( + TimeSpan? baseInterval = null, + TimeSpan? maxInterval = null, + TimeSpan? increment = null, + int maxRetryCount = -1) + { + this.Strategy = ReconnectionStrategy.LinearBackoff; + this.BaseInterval = baseInterval ?? TimeSpan.FromSeconds(1); + this.MaxInterval = maxInterval ?? TimeSpan.FromMinutes(5); + this.BackoffMultiplier = (increment ?? TimeSpan.FromSeconds(1)).TotalMilliseconds; + this.MaxRetryCount = maxRetryCount; + } + + /// + /// 使用自定义连接策略 + /// + /// 自定义连接动作 + public void UseCustom(Func connectAction) + { + this.Strategy = ReconnectionStrategy.Custom; + this.ConnectAction = connectAction; + } + + /// + /// 计算下次重连间隔 + /// + /// 当前尝试次数 + /// 当前间隔 + /// 下次重连间隔 + private TimeSpan CalculateNextInterval(int attemptCount, TimeSpan currentInterval) + { + return this.Strategy switch + { + ReconnectionStrategy.Simple => this.BaseInterval, + ReconnectionStrategy.ExponentialBackoff => TimeSpan.FromMilliseconds(Math.Min( + this.BaseInterval.TotalMilliseconds * Math.Pow(this.BackoffMultiplier, attemptCount - 1), + this.MaxInterval.TotalMilliseconds)), + ReconnectionStrategy.LinearBackoff => TimeSpan.FromMilliseconds(Math.Min( + this.BaseInterval.TotalMilliseconds + (attemptCount - 1) * this.BackoffMultiplier, + this.MaxInterval.TotalMilliseconds)), + _ => this.BaseInterval + }; + } +} \ No newline at end of file diff --git a/src/TouchSocket/Common/Options/ServiceSslOption.cs b/src/TouchSocket/Options/ServiceSslOption.cs similarity index 100% rename from src/TouchSocket/Common/Options/ServiceSslOption.cs rename to src/TouchSocket/Options/ServiceSslOption.cs diff --git a/src/TouchSocket/Common/Options/SslOption.cs b/src/TouchSocket/Options/SslOption.cs similarity index 96% rename from src/TouchSocket/Common/Options/SslOption.cs rename to src/TouchSocket/Options/SslOption.cs index 03bf2f5a2..417c47597 100644 --- a/src/TouchSocket/Common/Options/SslOption.cs +++ b/src/TouchSocket/Options/SslOption.cs @@ -57,5 +57,5 @@ public abstract class SslOption /// SSL验证回调。 /// 用于自定义证书验证逻辑 /// - public RemoteCertificateValidationCallback CertificateValidationCallback { get; set; } + public RemoteCertificateValidationCallback CertificateValidationCallback { get; set; } = (sender, certificate, chain, sslPolicyErrors) => true; } \ No newline at end of file diff --git a/src/TouchSocket/Common/Options/TcpListenOption.cs b/src/TouchSocket/Options/TcpListenOption.cs similarity index 90% rename from src/TouchSocket/Common/Options/TcpListenOption.cs rename to src/TouchSocket/Options/TcpListenOption.cs index 756916387..a6dccfc9a 100644 --- a/src/TouchSocket/Common/Options/TcpListenOption.cs +++ b/src/TouchSocket/Options/TcpListenOption.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// @@ -30,11 +27,6 @@ public class TcpListenOption /// public IPHost IpHost { get; set; } - /// - /// 发送超时时间 - /// - public int SendTimeout { get; set; } - /// /// 是否使用地址复用 /// @@ -48,7 +40,7 @@ public class TcpListenOption /// /// 禁用延迟发送 /// - public bool? NoDelay { get; set; } + public bool NoDelay { get; set; } = true; /// /// 是否使用ssl加密 diff --git a/src/TouchSocket/Options/TransportOption.cs b/src/TouchSocket/Options/TransportOption.cs new file mode 100644 index 000000000..b9dc08fa7 --- /dev/null +++ b/src/TouchSocket/Options/TransportOption.cs @@ -0,0 +1,82 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.IO.Pipelines; + +namespace TouchSocket.Sockets; + +/// +/// 表示传输相关的配置选项。 +/// +public class TransportOption +{ + /// + /// 初始化 类的新实例。 + /// + public TransportOption() + { + this.ReceivePipeOptions = CreateDefaultPipeOptions(); + this.SendPipeOptions = CreateDefaultPipeOptions(); + } + + /// + /// 获取或设置最大缓冲区大小(字节)。 + /// + public int MaxBufferSize { get; set; } = 1024 * 1024 * 10; + + /// + /// 获取或设置最小缓冲区大小(字节)。 + /// + public int MinBufferSize { get; set; } = 1024; + + /// + /// 获取或设置接收管道的选项。 + /// + public PipeOptions ReceivePipeOptions { get; set; } + + /// + /// 获取或设置发送管道的选项。 + /// + public PipeOptions SendPipeOptions { get; set; } + + public bool BufferOnDemand { get; set; } = true; + + /// + /// 创建默认的 。 + /// + public static PipeOptions CreateDefaultPipeOptions() + { + return new PipeOptions( + pool: null, + readerScheduler: null, + writerScheduler: null, + pauseWriterThreshold: -1, + resumeWriterThreshold: -1, + minimumSegmentSize: -1, + useSynchronizationContext: true); + } + + /// + /// 创建注重调度的 ,适合需要主线程调度的场景。 + /// + public static PipeOptions CreateSchedulerOptimizedPipeOptions() + { + return new PipeOptions( + pool: null, + readerScheduler: PipeScheduler.Inline, + writerScheduler: PipeScheduler.Inline, + pauseWriterThreshold: -1, + resumeWriterThreshold: -1, + minimumSegmentSize: -1, + useSynchronizationContext: false); + } +} \ No newline at end of file diff --git a/src/TouchSocket/Plugins/CheckClearPlugin.cs b/src/TouchSocket/Plugins/CheckClearPlugin.cs index f93463a8e..c8be8d3df 100644 --- a/src/TouchSocket/Plugins/CheckClearPlugin.cs +++ b/src/TouchSocket/Plugins/CheckClearPlugin.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Resources; namespace TouchSocket.Sockets; @@ -21,52 +18,67 @@ namespace TouchSocket.Sockets; /// 检查清理连接插件。服务器与客户端均适用。 /// [PluginOption(Singleton = true)] -public sealed class CheckClearPlugin : PluginBase, ILoadedConfigPlugin where TClient : class, IDependencyClient, IClosableClient, IOnlineClient +public sealed class CheckClearPlugin : PluginBase, ILoadedConfigPlugin + where TClient : class, IDependencyClient, IClosableClient { private static readonly DependencyProperty s_checkClearProperty = new("CheckClear", false); private readonly ILog m_logger; + private readonly CheckClearType m_checkClearType; + private readonly TimeSpan m_tick; + private readonly Func m_onClose; /// /// 检查清理连接插件。服务器与客户端均适用。 /// - public CheckClearPlugin(ILog logger) + /// 日志记录器 + /// 配置选项 + public CheckClearPlugin(ILog logger, CheckClearOption options) { - this.OnClose = async (client, type) => - { - switch (this.CheckClearType) - { - case CheckClearType.OnlyReceive: - await client.CloseAsync(TouchSocketResource.TimedoutWithoutReceiving).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - break; - case CheckClearType.OnlySend: - await client.CloseAsync(TouchSocketResource.TimedoutWithoutSending).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - break; - case CheckClearType.All: - default: - await client.CloseAsync(TouchSocketResource.TimedoutWithoutAll).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - break; - } - }; + ThrowHelper.ThrowIfNull(logger, nameof(logger)); + ThrowHelper.ThrowIfNull(options, nameof(options)); + this.m_logger = logger; + this.m_checkClearType = options.CheckClearType; + this.m_tick = options.Tick; + + if (options.OnClose != null) + { + this.m_onClose = options.OnClose; + } + else + { + this.m_onClose = this.DefaultOnClose; + } + } + + private async Task DefaultOnClose(TClient client, CheckClearType type) + { + switch (type) + { + case CheckClearType.OnlyReceive: + await client.CloseAsync(TouchSocketResource.TimedoutWithoutReceiving).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + break; + case CheckClearType.OnlySend: + await client.CloseAsync(TouchSocketResource.TimedoutWithoutSending).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + break; + case CheckClearType.All: + default: + await client.CloseAsync(TouchSocketResource.TimedoutWithoutAll).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + break; + } } /// - /// 清理统计类型。默认为:。当设置为时, - /// 则只检验发送方向是否有数据流动。没有的话则会断开连接。 + /// 清理统计类型。 /// - public CheckClearType CheckClearType { get; set; } = CheckClearType.All; + public CheckClearType CheckClearType => this.m_checkClearType; /// - /// 当因为超出时间限定而关闭。 + /// 清理无数据交互的Client。 /// - public Func OnClose { get; set; } - - /// - /// 获取或设置清理无数据交互的Client,默认60秒。 - /// - public TimeSpan Tick { get; set; } = TimeSpan.FromSeconds(60); + public TimeSpan Tick => this.m_tick; /// public async Task OnLoadedConfig(IConfigObject sender, ConfigEventArgs e) @@ -75,58 +87,6 @@ public sealed class CheckClearPlugin : PluginBase, ILoadedConfigPlugin await e.InvokeNext().ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - /// - /// 设置清理统计类型。此方法允许指定在何种情况下应清理统计信息。 - /// 默认情况下,清理类型设置为,表示所有情况都进行清理。 - /// 如果设置为,则仅检验发送方向是否有数据流动, - /// 若没有数据流动,则断开连接。 - /// - /// 要设置的清理统计类型。 - /// 返回当前实例,以支持链式调用。 - public CheckClearPlugin SetCheckClearType(CheckClearType clearType) - { - this.CheckClearType = clearType; - return this; - } - - /// - /// 设置在超出时间限定而关闭时的回调操作。 - /// - /// 一个Action委托,包含客户端对象和检查清除类型作为参数,在关闭操作执行时会被调用。 - /// 返回当前的CheckClearPlugin实例,以支持链式调用。 - public CheckClearPlugin SetOnClose(Action action) - { - Task Func(TClient client, CheckClearType checkClearType) - { - action.Invoke(client, checkClearType); - return EasyTask.CompletedTask; - } - this.SetOnClose(Func); - return this; - } - - /// - /// 设置在超出时间限定而关闭时的回调操作。 - /// - /// 一个Func委托,包含客户端对象和检查清除类型作为参数,在关闭操作执行时会被调用。 - /// 返回当前的CheckClearPlugin实例,以支持链式调用。 - public CheckClearPlugin SetOnClose(Func func) - { - this.OnClose = func; - return this; - } - - /// - /// 设置清理无数据交互的Client,默认60秒。 - /// - /// 清理无数据交互的Client的时间间隔 - /// 返回配置后的实例,支持链式调用 - public CheckClearPlugin SetTick(TimeSpan timeSpan) - { - this.Tick = timeSpan; - return this; - } - private void CheckWithSessionClient(TClient client) { if (client is null) @@ -147,12 +107,12 @@ public sealed class CheckClearPlugin : PluginBase, ILoadedConfigPlugin { if (first) { - await Task.Delay(this.Tick).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await Task.Delay(this.m_tick).ConfigureAwait(EasyTask.ContinueOnCapturedContext); first = false; } else { - await Task.Delay(TimeSpan.FromMilliseconds(this.Tick.TotalMilliseconds / 10.0)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await Task.Delay(TimeSpan.FromMilliseconds(this.m_tick.TotalMilliseconds / 10.0)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } if (client is IOnlineClient onlineClient && !onlineClient.Online) @@ -160,27 +120,27 @@ public sealed class CheckClearPlugin : PluginBase, ILoadedConfigPlugin return; } - if (this.CheckClearType == CheckClearType.OnlyReceive) + if (this.m_checkClearType == CheckClearType.OnlyReceive) { - if (DateTimeOffset.UtcNow - client.LastReceivedTime > this.Tick) + if (DateTimeOffset.UtcNow - client.LastReceivedTime > this.m_tick) { - await this.CloseClientAsync(client, this.CheckClearType).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.CloseClientAsync(client, this.m_checkClearType).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return; } } - else if (this.CheckClearType == CheckClearType.OnlySend) + else if (this.m_checkClearType == CheckClearType.OnlySend) { - if (DateTimeOffset.UtcNow - client.LastSentTime > this.Tick) + if (DateTimeOffset.UtcNow - client.LastSentTime > this.m_tick) { - await this.CloseClientAsync(client, this.CheckClearType).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.CloseClientAsync(client, this.m_checkClearType).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return; } } else { - if (DateTimeOffset.UtcNow - client.GetLastActiveTime() > this.Tick) + if (DateTimeOffset.UtcNow - client.GetLastActiveTime() > this.m_tick) { - await this.CloseClientAsync(client, this.CheckClearType).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.CloseClientAsync(client, this.m_checkClearType).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return; } } @@ -190,32 +150,32 @@ public sealed class CheckClearPlugin : PluginBase, ILoadedConfigPlugin private async Task CheckWithClient(TClient client) { - if (!client.Online) + if (client is IOnlineClient onlineClient && !onlineClient.Online) { return; } - if (this.CheckClearType == CheckClearType.OnlyReceive) + if (this.m_checkClearType == CheckClearType.OnlyReceive) { - if (DateTimeOffset.UtcNow - client.LastReceivedTime > this.Tick) + if (DateTimeOffset.UtcNow - client.LastReceivedTime > this.m_tick) { - await this.CloseClientAsync(client, this.CheckClearType).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.CloseClientAsync(client, this.m_checkClearType).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return; } } - else if (this.CheckClearType == CheckClearType.OnlySend) + else if (this.m_checkClearType == CheckClearType.OnlySend) { - if (DateTimeOffset.UtcNow - client.LastSentTime > this.Tick) + if (DateTimeOffset.UtcNow - client.LastSentTime > this.m_tick) { - await this.CloseClientAsync(client, this.CheckClearType).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.CloseClientAsync(client, this.m_checkClearType).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return; } } else { - if (DateTimeOffset.UtcNow - client.GetLastActiveTime() > this.Tick) + if (DateTimeOffset.UtcNow - client.GetLastActiveTime() > this.m_tick) { - await this.CloseClientAsync(client, this.CheckClearType).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.CloseClientAsync(client, this.m_checkClearType).ConfigureAwait(EasyTask.ContinueOnCapturedContext); return; } } @@ -223,11 +183,11 @@ public sealed class CheckClearPlugin : PluginBase, ILoadedConfigPlugin private async Task CloseClientAsync(TClient client, CheckClearType checkClearType) { - if (this.OnClose != null) + if (this.m_onClose != null) { try { - await this.OnClose.Invoke(client, checkClearType).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.m_onClose.Invoke(client, checkClearType).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } catch (Exception ex) { @@ -252,7 +212,7 @@ public sealed class CheckClearPlugin : PluginBase, ILoadedConfigPlugin if (sender is IConnectableService connectableService) { - await Task.Delay(TimeSpan.FromMilliseconds(this.Tick.TotalMilliseconds / 10.0)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await Task.Delay(TimeSpan.FromMilliseconds(this.m_tick.TotalMilliseconds / 10.0)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); foreach (var client in connectableService.GetClients()) { this.CheckWithSessionClient(client as TClient); @@ -260,7 +220,7 @@ public sealed class CheckClearPlugin : PluginBase, ILoadedConfigPlugin } else if (sender is TClient client) { - await Task.Delay(this.Tick).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await Task.Delay(this.m_tick).ConfigureAwait(EasyTask.ContinueOnCapturedContext); await this.CheckWithClient(client).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } else diff --git a/src/TouchSocket/Plugins/Interfaces/IIdChangedPlugin.cs b/src/TouchSocket/Plugins/Interfaces/IIdChangedPlugin.cs index 594defe1b..5916e68c0 100644 --- a/src/TouchSocket/Plugins/Interfaces/IIdChangedPlugin.cs +++ b/src/TouchSocket/Plugins/Interfaces/IIdChangedPlugin.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Sockets; diff --git a/src/TouchSocket/Plugins/Interfaces/IServerStartedPlugin.cs b/src/TouchSocket/Plugins/Interfaces/IServerStartedPlugin.cs index a9ca76d98..3722af02b 100644 --- a/src/TouchSocket/Plugins/Interfaces/IServerStartedPlugin.cs +++ b/src/TouchSocket/Plugins/Interfaces/IServerStartedPlugin.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Sockets; diff --git a/src/TouchSocket/Plugins/Interfaces/IServerStoppedPlugin.cs b/src/TouchSocket/Plugins/Interfaces/IServerStoppedPlugin.cs index a445aaee5..d9251a2e4 100644 --- a/src/TouchSocket/Plugins/Interfaces/IServerStoppedPlugin.cs +++ b/src/TouchSocket/Plugins/Interfaces/IServerStoppedPlugin.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Sockets; diff --git a/src/TouchSocket/Plugins/Interfaces/ITcpClosedPlugin.cs b/src/TouchSocket/Plugins/Interfaces/ITcpClosedPlugin.cs index d579dfd27..3d1d644cb 100644 --- a/src/TouchSocket/Plugins/Interfaces/ITcpClosedPlugin.cs +++ b/src/TouchSocket/Plugins/Interfaces/ITcpClosedPlugin.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Sockets; diff --git a/src/TouchSocket/Plugins/Interfaces/ITcpClosingPlugin.cs b/src/TouchSocket/Plugins/Interfaces/ITcpClosingPlugin.cs index 8b4175734..dea62063c 100644 --- a/src/TouchSocket/Plugins/Interfaces/ITcpClosingPlugin.cs +++ b/src/TouchSocket/Plugins/Interfaces/ITcpClosingPlugin.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// diff --git a/src/TouchSocket/Plugins/Interfaces/ITcpConnectedPlugin.cs b/src/TouchSocket/Plugins/Interfaces/ITcpConnectedPlugin.cs index 442b1ae67..60e92f437 100644 --- a/src/TouchSocket/Plugins/Interfaces/ITcpConnectedPlugin.cs +++ b/src/TouchSocket/Plugins/Interfaces/ITcpConnectedPlugin.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// diff --git a/src/TouchSocket/Plugins/Interfaces/ITcpConnectingPlugin.cs b/src/TouchSocket/Plugins/Interfaces/ITcpConnectingPlugin.cs index dbf04bad1..512936598 100644 --- a/src/TouchSocket/Plugins/Interfaces/ITcpConnectingPlugin.cs +++ b/src/TouchSocket/Plugins/Interfaces/ITcpConnectingPlugin.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Sockets; diff --git a/src/TouchSocket/Plugins/Interfaces/ITcpReceivedPlugin.cs b/src/TouchSocket/Plugins/Interfaces/ITcpReceivedPlugin.cs index e63b6e986..6fad3502b 100644 --- a/src/TouchSocket/Plugins/Interfaces/ITcpReceivedPlugin.cs +++ b/src/TouchSocket/Plugins/Interfaces/ITcpReceivedPlugin.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// diff --git a/src/TouchSocket/Plugins/Interfaces/ITcpReceivingPlugin.cs b/src/TouchSocket/Plugins/Interfaces/ITcpReceivingPlugin.cs index 6835f8d49..3ace2c78b 100644 --- a/src/TouchSocket/Plugins/Interfaces/ITcpReceivingPlugin.cs +++ b/src/TouchSocket/Plugins/Interfaces/ITcpReceivingPlugin.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// @@ -29,5 +26,5 @@ public interface ITcpReceivingPlugin : IPlugin /// 发送数据的客户端会话对象。 /// 包含接收数据事件相关的参数。 /// 一个Task对象,代表异步操作的结果。 - Task OnTcpReceiving(ITcpSession client, ByteBlockEventArgs e); + Task OnTcpReceiving(ITcpSession client, BytesReaderEventArgs e); } \ No newline at end of file diff --git a/src/TouchSocket/Plugins/Interfaces/ITcpSendingPlugin.cs b/src/TouchSocket/Plugins/Interfaces/ITcpSendingPlugin.cs index b602bd2fc..d01b3d6b3 100644 --- a/src/TouchSocket/Plugins/Interfaces/ITcpSendingPlugin.cs +++ b/src/TouchSocket/Plugins/Interfaces/ITcpSendingPlugin.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// diff --git a/src/TouchSocket/Plugins/Interfaces/IUdpReceivedPlugin.cs b/src/TouchSocket/Plugins/Interfaces/IUdpReceivedPlugin.cs index bd33b7d2a..60fd3974b 100644 --- a/src/TouchSocket/Plugins/Interfaces/IUdpReceivedPlugin.cs +++ b/src/TouchSocket/Plugins/Interfaces/IUdpReceivedPlugin.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// diff --git a/src/TouchSocket/Plugins/Interfaces/IUdpReceivingPlugin.cs b/src/TouchSocket/Plugins/Interfaces/IUdpReceivingPlugin.cs index 2a4328cc0..3513f8038 100644 --- a/src/TouchSocket/Plugins/Interfaces/IUdpReceivingPlugin.cs +++ b/src/TouchSocket/Plugins/Interfaces/IUdpReceivingPlugin.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// diff --git a/src/TouchSocket/Plugins/Interfaces/IUdpSendingPlugin.cs b/src/TouchSocket/Plugins/Interfaces/IUdpSendingPlugin.cs index fcb53d8e5..9b23019eb 100644 --- a/src/TouchSocket/Plugins/Interfaces/IUdpSendingPlugin.cs +++ b/src/TouchSocket/Plugins/Interfaces/IUdpSendingPlugin.cs @@ -10,9 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// diff --git a/src/TouchSocket/Plugins/ReconnectionPlugin.cs b/src/TouchSocket/Plugins/ReconnectionPlugin.cs index af5a83dcf..8bde776b9 100644 --- a/src/TouchSocket/Plugins/ReconnectionPlugin.cs +++ b/src/TouchSocket/Plugins/ReconnectionPlugin.cs @@ -1,19 +1,15 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ -using System; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; using TouchSocket.Resources; namespace TouchSocket.Sockets; @@ -21,280 +17,126 @@ namespace TouchSocket.Sockets; /// /// 重连插件 /// -public abstract class ReconnectionPlugin : PluginBase, ILoadedConfigPlugin where TClient : IDisposableObject, IConnectableClient, IOnlineClient, ILoggerObject +/// 客户端类型 +public class ReconnectionPlugin : PluginBase, ILoadedConfigPlugin + where TClient : IConnectableClient, IOnlineClient, IDependencyClient, IClosableClient { - private CancellationTokenSource m_cancellationBeginReconnectTaskTokenSource; - private bool m_polling; - private TimeSpan m_tick = TimeSpan.FromSeconds(1); + private readonly CancellationTokenSource m_cts = new CancellationTokenSource(); + private readonly ReconnectionOption m_options; /// /// 重连插件 /// - protected ReconnectionPlugin() + /// 重连配置选项 + public ReconnectionPlugin(ReconnectionOption options) { - this.ActionForConnect = async (c) => - { - try - { - await c.ConnectAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - return true; - } - catch - { - return false; - } - }; - this.m_cancellationBeginReconnectTaskTokenSource = new CancellationTokenSource(); + ThrowHelper.ThrowIfNull(options, nameof(options)); + this.m_options = options; } /// - /// 每个周期可执行的委托。用于检验客户端活性。返回表示存活,返回 + /// 获取用于取消操作的 /// - public abstract Func> ActionForCheck { get; set; } + public CancellationToken CancellationToken => this.m_cts.Token; /// - /// ActionForConnect + /// 轮询时间间隔 /// - public Func> ActionForConnect { get; set; } + public TimeSpan PollingInterval => this.m_options.PollingInterval; /// - /// 检验时间间隔 + /// 重连选项 /// - public TimeSpan Tick => this.m_tick; + public ReconnectionOption Options => this.m_options; /// public async Task OnLoadedConfig(IConfigObject sender, ConfigEventArgs e) { - _ = EasyTask.Run(this.BeginReconnect, sender, this.m_cancellationBeginReconnectTaskTokenSource.Token); + if (sender is TClient client) + { + _ = EasyTask.SafeRun(this.StartReconnectionLoop, client); + } await e.InvokeNext().ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - /// - /// 设置一个周期性执行的委托,用于检查客户端状态。 - /// - /// 一个委托,接受客户端实例和周期次数作为参数,返回一个任务,该任务结果为布尔值。 - /// 返回当前ReconnectionPlugin实例,以便链式调用。 - public ReconnectionPlugin SetActionForCheck(Func> actionForCheck) - { - this.ActionForCheck = actionForCheck; - return this; - } - - /// - /// 设置每个周期执行的委托。用于判断客户端是否存活。如果返回True,表示客户端存活。返回False,表示客户端失活,需要立即重连。返回null,则表示跳过此次检查。 - /// - /// 一个委托,接受一个客户端实例和一个整型参数,返回一个可空的布尔值。 - /// 返回当前ReconnectionPlugin实例,支持链式调用。 - public ReconnectionPlugin SetActionForCheck(Func actionForCheck) - { - // 将传入的委托包装成异步方法,以适应可能的异步检查逻辑。 - this.ActionForCheck = async (c, i) => - { - // 不等待任务完成,直接返回委托的执行结果。 - await EasyTask.CompletedTask.ConfigureAwait(EasyTask.ContinueOnCapturedContext); - return actionForCheck.Invoke(c, i); - }; - return this; - } - - /// - /// 设置连接动作 - /// - /// 一个异步方法,尝试建立连接,并返回一个布尔值指示连接是否成功 - /// 返回当前实例,以便支持链式调用 - public ReconnectionPlugin SetConnectAction(Func> tryConnect) - { - // 将提供的尝试连接方法赋值给内部变量,以供后续连接使用 - this.ActionForConnect = tryConnect; - // 返回当前实例,支持链式调用 - return this; - } - - /// - /// 设置连接动作 - /// - /// 失败时间隔时间 - /// 失败时回调(参数依次为:客户端,本轮尝试重连次数,异常信息)。如果回调为null或者返回,则终止尝试下次连接。 - /// 成功连接时回调 - /// - public ReconnectionPlugin SetConnectAction(TimeSpan sleepTime, - Func failCallback = default, - Action successCallback = default) - { - this.SetConnectAction(async client => - { - var tryT = 0; - while (true) - { - try - { - if (client.Online) - { - return true; - } - else - { - await Task.Delay(1000).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - await client.ConnectAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - - successCallback?.Invoke(client); - return true; - } - catch (Exception ex) - { - await Task.Delay(sleepTime).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - if (failCallback?.Invoke(client, ++tryT, ex) != true) - { - return true; - } - } - } - }); - return this; - } - - /// - /// 设置连接动作 - /// - /// 尝试重连次数,设为-1时则永远尝试连接 - /// 是否输出日志。 - /// 失败时,停留时间 - /// 成功回调函数 - /// - public ReconnectionPlugin SetConnectAction(int tryCount = 10, bool printLog = false, int sleepTime = 1000, Action successCallback = null) - { - this.SetConnectAction(async client => - { - var tryT = tryCount; - while (tryCount < 0 || tryT-- > 0) - { - try - { - if (client.Online) - { - return true; - } - else - { - await Task.Delay(1000).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - await client.ConnectAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - successCallback?.Invoke(client); - return true; - } - catch (Exception ex) - { - if (printLog) - { - client.Logger?.Exception(this, ex); - } - await Task.Delay(sleepTime).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - } - return true; - }); - return this; - } - - /// - /// 设置连接动作 - /// - /// 一个函数,用于尝试连接操作 参数为客户端实例,返回布尔值表示连接是否成功 - /// 返回当前实例,以便链式调用 - public ReconnectionPlugin SetConnectAction(Func tryConnect) - { - // 将传入的连接动作设置为内部使用的异步操作 - // 这样做是为了统一连接动作的处理方式,便于后续的异步重连逻辑 - this.ActionForConnect = (c) => Task.FromResult(tryConnect.Invoke(c)); - // 返回当前实例,支持链式调用 - return this; - } - - /// - /// 使用轮询保持活性。 - /// - /// 轮询的时间间隔。 - /// 返回当前的ReconnectionPlugin实例,用于链式调用。 - public ReconnectionPlugin UsePolling(TimeSpan tick) - { - // 设置轮询时间间隔 - this.m_tick = tick; - // 标记为使用轮询方式保持活性 - this.m_polling = true; - // 返回当前实例,支持链式调用 - return this; - } - /// protected override void Dispose(bool disposing) { if (disposing) { - this.m_cancellationBeginReconnectTaskTokenSource.Cancel(); + this.m_cts.SafeCancel(); + this.m_cts.SafeDispose(); } base.Dispose(disposing); } - private async Task BeginReconnect(IConfigObject sender, CancellationToken token) + private async Task StartReconnectionLoop(TClient client) { - if (!this.m_polling) + // 初始延时,避免过快重连 + await Task.Delay(1000, CancellationToken.None); + + if (this.m_options.LogReconnection) { - return; + client.Logger?.Debug(this, TouchSocketResource.PollingBegin.Format(this.PollingInterval)); } - if (sender is not TClient client) - { - return; - } - - client.Logger?.Debug(this, TouchSocketResource.PollingBegin.Format(this.Tick)); - - var failCount = 0; - try { - while (true) + while (!this.DisposedValue && !this.m_cts.Token.IsCancellationRequested) { - if (this.DisposedValue || token.IsCancellationRequested) + await Task.Delay(this.PollingInterval, this.m_cts.Token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + if (client.PauseReconnection) { - client.Logger?.Debug(this, TouchSocketResource.PollingWillEnd); - return; + if (this.m_options.LogReconnection) + { + client.Logger?.Debug(this, TouchSocketResource.PauseReconnection); + } + continue; } - await Task.Delay(this.Tick, CancellationToken.None).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + try { - var b = await this.ActionForCheck.Invoke(client, failCount).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - if (b == null) + var checkResult = await this.m_options.CheckAction.Invoke(client).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + switch (checkResult) { - continue; - } - else if (b == false) - { - if (await this.ActionForConnect.Invoke(client).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) - { - failCount = 0; - } - } - else - { - failCount = 0; + case ConnectionCheckResult.Skip: + continue; + case ConnectionCheckResult.Dead: + await this.m_options.ConnectAction.Invoke(client, this.m_cts.Token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + break; + case ConnectionCheckResult.Alive: + break; } } catch (Exception ex) { - client.Logger?.Exception(this, ex); + if (this.m_options.LogReconnection) + { + client.Logger?.Exception(this, ex); + } } } } + catch (OperationCanceledException) + { + // 正常取消,不记录错误 + } catch (Exception ex) { - client.Logger?.Exception(this, ex); + if (this.m_options.LogReconnection) + { + client.Logger?.Exception(this, ex); + } } finally { - client.Logger?.Debug(this, TouchSocketResource.PollingEnd); + if (this.m_options.LogReconnection) + { + client.Logger?.Debug(this, TouchSocketResource.PollingEnd); + } } } } \ No newline at end of file diff --git a/src/TouchSocket/Plugins/TcpCommandLinePlugin.cs b/src/TouchSocket/Plugins/TcpCommandLinePlugin.cs index 8310173b2..62c9eb516 100644 --- a/src/TouchSocket/Plugins/TcpCommandLinePlugin.cs +++ b/src/TouchSocket/Plugins/TcpCommandLinePlugin.cs @@ -10,12 +10,8 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Diagnostics.CodeAnalysis; using System.Reflection; -using System.Threading.Tasks; -using TouchSocket.Core; namespace TouchSocket.Sockets; @@ -23,6 +19,7 @@ namespace TouchSocket.Sockets; /// Tcp命令行插件。 /// [DynamicMethod] +[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] public abstract class TcpCommandLinePlugin : PluginBase, ITcpReceivedPlugin { private readonly ILog m_logger; @@ -69,9 +66,7 @@ public abstract class TcpCommandLinePlugin : PluginBase, ITcpReceivedPlugin /// 返回当前的TcpCommandLinePlugin实例,以支持链式调用。 public TcpCommandLinePlugin NoReturnException() { - // 设置是否在执行异常时返回异常的标志为false this.ReturnException = false; - // 返回当前实例,以支持链式调用 return this; } @@ -84,7 +79,7 @@ public abstract class TcpCommandLinePlugin : PluginBase, ITcpReceivedPlugin } try { - var strs = e.ByteBlock.ToString().Split(' '); + var strs = e.Memory.Span.ToUtf8String().Split(' '); if (strs.Length > 0 && this.m_pairs.TryGetValue(strs[0], out var method)) { var ps = method.Info.GetParameters(); diff --git a/src/TouchSocket/Plugins/TcpReconnectionPlugin.cs b/src/TouchSocket/Plugins/TcpReconnectionPlugin.cs deleted file mode 100644 index f88bee44b..000000000 --- a/src/TouchSocket/Plugins/TcpReconnectionPlugin.cs +++ /dev/null @@ -1,57 +0,0 @@ -//------------------------------------------------------------------------------ -// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 -// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 -// CSDN博客:https://blog.csdn.net/qq_40374647 -// 哔哩哔哩视频:https://space.bilibili.com/94253567 -// Gitee源代码仓库:https://gitee.com/RRQM_Home -// Github源代码仓库:https://github.com/RRQM -// API首页:https://touchsocket.net/ -// 交流QQ群:234762506 -// 感谢您的下载和使用 -//------------------------------------------------------------------------------ - -using System; -using System.Threading.Tasks; -using TouchSocket.Core; - -namespace TouchSocket.Sockets; - -internal sealed class TcpReconnectionPlugin : ReconnectionPlugin, ITcpClosedPlugin where TClient : ITcpClient -{ - public override Func> ActionForCheck { get; set; } - - public TcpReconnectionPlugin() - { - this.ActionForCheck = (c, i) => Task.FromResult(c.Online); - } - - public async Task OnTcpClosed(ITcpSession client, ClosedEventArgs e) - { - await e.InvokeNext().ConfigureAwait(EasyTask.ContinueOnCapturedContext); - - if (client is not TClient tClient) - { - return; - } - - if (e.Manual) - { - return; - } - - _ = Task.Run(async () => - { - while (true) - { - if (this.DisposedValue) - { - return; - } - if (await this.ActionForConnect.Invoke(tClient).ConfigureAwait(EasyTask.ContinueOnCapturedContext)) - { - return; - } - } - }); - } -} \ No newline at end of file diff --git a/src/TouchSocket/Readme.md b/src/TouchSocket/Readme.md index 710796a5c..ebae8e748 100644 --- a/src/TouchSocket/Readme.md +++ b/src/TouchSocket/Readme.md @@ -10,14 +10,13 @@ TouchSocket 是 .Net(包括 C#、VB.Net、F#)的一个整合性的 socket ## 支持的目标框架 -- net45 - net462 - net472 - net481 - netstandard2.0 - netstandard2.1 - net6.0 -- net9.0 +- net10.0 - net8.0 ## 使用方法 diff --git a/src/TouchSocket/Receiver/IReceiver.cs b/src/TouchSocket/Receiver/IReceiver.cs index 64da0abbf..31941bab8 100644 --- a/src/TouchSocket/Receiver/IReceiver.cs +++ b/src/TouchSocket/Receiver/IReceiver.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Sockets; @@ -24,26 +20,10 @@ namespace TouchSocket.Sockets; /// 接收结果的类型,必须继承自 public interface IReceiver : IDisposableObject where TResult : IReceiverResult { - /// - /// 获取或设置是否启用缓存模式。 - /// - /// 设为即启用缓存模式。在缓存模式下,的数据如果大于0。 - /// 即会缓存未消费的数据。 - /// - /// - bool CacheMode { get; set; } - - /// - /// 获取或设置最大缓存大小。 - /// 这决定了缓存能够存储的最大数据量,以字节为单位。 - /// - int MaxCacheSize { get; set; } - - /// /// 异步读取操作。 /// - /// 用于取消异步读取操作的取消令牌。 + /// 用于取消异步读取操作的取消令牌。 /// 一个,其结果是异步读取的数据。 - ValueTask ReadAsync(CancellationToken token); + ValueTask ReadAsync(CancellationToken cancellationToken); } \ No newline at end of file diff --git a/src/TouchSocket/Receiver/IReceiverClient.cs b/src/TouchSocket/Receiver/IReceiverClient.cs index 74b52cd6c..7d84e11ef 100644 --- a/src/TouchSocket/Receiver/IReceiverClient.cs +++ b/src/TouchSocket/Receiver/IReceiverClient.cs @@ -19,10 +19,11 @@ namespace TouchSocket.Sockets; /// 接收结果的类型,必须继承自接口 public interface IReceiverClient where TResult : IReceiverResult { - /// - /// 获取一个同步数据接收器 + + /// + /// 创建一个同步数据接收器实例。 /// - /// 返回一个IReceiver接口实例,用于接收类型为TResult的数据 + /// 返回一个实现了接口的接收器对象。 IReceiver CreateReceiver(); /// diff --git a/src/TouchSocket/Receiver/IReceiverResult.cs b/src/TouchSocket/Receiver/IReceiverResult.cs index b6aedd167..3db4b8fa9 100644 --- a/src/TouchSocket/Receiver/IReceiverResult.cs +++ b/src/TouchSocket/Receiver/IReceiverResult.cs @@ -10,8 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// @@ -22,7 +20,7 @@ public interface IReceiverResult : IBlockResult /// /// 获取接收到的数据字节块 /// - ByteBlock ByteBlock { get; } + ReadOnlyMemory Memory { get; } /// /// 获取与接收数据相关的请求信息 diff --git a/src/TouchSocket/Receiver/InternalUdpReceiver.cs b/src/TouchSocket/Receiver/InternalUdpReceiver.cs index 78ae74a95..59a57ceea 100644 --- a/src/TouchSocket/Receiver/InternalUdpReceiver.cs +++ b/src/TouchSocket/Receiver/InternalUdpReceiver.cs @@ -10,160 +10,63 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; +using System.Net; namespace TouchSocket.Sockets; -/// -/// Receiver -/// -internal sealed class InternalUdpReceiver : BlockSegment, IReceiver +internal sealed class InternalUdpReceiver : SafetyDisposableObject, IReceiver { #region 字段 private readonly IReceiverClient m_client; - private readonly AsyncAutoResetEvent m_resetEventForComplateRead = new AsyncAutoResetEvent(false); - private ByteBlock m_byteBlock; - private ByteBlock m_cacheByteBlock; - private bool m_cacheMode; - private int m_maxCacheSize = 1024 * 64; - private UdpReceiverResult m_receiverResult; - private IRequestInfo m_requestInfo; + private readonly AsyncExchange<(ReadOnlyMemory, IRequestInfo, EndPoint)> m_asyncExchange = new(); + private string m_msg; #endregion 字段 - /// - /// Receiver - /// - /// - public InternalUdpReceiver(IReceiverClient client) : base(true) + + public InternalUdpReceiver(IReceiverClient client) { this.m_client = client; } - public bool CacheMode { get => this.m_cacheMode; set => this.m_cacheMode = value; } - - public int MaxCacheSize { get => this.m_maxCacheSize; set => this.m_maxCacheSize = value; } - - public async Task Complete(string msg) + public void Complete(string msg) { - try - { - this.m_receiverResult.IsCompleted = true; - this.m_receiverResult.Message = msg; - await this.InputReceive(default, default, default).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - catch - { - } + this.m_msg = msg; + this.m_asyncExchange.Complete(); } /// - public async Task InputReceive(System.Net.EndPoint remoteEndPoint, ByteBlock byteBlock, IRequestInfo requestInfo) + public async Task InputReceive(System.Net.EndPoint remoteEndPoint, ReadOnlyMemory memory, IRequestInfo requestInfo, CancellationToken cancellationToken) { if (this.DisposedValue) { return; } - - if (this.m_cacheMode && byteBlock != null) - { - ByteBlock bytes; - if (this.m_cacheByteBlock == null) - { - bytes = new ByteBlock(byteBlock.Length); - bytes.Write(byteBlock.Span); - } - else if (this.m_cacheByteBlock.CanReadLength > 0) - { - bytes = new ByteBlock(byteBlock.Length + this.m_cacheByteBlock.CanReadLength); - bytes.Write(this.m_cacheByteBlock.Span.Slice(this.m_cacheByteBlock.Position, this.m_cacheByteBlock.CanReadLength)); - bytes.Write(byteBlock.Span); - - this.m_cacheByteBlock.Dispose(); - } - else - { - bytes = new ByteBlock(byteBlock.Length); - bytes.Write(byteBlock.Span); - this.m_cacheByteBlock.Dispose(); - } - - bytes.SeekToStart(); - this.m_cacheByteBlock = bytes; - this.m_requestInfo = requestInfo; - } - else - { - this.m_byteBlock = byteBlock; - this.m_requestInfo = requestInfo; - } - - this.m_receiverResult.EndPoint = remoteEndPoint; - if (this.m_cacheMode) - { - this.m_receiverResult.ByteBlock = this.m_cacheByteBlock; - this.m_receiverResult.RequestInfo = this.m_requestInfo; - } - else - { - this.m_receiverResult.ByteBlock = this.m_byteBlock; - this.m_receiverResult.RequestInfo = this.m_requestInfo; - } - await base.TriggerAsync().ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.m_asyncExchange.WriteAsync((memory, requestInfo, remoteEndPoint), cancellationToken); } /// - public ValueTask ReadAsync(CancellationToken token) + public async ValueTask ReadAsync(CancellationToken cancellationToken) { - if (this.m_receiverResult.IsCompleted) + var readLease = await this.m_asyncExchange.ReadAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + var (memory, requestInfo, endpoint) = readLease.Value; + return new UdpReceiverResult(readLease.Dispose) { - return EasyValueTask.FromResult(this.m_receiverResult); - } - else - { - return base.ProtectedReadAsync(token); - } - } - - protected override void CompleteRead() - { - if (this.m_cacheMode && this.m_cacheByteBlock.CanReadLength > this.m_maxCacheSize) - { - ThrowHelper.ThrowArgumentOutOfRangeException_MoreThan(nameof(this.m_cacheByteBlock.CanReadLength), this.m_cacheByteBlock.CanReadLength, this.m_maxCacheSize); - } - this.m_byteBlock = default; - this.m_requestInfo = default; - this.m_receiverResult.RequestInfo = default; - this.m_receiverResult.ByteBlock = default; - base.CompleteRead(); - } - - protected override IUdpReceiverResult CreateResult(Action actionForDispose) - { - this.m_receiverResult = new UdpReceiverResult(actionForDispose); - return this.m_receiverResult; + IsCompleted = readLease.IsCompleted, + Memory = memory, + RequestInfo = requestInfo, + Message = this.m_msg, + EndPoint = endpoint + }; } /// - protected override void Dispose(bool disposing) + protected override void SafetyDispose(bool disposing) { - if (this.DisposedValue) - { - return; - } - if (disposing) { this.m_client.ClearReceiver(); - this.m_resetEventForComplateRead.Set(); - this.m_resetEventForComplateRead.SafeDispose(); } - this.m_byteBlock = null; - this.m_requestInfo = null; - base.Dispose(disposing); } } \ No newline at end of file diff --git a/src/TouchSocket/Receiver/UdpReceiverResult.cs b/src/TouchSocket/Receiver/UdpReceiverResult.cs index b039eb1b0..2959817ef 100644 --- a/src/TouchSocket/Receiver/UdpReceiverResult.cs +++ b/src/TouchSocket/Receiver/UdpReceiverResult.cs @@ -10,14 +10,10 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; using System.Net; namespace TouchSocket.Sockets; -/// -/// ReceiverResult -/// internal class UdpReceiverResult : InternalReceiverResult, IUdpReceiverResult { public UdpReceiverResult(Action disAction) : base(disAction) diff --git a/src/TouchSocket/Resources/TouchSocketResource.Designer.cs b/src/TouchSocket/Resources/TouchSocketResource.Designer.cs index c83200c72..d276f8d59 100644 --- a/src/TouchSocket/Resources/TouchSocketResource.Designer.cs +++ b/src/TouchSocket/Resources/TouchSocketResource.Designer.cs @@ -123,6 +123,15 @@ namespace TouchSocket.Resources { } } + /// + /// 查找类似 Pause reconnection. 的本地化字符串。 + /// + public static string PauseReconnection { + get { + return ResourceManager.GetString("PauseReconnection", resourceCulture); + } + } + /// /// 查找类似 Start polling based reconnection, with an interval of {0}. 的本地化字符串。 /// @@ -195,6 +204,15 @@ namespace TouchSocket.Resources { } } + /// + /// 查找类似 Transport is null. 的本地化字符串。 + /// + public static string TransportIsNull { + get { + return ResourceManager.GetString("TransportIsNull", resourceCulture); + } + } + /// /// 查找类似 Udp is not running. 的本地化字符串。 /// diff --git a/src/TouchSocket/Resources/TouchSocketResource.resx b/src/TouchSocket/Resources/TouchSocketResource.resx index e0cd532bb..a177d2d19 100644 --- a/src/TouchSocket/Resources/TouchSocketResource.resx +++ b/src/TouchSocket/Resources/TouchSocketResource.resx @@ -1,76 +1,96 @@  + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + + + + + + + + + + + + + + + + + + - + + @@ -89,13 +109,13 @@ text/microsoft-resx - 1.3 + 2.0 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 No client with Id '{0}' was found. @@ -148,4 +168,10 @@ Polling reconnection has ended + + Transport is null. + + + Pause reconnection. + \ No newline at end of file diff --git a/src/TouchSocket/Resources/TouchSocketResource.zh-CN.resx b/src/TouchSocket/Resources/TouchSocketResource.zh-CN.resx index 695c9fbf3..cca6052c7 100644 --- a/src/TouchSocket/Resources/TouchSocketResource.zh-CN.resx +++ b/src/TouchSocket/Resources/TouchSocketResource.zh-CN.resx @@ -16,7 +16,7 @@ text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, ... - System.Resources.ResXResourceWriter, System.Windows.Forms, ... + System.Resources.ResXResourceWriter, System.Windows.Forms, ... this is my long stringthis is a comment Blue @@ -41,7 +41,7 @@ extensible. For a given mimetype the value must be set accordingly: Note - application/x-microsoft.net.object.binary.base64 is the format - that the ResXResourceWriter will generate, however the byteBlock can + that the ResXResourceWriter will generate, however the reader can read any of the formats listed below. mimetype: application/x-microsoft.net.object.binary.base64 @@ -182,4 +182,10 @@ 轮询重新连接已结束 + + Transport意外为空。 + + + 暂停重连。 + \ No newline at end of file diff --git a/src/TouchSocket/TouchSocket.csproj b/src/TouchSocket/TouchSocket.csproj index 6f3344ba5..ca1e70e3d 100644 --- a/src/TouchSocket/TouchSocket.csproj +++ b/src/TouchSocket/TouchSocket.csproj @@ -1,6 +1,6 @@ - net481;net45;net462;net472;netstandard2.0;netstandard2.1;net6.0;net9.0;net8.0 + net462;netstandard2.0;netstandard2.1;net6.0;net10.0;net8.0 Tcp;Udp;Ssl;Socket;Saea;TouchSocket TouchSocket是.Net(包括 C# 、VB.Net、F#)的一个整合性的socket网络通信框架。包含了 tcp、udp、ssl等一系列的通信模块。一键式解决 tcp 黏分包问题,udp大数据包分片组合问题等。使用协议模板,可快速实现「固定包头」、「固定长度」、「区间字符」等一系列的数据报文解析。 @@ -10,7 +10,8 @@ TouchSocket is Net (including C #, VB An integrated socket network communication - + + diff --git a/src/TouchSocket/Transports/ITransport.cs b/src/TouchSocket/Transports/ITransport.cs new file mode 100644 index 000000000..302ed6a0d --- /dev/null +++ b/src/TouchSocket/Transports/ITransport.cs @@ -0,0 +1,21 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +namespace TouchSocket.Sockets; + +public interface ITransport : ITransportReader, ITransportWriter, IClosableClient +{ + /// + /// 获取连接关闭时的事件参数。 + /// + ClosedEventArgs ClosedEventArgs { get; } +} \ No newline at end of file diff --git a/src/TouchSocket/Transports/ITransportReader.cs b/src/TouchSocket/Transports/ITransportReader.cs new file mode 100644 index 000000000..b24c6262a --- /dev/null +++ b/src/TouchSocket/Transports/ITransportReader.cs @@ -0,0 +1,31 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.IO.Pipelines; + +namespace TouchSocket.Sockets; + +/// +/// 传输读取器接口,提供管道读取器和读取锁定器 +/// +public interface ITransportReader +{ + /// + /// 获取管道读取器,用于从传输层读取数据 + /// + PipeReader Reader { get; } + + /// + /// 获取读取锁定器,用于同步读取操作 + /// + SemaphoreSlim ReadLocker { get; } +} diff --git a/src/TouchSocket/Transports/ITransportWriter.cs b/src/TouchSocket/Transports/ITransportWriter.cs new file mode 100644 index 000000000..5fbb492d9 --- /dev/null +++ b/src/TouchSocket/Transports/ITransportWriter.cs @@ -0,0 +1,31 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.IO.Pipelines; + +namespace TouchSocket.Sockets; + +/// +/// 传输写入器接口,提供管道写入器和写入锁定器 +/// +public interface ITransportWriter +{ + /// + /// 获取管道写入器,用于向传输层写入数据 + /// + PipeWriter Writer { get; } + + /// + /// 获取写入锁定器,用于同步写入操作 + /// + SemaphoreSlim WriteLocker { get; } +} diff --git a/src/TouchSocket/Transports/TcpTransport.cs b/src/TouchSocket/Transports/TcpTransport.cs new file mode 100644 index 000000000..62b422ff4 --- /dev/null +++ b/src/TouchSocket/Transports/TcpTransport.cs @@ -0,0 +1,366 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.IO.Pipelines; +using System.Net.Security; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using TouchSocket.Resources; + +namespace TouchSocket.Sockets; + +internal sealed class TcpTransport : BaseTransport +{ + private readonly TcpCore m_tcpCore; + private readonly Socket m_socket; + private readonly bool m_bufferOnDemand; + private PipeReader m_reader; + private PipeWriter m_writer; + + /// + /// 获取用于读取数据的管道读取器 + /// + public override PipeReader Reader => this.m_reader ?? base.Reader; + + /// + /// 获取用于写入数据的管道写入器 + /// + public override PipeWriter Writer => this.m_writer ?? base.Writer; + + /// + /// 获取是否使用SSL + /// + public bool UseSsl { get; private set; } + + public TcpTransport(TcpCore tcpCore, TransportOption option) : base(option) + { + this.m_tcpCore = tcpCore ?? throw new ArgumentNullException(nameof(tcpCore)); + this.m_socket = tcpCore.Socket; + this.m_bufferOnDemand = option.BufferOnDemand; + this.Start(); + } + + /// + /// 服务端SSL认证 + /// + /// SSL配置选项 + public async Task AuthenticateAsync(ServiceSslOption sslOption) + { + if (this.UseSsl) + { + return; + } + var sourceStream = new TransportStream(this); + SslStream sslStream; + if (sslOption.CertificateValidationCallback != null) + { + sslStream = new SslStream(sourceStream, false, sslOption.CertificateValidationCallback); + } + else + { + sslStream = new SslStream(sourceStream, false); + } + + await sslStream.AuthenticateAsServerAsync(sslOption.Certificate, sslOption.ClientCertificateRequired + , sslOption.SslProtocols + , sslOption.CheckCertificateRevocation).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + this.m_reader = PipeReader.Create(sslStream); + this.m_writer = PipeWriter.Create(sslStream); + this.UseSsl = true; + } + + /// + /// 客户端SSL认证 + /// + /// SSL配置选项 + public async Task AuthenticateAsync(ClientSslOption sslOption) + { + if (this.UseSsl) + { + return; + } + var sourceStream = new TransportStream(this); + SslStream sslStream; + if (sslOption.CertificateValidationCallback != null) + { + sslStream = new SslStream(sourceStream, false, sslOption.CertificateValidationCallback); + } + else + { + sslStream = new SslStream(sourceStream, false); + } + + await sslStream.AuthenticateAsClientAsync(sslOption.TargetHost, sslOption.ClientCertificates, sslOption.SslProtocols, sslOption.CheckCertificateRevocation).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + + this.m_reader = PipeReader.Create(sslStream); + this.m_writer = PipeWriter.Create(sslStream); + this.UseSsl = true; + } + + /// + public override async Task CloseAsync(string msg, CancellationToken cancellationToken = default) + { + try + { + if (this.m_writer != null) + { + // 完成发送管道 + await this.m_writer.CompleteAsync().SafeWaitAsync(cancellationToken); + } + + if (this.m_reader != null) + { + // 等待发送管道读取器完成 + await this.m_reader.CompleteAsync().SafeWaitAsync(cancellationToken); + } + await base.CloseAsync(msg, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + this.Close(); + return Result.Success; + } + catch (Exception ex) + { + return Result.FromException(ex.Message); + } + } + + /// + /// 同步关闭连接 + /// + /// 操作结果 + public Result Close() + { + try + { + var socket = this.m_socket; + if (socket == null) + { + return Result.Success; + } + + + try + { + if (socket.Connected) + { + socket.Shutdown(SocketShutdown.Both); + } + } + catch (SocketException) + { + // Socket已经断开或未连接,忽略此异常 + } + catch (ObjectDisposedException) + { + // Socket已经被释放,忽略此异常 + } + + socket.Close(); + return Result.Success; + } + catch (Exception ex) + { + return Result.FromException(ex); + } + } + + /// + /// 运行接收循环,优化了ValueTask的快速路径处理 + /// + /// 取消令牌 + protected override async Task RunReceive(CancellationToken cancellationToken) + { + var pipeWriter = this.m_pipeReceive.Writer; + + try + { + while (!cancellationToken.IsCancellationRequested) + { + TcpOperationResult result; + if (this.m_bufferOnDemand) + { + result = await this.m_tcpCore.WaitForDataAsync(); + + if (result.SocketError != SocketError.Success) + { + // 处理接收失败的情况 + this.m_closedEventArgs ??= new ClosedEventArgs(false, TouchSocketResource.RemoteDisconnects); + var socketException = new SocketException((int)result.SocketError); + await CompleteReceivePipeAsync(pipeWriter, socketException).ConfigureAwait(false); + break; + } + } + var memory = pipeWriter.GetMemory(this.ReceiveBufferSize); + var receiveTask = this.m_tcpCore.ReceiveAsync(memory); + + + + // 快速路径:如果操作同步完成,避免异步机制的开销 + if (receiveTask.IsCompleted) + { + result = receiveTask.Result; + } + else + { + // 慢速路径:异步等待 + result = await receiveTask.ConfigureAwait(false); + } + + if (result.SocketError == SocketError.Success) + { + if (result.BytesTransferred == 0) + { + // 远程连接已断开 + this.m_closedEventArgs ??= new ClosedEventArgs(false, TouchSocketResource.RemoteDisconnects); + await CompleteReceivePipeAsync(pipeWriter, null).ConfigureAwait(false); + return; + } + + // 更新接收计数器 + this.m_receiveCounter.Increment(result.BytesTransferred); + + // 推进缓冲区指针 + pipeWriter.Advance(result.BytesTransferred); + + // 刷新数据到管道 + var flushTask = pipeWriter.FlushAsync(cancellationToken); + + FlushResult flushResult; + + // 快速路径:如果刷新同步完成 + if (flushTask.IsCompleted) + { + flushResult = flushTask.Result; + } + else + { + // 慢速路径:异步等待刷新 + flushResult = await flushTask.ConfigureAwait(false); + } + + if (flushResult.IsCompleted) + { + break; + } + } + else + { + // 处理接收失败的情况 + this.m_closedEventArgs ??= new ClosedEventArgs(false, TouchSocketResource.RemoteDisconnects); + var socketException = new SocketException((int)result.SocketError); + await CompleteReceivePipeAsync(pipeWriter, socketException).ConfigureAwait(false); + break; + } + } + } + catch (Exception ex) + { + this.m_closedEventArgs ??= new ClosedEventArgs(false, ex.Message); + await CompleteReceivePipeAsync(pipeWriter, ex).ConfigureAwait(false); + } + finally + { + // 取消内置接收和发送任务 + base.m_tokenSource.SafeCancel(); + } + } + + /// + /// 运行发送循环,优化了ValueTask的快速路径处理 + /// + /// 取消令牌 + protected override async Task RunSend(CancellationToken cancellationToken) + { + var pipeReader = this.m_pipeSend.Reader; + + try + { + while (!cancellationToken.IsCancellationRequested) + { + //不订阅取消令牌,安全退出。 + var readTask = pipeReader.ReadAsync(CancellationToken.None); + + ReadResult readResult; + + // 快速路径:如果读取同步完成 + if (readTask.IsCompleted) + { + readResult = readTask.Result; + } + else + { + // 慢速路径:异步等待 + readResult = await readTask.ConfigureAwait(false); + } + + if (readResult.IsCanceled) + { + break; + } + + if (readResult.IsCompleted && readResult.Buffer.IsEmpty) + { + break; + } + + try + { + // 发送数据 + await this.m_tcpCore.SendAsync(readResult.Buffer).ConfigureAwait(false); + + // 推进读取位置 + pipeReader.AdvanceTo(readResult.Buffer.End); + + // 更新发送计数器 + this.m_sentCounter.Increment(readResult.Buffer.Length); + + if (readResult.IsCompleted) + { + break; + } + } + catch (Exception ex) + { + // 处理发送失败的情况 + pipeReader.Complete(ex); + break; + } + } + } + catch (Exception ex) when (ex is not OperationCanceledException) + { + pipeReader.Complete(ex); + } + finally + { + pipeReader.Complete(); + } + } + + /// + /// 完成接收管道的辅助方法,优化异常处理路径 + /// + /// 管道写入器 + /// 可选的异常 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static async ValueTask CompleteReceivePipeAsync(PipeWriter pipeWriter, Exception exception) + { + if (exception == null) + { + await pipeWriter.CompleteAsync().SafeWaitAsync().ConfigureAwait(false); + } + else + { + await pipeWriter.CompleteAsync(exception).SafeWaitAsync().ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/src/TouchSocket/WaitingClient/IWaitingClient.cs b/src/TouchSocket/WaitingClient/IWaitingClient.cs index 85e65cdea..d91d11e5f 100644 --- a/src/TouchSocket/WaitingClient/IWaitingClient.cs +++ b/src/TouchSocket/WaitingClient/IWaitingClient.cs @@ -10,11 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// @@ -40,19 +35,19 @@ public interface IWaitingClient /// 异步发送 /// /// 要发送的数据,使用内存表示 - /// 取消令箭,用于取消操作 + /// 取消令箭,用于取消操作 /// 客户端没有连接 /// 发送数据超长 /// 其他异常 /// 返回的数据,类型为ResponsedData - Task SendThenResponseAsync(ReadOnlyMemory memory, CancellationToken token); + Task SendThenResponseAsync(ReadOnlyMemory memory, CancellationToken cancellationToken); /// /// 异步发送请求并等待响应 /// /// 请求信息,包含发送请求所需的所有细节 - /// 用于取消操作的取消令牌 + /// 用于取消操作的取消令牌 /// 返回一个任务,该任务的结果是服务器的响应数据 - Task SendThenResponseAsync(IRequestInfo requestInfo, CancellationToken token); + Task SendThenResponseAsync(IRequestInfo requestInfo, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/src/TouchSocket/WaitingClient/InternalWaitingClient.cs b/src/TouchSocket/WaitingClient/InternalWaitingClient.cs index 23f0d967a..57163b5ba 100644 --- a/src/TouchSocket/WaitingClient/InternalWaitingClient.cs +++ b/src/TouchSocket/WaitingClient/InternalWaitingClient.cs @@ -10,11 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Sockets; internal sealed class InternalWaitingClient : IWaitingClient @@ -35,9 +30,9 @@ internal sealed class InternalWaitingClient : IWaitingClient SendThenResponseAsync(ReadOnlyMemory memory, CancellationToken token = default) + public async Task SendThenResponseAsync(ReadOnlyMemory memory, CancellationToken cancellationToken = default) { - await this.m_semaphoreSlim.WaitAsync(token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.m_semaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); try { @@ -49,15 +44,11 @@ internal sealed class InternalWaitingClient : IWaitingClient : IWaitingClient : IWaitingClient SendThenResponseAsync(IRequestInfo requestInfo, CancellationToken token) + public async Task SendThenResponseAsync(IRequestInfo requestInfo, CancellationToken cancellationToken) { - await this.m_semaphoreSlim.WaitAsync(token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + await this.m_semaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); try { @@ -134,9 +121,9 @@ internal sealed class InternalWaitingClient : IWaitingClient : IWaitingClient -/// 响应数据。 +/// 表示响应数据的结构体,包含响应的字节数据和请求信息。 /// -public readonly struct ResponsedData +public readonly struct ResponsedData : IDisposable { + private readonly ByteBlock m_byteBlock; + /// - /// 构造函数 + /// 初始化 结构体的新实例。 /// - /// 响应的数据 - /// 请求信息 - public ResponsedData(ByteBlock byteBlock, IRequestInfo requestInfo) + /// 响应的字节数据。 + /// 请求信息。 + public ResponsedData(ReadOnlyMemory memory, IRequestInfo requestInfo) { - this.ByteBlock = byteBlock; + if (!memory.IsEmpty) + { + var data = memory.Span; + this.m_byteBlock = new ByteBlock(data.Length); + this.m_byteBlock.Write(data); + this.m_byteBlock.SeekToStart(); + } + this.RequestInfo = requestInfo; } /// - /// 数据 + /// 获取响应的字节数据。 /// - [Obsolete($"使用此属性可能带来不必要的性能消耗,请使用{nameof(ByteBlock)}代替")] - public byte[] Data => this.ByteBlock?.ToArray(); + public ReadOnlyMemory Memory => this.m_byteBlock?.Memory ?? ReadOnlyMemory.Empty; /// - /// ByteBlock - /// - public ByteBlock ByteBlock { get; } - - /// - /// RequestInfo + /// 获取请求信息。 /// public IRequestInfo RequestInfo { get; } + + /// + /// 释放资源。 + /// + public void Dispose() + { + this.m_byteBlock.SafeDispose(); + } } \ No newline at end of file diff --git a/src/TouchSocket/WaitingClient/WaitingClientExtension.cs b/src/TouchSocket/WaitingClient/WaitingClientExtension.cs index 165d3d0de..a99122637 100644 --- a/src/TouchSocket/WaitingClient/WaitingClientExtension.cs +++ b/src/TouchSocket/WaitingClient/WaitingClientExtension.cs @@ -10,12 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Sockets; /// @@ -113,20 +107,26 @@ public static class WaitingClientExtension /// 发送数据并等待 /// /// 等待客户端接口 - /// 要发送的消息 - /// 取消令箭 + /// 要发送的消息 + /// 取消令箭 /// 客户端没有连接 /// 发送数据超长 /// 其他异常 /// 返回的数据 - public static async Task SendThenResponseAsync(this IWaitingClient client, string msg, CancellationToken token) + public static async Task SendThenResponseAsync(this IWaitingClient client, string value, CancellationToken cancellationToken) where TClient : IReceiverClient, ISender, IRequestInfoSender where TResult : IReceiverResult { - using (var byteBlock = new ByteBlock(1024)) + var byteBlock = new ByteBlock(1024); + + try { - byteBlock.WriteNormalString(msg, Encoding.UTF8); - return await client.SendThenResponseAsync(byteBlock.Memory, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + WriterExtension.WriteNormalString(ref byteBlock, value, Encoding.UTF8); + return await client.SendThenResponseAsync(byteBlock.Memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + } + finally + { + byteBlock.Dispose(); } } @@ -134,22 +134,28 @@ public static class WaitingClientExtension /// 发送数据并等待 /// /// 等待客户端接口 - /// 要发送的消息 + /// 要发送的消息 /// 超时时间,默认为5000毫秒 /// 客户端没有连接时抛出的异常 /// 发送数据超长时抛出的异常 /// 其他异常 /// 返回从客户端接收到的数据 - public static async Task SendThenResponseAsync(this IWaitingClient client, string msg, int millisecondsTimeout = 5000) + public static async Task SendThenResponseAsync(this IWaitingClient client, string value, int millisecondsTimeout = 5000) where TClient : IReceiverClient, ISender, IRequestInfoSender where TResult : IReceiverResult { - using (var byteBlock = new ByteBlock(1024)) + var byteBlock = new ByteBlock(1024); + + try { - byteBlock.WriteNormalString(msg, Encoding.UTF8); + WriterExtension.WriteNormalString(ref byteBlock, value, Encoding.UTF8); return await client.SendThenResponseAsync(byteBlock.Memory, millisecondsTimeout).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } + finally + { + byteBlock.Dispose(); + } } @@ -214,102 +220,6 @@ public static class WaitingClientExtension } #endregion SendThenResponseAsync - #region SendThenReturnAsync - - /// - /// 异步发送消息并返回结果 - /// - /// 发送客户端 - /// 待发送的消息 - /// 超时时间,单位为毫秒,默认为5000毫秒 - /// 客户端没有连接 - /// 发送数据超长 - /// 其他异常 - /// 返回的数据 - public static Task SendThenReturnAsync(this IWaitingClient client, string msg, int millisecondsTimeout = 5000) - where TClient : IReceiverClient, ISender, IRequestInfoSender - where TResult : IReceiverResult - { - // 使用UTF-8编码将字符串消息转换为字节数组 - return SendThenReturnAsync(client, Encoding.UTF8.GetBytes(msg), millisecondsTimeout); - } - - /// - /// 异步发送数据并返回响应,如果超时则抛出异常。 - /// - /// 实现IWaitSender接口的客户端对象,用于发送数据。 - /// 待发送的数据,以的形式指定。 - /// 操作超时的时间,以毫秒为单位,默认为5000毫秒(5秒)。 - /// 返回发送操作的响应数据,以byte数组形式。 - /// 当操作超时时,此异常被抛出。 - public static async Task SendThenReturnAsync(this IWaitingClient client, ReadOnlyMemory memory, int millisecondsTimeout = 5000) - where TClient : IReceiverClient, ISender, IRequestInfoSender - where TResult : IReceiverResult - { - // 创建一个CancellationTokenSource对象,并设置超时时间 - using (var tokenSource = new CancellationTokenSource(millisecondsTimeout)) - { - try - { - // 使用client对象的SendThenReturnAsync方法发送数据并返回响应 - return await client.SendThenReturnAsync(memory, tokenSource.Token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - } - catch (OperationCanceledException) - { - // 如果操作被取消(即达到超时时间),则抛出TimeoutException异常 - throw new TimeoutException(); - } - } - } - - /// - /// 异步发送请求并返回响应数据 - /// - /// 客户端类型 - /// 接收结果类型,必须实现IReceiverResult接口 - /// 等待客户端实例,提供发送和接收功能 - /// 请求信息,包含要发送的数据 - /// 操作超时时间(以毫秒为单位),默认为5000毫秒 - /// 返回一个任务,该任务结果是一个字节数组,包含响应数据 - /// 当操作超时时,抛出此异常 - public static async Task SendThenReturnAsync(this IWaitingClient client, IRequestInfo requestInfo, int millisecondsTimeout = 5000) - where TClient : IReceiverClient, ISender, IRequestInfoSender - where TResult : IReceiverResult - { - // 创建一个CancellationTokenSource对象,并设置超时时间 - using (var tokenSource = new CancellationTokenSource(millisecondsTimeout)) - { - try - { - // 使用client对象的SendThenReturnAsync方法发送数据并返回响应 - return (await client.SendThenResponseAsync(requestInfo, tokenSource.Token).ConfigureAwait(EasyTask.ContinueOnCapturedContext)).ByteBlock?.ToArray(); - } - catch (OperationCanceledException) - { - // 如果操作被取消(即达到超时时间),则抛出TimeoutException异常 - throw new TimeoutException(); - } - } - } - - /// - /// 在给定的客户端上异步发送数据并返回响应数据。 - /// - /// 发送和请求信息的客户端类型。 - /// 接收结果的类型。 - /// 一个等待客户端对象。 - /// 要发送的数据。 - /// 用于取消操作的取消令牌。 - /// 一个异步任务,该任务的结果是返回的响应数据。 - public static async Task SendThenReturnAsync(this IWaitingClient client, ReadOnlyMemory memory, CancellationToken token) - where TClient : IReceiverClient, ISender, IRequestInfoSender - where TResult : IReceiverResult - { - // 使用client对象的SendThenResponseAsync方法发送数据并返回响应 - return (await client.SendThenResponseAsync(memory, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext)).ByteBlock?.ToArray(); - } - - #endregion SendThenReturnAsync #region SendThenResponse @@ -320,15 +230,15 @@ public static class WaitingClientExtension /// 接收结果类型,必须实现IReceiverResult接口。 /// 实现等待客户端接口的实例。 /// 要发送的数据,以只读内存形式提供。 - /// 用于取消操作的取消令牌。 + /// 用于取消操作的取消令牌。 /// 包含操作结果的ResponsedData对象。 [AsyncToSyncWarning] - public static ResponsedData SendThenResponse(this IWaitingClient client, ReadOnlyMemory memory, CancellationToken token) + public static ResponsedData SendThenResponse(this IWaitingClient client, ReadOnlyMemory memory, CancellationToken cancellationToken) where TClient : IReceiverClient, ISender, IRequestInfoSender where TResult : IReceiverResult { // 调用异步版本的SendThenResponse方法,并直接获取其结果。 - return client.SendThenResponseAsync(memory, token).GetFalseAwaitResult(); + return client.SendThenResponseAsync(memory, cancellationToken).GetFalseAwaitResult(); } /// @@ -336,18 +246,18 @@ public static class WaitingClientExtension /// /// 等待客户端接口 /// 要发送的消息 - /// 取消令箭 + /// 取消令箭 /// 客户端没有连接 /// 发送数据超长 /// 其他异常 /// 返回的数据 [AsyncToSyncWarning] - public static ResponsedData SendThenResponse(this IWaitingClient client, string msg, CancellationToken token) + public static ResponsedData SendThenResponse(this IWaitingClient client, string msg, CancellationToken cancellationToken) where TClient : IReceiverClient, ISender, IRequestInfoSender where TResult : IReceiverResult { // 将字符串消息转换为字节数组 - return client.SendThenResponseAsync(msg, token).GetFalseAwaitResult(); + return client.SendThenResponseAsync(msg, cancellationToken).GetFalseAwaitResult(); } /// @@ -401,78 +311,4 @@ public static class WaitingClientExtension } #endregion SendThenResponse - - #region SendThenReturn - - /// - /// 发送字节流 - /// - /// 要发送数据的客户端 - /// 要发送的消息 - /// 等待返回数据的超时时间,默认为5000毫秒 - /// 客户端没有连接 - /// 发送数据超长 - /// 其他异常 - /// 返回的数据 - [AsyncToSyncWarning] - public static byte[] SendThenReturn(this IWaitingClient client, string msg, int millisecondsTimeout = 5000) - where TClient : IReceiverClient, ISender, IRequestInfoSender - where TResult : IReceiverResult - { - // 使用UTF-8编码将字符串消息转换为字节流,并调用重载的SendThenReturn方法 - return SendThenReturnAsync(client, Encoding.UTF8.GetBytes(msg), millisecondsTimeout).GetFalseAwaitResult(); - } - - /// - /// 使用指定的超时时间通过IWaitSender发送数据并返回响应。 - /// 此方法通过创建一个带有超时时间的CancellationTokenSource来控制操作的超时。 - /// 如果操作在指定的超时时间内完成,则返回操作的结果,否则抛出一个超时异常。 - /// - /// 实现IWaitSender接口的实例,用于发送数据并等待响应。 - /// 只读字节内存,表示要发送的数据。 - /// 操作的超时时间,以毫秒为单位,默认为5000毫秒(5秒)。 - /// 操作成功时返回字节数组,包含响应数据。 - /// 当操作超时时,会抛出此异常。 - [AsyncToSyncWarning] - public static byte[] SendThenReturn(this IWaitingClient client, ReadOnlyMemory memory, int millisecondsTimeout = 5000) - where TClient : IReceiverClient, ISender, IRequestInfoSender - where TResult : IReceiverResult - { - // 创建一个CancellationTokenSource对象,并设置超时时间 - using (var tokenSource = new CancellationTokenSource(millisecondsTimeout)) - { - try - { - // 异步发送数据并返回结果,使用取消令牌来控制超时 - return client.SendThenReturnAsync(memory, tokenSource.Token).GetFalseAwaitResult(); - } - catch (OperationCanceledException) - { - // 当操作被取消时,抛出一个超时异常 - ThrowHelper.ThrowTimeoutException(); - return default; - } - } - } - - /// - /// 向接收方发送数据后返回发送结果。 - /// 此方法适用于需要同步发送数据并立即获取结果的场景。 - /// - /// 提供发送和接收功能的客户端对象。 - /// 要发送的数据,以形式表示。 - /// 用于取消操作的取消令牌。 - /// 客户端类型,必须实现接口。 - /// 接收结果类型,必须实现接口。 - /// 返回发送后的结果数据,类型为数组。 - [AsyncToSyncWarning] - public static byte[] SendThenReturn(this IWaitingClient client, ReadOnlyMemory memory, CancellationToken token) - where TClient : IReceiverClient, ISender, IRequestInfoSender - where TResult : IReceiverResult - { - // 调用异步版本的SendThenReturn方法,并直接获取其结果 - return client.SendThenReturnAsync(memory, token).GetFalseAwaitResult(); - } - - #endregion SendThenReturn } \ No newline at end of file diff --git a/src/TouchSocket/WaitingClient/WaitingOptions.cs b/src/TouchSocket/WaitingClient/WaitingOptions.cs index 9ed0df1bd..f5622ba8f 100644 --- a/src/TouchSocket/WaitingClient/WaitingOptions.cs +++ b/src/TouchSocket/WaitingClient/WaitingOptions.cs @@ -10,10 +10,6 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ -using System; -using System.Threading.Tasks; -using TouchSocket.Core; - namespace TouchSocket.Sockets; ///